D.R.Y., or “Don’t Repeat Yourself” has become a mantra for developers in recent years to the level of becoming dogma. Unfortunately, as with most dogma, it is often viewed more of an absolute rule and less of a general guideline that often should be ignored.
The opposite of D.R.Y.
Since we have this nice cheesy acronym for eliminating redundant code it helps to discuss the topic with an equally cheesy acronym to refer to the reciprocal: W.E.T., or “Write Everything Twice.”
What is worse than W.E.T.?
Coupling is often worse than writing code that is not D.R.Y. And when D.R.Y. is taken to its extreme by dogmatic programmers they often end up with highly-coupled code where code that was similar becomes intertwined to avoid “repeating” and thus multiple use-cases all have to be handled by the same code, increasing complexity and with it test permutations and likelihood of errors in the code.
A real-world example
We are building a desktop app in Go for a developer productivity platform that helps programmers developer and debug their projects. The app itself scans directories for potential programs and load the list as part of loading the app’s JSON-format configuration file. Then each user’s project itself has its own JSON-format configuration file. Finally JSON is generated by the app as part of an API that allows other programs to call and interact with it.
When I first started developing the desktop app I defined a single type of “Project” object. However, as the project evolved I found a constant need to refactor and it became harder and harder to get my Project object to work in all use-cases.
And then it hit me. I was doing it wrong.
Make each use-case independent
So the solution was to create a separate Go package for each use-case and had a different Project object designed specifically for each use-case:
- Lightweight Project object for the app’s config
- Heavy Project object for reading and writing each project’s config.
- Heavy Project object optimized for outputting and update project information via the API
- Several partitioned Project objects for actually working with the project in the app.
Coupling becomes explicit, and easy to debug
In addition to these use-cases I added factory methods to accept one type of project object and transform into another type. While this resulted in a lot more code with what appears to be a lot of duplication, each project object was decoupled from the others and free to address its use-case without all the complexity.
The coupling required to transform from one to another was based on the identities of the objects and not complex logic flow meaning this type of code is much easier to maintain.
Find your own truth
The moral of this story? No matter what “conventional wisdom” says or which “guru” taught it to you, always ask yourself if they actually make sense. Then seek a tangible answer to your own questions before you simply assuming the convention wisdom is correct.
Quite often you will find conventional wisdom was developed prior to more recent discoveries — especially in programming — or the series of storytellers who propagated it so distorted it — as experienced when playing the game of telephone — that it now bares little resemblance to its original intent.
 For non-Go developers, Go does not have classes. Go uses
structs for what would be most similar to a class in Java, PHP, Python, Ruby, .NET and many other programming languages.