Introduction
In software development, there is often a trade-off between clarity and DRYness (Don’t Repeat Yourself). Clarity refers to the degree to which your code is easy to understand, maintain, and modify, while DRYness refers to the degree to which your code avoids duplication and follows the principle of “one logical concept, one piece of code”. While both clarity and DRYness are important qualities of good code, it is often the case that achieving one can come at the expense of the other. In this article, we will explore why clarity is often more important than DRYness when writing software, and how to strike the right balance between these two conflicting goals.
The Need for Clarity
We read more than we write code
One of the main reasons why clarity is often more important than DRYness is that, in the long run, code is read much more often than it is written. This is because, as software evolves and changes over time, developers need to constantly read and understand the existing codebase in order to add new features, fix bugs, or optimize performance. In contrast, writing code is a relatively infrequent activity, compared to the amount of time spent reading and understanding code. Therefore, it is important that your code is clear and straightforward to understand without any hidden dependencies, this way newly onboarded engineers who weren’t there in it’s inception can quickly get up to speed with the codebase.
Too much abstractions
Another reason why clarity is often more important than DRYness is that, in order to be truly DRY, your code needs to be abstract and generic, which can make it less clear and more difficult to understand. For example, if you have a piece of code that calculates the sum of an array of numbers, you might want to make that code DRY by extracting the common elements into a separate function, such as a sum function. However, this can make the code less clear, because the sum function is now abstract and generic, and it is not immediately obvious what it does or how it works. In contrast, if you keep the code in the original form, it is more clear and self-explanatory, even if it is not DRY. Furthermore, in order to achieve DRYness, your code needs to follow a strict set of rules and conventions, such as using the same names, the same signatures, and the same structures for similar concepts. However, these rules and conventions can make your code less flexible and less adaptable, which can reduce its clarity and hinder its evolution.
Restoring Balance
One of the key ways to strike the right balance between clarity and DRYness is to focus on the readability of your code, rather than its brevity or conciseness. This means that you should prioritize making your code easy to follow and understand, over making it short and concise. For example, instead of using complex, abstract, or generic constructs, such as higher-order functions, closures, or generics, you should use simple, concrete, and explicit constructs, such as for-loops, if-statements, or switch-statements, that are easy to read and understand. This will make your code more clear and more readable, even if it is not DRY.
Another way to strike the right balance between clarity and DRYness is to focus on the maintainability of your code, rather than its reusability or modularity. This means that you should prioritize making your code easy to modify, making room for future evolution, over making it reusable and composable. For example, instead of using global or shared functions, variables, or constants, you should use local or private functions, variables, or constants, that are specific to a particular context or scope. This will make your code more clear and more maintainable, even if it is not DRY.
Finally, one of the most effective ways to strike the right balance between clarity and DRYness is to follow the principle of “one concept, one piece of code”. This means that, instead of trying to abstract and generalize your code, you should focus on expressing each concept in a single, self-contained, and coherent piece of code. For example, instead of trying to extract the common elements of a piece of code into a separate function, you should keep the code in its original form, and express each concept in a single, clear, and concise block of code. This will make your code more clear and more readable.
For Example
Let’s say we have two screens, a enter new password
screen and a send reset password link
screen. Both have a single text, input textfield and call to action button. Given their similarities one might be tempted to try to reuse the same screen for both purpose and sort of inject some type of use case logical differentiator into the same class. While this approach will work, it’s DRY but actually problematic and not as flexible for future evolution of the software. What happens when a divergence in their UI emerges ? due to changing requirements or business needs let’s say now we need 3 input textfields for the enter new password
screen, a repeat password input field, and capcha field in addition to the existing UI. Well now we have a tightly coupled screen that’s sort of reusable but not as flexible. We would either have to go decouple our ‘DRY’ screen or add more use case logic somewhere to toggle between the enter new password
and send reset password link
use cases. This is what we sometimes introduce into our codebase when we chase DRY as the guiding north star of our software development practice.
Conclusion
In conclusion, clarity is often more important than DRYness when writing software, because code is read much more often than it is written, and because DRYness can come at the expense of clarity. While striking the right balance between them is important to avoid too much code duplication, you should always focus on the readability, and maintainability, your code, rather than on its brevity, reusability, or modularity. By following these principles, you can write clear, readable, and maintainable code that is easy to understand and that can evolve and adapt to changing requirements.
Happy Coding 🌟