The Importance of Writing Code That Humans Can Read

Created: 2015-12-19 01:44 Updated: 2015-12-19 01:44 Source: http://www.sitepoint.com/importance-of-code-that-humans-can-read/ Notebook: All Tech/Frontend Development

The Importance of Writing Code That Humans Can Read


JavaScript
Success! Subscribed.

Claim your free ebook!

MEAN Stack


Microsoft - helping keep SitePoint free!

This article was peer reviewed by Matt Burnett, Simon Codrington and Nilson Jacques. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

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 JSF*ck (NSFW, by the way), 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

More from this author

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
 getUsersRequest   XMLHttpRequest
getUsersRequest'GET' '/api/users' 
getUsersRequestaddEventListener'load' function 
    // Do something with users


getUsersRequest

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

// Load post data from API
 getPostsRequest   XMLHttpRequest
getPostsRequest'GET' '/api/posts' 
getPostsRequestaddEventListener'load' function 
    // Do something with posts


getPostsRequest

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 getUserscallback 
     getUsersRequest   XMLHttpRequest
    getUsersRequest'GET' '/api/users' 
    getUsersRequestaddEventListener'load' function 
        callbackJSONparsegetUsersRequestresponseText
    

    getUsersRequest


// Load post data from API
function getPostscallback 
     getPostsRequest   XMLHttpRequest
    getPostsRequest'GET' '/api/posts' 
    getPostsRequestaddEventListener'load' function 
        callbackJSONparsegetPostsRequestresponseText
    

    getPostsRequest


// Because of proper naming, it’s easy to understand this code 
// without reading the actual functions
getUsersfunctionusers 
    // Do something with users

getPostsfunctionposts 
    // 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 fetchJsonurl callback 
     request   XMLHttpRequest
    request'GET' url 
    requestaddEventListener'load' function 
        callbackJSONparserequestresponseText
    

    request


// The below code is still easy to understand 
// without reading the above function
fetchJson'/api/users' functionusers 
    // Do something with users

fetchJson'/api/posts' functionposts 
    // 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 HttpRequesturl 
    request   XMLHttpRequest

    body  undefined
    method  HttpRequestMETHOD_GET
    url  url

    responseParser  undefined


HttpRequestMETHOD_GET  'GET'
HttpRequestMETHOD_POST  'POST'

HttpRequestprototypesetMethod  functionmethod 
    method  method
    return 


HttpRequestprototypesetBody  functionbody 
     typeof body  'object' 
        body  JSONstringifybody
    

    body  body
    return 


HttpRequestprototypesetResponseParser  functionresponseParser 
     typeof responseParser  'function' return

    responseParser  responseParser
    return 


HttpRequestprototypesend  functioncallback 
    requestaddEventListener'load' function 
         responseParser 
            callbackresponseParserrequestresponseText
          
            callbackrequestresponseText
        
     false

    requestmethod url 
    requestbody
    return 

app.js

 HttpRequest'/users'
    setResponseParserJSONparse
    functionusers 
        // Do something with users
    

 HttpRequest'/posts'
    setResponseParserJSONparse
    functionposts 
        // Do something with posts
    

// Create a new user
 HttpRequest'/user'
    setMethodHttpRequestMETHOD_POST
    setBody
        name 'Tim'
        email 'info@example.com'
    
    setResponseParserJSONparse
    functionuser 
        // Do something with new user
    

The HttpRequest class created above is now very configurable, so can be applied for many of our API calls. Despite the implementation—a series of chained method calls—being more complex, the class’s features are easy to maintain. Finding a balance between implementation and reusability can be difficult and is project-specific.

When using OOP, design patterns make a great addition. Although they don’t improve readability per se, consistency does!

Human Syntax

Files, functions, objects, those are just the rough lines. They make your code easy to scan. Making code easy to read is a much more nuanced art. The tiniest detail can make a major difference. Limiting your line length to 80 characters, for example, is a simple solution that is often enforced by editors through a vertical line. But there’s more!

