In this post we are going explore 5 different ways to achieve the DRY (Don’t Repeat Yourself) principle when building software and systems.
Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. – The Pragmatic Programmer
More code is more complexity which adds a cost of maintainability to the project. For this reason it is very important to always have only one code implementation for a piece of functionality (which is generic enough to handle “simular” functionality) within the system.
You should never be repeating code or system logic. Because Duplication is Evil! (DIE)
Depending on the system architecture and programming paradigm, DRY can be achieved in several ways these are listed below:
1) Generic subroutines
When you need to write new code which has similar (or the exact same) functionality to previous code already in the project you should take the logic (the previous code) and make it a generic callable sub routine, which can be used for multiple purposes.
This generic sub routine can cover all the use-cases via parameter values passed to it.
As in the example below, say we have a e-commerce program. We have the subroutine calcItemPrice which calculates the price of items we sell. The price returned includes $20 for shipping and $2 for a credit card fee.
Now say at a later date our client or boss comes along and says “if the customer lives in the same country as ours we will offer free shipping”. We could add this functionality through a subroutine like we see in calcItemPriceFreeShipping however this is bad and goes against DRY because we are repeating logic.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
calcItemPrice(itemprice){ shipping = 20; return itemprice + shipping + 2;//two dollar credit card processing fee } /*wrong*/ calcItemPriceFreeShipping(itemprice){ return itemprice + 2;//two dollar credit card processing fee } /*correct*/ calcItemPrice_Correct(itemprice,freeshipping){ shipping = 20; if(freeshipping = true) shipping = 0; return itemprice + shipping + 2;//two dollar credit card processing fee } |
Why is it bad? Because we are repeating logic, and this has problems as I explain below.
Now lets say the government comes along at some date in the future and says, “we want to take a tax on all goods sold of 10%”. Instead of having to change code in both sub routines (calcItemPrice and calcItemPriceFreeShipping). We now only have to change it in one (calcItemPrice_Correct).
Our logic now becomes:
1 2 3 4 5 |
calcItemPrice_Correct(itemprice,freeshipping){ shipping = 20; if(freeshipping = true) shipping = 0; return (itemprice + shipping + 2) + 10%; } |
2) Inheritance
In Object Oriented Programming, inheritance by definition helps you achieve DRY ideology. A child class, extends the functionality of its parent class. The parent class is generic in nature and provides functionality to multiple deriving child classes.
For example if you have the parent class “Shape” and the 2 child classes “Square” and “Rectangle” , the Shape class can provide the logic to calculate the shapes area (Width x Height) so this logic is not repeated in both the child classes.
3) Templates and generics
Templates and generics allows us to implement a algorithm which can work with multiple types of input. Eg, we could have a subroutine called calculateAverage() which given a list of numbers computes the average.
Using templates / generics (called differently depending on the type of programming language) we can write the algorithm once to work with both peoples ages (integers) and money eg $1.35 (floating point).
Through templates and generics we eliminate having to repeat the algorithm twice for each data type.
4) DLL or SO
At a higher level (beyond a single program), you can achieve DRY ideology across multiple programs. This is what is called Library’s, Shared Objects or Dynamic Link Library (depending on the operating system). The advantages of this is if code in a shared library has a bug, fixing the bug results in a fix across all programs and systems which utilize the library.
For example, if we are writing a web browser that supports rendering jpeg images. We can use libjpeg (a common library which has the algorithms to decode jpeg compressed images).
Then perhaps tomorrow we are tasked with writing a music application which shows the album cover of the current song from a jpeg image file. We should use the libjpeg library in this program also.
Now lets say there is bad and uncommon bug, that renders red pixels black in both our browser and music app when the image has exactly 12321 red pixels . To fix both programs we only have to change code in the commonly shared library (libjpeg) and both programs benefit from this fix.
5) API provided by multiple systems over a network (service oriented architecture)
A common example is a web API or webservice, in which 2 systems communicate to each other in HTTP. The systems could talk in any protocol (and theres others much more efficient) but HTTP is very common today.
Lets say we run a chain of jewellery stores, and customer come in and want a quote on custom jewellery pieces. We are tasked with developing a program which calculates the cost for the person buying the jewellery. Within this program we could have logic that calls a central system (communicating over the internet) to get the current price for gold.
Another example might be within a bank, all the tellers computers sync with a central time server (a service which is the Single-Source-Of-Truth for the correct time). This is important so that all the transactions are exactly the same and in sync.
Having a single source of truth for the price of gold and the time is important. And the logic to determine the values are not repeated with each computer within the total system.
Conclusion
As seen its important to architect programs and systems to achieve DRY at all levels of a application. Simply put, less duplication of logic is easier to maintain and easier fix when things go wrong.
Is there any other ways to achieve DRY and Single-Source-Of-Truth, that I have left out? Leave a comment below.
Pete
Regarding the “generic subroutines section”…
* You have hard-coded in the shipping and the credit card fee values, so your subroutine is only generic for consumers that use those fee rates. If these are constant then they should be declared as constants, if they are variable then they should either be parameters or variables on the declaring class.
* Your test of the freeshipping variable is needlessly verbose. If it is a boolean then you do not need to test that it is true. Logical test expressions like this compile to give an expression tree that evaluates with a boolean result, so you can just as easily use the boolean variable as the logical test in your if without needing to explicitly testing it for true
* Your assignment of the shipping variable is poorly optimized. You use one branch and potentially two assignment operations where one branch and one assignment could be used (shipping = freeshipping ? 0 : 20;)
* Your 10% tax rate does not function as expected. Instead of returning the new price including the 10% hike, it returns only the value of the 10% tax, without the value of the original item (“* 10%” should actually read “* 110%”)
Jason Whatson
Thanks for the code review, however the point wasn’t to produce perfect optimal code. The point was to show how to rewrite a subroutine to handle a simular but different use case. And why having 2 simular but slightly different subroutines is a problem when we need to come back and change code.
It was written in a my own c-style pseudocode. Not any actual language that can be compiled.
As you suggested, I have changed my “pseudo tax calculation”.
football
It will truly allow you to access to the most up to the minute scores of all playing teams.
Gene Wojciechowski’s ode to college football is a
great read. Among those that came out in the wish list is a better line play, addition of team entrances, and crowd
atmosphere.
Neha
relly important