It's time to be honest with ourselves and admit that we do not treat our frontend code the same way we treat our backend code, and this shows. When we talk about our code, we talk about applying the rules of clean code, about keeping it simple, about writing unit tests. Yet, when it comes to writing our frontend code, we knowingly skip all those rules that we usually self-impose. We do that because we somehow 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 crap. 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 things that we usually recognize as Bad Things™ for backend: duplication, coupling, clutter, lack of unit tests, bad naming, etc. Let's take a closer look at some of these anti-patterns showing up in our frontend code.
Surely you are familiar with the concept of a God Class: a class having thousands of lines of code, behaving like an omniscient, omnipotent, omnipresent god. In the backend, we would normally split that class into multiple loosely coupled, single-responsibility classes. However, this is not the case of frontend code, where we let HTML or JSP files grow like they're set to enter the Guiness World Records book. Use your 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 you to include in your project. Web Components are even designed by the World Wide Web Consortium (W3C) as a set of features to be supported directly by the browsers to allow the creation of 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 code 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"/>
unde formInput este un template având următorul conținut:
<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 that 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, etc. 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. In the future, you will thank yourself for it.
Usually, the JavaScript code in a project is not unit tested. Sometimes it's fine because they are only a few lines that do some cool animation tricks or a bit of input validation. However, a few lines can quickly turn into tens, hundreds and then thousands of lines. Before you know it, you've got yourself 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 contain code dealing with backend communication, data validation, error handling, application logic, state management and visual rendering. In order to be easily testable, that function must be split in such a way that each concern listed above is dealt with separately, in isolation from the others.
$(document).ready(function() {
$.get('/siteSections', function(subsectionNames) {
for (i = 0; i < 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 < 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 that allows us to set and get the width of a site section through the JQuery API. We have pushed JQuery outside our setWidthOnSiteSections function. Now, we can easily test the setWidthOnSiteSections function because we can pass anything we want for the siteSection parameter. We can use our favorite mocking and stubbing library, or pass a stub built by ourselves 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 this: immediately invoked function expressions, using let instead of var to limit the scope of the variable, or bundling features into ES6 modules.
We took a hard look at the state of our frontend codebase and we admit that 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 that pop up in this medium as well. All it takes is some "can-do" attitude that will open up a path towards a better tomorrow, where we are neither afraid nor disgusted to touch the frontend.