Naming

Appropriate naming can cause instant recognition, saving you the need to look up what a value is or what a function does.

Functions are usually in camel case. Starting them with a verb, followed by a subject often helps.

function getApiUrl  /* ... */ 
function setRequestMethod  /* ... */ 
function findItemsByIdn  /* ... */ 
function hideSearchForm  /* ... */ 

For variable names, try to apply the inverted pyramid methodology. The subject comes first, properties come later.

 element  documentgetElementById'body'
    elementChildren  elementchildren
    elementChildrenCount  elementChildrenlength

// When defining a set of colours, I prefix the variable with “color”
 colorBackground  0xFAFAFA
    colorPrimary  0x663399

// When defining a set of background properties, I use background as base
 backgroundColor  0xFAFAFA
    backgroundImages  'foo.png' 'bar.png'

// Context can make all the difference
 headerBackgroundColor  0xFAFAFA
    headerTextColor  0x663399

It’s also important being able to tell the difference between regular variables, and special ones. The name of constants, for example, are often written in uppercase and with underscores.

 URI_ROOT  windowlocationhref

Classes are usually in camel case, starting with an uppercase letter.

function FooObject 
    // ...

A small detail is abbreviations. Some chose to write abbreviations in full uppercase while others choose to stick with camel case. Using the former may make it more difficult to recognize subsequent abbreviations.

Compactness and Optimisation

In many codebases, you may come across “special” code to reduce the number of characters, or to increase an algorithm’s performance.

A one-liner is an example of compact code. Unfortunately, they often rely on hacks or obscure syntax. A nested ternary operator, as seen below, is a common case. Despite being compact, it can also take a second or two to understand what it does, as opposed to regular if-statements. Be careful with syntactical shortcuts.

// Yay, someone managed to make this a one-liner!
 state  isHidden  'hidden'  isAnimating  'animating'  

// Yay, someone managed to make this readable!
 state  
 isAnimating state  'animating'
 isHidden state  'hidden'

Micro-optimisations are performance optimisations, often of little impact. Most of the time, they are less readable than a less performant equivalent.

// This may be most performant
$elchecked

// But these are still fast, and are much easier to read
// Source: http://jsperf.com/prop-vs-ischecked/5
$el'checked'
$el':checked'
$el'checked'

JavaScript compilers are really good in optimising code for us, and they keep getting better. Unless the difference between unoptimised and optimised code is noticeable, which often is after thousands or millions of operations, going for the easier read is recommended.

Non-Code

Call it irony, but a better way to keep code readable is to add syntax that isn’t executed. Let’s call it non-code.

Whitespace

I’m pretty sure every developer has had another developer supply, or has inspected a site’s minified code—code where most whitespace is removed. Coming across that the first time can be quite a surprise. In different visual artistic fields, like design and typography, void space is as important as fill. You will want to find the delicate balance between the two. Opinions on that balance vary per company, per team, per developer. Luckily, there are some universally agreed rules:

  • one expression per line,
  • indent the contents of a block,
  • an extra break can be used to separate sections of code.

Any other rule should be discussed with whoever you work with. Whatever code style you agree on, consistency is key.

function sendPostRequesturl data cb 
    // A few assignments grouped together and neatly indented
     requestMethod  'POST'
        requestHeaders  
            'Content-Type' 'text/plain'
        

    // XMLHttpRequest initialisation, configuration and submission
     request   XMLHttpRequest
    requestaddEventListener'load' cb false
    requestrequestMethod url false
    requestdata

Comments

Much like whitespace, comments can be a great way to give your code some air, but also allows you to add details to code. Be sure to add comments to show:

  • explanation and argumentation of non-obvious code,
  • which bug or oddity a fix resolves, and sources when available.

// Sum values for the graph’s range
 sum  valuesreducefunctionpreviousValue currentValue  
    return previousValue  currentValue

Not all fixes are obvious. Putting additional information can clarify a lot:

 'addEventListener'  element 
    elementaddEventListener'click' myFunc

