blog

  • Home
  • blog
  • The Importance of Writing Code That Humans Can Read

The Importance of Writing Code That Humans Can Read

Have you ever finished a project in a single run without ever needing to look at the code again? Neither have I. When working on an older project, you probably want to spend little to no time figuring out how code works. Readable code is imperative to keep a product maintainable and to keep yourself, and your colleagues or collaborators happy.

Exaggerated examples of unreadable code can be found on JS1k contests, where the goal is to write the best JavaScript applications with 1024 characters or less, and JSFuck, an esoteric programming style which uses only six different characters to write JavaScript code. Looking at code on either of these sites will make you wonder what is going on. Imagine writing such code and trying to fix a bug months later.

If you surf the internet regularly, or build interfaces, you may know that it’s easier to quit a large, bulky form than one that seems simple and small. The same can be said about code. When perceived as easier to read and to work on, one may enjoy working on it more. At least it will save you tossing out your computer in frustration.

In this article, I’m going to look at tips and tricks to make your code more readable, as well as pitfalls to avoid.

Code Splitting

[author_more]

Sticking with the form analogy, forms are sometimes split in parts, making them appear less of a hurdle. The same can be done with code. By splitting it up into parts, readers can skip to what is relevant for them instead of ploughing through a jungle.

Across Files

For years, we have been optimising things for the web. JavaScript files are no exception of that. Think of minification and pre-HTTP/2, we saved HTTP requests by combining scripts into a single one. Today, we can work as we want and have a task runner like Gulp or Grunt process our files. It’s safe to say we get to program the way we like, and leave optimization (such as concatenation) to tools.

// Load user data from API
var getUsersRequest = new XMLHttpRequest();
getUsersRequest.open('GET', '/api/users', true);
getUsersRequest.addEventListener('load', function() {
    // Do something with users
});

getUsersRequest.send();

//---------------------------------------------------
// Different functionality starts here. Perhaps
// this is an opportunity to split into files.
//---------------------------------------------------

// Load post data from API
var getPostsRequest = new XMLHttpRequest();
getPostsRequest.open('GET', '/api/posts', true);
getPostsRequest.addEventListener('load', function() {
    // Do something with posts
});

getPostsRequest.send();

Functions

Functions allow us to create blocks of code we can reuse. Normally, a function’s content is indented, making it easy to see where a function starts and ends. A good habit is to keep functions tiny—10 lines or less. When a function is named correctly, it’s also easy to understand what is happening when it’s being called. We’ll get to naming conventions later.

// Load user data from API
function getUsers(callback) {
    var getUsersRequest = new XMLHttpRequest();
    getUsersRequest.open('GET', '/api/users', true);
    getUsersRequest.addEventListener('load', function() {
        callback(JSON.parse(getUsersRequest.responseText));
    });

    getUsersRequest.send();
}

// Load post data from API
function getPosts(callback) {
    var getPostsRequest = new XMLHttpRequest();
    getPostsRequest.open('GET', '/api/posts', true);
    getPostsRequest.addEventListener('load', function() {
        callback(JSON.parse(getPostsRequest.responseText));
    });

    getPostsRequest.send();
}

// Because of proper naming, it’s easy to understand this code 
// without reading the actual functions
getUsers(function(users) {
    // Do something with users
});
getPosts(function(posts) {
    // Do something with posts
});

We can simplify the above code. Note how both functions are almost identical? We can apply the Don’t Repeat Yourself (DRY) principle. This prevents clutter.

function fetchJson(url, callback) {
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.addEventListener('load', function() {
        callback(JSON.parse(request.responseText));
    });

    request.send();
}

// The below code is still easy to understand 
// without reading the above function
fetchJson('/api/users', function(users) {
    // Do something with users
});
fetchJson('/api/posts', function(posts) {
    // Do something with posts
});

What if we want to create a new user through a POST request? At this point, one option is to add optional arguments to the function, introducing new logic to the function, making it too complex for one function. Another option is to create a new function specifically for POST requests, which would result in duplicate code.

We can get the best of both with object-oriented programming, allowing us to create a configurable single-use object, while keeping it maintainable.

Note: if you need a primer specifically on object-oriented JavaScript, I recommend this video: The Definitive Guide to Object-Oriented JavaScript

Object Oriented Programming

Consider objects, often called classes, a cluster of functions that are context-aware. An object fits beautifully in a dedicated file. In our case, we can build a basic wrapper for XMLHttpRequest.

HttpRequest.js

function HttpRequest(url) {
    this.request = new XMLHttpRequest();

    this.body = undefined;
    this.method = HttpRequest.METHOD_GET;
    this.url = url;

    this.responseParser = undefined;
}

HttpRequest.METHOD_GET = 'GET';
HttpRequest.METHOD_POST = 'POST';

HttpRequest.prototype.setMethod = function(method) {
    this.method = method;
    return this;
};

HttpRequest.prototype.setBody = function(body) {
    if (typeof body === 'object') {
        body = JSON.stringify(body);
    }

    this.body = body;
    return this;
};

HttpRequest.prototype.setResponseParser = function(responseConverter) {
    if (typeof responseParser !== 'function') return;

    this.responseParser = responseParser;
    return this;
};

HttpRequest.prototype.send = function(callback) {
    this.request.addEventListener('load', function() {
        if (this.responseParser) {
            callback(this.responseParser(this.request.responseText));
        } else {
            callback(this.request.responseText);
        }
    }, false);

    this.request.open(this.method, this.url, true);
    this.request.send(this.body);
    return this;
};

app.js

new HttpRequest('/users')
    .setResponseParser(JSON.parse)
    .send(function(users) {
        // Do something with users
    });

new HttpRequest('/posts')
    .setResponseParser(JSON.parse)
    .send(function(posts) {
        // Do something with posts
    });

// Create a new user
new HttpRequest('/user')
    .setMethod(HttpRequest.METHOD_POST)
    .setBody({
        name: 'Tim',
        email: 'info@example.com'
    })
    .setResponseParser(JSON.parse)
    .send(function(user) {
        // Do something with new user
    });

Continue reading %The Importance of Writing Code That Humans Can Read%

LEAVE A REPLY