Clean Code: Don't Repeat Yourself (DRY)
Ever had to fix a bug, and then another, and another... only to realize it was the same line of code copied all over the place? That is the kind of pain the "Don't Repeat Yourself" (DRY) principle helps us avoid.
The idea is simple: every piece of knowledge in a system should have a single, unambiguous, authoritative representation. In other words, avoid duplicating code. Let's see how this plays out in practice.
interface Invoice {
id: string;
amount: number;
dueDate: string;
}
// Bad: Repeated formatting logic
function printInvoice(invoice: Invoice): string {
return `Invoice #${invoice.id} - Amount: $${invoice.amount} - Due: ${invoice.dueDate}`;
}
function printInvoiceReminder(invoice: Invoice): string {
return `Invoice #${invoice.id} - Amount: $${invoice.amount} - Due: ${invoice.dueDate} (Reminder)`;
}
// Good: Extract repeated logic into a helper function
function formatInvoice(invoice: Invoice): string {
return `Invoice #${invoice.id} - Amount: $${invoice.amount} - Due: ${invoice.dueDate}`;
}
function printInvoice(invoice: Invoice): string {
return formatInvoice(invoice);
}
function printInvoiceReminder(invoice: Invoice): string {
return `${formatInvoice(invoice)} (Reminder)`;
}
The example above is simple, but this principle applies to more than just utility functions. If you find yourself writing the same logic in multiple places, it's a sign that you should probably extract it into its own function, class, or module.
One Change, Many Places
Here's another common scenario. Imagine you have the logic for a user's full name scattered around your application.
// Bad
function getGreeting(user: { firstName: string; lastName:string }): string {
return `Hello, ${user.firstName} ${user.lastName}!`;
}
function getFormalSalutation(user: { firstName: string; lastName:string }): string {
return `Dear ${user.firstName} ${user.lastName},`;
}
// Good
function getFullName(user: { firstName: string; lastName: string }): string {
return `${user.firstName} ${user.lastName}`;
}
function getGreeting(user: { firstName: string; lastName: string }): string {
return `Hello, ${getFullName(user)}!`;
}
function getFormalSalutation(user: { firstName: string; lastName: string }): string {
return `Dear ${getFullName(user)},`;
}
By creating a getFullName
function, we now have a single source of truth for how a user's full name is constructed. If we ever need to change it (e.g., to add a middle name), we only have to update it in one place.
By keeping your code DRY, you make it more maintainable, easier to refactor, and less prone to bugs. So next time you're about to copy and paste a block of code, take a moment to think if there's a better way.