Embrace Simplicity and Efficiency in Software using YAGNI and DRY principles

Two principles that stand as cornerstones in the realm of software craftsmanship are YAGNI (You Aren’t Gonna Need It) and DRY (Don’t Repeat Yourself). For developers, understanding and applying these concepts is crucial for writing clean, efficient, and maintainable code.
Understanding YAGNI
YAGNI is a principle that emphasizes the importance of not implementing something until it is necessary. In the context of Software, this means avoiding the temptation to build features or create abstractions that are not currently required by the application. Overengineering can lead to complex, hard-to-maintain codebases, and YAGNI acts as a guardrail against this.
Understanding DRY
Conversely, DRY is a principle that focuses on reducing the repetition of software patterns. For Software developers, DRY is about recognizing opportunities to use features to abstract and reuse code effectively, thus avoiding redundancy and simplifying modifications.
YAGNI & DRY: A Synergistic Approach
While YAGNI prevents unnecessary work, DRY streamlines what is necessary. Together, they guide Software developers in creating applications that are both minimal in unnecessary complexity and maximally efficient in their use of abstractions and code reuse.
Implementing YAGNI
Consider an Angular application that requires a user authentication feature. Following YAGNI, a developer should resist the urge to implement various authentication methods (like biometrics or two-factor authentication) until they are explicitly required.
Example: Avoiding Premature Abstraction
// Avoid
class AuthenticationService {
loginWithFacebook(token: string) { /* ... */ }
loginWithGoogle(token: string) { /* ... */ }
loginWithTwitter(token: string) { /* ... */ }
}
// YAGNI-compliant
class AuthenticationService {
login(token: string) { /* ... */ }
}
In the YAGNI-compliant example, the AuthenticationService
is simplified to a single login
method, avoiding the complexity of multiple methods until such time as they are needed.
Example: Feature-based Module Structure
Angular’s modularity allows you to structure features as separate modules, which aligns with YAGNI by not loading features until they are needed.
@NgModule({
declarations: [LoginComponent],
imports: [CommonModule],
})
export class LoginModule {}
Implementing DRY in an Angular Service
Services in Angular are singleton objects that can be injected into components and other services. They are ideal for writing DRY code because they allow you to define logic in one place and reuse it throughout your application.
Example: Using Angular Services for Shared Logic
Suppose you have an application where multiple components need to access and display user data. Instead of writing HTTP requests in each component, you can create a UserService
that encapsulates the logic for fetching user data.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserDataService {
private apiUrl = 'https://api.example.com/users';
constructor(private http: HttpClient) {}
getUserById(userId: string): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${userId}`);
}
getAllUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
}
Any component that needs user data can inject this service and use its methods to fetch data, ensuring that the HTTP request logic is written only once.
Example: Tooltip Directive
Imagine you want to add a tooltip to various elements in your application. Instead of repeating the tooltip logic in every component, you can create a TooltipDirective
that can be used anywhere. The purpose of this code isn’t to show best practice, but rather adhere to the DRY principle.
import { Directive, ElementRef, Input, HostListener, Renderer2, OnDestroy } from '@angular/core';
@Directive({
selector: '[appTooltip]'
})
export class TooltipDirective implements OnDestroy {
@Input('appTooltip') tooltipText: string;
private tooltipElement: HTMLElement;
private hasView = false;
constructor(private el: ElementRef, private renderer: Renderer2) {}
@HostListener('mouseenter') onMouseEnter() {
if (!this.hasView) {
this.createTooltip(this.tooltipText);
this.hasView = true;
}
this.renderer.setStyle(this.tooltipElement, 'display', 'block');
}
@HostListener('mouseleave') onMouseLeave() {
this.renderer.setStyle(this.tooltipElement, 'display', 'none');
}
private createTooltip(text: string) {
this.tooltipElement = this.renderer.createElement('span');
const textNode = this.renderer.createText(text);
this.renderer.appendChild(this.tooltipElement, textNode);
this.renderer.appendChild(this.el.nativeElement, this.tooltipElement);
// Apply styles and positioning logic to this.tooltipElement
}
ngOnDestroy() {
// Perform cleanup
if (this.tooltipElement) {
this.renderer.removeChild(this.el.nativeElement, this.tooltipElement);
}
}
}
Best Practices
To adhere to YAGNI and DRY:
- Regularly refactor code to identify and eliminate redundancies.
- Resist adding features until they are explicitly requested.
- Use your software’s built-in features, like services and directives in Angular, to abstract common functionality.
YAGNI and DRY are not just principles; they are practices that, when implemented effectively in Software, lead to cleaner, more maintainable, and more efficient codebases. As you progress in your software development career, remember to adhere to fundamental principles that will help you write code that is both effective and easy to maintain.