// IE8 and lower do not support .addEventListener, 
// so .attachEvent should be used instead
// http://caniuse.com/#search=addEventListener
// https://msdn.microsoft.com/en-us/library/ms536343%28VS.85%29.aspx
 
    elementattachEvent'click' myFunc

Inline Documentation

When writing object-oriented software, inline docs can, much like regular comments, give some breathing space to your code. They also help clarify the purpose and details of a property or method. Many IDEs use them for hints, and generated documentation tools use them too! Whatever the reason is, writing docs is an excellent practise.

/**
 * Create a HTTP request
 * @constructor
 * @param {string} url
 */
function HttpRequesturl 
    // ...


/**
 * Set an object of headers
 * @param {Object} headers
 * @return {HttpRequest}
 */
HttpRequestprototypesetHeaders  functionheaders 
      header  headers 
        headersheader  headersheader
    

    // Return self for chaining
    return 

Callback Puzzles

Events and asynchronous calls are great JavaScript features, but it often makes code harder to read.

Async calls are often provided with callbacks. Sometimes, you want to run them in sequence, or wait for all of them to be ready.

function doRequesturl success error  /* ... */ 

doRequest'https://example.com/api/users' functionusers 
    doRequest'https://example.com/api/posts' functionposts 
        // Do something with users and posts
     functionerror 
        // /api/posts went wrong
    
 functionerror 
    // /api/users went wrong

The Promise object was introduced in ES2015 (also known as ES6) to solve both issues. It allows you to flatten down nested async requests.

function doRequesturl 
    return  Promisefunctionresolve reject 
        // Initialise request
        // Call resolve(response) on success
        // Call reject(error) on error
    


// Request users first
doRequest'https://example.com/api/users'
// .then() is executed when they all executed successfully
functionusers  /* ... */ 
// .catch() is executed when any of the promises fired the reject() function
catchfunctionerror  /* ... */ 

// Run multiple promises parallel
Promise
    doRequest'https://example.com/api/users'
    doRequest'https://example.com/api/posts'

functionresponses  /* ... */ 
catchfunctionerror  /* ... */ 

Although we introduced additional code, this is easier to interpret correctly. You can read more about Promises here: JavaScript Goes Asynchronous (and It’s Awesome)

ES6/ES2015

If you are aware of the ES2015 spec, you may have noticed that all code examples in this article are of older versions (with the exception of the Promise object). Despite ES6 giving us great features, there are some concerns in terms of readability.

The fat arrow syntax defines a function that inherits the value of from its parent scope. At least, that is why it was designed. It is tempting to use it to define regular functions as well.

 add  a b  a  b
console  

Another example is the rest and spread syntax.

/**
 * Sums a list of numbers
 * @param {Array} numbers
 * @return {Number}
 */
function numbers 
    return nreducefunctionpreviousValue currentValue 
        return previousValue  currentValue
     


  

/**
 * Sums a, b and c
 * @param {Number} a
 * @param {Number} b
 * @param {Number} c
 * @return {Number}
 */
function a b c 
    return a  b  c


  

My point is that the ES2015 spec introduces a lot useful, but obscure, sometimes confusing syntax that lends itself to being abused for one-liners. I don’t want to discourage using these features. I want to encourage caution using them.

Conclusion

Keeping your code readable and maintainable is something to keep in mind at every stage of your project. From the file system to tiny syntactic choices, everything matters. Especially on teams, it’s hard to enforce all rules all the time. Code review can help, but still leaves room for human error. Luckily, there are tools to help you with that!

  • JSHint – a JavaScript linter to keep code error-free
  • Idiomatic – a popular code style standard, but feel free to deviate
  • EditorConfig – defining cross-editor code styles

Other than code quality and style tools, there are also tools that makes any code easier to read. Try different syntax highlight themes, or try a minimap to see a top-down overview of your script (Atom, Brackets).

What are your thoughts on writing readable and maintainable code? I’d love to hear them in the comments below.


View static HTML