Reframing the DRY principle as DRUM
As a fledgling programmer you’re bombarded by commandments telling you what’s considered right programming and wrong programming. These edicts can seem very useful and on the surface seem irrefutable. But if they are applied without deeper thought they can do more harm than good. One of the worst offenders is ‘thy code shalt have abundant comments’. Another questionable one is the DRY principle.
‘DRY’ is an acronym meaning: Don’t Repeat Yourself. It is often interpreted to mean there should be no similar blocks of code in a codebase. And certainly no copy-pasted code. But in my opinion this interpretation is too broad. I’d like to propose a new acronym: ‘DRUM’ (Don’t Repeat Universal Meaning).
Take the following example:
Our business domain has two commands:
- Produce an account statement
- Produce a payment overdue notice
In the code above, these two commands have a handler. This handling code is run whenever a command is executed. The code is very similar. In each handler the following happens:
- Retrieve the account based on the ID in the command data
- Initialise a variable called
$accountTotal
- Loop though all the items in the account and increment the
$accountTotal
by the item’s total - Generate a PDF by passing the template path, account and account total to a PDF generator
The only difference is the template that’s used when generating a PDF.
If we were thinking in a ‘DRY’ way, we’d dutifully set about removing the repeated code:
What we’ve done here is:
- Moved the block of code that was repeated in the previous example into an extendable class
- Made the single part that differed (the PDF template path) into a variable
- Extended that class twice
- In both overridden classes: call the parent class, passing in the template path
By doing this we have successfully not repeated ourselves.
But it has drawbacks. If the way in which our system handles one of these commands changes (for example if the account statement needs to become a CSV instead of a PDF) our situation would be:
- We’d have to introduce more exceptions which means a more fragile design and harder to understand logic
- The refactor would affect both handlers— so the scope of the change would be larger — meaning more potential for bugs and more broken unit tests
These issues have all arisen because in the original example we were mostly only superficially repeating ourselves — which is not a problem at all. The code looked similar only because we happen to be producing an account statement and a payment overdue notice in a similar way. But these two things are not the same. They are two independent concepts and it’s a mistake to couple them.
If we now approach the original code in terms of ‘DRUM’ (Don’t Repeat Universal Meaning) we can craft the following:
We now have two non-coupled handlers for managing the two very separate domain commands. If the way in which our system produces the output of one of these commands changes: we’ll be able to easily adapt to that change without concern for the other command handler. This negates the drawbacks listed above.
However, you’ll notice that there was one aspect of the code that was not repeated. This was the way in which the account total was calculated. This is because it’s a piece of universal meaning (and we Don’t Repeat Universal Meaning). The way in which we avoided repeating the universal meaning was to abstract that functionality away. In the case above we have used the SOLID principles of OOP to inject that functionality as a dependent class. But the principle could be applied to other programming paradigms like functional programming.
The take-aways of this article are:
- When the DRY principle comes to mind think in terms of what exactly it is you’re trying to avoid repeating. Is it universal meaning? Or is it something superficial?
- Ask yourself: when one of these things changes, should they all change in all circumstances?
- Copy and pasting code isn’t always a bad option 😈