DEV Community

mohamed Tayel
mohamed Tayel

Posted on

C# Clean Code:DRY Principle

Meta Descripation: The DRY (Don't Repeat Yourself) principle is essential for writing clean, maintainable code. Learn how to eliminate code duplication, improve maintainability, and reduce errors by centralizing functionality into reusable components. This article provides a clear example of refactoring code to follow the DRY principle, ensuring efficient and error-free updates to your logic.

The DRY (Don't Repeat Yourself) principle is a fundamental concept in clean coding practices. Its purpose is to eliminate redundancy in code by reducing the repetition of logic and making the system easier to maintain. At first glance, it might seem like common sense, but let’s break down how often developers unintentionally violate this principle.

Imagine you are building a system where you need to perform a specific calculation or retrieve the same type of data in different parts of the codebase. It may feel easy to simply copy and paste a few lines of code whenever needed. However, this approach leads to repeated code, and when something needs to change—say, a formula or logic—the same change must be made in every location where the code was repeated. This is error-prone and time-consuming.

DRY Principle in The Pragmatic Programmer

The book The Pragmatic Programmer describes the DRY principle as: "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system." In essence, each functionality should be written once and only once. If applied correctly, changes to any piece of knowledge only need to be made in one place, without affecting logically related elements elsewhere.

Let’s explore a situation where this principle isn't followed and learn how to fix it.

A DRY Violation Example

Imagine you have two classes in your application: Smartphone and Laptop. Both classes have similar properties such as Name, Price, Brand, and WarrantyPeriod. In each of these classes, there's a method called ApplyDiscount, which calculates the price after applying a discount. The issue here is that the same ApplyDiscount method exists in both classes, violating the DRY principle. Let’s look at how to refactor this.

Smartphone Class (Before Refactoring):

public class Smartphone
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Brand { get; set; }
    public int WarrantyPeriod { get; set; }

    public decimal ApplyDiscount(decimal discountPercentage)
    {
        return Price - (Price * discountPercentage / 100);
    }
}
Enter fullscreen mode Exit fullscreen mode

Laptop Class (Before Refactoring):

public class Laptop
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Brand { get; set; }
    public int WarrantyPeriod { get; set; }

    public decimal ApplyDiscount(decimal discountPercentage)
    {
        return Price - (Price * discountPercentage / 100);
    }
}
Enter fullscreen mode Exit fullscreen mode

In both classes, the ApplyDiscount method does the exact same thing—this is a classic example of code repetition. If we need to change the discount formula, we would have to modify it in both places, increasing the chance for errors.

Refactoring to Follow DRY

To resolve this, we will extract the common functionality into a separate class. Let’s create a DiscountCalculator class that handles the discount logic and call this from both Smartphone and Laptop.

DiscountCalculator Class:

public static class DiscountCalculator
{
    public static decimal CalculateDiscount(decimal price, decimal discountPercentage)
    {
        return price - (price * discountPercentage / 100);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we can modify both Smartphone and Laptop to use this new class instead of having their own duplicate ApplyDiscount methods.

Smartphone Class (After Refactoring):

public class Smartphone
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Brand { get; set; }
    public int WarrantyPeriod { get; set; }

    public decimal ApplyDiscount(decimal discountPercentage)
    {
        return DiscountCalculator.CalculateDiscount(Price, discountPercentage);
    }
}
Enter fullscreen mode Exit fullscreen mode

Laptop Class (After Refactoring):

public class Laptop
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Brand { get; set; }
    public int WarrantyPeriod { get; set; }

    public decimal ApplyDiscount(decimal discountPercentage)
    {
        return DiscountCalculator.CalculateDiscount(Price, discountPercentage);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, the ApplyDiscount logic exists in a single place—the DiscountCalculator class. If the discount formula ever changes, we only need to modify it in one place, reducing the risk of errors and making the code much easier to maintain.

Benefits of Following DRY

  • Maintainability: Changes to logic can be made in one place and reflected throughout the system, simplifying maintenance.
  • Reduced Errors: By having a single source of truth for a particular logic, you minimize the chances of inconsistencies or bugs.
  • Efficiency: Code reuse leads to a more streamlined system, which can improve overall performance.

What About WET?

You may also encounter the term WET, which stands for "Write Everything Twice." This acronym describes the opposite of DRY and is a common mistake among developers, especially when facing tight deadlines or during rapid prototyping.

By following the DRY principle, you ensure that each piece of knowledge has a single, clear representation in your system. This minimizes redundancy and makes your code easier to manage as your application grows.

In conclusion, the DRY principle is essential for writing clean, maintainable code. By centralizing logic into reusable functions or classes, you reduce repetition and improve the reliability of your codebase.

Top comments (0)