Embrace Simplicity and Efficiency in Software using YAGNI and DRY principles

Sheldon Cohen
3 min readNov 9, 2023

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.

--

--

Sheldon Cohen

Technology professional with 15+ years of software development