JavaScript Refactoring Techniques: Specific to Generic Code

In a recent thread on SitePoint’s forums, some code was given to let one dropdown box control when another dropdown box is visible. Even though the code worked just fine, I realized that it left much to be desired. It was brittle and incapable of withstanding even small changes to the accompanying HTML.

Here is the original CSS code:

#second { display: none; }
#second.show { display: block; }

and the original JavaScript code:

document.getElementById("location").onchange = function () {
  if (this[this.selectedIndex].value === "loc5") {
    document.getElementById("second").className = "show";
  } else {
    document.getElementById("second").className = "";
  }
};

In this article, I’ll demonstrate some simple techniques that can be applied to the above code in order to make it easier to reuse and more accommodating to future change.

Knowing Which Path to Take

JavaScript has many ways to achieve the same task, and some of them work better than others. Are there ways to improve the code right now so that we don’t have to come back to it later on? Sure! But when there are several possible methods of doing something, how can we determine which one is likely to work best?

One common technique for improving code is to remove duplication (using the don’t repeat yourself principle). From there though, it can be more useful to go from specific to more generic code which allows us to handle a wider range of situations.

Specific code tends to be brittle when it comes to handling future changes. Code doesn’t exist in a vacuum, and will need to change in response to other actions around it and in the HTML code. With the benefit of past experience though, we can look at common changes that occur and improvements that reduce number of times we need to revisit the code. Invariably you will find that this means making the code more generic.

But beware! It can be easy to make our code too generic to the point that it becomes difficult to understand. Striking a good balance between generic and readable is where we find improved code.

Transforming From Specific to Generic

During the course of test driven development (TDD) you can’t help but to come across this principle as a part of the process:

As the tests get more specific, the code gets more generic.

The Cycles of TDD by Robert C. Martin covers this idea well. The main benefit here is that generic code ends up being able to handle a wider range of situations and scenarios.

Looking at the above code, some obvious specific to generic improvements are immediately available.

After making all of these improvements, we’ll end up with code that is more resilient to future changes, and is easier to update. So let’s get started …

Use Variables to Prevent Duplication

The ID of the dropdown box (“location”) and its trigger value (“loc5”) are useful references to keep together. The second <select> element is also being referred to twice, which we can pull out to a separate variable to prevent clutter and provide easier maintenence.

For example, instead of having two references to the same element that would need to be changed if the element’s ID changed:

// bad code
if (...) {
  document.getElementById("second").className = "show";
} else {
  document.getElementById("second").className = "";
}

We can store a reference to this element in a variable, limiting future change to only the place where the variable is assigned:

// good code
var target = document.getElementById("second");
if (...) {
  target.className = "show";
} else {
  target.className = "";
}

By pulling these strings out together to the top of the code, and separating out the parts of the if condition, the specific to generic technique results in code that is easier to maintain, both now and in the future. If any of the identifiers or option values are changed, they can all be easily found in the one place, instead of hunting through the code for all of their occurences.

// improved code
var source = document.getElementById("location");
var target = document.getElementById("second");
var triggerValue = "loc5";

source.onchange = function () {
  var selectedValue = this[this.selectedIndex].value;
  if (selectedValue === triggerValue) {
    target.className = "show";
  } else {
    target.className = "";
  }
};