Repeat Yourself — sometimes it is a good thing
By: Joana Borges Late
Tags: Programming, Programming Principles, Javascript Tips, JavaScript

DRY it right

Consider a small web application: a page to monitor browser performance while painting a canvas. This code sample is about the part where the most recent information is stored in two lists with 40 items each.

Note: the code samples here are missing “use strict” at the beginning of each .js file.

Original code (acceptable)

The code above works well. It was written in a relaxed way, acceptable considering it is a small test application (1 file, less than 200 lines).

The only difference between the registerStart and registerDuration functions is the names. So we could think of using the DRY principle on them. In fact, DRY is not necessary here (two small functions in a small code). However, let’s go ahead to see what happens:

Refactored code — 1 (bad)

In my opinion, the correct way to apply the DRY principle is to create auxiliary functions in a library: a specific module to ONLY serve other modules. Let’s do it.

The main.js module is great. Much clearer. We don’t even need a comment to understand how a start or duration is registered. We know that the value will be placed at the end of the respective list. We can check each function in the blink of an eye!

In the library.js file, we got an excellent name for the helper function: placeTailInList. Just by the name, we may guess its internal mechanics. placeTailInList uses only universal names like “n”, “list”, and “tail” which we are used to. Easy.

However, we’ve created a problem for the future: we will not be able to change the desired length of starts without also changing the length of durations. Let’s fix this:

Refactored code — 2 (bad)

Good news: we achieved the independence of the starts and durations lists. And we were able to eliminate arbitrary use of the numbers 40 and 39 in (bottom of) placeTailInList.

Bad news: we have summoned a horrendous creature with many frenetic hands: The Mess. There are other problems but our focus must send The Mess away:

Refactored code — 3 (bad)

By dividing placeTailInList into two functions (BOSS and WORKER), we were able to eliminate The Mess!!

The boss function prepares the arguments to be consumed by the worker function which runs perfectly, IGNORING ITS CONTEXT.

We have an imperfection: we are using flags in the form of literal strings. It is very practical but it is easy to create a bug that neither the compiler nor the interpreter can identify. This technique is only acceptable for small programs.

We have a problem: the boss function is making assumptions about the maximum (40) length of each list. Making assumptions is prohibited for a library function. This is the full list of prohibitions for a library function:

it cannot read global variables it cannot write global variables it cannot call functions from other modules it cannot make assumptions about the context

Why so many rules if we can break them all and the code runs smoothly? Because breaking these rules makes the code LESS MAINTAINABLE and more prone to raise bugs in future editions.

Furthermore, what if instead of 2 lists (starts and durations) we had 20 lists to process? Our boss function would not have a DRY appearance. Let's correct it:

Refactored code — 4 (bad)

We improved the boss function (placeTailInList). It can now handle any number of lists without needing to be modified. And we are checking every string flag it receives. Also, we put the maximum length of each list (40) in just one place, with global access.

We have a problem. The boss function is reading a global variable (lengths) and this is prohibited for a library function.

We have another problem. The check of the string flag happens at run time. We are reducing the performance of the execution of the application to make a check that should be done at compile time. It’s time for more fixing:

Refactored code — 5 (good)

Friend, we made it, finally! Our code is so good that we no longer need a boss function in library.js.

The code now is more readable, robust, and maintainable than the original. It has fewer lines than… Oops! After all the refactorings our code now has 30% more lines than the original code. Wasn’t it supposed to DRY?

Humm … we have one last card up our sleeve. The registerStart and registerDuration functions… their content is just a simple line. We could eliminate them and put their content directly where they are called. This would make the code shorter. OK let’s try:

Refactored code — 6 (bad)

OH NO! We have summoned The Mess again. It seems that those 2 functions with only one line each had an important role: FILTER COMPLEXITY. Now we have thrown their complexity (internal details) directly on their callers…

We must refactor one step back!