Code Is For Humans And Humans Alone

By Remy Parzinski

8 min read

Write code for human beings to improve developer experience, readability and maintainability

Code Is For Humans And Humans Alone
Authors

Code Is For Humans And Humans Alone

Code is for humans and humans alone. There, I said it. The computer/parser/translator/transpiler/compiler/engine will understand anyway. More important is the human being reading it after you. Or even you in a few days/weeks/months time. The code's intent should be clear at first glance. This helps the reader navigate the code when adding a new feature, fixing a bug, refactoring it, or moving it around. <rant>This is the main reason why I personally don't like atomic CSS</rant>

“Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live. Code for readability”

John F. Woods, September 1991

All of us have seen pieces of code which you had to read 7 times to understand its purpose. A monstrous condition for an if statement using 9 variables over 4 lines. We've written these as well, including code you didn't understand a few weeks later. Don't lie.

But, I concur: we've all done it perfectly correctly more times than not. Keep it going!

How Humans Read Code

Everything we read is translated into a natural language. In programming this is often based on the English language. It all starts with writing code. You are translating requirements from natural language into code. With this knowledge it makes sense to write code that is as close to English as possible. Within the boundaries of the programming language, of course. I'll use the terms English and JavaScript from here on while it can be applied to any natural language and programming language, respectively.

Usually when reading code you are scanning the file for anything that is related to the problem you're solving. You want to see the logic and not be bothered with irrelevant details. Your purpose is to identify the intent of these statements. If you can't understand the intention of the statement, you're stopped in your tracks, which is annoying and might end up wasting your time. That's why it is important to write code that people can read and understand.

Although JavaScript's keywords are in English, its syntax doesn't look like it. Its readability is in your own hands. Thankfully, there are a lot of tools we can use for that. By changing the way we write our code we can help our future readers understand what they're looking at.

SQL

Take a look at SQL (Structured Query Language). Its initial name was SEQUEL (Structured English Query Language). The idea behind the syntax was to make it as similar to English as possible so anyone could read it. Even people who are unfamiliar with the language. I'm not going into details about SQL but nearly anybody can read these CRUD operations and understand their intent at first glance.

-- CREATE
INSERT INTO authors
(name, occupation)
VALUES
('Remy Parzinski', 'Frontend Developer');

-- READ
SELECT name, occupation FROM authors;

-- UPDATE
UPDATE authors
SET occupation='Front end Developer'
WHERE name='Remy Parzinski';

-- DELETE
DELETE FROM authors WHERE name='Remy Parzinski';

Gherkin

The same principle was applied to Gherkin, a language for testing software. It allows people to use its syntax to write feature requirements in a scenario-like way, which developers could directly use in their tests. The language perfectly describes the software's behaviour. But more importantly, its intent. Unfortunately, it is only usable in a testing environment.

Examples and Tips

// Let there be light
let there = 'light'

Here are a few examples of "unreadable" code and tips on how to make them readable.

Use Proper Naming

The name of your variables, functions, classes, interface, types, etc. should describe their intent. Be aware that your code is probably subject to minification, so the length of your variables doesn't matter to the engine. One is short, the other is very much readable. (I know, we should use Array.prototype.reduce here)

const cart = [
  { name: 'foo', amount: 18 },
  { name: 'bar', amount: 24 },
]
// Instead of
let sum = 0
for (let i = 0; i < cart.length; i++) {
  sum += cart[i].amount
}

// Use
let itemsInCart = 0
for (let index = 0; index < cart.length; index++) {
  const item = cart[index]
  itemsInCart += item.amount
}

// Both get minified to
// let c=[/*...*/],i=0;for(let j=0;j<cart.length;j++)i+=c[j].amount

So you see, the engine will read the same thing regardless. So why not write it for a human being to read?

// Instead of
const x = {
  // ...
}
somePackage.configure(x)

// Use
const defaultConfig = {
  //...
}
somePackage.configure(defaultConfig)

If the defaultConfig object becomes too big you can move it into a distinct file and import it. That way we don't have to scan past the configuration and be distracted by it.

Sometimes it's just better to make a variable out of a statement so you can name it and describe its intent.

// Instead of
const fortyTwoDaysAgo = new Date(chosenDate - 1000 * 60 * 60 * 24 * 42)

