A List Apart

Menu
Illustration for the Coding with Clarity article

Illustration by Dougal MacPherson

Coding with Clarity

Working code isn’t necessarily good code. Your code also needs to be easy to read, understand, and modify. It needs clarity, and to achieve that, it has to be organized well, with careful planning and proper separation of ideas taking place before you even open your code editor. Coding for clarity is something that separates the great developers from the merely good, and there are a few basic principles that can set you on that path.

Article Continues Below

Note: Though the principles in this article are applicable to a variety of programming languages, most of the examples pull from object-oriented JavaScript. If you’re not familiar with this, A List Apart has articles on the module pattern and prototypal inheritance to help bring you up to speed.

The single responsibility principle

Imagine you’re working on a home project and you pick up a drill to drive a screw into a wall. When you pull the drill away from the screw, you discover that this drill has an interesting feature: it squirts a quick-drying drywall compound over the driven screw to hide it. Well, that’s great if you want to paint over the screw, but that’s not always the case. You shouldn’t have to get a second drill just to drill a hole in something. The drill would be much more usable and reliable if it just did one thing, and it would also be flexible enough to use in a variety of situations.

The single responsibility principle states that a block of code should do one thing, and do it well. Like the drill above, limiting its functionality actually increases the usefulness of a block of code. Coding this way not only saves you a lot of headache, but it will save future developers on the project a lot of headache as well.

Think of functions and methods in terms of responsibilities. As you increase its responsibilities, a block of code becomes less flexible and reliable, more demanding of changes, and more susceptible to errors. For the most clarity, each function or method should have one responsibility.

If you’re describing what a function does and you have to use the word “and,” that function is probably too complex. What a function does should be simple enough to explain with only a descriptive function name and descriptive arguments.

I was tasked recently with creating an electronic version of the Myers-Briggs personality test. I’d done this before, and when I first approached the problem a few years ago, I coded one giant function called processForm—it gathered the scores, generated the charts, and took care of everything in the DOM to display things to the user.

The problem was that if anything had to change, you had to search through a mountain of code to figure out where to make the alteration. Also, if something went wrong in the middle of the function, it was a lot harder to find the error.

So when facing the problem this time, I broke everything down into single-responsibility functions wrapped up in a module object instead. The resulting function called upon form submission looked like this:

return {
    processForm: function() {
        getScores();
        calculatePercentages();
        createCharts();
        showResults();
    }
};

(View complete app here)

Extremely easy to read, understand, and modify—even a non-coder can make sense of this. And each of those functions does (you guessed it!) only one thing. This is the single responsibility principle in action.

If I wanted to add form validation, rather than having to modify a giant working function (potentially breaking it), I could simply add a new method. This approach also enables related logic and variables to be segmented off, cutting down on conflicts for greater reliability, and it makes it very easy to reuse the function for other purposes if needed.

So remember: one function, one responsibility. Large functions are where classes go to hide. If a function does lots of things that are closely tied together and that are working with the same data, it would make more sense to break it up into an object with methods, much like I did with my large form function.

Command-query separation

The funniest email chain I’ve ever seen was the series of Missing Missy posters from David Thorne about a missing cat. Each time his coworker Shannon makes a request, David complies, but puts his own little twist on it and delivers something different than what was expected. The exchange is very funny and worth a read, but it’s less funny when your code does the same thing.

Command-query separation provides a basis of safeguarding your code against unintended side effects to avoid surprises when functions are called. Functions fall into one of two categories: commands, which perform an action, and queries, which answer a question. You should not mix them. Consider the following function:

function getFirstName() {
    var firstName = document.querySelector("#firstName").value;
    firstName = firstName.toLowerCase();
    setCookie("firstName", firstName);
    if (firstName === null) {
        return "";
    }
    return firstName;
}
 
var activeFirstName = getFirstName();

This is a simplistic example—most side effects are harder to find—but you can see some potentially unanticipated side effects in action.

The function name, getFirstName, tells us that the function is going to return the first name. But the first thing it does is convert the name to lowercase. The name says it’s getting something (a query), but it’s also changing the state of the data (a command)—a side effect that is not clear from the function name.

Worse, the function then sets a cookie for the first name without telling us, potentially overwriting something we could have been counting on. A query function should never, ever overwrite data.

A good rule of thumb is that if your function answers a question, it should return a value and not alter the state of the data. Conversely, if your function does something, it should alter the state of the data and not return a value. For maximum clarity, a function should never return a value and alter the state of the data.

A better version of the code above would be:

function getFirstName() {
    var firstName = document.querySelector("#firstName").value;
    if (firstName === null) {
        return "";
    }
    return firstName;
}
 
setCookie("firstName", getFirstName().toLowerCase());

This is a basic example, but hopefully you can see how this separation can clarify intent and prevent errors. As functions and code bases become larger, separation becomes much more important, as hunting for the function definition whenever you want to use it just to find out what it does is not an efficient use of anybody’s time.

Loose coupling

Consider the difference between a jigsaw puzzle and Lego blocks. With a jigsaw puzzle, there’s only one way to put the pieces together, and there’s only one finished product. With Lego, you can put the pieces together any way you want to make any end result you want. If you had to pick one of these types of building block to work with before you knew what you’d be building, which would you choose?

Coupling is a measure of how much one program unit relies on others. Too much coupling (or tight coupling) is rigid and should be avoided. That’s the jigsaw puzzle. We want our code to be flexible, like Lego blocks. That’s loose coupling, and it generally results in much greater clarity.

