Violating the DRY Principle : when duplication leads to better outcomes
Photo by Luca Bravo on Unsplash
Recently, while working on a project, I began to reflect on the application of the DRY principle. I often avoid repetition, ensuring that every piece of knowledge in the system had a single, unambiguous representation. This approach had served me well in keeping a clean and maintainable codebase.
However, I realized that there are times when strictly adhering to DRY might not be the best approach. This insight led me to consider the scenarios where violating the DRY principle is not only acceptable but can actually lead to better outcomes.
The DRY (Don't Repeat Yourself) principle is often touted as a fundamental best practice, aiming to reduce redundancy and ensure maintainability. However, like many other guidelines in software development, DRY is not an absolute rule but a heuristic.
When breaking the DRY principle is a good idea
One of the core principles that can guide us in understanding when to violate the DRY principle is the Single Responsibility Principle (SRP) from the SOLID principles. The SRP states that "A class should have only one reason to change."
The term “reason” refers to a distinct cause or factor that might necessitate changes in a class. This can be seen as a specific domain responsibility or concern that the class is intended to address.
Let's see an example :
Imagine a scenario in a student management system. We have a Student
class with two methods: one to determine if a student is passing their courses and another to check if they qualify for a scholarship.
Below is a Pseudocode :
class Student:
function isPassing():
return calculateTotalPoints() > 50
function qualifiesForScholarship():
return calculateTotalPoints() > 90
private function calculateTotalPoints():
// Calculate total points from all courses
// ...
Initially, this works well. The calculateTotalPoints
function is used to evaluate both whether a student is passing and if they qualify for a scholarship.
One thing to keep in mind is that if business rules come from different sources, we should assume that they are more likely to change independently. In this case, the management of scholarship can be different from the one who is managing the remaining part. They can be even in different modules, maintained by a different developer or different team.
So when the rules change. The scholarship administration decides that only specific courses should count towards scholarship eligibility, while all courses should still count towards passing. A developer, tasked with updating the system, modifies the calculateTotalPoints
function to exclude certain courses:
private function calculateTotalPoints():
// Calculate total points,
//omitting non-qualifying courses for scholarships
// ...
The developer changes this function to meet the new scholarship criteria. However, since isPassing
also relies on calculateTotalPoints
, this change unintentionally affects it as well. As a result, some students who should have passed are incorrectly flagged as failing because the calculation logic was altered.
It is true that we could easily prevent such a situation if we had unit tests, but this situation highlights a key issue, the calculateTotalPoints
function has multiple reasons to change, violating the SRP. By conflating the logic for passing and scholarship eligibility, the function is burdened with multiple responsibilities.
Now, let’s imagine how angry they will be !
Conclusion
In some cases, slight repetition of code can actually make it clearer and easier to work with. By avoiding code repetition, you might inadvertently increase dependencies between unrelated parts of the system, complicating future changes.
Knowing when to apply the DRY principle and when to allow some repetition is crucial for creating code that is not only efficient but also clear, maintainable, and adaptable.
The DRY principle is an essential concept in software design, encouraging efficiency and minimizing repetition, but it requires thoughtful application (as with all principles and patterns). Less code doesn’t always mean better code.
Peace ✌️