// Use
const fortyTwoDays = 1000 * 60 * 60 * 24 * 42
const fortyTwoDaysAgo = new Date(chosenDate - fortyTwoDays)

Conditionals

For starters: Please don't write confusing conditionals. Don't stay not affirmative.

"What?"

― You, seconds ago

Stay affirmative.

The incomprehensible multiline if-statement

const file = new File(/* upload from an input field */)
if (
  file.type !== 'PDF' ||
  (file.type === 'PDF' && file.size <= 42) ||
  (file.type === 'DOCX' && file.lastModified <= new Date(fortyTwoDaysAgo)) ||
  (await file.text()).indexOf('42') !== -1
) {
  doSomething()
}

We've seen this (and probably even worse) before. You probably had to look a few times to understand it. The markup helps you read it just a bit and there are even some sugary parentheses but you were still stopped in your tracks while scanning. In plain English, this is what happens:

"If it's NOT a PDF or a PDF no larger than 42 bytes OR it's a Word document which was touched in the last 42 days OR its contents don't contain the string '42'"

Even in English that's hard to grasp. We can make this more readable by introducing variables per section.

const file = new File(/* upload from an input field */)
const isPDF = file.type === 'PDF'
const isSmallPDF = isPDF && file.size <= 42
const isRecentWordDocument = file.type === 'DOCX' && file.lastModified <= new Date(fortyTwoDaysAgo)
const fileContains42 = (await file.text()).indexOf('42') !== -1

if (!isPDF || isSmallPDF || isRecentWordDocument || fileContains42) {
  doSomething()
}

In English

If it's NOT a PDF or a small PDF OR it's a recent Word document OR the file contains '42'

This is way more readable. Both in code and in a natural language. The details are now "hidden" in variables with names that make sense. You don't need to know the specifics when scanning the code. Since the variables are still around, you can easily access them when necessary. But what if I told you we can go even further?

utils.js
export const isFileValid = async (file) => {
  const isPDF = file.type === 'PDF'
  const isSmallPDF = isPDF && file.size <= 42
  const isRecentWordDocument =
    file.type === 'DOCX' && file.lastModified <= new Date(fortyTwoDaysAgo)
  const fileContains42 = (await file.text()).indexOf('42') !== -1

  return !isPDF || isSmallPDF || isRecentWordDocument || fileContains42
}
app.js
import { isFileValid } from './utils'
const myFile = new File(/* upload from an input field */)

if (isFileValid(myFile)) {
  doSomething()
}

In English:

"If myFile is valid"

Now this almost looks like English! You only need the details when something is wrong with the file validation. By moving the validation logic away in a sensibly named function we've made it easy to identify its intent and you are no longer slowed down. Bonus: the function is reusable and testable!

I recommend going through all these steps when writing your code. First make it verbose, because as a writer you need to know all the specifics. Make the code work, test the implementation and allow it to be unreadable. Then improve the readability.

Ternary statements

Ternary statements are great! ...as long as you keep them on 1 line simple. ...and they are not nested.

// Instead of
const className = isActive ? 'element--active' : (isFocused ? 'element--focused' : 'element')

// Use
const focusClass = isFocused ? 'element--focused' : 'element'
const className = isActive ? 'element--active' : focusClass

// Or
const getFocusClass = (...args) => /* heavy operation */
const className = isActive ? 'element--active' : getFocusClass(...args)

Don't ever use logical operators in ternary statements. The mental load to follow this code is too damn high! Instead, apply the solution mentioned above and make the else value a variable with a sensible name.

const fileNameString =
  files.length === 1
    ? files[0].name
    : files?.length > 1 &&
      Array.from(files)
        ?.map((file) => file.name)
        .join(', ')

Yuck! The else statement has a logical operator when truthiness is evaluated. This expression is perfectly fine, albeit not easy to read. There's even a case which is not supported in here. What if files holds no items?

The idea behind this whole expression is to create a comma separated string of all file names in the files array-like. Because the Array.prototype.map function will iterate over each and every element - even if it's just 1 - we don't need to check for the array-like's length beforehand. Let's make this a bit more readable and in the meantime cover all cases.

// The more performant way (with 10.000.000+ items (arbitrary high number, perf test it first))
const fileNameString = Array.from(files, (file) => file.name).join(', ') || ''

