I’ve been working on an old code base recently, a one which I have inherited, and looking through it I was frustrated at a lot of the code involved. Don’t get me wrong, that’s normal for most programmers when they inherit an old project, but I’m not sure this followed good programming practices from when it was built (about 7 years ago).
Looking at this made me go back and look at some of my old code, and look at how it could have been made better. As part of this I looked at some programming basics which I have undoubtedly violated several times over the years, and realised that a lot of basics seem to be forgotten once a project comes along with a tight deadline.
The first of these I came across was DRY programming. I’ll avoid the easy jokes around that, and elaborate: DRY is Don’t Repeat Yourself. It’s something that people don’t think about when they are learning, or trying to hit a deadline and things just need to work. It’s one of those things that get left by the wayside and added to the “I’ll refactor that later” list. Everyone has them. No-one gets to them.
DRY programming means spotting early on what you’re going to need more than once - sometimes even as you’re writing it - and writing it in such a way that you don’t have to type that whole code block again, but instead you can call a function to run it, include it as separate section, or otherwise make it easy to access.
Sometimes this is easy to spot, others less so. But sometimes it’s not even always about refactoring out to somewhere different. It can be as simple as realising there’s a better way of writing something. As I deal with PHP most of the time, let’s take an example of building a drop down for the expiry year of a credit/debit card. You can write out each of the years as options individually:
<select name=”expiry”>
<option>2015</option>
<option>2016</option>
<option>2017</option>
<option>2018</option>
<option>2019</option>
<option>2020</option>
</select>
That works. Though at the end of this year, I have to remember to edit the file, remove the 2015 option, and add a new one to the end for 2021. Each and every year I’ll have to remember to edit the file and remove the year just passed, and add a new one to the end. Not only that, but I’ve had to type all the years out manually. Ok, so I copied and pasted, then changed the number. It’s still repetition. What would you then do if you wanted a year range of 100 years? That’s a lot of typing or copying and pasting.
In this case, we can use DRY to realise there’s something happening multiple times, and not require the repetition. Loops in this case.
echo “<select name=’expiry’>”;
$start_year = date(‘Y’);
for ($i = $start_year; $i < $start_year + 6; $i++)
{
echo “<option>” . $i . “</option>”;
}
echo “</select>”;
Okay, so in this example we only save one line of code, but if we wanted more years then we would save much more. This also has the advantage of not needing to be edited every year to make the dates relevant, so we save time each year too.
As a slightly more real-world example, when I was playing with HTML5 canvas for a project, I started writing CanvasShapes, a JavaScript library for a few simple shapes on HTML5 canvas. Nothing too clever, just a small library.
As part of this, I had the code for building a square:
this.square = function(sideLength, leftPosition, topPosition) {
this.context.beginPath();
this.context.rect(leftPosition, topPosition, sideLength, sideLength);
this.context.fill();
// only draw a line if we want one.
if (this.lineWidth > 0)
{
this.context.stroke();
}
};
And the code for a rectangle:
this.rectangle = function(width, height, leftPosition, topPosition) {
this.context.beginPath();
this.context.rect(leftPosition, topPosition, width, height);
this.context.fill();
// only draw a line if we want one.
if (this.lineWidth > 0)
{
this.context.stroke();
}
};
I then realised that a square is basically a rectangle with all the sides the same, and so refactored to:
this.square = function(sideLength, leftPosition, topPosition) {
this.rectangle(sideLength, sideLength, leftPosition, topPosition);
};
It does the same job, but means if I need to change the way the shapes draw, then I only need to change it in one place. The rectangle later evolved to become:
this.rectangle = function(width, height, leftPosition, topPosition) {
this.context.beginPath();
this.context.rect(leftPosition, topPosition, width, height);
this.drawShape();
};
The filling of the shape and drawing of the lines if required were the same in every shape, and so I was repeating myself.
I did have the luxury of writing that in my spare time, so I could take the time to refactor as I went. But the process of it should be part of development anyway. Think “Am I going to do this more than once?”. If the answer is yes, then there’s probably a better way than writing the code in several places.
Another real world example (without code) was when I was working on a different set of code and it had error handling as part of the response. There were three places where the error could be returned from, so the list of all the error codes and their meaning was in those three places. If a new code was added, it had to be added three times. If a new place they could occur was introduced, the code needed to be copied and pasted to that location.
When I was working in this area and needing to use the errors, I saw that I would need to copy and paste the code, or I would need to refactor it out into a separate location in order to stop the endless duplication which could arise. I opted for the latter for sanity reasons (and best practices) which was welcomed when a new error code was added two weeks later.
So next time you’re writing code, remember: Don’t Repeat Yourself.
Update: This post was updated on 4th June 2020 to reflect the migration of the repository to GitLab from GitHub
This was originally posted on my blog at https://www.garybell.co.uk/coding-basics-dry-programming/ on 27th July 2015
Photo credit: Tobias Jelskov on Unsplash
Top comments (2)
One point that is often overlooked with DRY is that you should repeat yourself when two things solve different problems (that is, mean different things) coincidentally in the same way.
Otherwise you'll introduce an accidental dependency between two things that are only accidentally the same.
Really, it should be Don't Repeat Your Meanings, but that's not so catchy.
True. That's one of those things that comes with time and experience, though. Something which can't easily be taught.
The problem with things like principles like DRY, YAGNI, and SOLID are that they are, ultimately, ideals. It's not always straightforward to keep code adhering to all of those due to time and/or budget constraints, as well as the human element i.e. developers. They are more like guidelines which you should be conscious of when developing, but be aware you aren't likely to stick to them all of the time.