The sorry state of frontend code

March 12, 2018

Let’s be honest, we do not treat frontend code the same way we treat backend code. And it shows. We talk about applying rules of clean code, about keeping it simple, about writing unit tests. Yet, when it comes to writing frontend code, we skip all those rules that we usually self impose. We convinced ourselves that frontend code is inferior to backend code. “That’s where the powerhorse lays: in the backend! The backend does all the important work. Frontend is nothing. It’s just used to display all that data that the backend worked hard for. So it is only natural that we don’t need to spend that much time on the frontend code. Quick and dirty will do it. We’ll revisit it later when things aren’t so hectic.”

But we never do, do we? It’s time to face the harsh reality that our frontend code is garbage. It is time to apply the Boyscout Rule to the frontend code too: Leave the campground cleaner than how you found it. Whenever we dive into the frontend code to add something to it, we should take a moment to gaze at the mess. Then, we should grab our golden shovel and dig our way out.

But what’s wrong with our frontend code, you ask? Well, not surprisingly, the same Bad Thingsā„¢ we shun from backend: duplication, coupling, clutter, lack of unit tests, bad naming, and so on. Let’s take a closer look at some of these anti-patterns showing up in our frontend code.

Not adhering to clean code

Surely you are familiar with the concept of a God Class having thousands of lines of code, behaving like an omniscient, omnipotent, omnipresent god. In the backend, we split it into many loosly coupled, single responsibility classes. But not in the frontend, where we let HTML or JSP files grow like they’re set to enter the Guiness World Records book. Use an equivalent of the JSPs include tag to compose the smaller view files into what was formally a God View file:

<jsp:include page="header" />
<jsp:include page="navigationMenu" />
<jsp:include page="landingPageDescription" />
<jsp:include page="latestNews" />
<jsp:include page="contactForm" />
<jsp:include page="footer" />

Next, find some duplication that you could refactor. Take for example the HTML code you copy-pasted to create another form, menu list or tab group. Javascript frameworks such as React, Angular and Polymer offer ready made web components for your project. The W3C are designing Web Components as a browser feature to create reusable widgets.

There is another subtler kind of duplication that you can find in your frontend code. For example, let’s take a look at the following code:

<div class="form-group">
    <label for="firstName"/>
    <input type="text" name="firstName" value="${user.firstName}"/>
</div>
<div class="form-group">
    <label for="lastName"/>
    <input type="text" name="lastName" value="${user.lastName}"/>
</div>
<div class="form-group">
    <label for="email"/>
    <input type="text" name="email" value="${user.email}"/>
</div>

At first glance, there is nothing wrong with the above. However, it takes a few seconds of mental processing to understand that the code refers to three form input fields. We can improve it, re-writing it like this:

<tmpl:formInput field="firstName"/>
<tmpl:formInput field="lastName"/>
<tmpl:formInput field="email"/>

where formInput is a template with the following contents:

<div class="form-group">
    <label for="${field}"/>
    <input type="text" name="${field}" value="${user[field]}"/>
</div>

In this second version, you can understand almost instantaneously that we’re dealing with three form input fields. Behold, we’ve applied the principles of encapsulation and single responsibility in the frontend!

There are several other code smells you can find in your frontend code: inconsistent naming, abbreviations, names that do not clearly express intent, duplication in CSS classes, commented code, comments that are no longer in sync with what the code does, and so on. Do not let it spread further! Take your pick and fix one small thing every day. By the end of the year, your codebase will be in a much better shape. Future you will thank you for it.

Lack of unit tests

Usually, JavaScript code in a project is not unit tested. Sometimes it’s fine, only a few lines doing cool animation tricks or input validation. But a few lines can quickly turn into tens, hundreds and then thousands of lines. Before you know it, there’s a pile of untested JavaScript code handling state and application logic. Writing unit tests for that kind of code isn’t impossible. You just have to take a look at it and figure out what prevents you from easily writing those unit tests. Remove those impediments one by one and you’ll get there. Here are a few common problems and solutions:

High coupling

It is far too easy in JavaScript to mix different concerns into one place. A single function might include backend communication, data validation, error handling, application logic, state management and visual rendering. That function must be split in such a way that each concern listed above is dealt with separately, in isolation from the others. Thus, the function becomes easily testable.

Third party dependencies

Your code might be tightly coupled to a library such as JQuery. You could isolate the 3rd party library by moving it up the call chain, inverting the dependency and doing some refactoring along the way. Let’s take a typical example:

$(document).ready(function() {
    $.get('/siteSections', function(subsectionNames) {
        for (i = 0; i &lt; subsectionNames.length; i++) {
            var idName = "#" + subsectionNames[i];
            var width = $(idName).width();

            if (idName =="#blog" || idName =="#calendar" || idName =="#contact")
                width += 15;

            $(idName + "-bg").css("width", width);
        }
    });
});

The purpose of the code above is to add 15 pixels to the width of certain site sections (namely “blog,” “calendar” and “contact”) by adjusting a CSS property. The following piece of refactored code is equivalent:

var siteSection = function(idName) {
    return {
        getWidth: function() { $(idName).width() },
        setWidth: function(width) { $(idName).css("width", width) }
    }
};

var setWidthOnSiteSections = function(subsectionNames, siteSection) {
    for (i = 0; i &lt; subsectionNames.length; i++) {
        var idName = "#" + subsectionNames[i];
        var width = siteSection(idName).getWidth();

        if (idName == "#blog" || idName == "#calendar" || idName == "#contact") {
            width += 15;
        }

        siteSection(idName + "-bg").setWidth(width);
    }
};

$(document).ready(function() {
    $.get('/siteSections', function(subsectionNames) {
        setWidthOnSiteSections(subsectionNames, siteSection)
    });
});

Notice how the setWidthOnSiteSections function no longer depends on JQuery. We extracted the dependency into a special siteSection function to adjust the width of a site section through the JQuery API. We have pushed JQuery outside of our setWidthOnSiteSections function. Now we can easily test the setWidthOnSiteSections function, passing anything we want for the siteSection parameter. Use your favorite mocking and stubbing library, or pass a stub built through the dynamic nature of the JavaScript language.

Global namespace pollution

JavaScript has a global scope where all the variables and functions end up if they’re not properly isolated. This can lead to naming conflicts and subtle bugs. There are several ways to avoid that: immediately invoked function expressions, using let instead of var to limit the scope of the variable, or bundling features into ES6 modules.

Side effects

Even if JavaScript is single threaded, you can still run into race conditions and bugs related to side-effects. In fact, this is one of the reasons the React framework appeals to such a broad audience of developers. A proper React application is developed with immutability and pure functions. Data flows unidirectionally from top-level components to the lower-level ones. As such, whenever something goes wrong within a React application, it is trivial to find the culprit and fix the issue. Whether or not you use React, it is very useful to follow these patterns to avoid side effects and headaches. As a bonus, it will make your application easier to test.

There is light at the end of the tunnel

We took a hard look at the state of our frontend codebase and we admit it is not a pretty sight. But we should not despair, for there is hope. We’ve seen this before and, thankfully, there are ready-made solutions to these problems. A “can do” attitude will open up a path toward a better tomorrow, where we are neither afraid nor disgusted to touch the frontend.

Categorised in: , , ,

Leave a Reply

Your email address will not be published. Required fields are marked *