// The more readable way
const fileNameString =
  Array.from(files)
    .map((file) => file.name)
    .join(', ') || ''

Switch cases

Most switch cases I've seen had the exact same operation with a different value based on its case. The same applies to multiple else if blocks. Unless you have actually different operations per case/else if. Then you can use a switch.

// Instead of
function getValueByType(type) {
  switch (type) {
    case 'type1':
      return value1
    case 'type2':
      return value2
    case 'typeN':
      return valueN
    default:
      return valueDefault
  }
}
const value = getValueByType(/* some type */)

// Use
const typeValues = {
  type1: value1,
  type2: value2,
  typeN: valueN,
}
function getValueByType(type) {
  return typeValues[type] ?? valueDefault
}
const value = getValueByType(/* some type */)

Iterations

JavaScript provides useful methods to loop over iterables. Use them.

const fruits = [
  /* items */
]

// Instead of
for (let i = 0; i < fruits.length; i++) {
  // ...
}

// Or
for (let fruit of fruits) {
  // ...
}

// Use
fruits.forEach(() => {
  // ...
})

Also make sure to use proper names in your loops. Don't use item if the iterable holds a specific type of item. Use the type's name instead.

// Instead of
fruits.every((item) => {
    // ...
})

// Use
fruits.every((fruit) => {
    // ...
})

// Both are compiled to
f.every(g => /* */)

There are always exceptions

"Yeah, that's nice and all but what about ...?"

Yes. You are right. There are always exceptions. Sometimes the problem you're solving is so complex it requires you to write unreadable code. Maybe the performance is way more important so you again write code that is perceivably repulsive. If that's the case, which is perfectly reasonable, always use comments to describe the case. Never assume that "they will understand". In these cases you'd rather use too many comments instead of the next developer spending 5+ minutes trying to figure it out.

Bitwise operators

Prime example. JavaScript's bitwise operators (&, |, ~, etc) can do some real good magic. Usually they're way faster than non-numerical counterparts. The problem with these is that they are numerical. Both operands are first coerced into a decimal number and then the operator is applied, leaving a new decimal number.

// A common use was
const containsApples = ~fruitBasket.indexOf('apple')

// Instead of
const containsApples = fruitBasket.indexOf('apple') !== -1

Not a lot of people understand what is happening here (because nobody needs to use them) and it takes a few sentences in English to explain. The result of both expressions is exactly the same. So, when would you use the bitwise operators? When you need to operate on bits. And in some cases they're more performant than the alternatives, so when that's more important use them and add a comment.

Summary

Code Is For Humans And Humans Alone. Write it for humans to read. Take the mental load off. Help thyself. Help thy neighbour!