Remember, code should be flexible enough to cover a wide variety of use cases. If you find yourself copying and pasting code and making minor changes, or rewriting code because code changed somewhere else, this is tight coupling in action. (For example, to make the getFirstName function from earlier reusable, you could replace the hard-coded firstName with a generic ID passed to the function.) Other signs of this include hard-coded IDs in functions, too many function parameters, multiple similar functions, and large functions that violate the single responsibility principle.

Tight coupling is most prevalent in a group of functions and variables that really should be a class instead, but it can also happen when classes depend on methods or properties from other classes. If you’re having trouble with interdependencies in functions, it’s probably time to think about breaking your functions into a class.

I encountered this when looking at some code for a series of interactive dials. The dials had a number of variables, including dimensions, handle size, fulcrum size, and more. Because of this, the developer was forced to either use an absurd amount of function parameters or create multiple copies of each function with the variables hard-coded in each one. Additionally, each dial did something different when interacted with. This led to three sets of nearly identical functions—one for each dial. In short, coupling was increased due to the hard-coding of variables and behavior, so, like a jigsaw puzzle, there was only one way to put those pieces together. The codebase was unnecessarily complex.

We solved the problem by breaking up the functions and variables into a reusable class that was instantiated for each of the three dials. We set up the class to take a function as an argument for output, so different outcomes could be configured when the individual dial objects were instantiated. As a result, we had fewer functions, and the variables were stored in only one place, making updates much easier.

Classes that interact with each other can also be culprits of tight coupling. Let’s say we have a class that can create objects of another class, like a college course that can create students. Our CollegeCourse class works fine. But then we need to add a parameter to the constructor of the Student class. Oh no! Now we have to modify our CollegeCourse class to account for the change in the Student class.

var CollegeCourse = (function() {
    function createStudent_WRONG(firstName, lastName, studentID) {
        /*
        If the Student constructor changes, we'll have to modify this method and all calls to it, too!
        */
    }

    function createStudent_RIGHT(optionsObject) {
        /*
        Passing an object as an argument allows the Student object to deal with the change. We may need to change this method, but we won’t need to change any existing calls to it.
        */
    }
}());

You shouldn’t have to modify a class because another class changes. This is a classic case of tight coupling. Constructor parameters can be passed as an object with the receiving object having fallback default values, which loosens coupling and means code won’t break when you add new parameters.

The point is that you should build your code like Lego blocks, not like jigsaw puzzle pieces. If you find yourself facing problems similar to the ones above, the problem is probably tight coupling.

High cohesion

Have you ever seen a kid clean a room by stuffing everything into the closet? Sure, it works, but it’s impossible to find anything and things that don’t belong together often get placed right next to each other. The same can happen with our code if we don’t strive for a high level of cohesion.

Cohesion is a measure of how much the various different program units belong together. A high level of cohesion is good and adds clarity to code blocks; a low level of cohesion is bad and leads to much confusion. Functions and methods in a code block should make sense together—they’ll have a high level of cohesion.

High cohesion means sticking related things, like database functions or functions relating to a particular element, in one block or module. This helps not only with understanding how such things are laid out and where to find them, but also with preventing naming conflicts. If you have 30 functions, the chances of a conflicting name are far greater than if you have 30 methods split over four classes.

If two or three functions use the same variables, they belong together; this is a great case for an object. If you have a series of functions and variables that control a page element, like a slider, it’s a great opportunity for high cohesion, so you should bundle them up into an object.

Remember the example above about the class we made that decoupled the solution for the dial? That’s a great case of high cohesion as a cure for tight coupling. In that case, high cohesion and tight coupling were on opposite ends of a sliding scale, and focusing on one fixed the other.

Repeated code is a sure sign of low cohesion. Similar lines of code should be broken into functions, and similar functions should be broken into classes. The rule of thumb here is that a line of code should never be repeated twice. In practice, this isn’t always possible, but for clarity’s sake you should always be thinking about how to cut down on repetition.

Similarly, the same bit of data should not exist in more than one variable. If you’re defining the same bit of data in multiple places, you definitely need a class. Or if you find yourself passing references to the same HTML element to multiple functions, the reference should probably be a property in an instance of a class.

Objects can even be put inside other objects to increase cohesion further. For example, you might put all AJAX functions in a single module that includes objects for form submission, grabbing content, and login syntax, like so:

Ajax.Form.submitForm();
Ajax.Content.getContent(7);
Ajax.Login.validateUser(username, password);

Conversely, you shouldn’t throw unrelated things together in the same class. An agency I used to work for had an internal API with an object called Common that had a hodgepodge of common methods and variables that had nothing to do with each other. The class became huge and confusing simply because there was little thought given to cohesion.

If properties are not used by multiple methods in a class, this can be a sign of low or bad cohesion. Similarly, if methods can’t be reused in a few different situations—or if a method isn’t used at all—this can also be a sign of low or bad cohesion.

High cohesion helps to alleviate tight coupling, and tight coupling is a sign that greater cohesion is needed. If the two ever come into conflict, though, choose cohesion. High cohesion is generally a greater help to the developer than loose coupling, although both can usually be accomplished together.

Conclusion

If our code is not immediately clear, problems occur. Achieving clarity is about so much more than proper indentation—it takes careful planning from the beginning of the project. While tough to master, abiding by the principles of single responsibility, command-query separation, loose coupling, and high cohesion can improve clarity in our code greatly. It should be a consideration in any significant programming project.

8 Reader Comments

Load Comments