Rethinking JavaScript readability
You probably know the famous quote that the code should be written for humans to understand and accidentally for computers to execute. Writing code that compiles is easy. Writing readable code is completely different thing. Working in a team is like sharing the kitchen with your roommates. You all should care for the dishes and keep clean. And it is not only because of the others but because of you. You don’t want your dinner in a mixed place and dirty dishes right.
The problem
JavaScript is an interesting language. Because of its non-strict nature we may write same thing in lots of different ways. The problem is that some of them may be difficult to read.
Let’s for example take a simple questionnaire app and its click handler:
var moveForward = function (event) {
// stores an answer and shows next question
};
nextButton.addEventListener('click', moveForward);
The user presses the next button, the application gets the input, sends it to the server and shows another question. The code is also pretty simple. Here is what the programmer reads:
1. We have a function for moving forward
2. Ah, here is a button that calls that function
Let’s make it complex. We have another button skip which also needs to call the moveForward
function. We still send an answer to the server but we explicitly say that this question is skipped and has no value. The most trivial implementation that follows:
var moveForward = function (event, skipped) {
// stores an answer and shows next question
};
nextButton.addEventListener('click', moveForward);
skipButton.addEventListener('click', function (event) {
moveForward(event, true);
});
Isn’t it ugly and a little bit awkward for the reader.
1. We have a function for moving forward with a skipped flag.
That should be a boolean right?
2. The next button is calling the function above.
3. There is also a skip button that has a handler attached.
What's inside? Ah, it also calls the moveForward function
but sets skipped to true.
So the programmer’s eyes are probably jumping back and forth between the handler of the skip* button and the moveForward
function. Then he/she realizes that the two buttons are doing the same thing but skipped
has a different value.
The code is not immediately clear. Yes, if you code all day long you’ll probably solve that puzzle for a second. However, I think we should all care about improving the readability of such snippets.
Recently I’m thinking a lot about how my code looks like and what every line is doing. More importantly, is it clear for the reader what I meant? Is he able to start making changes right away?
A proposal
A big part of the world is reading from left to right and top to bottom. Ideally when we open a JavaScript file we want to do the same thing. Over the last two months I defined one simple rule which helped me improve the readability of my code:
Write code as you write plain English. Form sentences which are easy to understand and easy to modify.
Let’s take the code above. Our application requires specific behavior which wasn’t there initially. We introduced the skipped
flag which makes the calling of moveForward
a little bit complex. Wouldn’t it better if we define how moveForward
is called but still leave the actual execution to when the click event occurs. Sure, bind
method may do the job.
var moveForward = function (skipped, event) {
// stores an answer and shows next question
};
nextButton.addEventListener('click', moveForward.bind(null, false));
skipButton.addEventListener('click', moveForward.bind(null, true));
We had to change the position of skipped
and event
parameters because bind
passes our custom argument first. Not bad but .bind
is still not clear enough. And because bind
requires a context we have to pass that awkward null
. It will be much better if we write:
nextButton.addEventListener('click', moveForward.callWith(false));
skipButton.addEventListener('click', moveForward.callWith(true));
It does exactly what it says.
Listen for `click` event and call `moveForward` with that parameter.
Writing readability helpers
A common question at JavaScript job interviews is “How you will implement the bind method?”. I saw the pattern for the first time while I was working with Ember. The framework has computed properties that look like that:
App.Person = Ember.Object.extend({
firstName: null,
lastName: null,
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
}.property('firstName', 'lastName')
});
Notice how fullName
function is defined and then we have .property
thingy at the end. When I saw that I dig into Ember’s code and found out that that’s simply a patch to Function.prototype
. We may use the same technique to create our callWith
helper.
Function.prototype.callWith = function () {
var originalFunction = this;
return function () {
// ...
};
};
Clearly callWith
should be a function. And more importantly, should return a function. We define a proxy to the original function that gives us the opportunity to apply additional logic before (or at) the actual executing.
Here is how the finished callWith
looks like.
var argsToArray = function (a) {
return Array.prototype.slice.call(a);
};
Function.prototype.callWith = function () {
var original = this;
var additionalArgs = argsToArray(arguments);
return function () {
var originalArgs = argsToArray(arguments);
return original.apply(this, originalArgs.concat(additionalArgs));
};
};
Along with the original arguments we should call the original function within the right context. So original.apply(this...
is required. Having the lines above we may write:
var whatsUp = function (a, b) {
return a + ' ' + b;
}.callWith('is coming');
whatsUp('Winter'); // Winter is coming
whatsUp('Arya Stark'); // Arya Stark is coming
Bit.js or Digging deeper into Function.prototype patches
Playing with the technique above makes me thing for some other useful utility methods. And as most people nowadays do I started writing a micro library - Bit.js. It’s just a ~2K file that needs to be loaded before the rest of the page’s JavaScript.
For example, for those moments when you need calling a function only once. A second call simply does nothing:
var killJonSnow = function () {
// this could happen only once
}.once();
killJonSnow(); // Nooooo, he is dead
killJonSnow(); // You already killed it
killJonSnow(); // ... seriously, he IS dead
The popular debounce
(or the similar throttle
) function:
var watchGameOfThrones = function () {
// ... fun
}.debounce(604800000); // 604800000 milliseconds === 1 week
watchGameOfThrones(); // It works!
watchGameOfThrones(); // Who dies we'll see next week ... does nothing
watchGameOfThrones(); // Next week bro, next week ... does nothing
Or very often we need to protect a function with if
expression:
var isSamwellTarlyHungry = function (time) {
return time >= 0;
};
var feedNightsWatch = function (time) {
// eating ...
}.callIf(isSamwellTarlyHungry);
feedNightsWatch(8); // eating ...
feedNightsWatch(22); // eating ...
feedNightsWatch(-2); // no eating
Conclusion
Of course you don’t need a library to write readable JavaScript. The same thing could be achieved with creating simple helpers that literally explain what the code is doing. For sure it takes time and sometimes is a matter of balance but readability is important.