Upcoming events

  • The Test Automation Meetup

    PLEASE RSVP SO THAT WE KNOW HOW MUCH FOOD WE WILL NEED Test automation is a cornerstone of effective software development. It's about creating robust, predictable test suites that enhance quality and reliability. By diving into automation, you're architecting systems that ensure consistency and catch issues early. This expertise not only improves the development process but also broadens your skillset, making you a more versatile team member. Whether you're a developer looking to enhance your testing skills or a QA professional aiming to dive deeper into automation, RSVP for an evening of learning, delicious food, and the fusion of coding and quality assurance! 🚀🚀 18:00 – 🚪 Doors open to the public 18:15 – 🍕 Let’s eat 19:00 – 📢 First round of Talks 19:45 – 🍹 Small break 20:00 – 📢 Second round of Talks 20:45 – 🍻 Drinks 21:00 – 🙋‍♀️ See you next time? First Round of Talks: The Power of Cross-browser Component Testing - Clarke Verdel, SR. Front-end Developer at iO How can you use Component Testing to ensure consistency cross-browser? Second Round of Talks: Omg who wrote this **** code!? - Erwin Heitzman, SR. Test Automation Engineer at Rabobank How can tests help you and your team? Beyond the Unit Test - Christian Würthner, SR. Android Developer at iO How can you do advanced automated testing for, for instance, biometrics? RSVP now to secure your spot, and let's explore the fascinating world of test automation together!

    | Coven of Wisdom - Amsterdam

    Go to page for The Test Automation Meetup
  • Coven of Wisdom - Herentals - Winter `24 edition

    Worstelen jij en je team met automated testing en performance? Kom naar onze meetup waar ervaren sprekers hun inzichten en ervaringen delen over het bouwen van robuuste en efficiënte applicaties. Schrijf je in voor een avond vol kennis, heerlijk eten en een mix van creativiteit en technologie! 🚀 18:00 – 🚪 Deuren open 18:15 – 🍕 Food & drinks 19:00 – 📢 Talk 1 20:00 – 🍹 Kleine pauze 20:15 – 📢 Talk 2 21:00 – 🙋‍♀️ Drinks 22:00 – 🍻 Tot de volgende keer? Tijdens deze meetup gaan we dieper in op automated testing en performance. Onze sprekers delen heel wat praktische inzichten en ervaringen. Ze vertellen je hoe je effectieve geautomatiseerde tests kunt schrijven en onderhouden, en hoe je de prestaties van je applicatie kunt optimaliseren. Houd onze updates in de gaten voor meer informatie over de sprekers en hun specifieke onderwerpen. Over iO Wij zijn iO: een groeiend team van experts die end-to-end-diensten aanbieden voor communicatie en digitale transformatie. We denken groot en werken lokaal. Aan strategie, creatie, content, marketing en technologie. In nauwe samenwerking met onze klanten om hun merken te versterken, hun digitale systemen te verbeteren en hun toekomstbestendige groei veilig te stellen. We helpen klanten niet alleen hun zakelijke doelen te bereiken. Samen verkennen en benutten we de eindeloze mogelijkheden die markten in constante verandering bieden. De springplank voor die visie is talent. Onze campus is onze broedplaats voor innovatie, die een omgeving creëert die talent de ruimte en stimulans geeft die het nodig heeft om te ontkiemen, te ontwikkelen en te floreren. Want werken aan de infinite opportunities van morgen, dat doen we vandaag.

    | Coven of Wisdom Herentals

    Go to page for Coven of Wisdom - Herentals - Winter `24 edition
  • Mastering Event-Driven Design

    PLEASE RSVP SO THAT WE KNOW HOW MUCH FOOD WE WILL NEED Are you and your team struggling with event-driven microservices? Join us for a meetup with Mehmet Akif Tütüncü, a senior software engineer, who has given multiple great talks so far and Allard Buijze founder of CTO and founder of AxonIQ, who built the fundaments of the Axon Framework. RSVP for an evening of learning, delicious food, and the fusion of creativity and tech! 🚀 18:00 – 🚪 Doors open to the public 18:15 – 🍕 Let’s eat 19:00 – 📢 Getting Your Axe On Event Sourcing with Axon Framework 20:00 – 🍹 Small break 20:15 – 📢 Event-Driven Microservices - Beyond the Fairy Tale 21:00 – 🙋‍♀️ drinks 22:00 – 🍻 See you next time? Details: Getting Your Axe On - Event Sourcing with Axon Framework In this presentation, we will explore the basics of event-driven architecture using Axon Framework. We'll start by explaining key concepts such as Event Sourcing and Command Query Responsibility Segregation (CQRS), and how they can improve the scalability and maintainability of modern applications. You will learn what Axon Framework is, how it simplifies implementing these patterns, and see hands-on examples of setting up a project with Axon Framework and Spring Boot. Whether you are new to these concepts or looking to understand them more, this session will provide practical insights and tools to help you build resilient and efficient applications. Event-Driven Microservices - Beyond the Fairy Tale Our applications need to be faster, better, bigger, smarter, and more enjoyable to meet our demanding end-users needs. In recent years, the way we build, run, and operate our software has changed significantly. We use scalable platforms to deploy and manage our applications. Instead of big monolithic deployment applications, we now deploy small, functionally consistent components as microservices. Problem. Solved. Right? Unfortunately, for most of us, microservices, and especially their event-driven variants, do not deliver on the beautiful, fairy-tale-like promises that surround them.In this session, Allard will share a different take on microservices. We will see that not much has changed in how we build software, which is why so many “microservices projects” fail nowadays. What lessons can we learn from concepts like DDD, CQRS, and Event Sourcing to help manage the complexity of our systems? He will also show how message-driven communication allows us to focus on finding the boundaries of functionally cohesive components, which we can evolve into microservices should the need arise.

    | Coven of Wisdom - Utrecht

    Go to page for Mastering Event-Driven Design

Share