Introduction to Readable Javascript

Every so often an article crops up with some big list of rules to follow to keep your code readable - It's something everyone seems to want, but it is also accompanied by a bloodbath in the comments from people who disagree on how to do it.

As a coding beginner, you eventually realise that you do need to make your code readable – people who write the clear, readable code are the ones that get hired, and the ones other developers like working with.

Rather than pass down a big list of rules that you must follow or else, I have attempted to distill advice about readable code into a series of guidelines and rules-of-thumb for you to follow, and steadily build on as you grow.

Why should we make code readable?

A major reason we try to make code readable is to reduce cognitive load - that is to say how much focus we need to maintain to work with it.

Contrary to what you might experience when you're learning to write code, most of the time when working in the industry is spent reading code – It is often much harder than simply writing it yourself (code you wrote a month ago is effectively written by someone else).

For developers, focus is one of the base currencies we deal with (aside from time, caffeine, and money), so being able to keep your code nice and readable is paramount to a code base you enjoy working on.

Keep it simple

A core tenet of readable code is to keep your code simple – Fair warning though, this is far easier said than done. It requires understanding the nuances of the problem, and being able to craft a concise solution that deals with all the edge cases. This kind of solution usually comes with experience with the problem – The best thing to do is to write a shitty solution first, and while it's still fresh in your mind, try and simplify it.

Tell people what you're doing

A core tenet of reading anything is whether we understand what is going on. The simplest way of making sure people understand what is going on is to simply tell them. In code, this comes in two forms: Comments, and the code itself.

Consider the following code example (and I admit it's a little contrived):

function m(a) {
	return a >= 0 ? a << 2 : -(a << 2);
}

This function looks damned complex. It must be doing something important. Here is what it does:

  1. Return the absolute value of a * 4

I bet you didn't guess that.

Now that we know what this function does, it's actually really simple – But why does it look so complex? Answer: it doesn't give us any hints as to what it's even trying to do.

  1. function s gives us very little to go on. We could use absMultiplyBy4 - You could strip it to multiplyBy4 but that would cause confusion when we start getting absolute values back
  2. a tells us nothing. Only by knowing what the function is supposed to do can we tell that it's supposed to be a number. Since this is a mathematical equation, it's hard to provide something exceptionally meaningful, but we can at least hint at the type of variable we expect – num or val would be much better.
  3. Our intention is to multiply by 4. Using a * 4 over a << 2 is much clearer, and falls back on elementary arithmetic instead of obscure binary arithmetic.
  4. We're returning the absolute value in a strange way. num >= 0 ? num : -num is a completely correct way to return an absolute number, but there is the standard library Math.abs to do it for us, and it's far more descriptive.
function absMultiplyBy4(num) {
	return Math.abs(num * 4);
} 

This is far more obvious.

Keep a consistent style

This is one piece of advice that you'll see everywhere, but I'm going to reiterate it here for completeness: Use a style guide. It doesn't really matter which one. If you don't know where to start, I would recommend giving the Google Javascript Style Guide a read - It is google specific, so be sure to skip anything that doesn't seem relevant.

The reason we use style guides is again to reduce the cognitive code of reading your code. It's hard enough to work out what code is doing without also having to work out what the hell is going on with the code style.

Even if you have no intention of using one, a style guide is well worth a read if you're starting out - it can introduce you to aspects of the language you had no idea existed.

The final point I want to make on style guides is what to do when you're working on a piece of code that doesn't conform to the style guide you're using – The correct thing to do in almost every circumstance is to maintain the style of the surrounding code. If you want to update the code to use the style guide, you should do it all at once.

Keep away from magic

An extension of "Tell people what you're doing" is to avoid magic in your codes - For those of you who aren't familiar with magic numbers and strings, they are values littered throughout your code, but it's unclear what they are for, or how similar values are related.

Think about this for a second:

$(elem).css({
	'padding': '15px',
	'margin-left': '-15px',
	'top': '15px',
});

Now I tell you that I want to change the padding from 15px to 20px. What other values do you need to change to account for that?

The answer is that you don't actually know. Any of those 15px values could be related to the padding – It's unclear (as you haven't told us what you're doing).

Now look at this:

const PADDING = 15; //px
const TOP = 15; //px

$(elem).css({
	'padding': PADDING + 'px',
	'margin-left': -PADDING + 'px',
	'top': TOP + 'px',
});

Stay on message

Much like reading a book or an article, reading code is easier if there is a single, cohesive message throughout, and clear breaks when we change our message.

class ScrollAnimationController {
	constructor() {
		let self = this;
		this.elements = $('[data-animate]');
		$(window).on('scroll', function(event) {
			self.elements.forEach(function(elem) {
				let trig = elem.offset().x;
				($(window).scrollTop < trig ? () => {} : () => {
                    if (!elem.hasClass('animating') && !elem.hasClass('animate')) {
                    	setTimeout(() => { elem.toggleClass('animating', false).toggleClass('animate', true); }, self.animationTime);
                    	elem.toggleClass('animating', true);
                    }		
                })();
			});
		});
        this.animationTime = 300;
        this.elem.attr('data-loaded', true);
	}
}

This code snippet is a simple animate-on-scroll controller - It's terrible, and doesn't do the job well, but it's not unlike something you'd find in production code. There is a semblance of a story here, but we get sidetracked too often and lose track of the original thread.

Let's break down what's wrong with this little snippet.

  1. There is no code grouping in this. It's one big mass that becomes hard to read, and related code isn't kept together.
  2. this.animationTime is initialised lower down than it is called. It won't cause any issues at runtime, because the $(window).on('scroll', ...) listener isn't executed until later. It is confusing to the reader though.
  3. (scrollTop > trig ? () => {} : () => {...})() is a really strange way of doing an if {...} statement – It's not immediately obvious what is happening.
  4. setTimeout(()=>{...})  has too much happening on one line. There are 4 distinct points to take in, and it's way too hard to tell where one stops and another starts.

A first pass fix of this block of code

class ScrollAnimationController {
	constructor() {
		let self = this;

		this.elements = $('[data-animate]');
        this.animationTime = 300;
		
        this.elements.attr('data-loaded', true);

		$(window).on('scroll', function(event) {
			self.elements.forEach(function(elem) {
				let trig = node.offset().x;

				if ($(window).scrollTop > trig) {
                    if (!elem.hasClass('animating') && !elem.hasClass('animate')) {
                    	setTimeout(() => { 
							elem.toggleClass('animating', false)
								.toggleClass('animate', true); 
						}, self.animationTime);

						elem.toggleClass('animating', true);
                    }
                }
			});
		});
	}
}

It's already much better, but that entire $(window).on('scroll', ...} block is a mess. We're currently inside constructor so we should be focussing on setting up our object. The scroll function doesn't fit that story, so we should separate it out.

class ScrollAnimationController {
	constructor() {
		let self = this;

		this.elements = $('[data-animate]');
        this.animationTime = 300;
		
        this.elements.attr('data-loaded', true);

		$(window).on('scroll', function(event) {
			self.checkElements();
		});
	},

	checkElements() {
		let scrollTop = $(window).scrollTop;

		this.elements.forEach(function(elem) {
			let trig = elem.offset().x;

			if (scrollTop > trig) {
				if (!elem.hasClass('animating') && !elem.hasClass('animate')) {
                    setTimeout(() => { 
						elem.toggleClass('animating', false)
							.toggleClass('animate', true); 
					}, self.animationTime);

					elem.toggleClass('animating', true);
                }			
            }
		});
	}
}

This is about as good as we're going to get by rearranging the code. Although it's miles better, it's still not too easy to break down and read. So the final step of making this code readable is to add comments to sections that aren't as clear.

class ScrollAnimationController {
	constructor() {
		let self = this;

		this.elements = $('[data-animate]');
        this.animationTime = 300; //ms
		
		//Mark all elements as loaded so we know this function has run
        this.elements.attr('data-loaded', true);

		//Check elements on scroll
		$(window).on('scroll', function(event) {
			self.checkElements();
		});
	},

	/**
	* Triggers animation on all elements above the current scroll position
	*/
	checkElements() {
		const scrollTop = $(window).scrollTop;

		this.elements.forEach(function(elem) {
			let trig = elem.offset().x;

			if (scrollTop > trig) {
				//Set up animation timer the first time this
				//element is visible
				if (!elem.hasClass('animating') && !elem.hasClass('animate')) {
                    setTimeout(() => { 
						elem.toggleClass('animating', false)
							.toggleClass('animate', true); 
					}, self.animationTime);

					//Add animating class to track transition
					elem.toggleClass('animating', true);
                }			
            }
		});
	}
}

Afterword

In the end, code readability is highly subjective - What you find readable someone else might not. You can't please everyone, but that doesn't mean you can't become your own best friend and write code that is easy for yourself.

Also, you aren't going to get all of this first time. It can take a long time to learn to write clear, concise code – So my advice is to write it badly the first time, then before you push your code out to the world: clean it up a little.

Now that you've reached the end of the article, what I want you to do is revisit some old code you have written – the older the better – and I want you to make it more readable. Practice will get you there sooner than articles like this ever will.

Show Comments

Sign up for the Newsletter!

Rather than checking back, drop your name and email below and get more helpful articles like this sent straight to your inbox.

100% Spam free - Guaranteed. Unsubscribe at any time.