Writing Expressive Code

Daniel King
6 min readSep 18, 2016

--

Computer science is no more about computers than astronomy is about telescopes.

About a year ago, I was introduced to the Ruby programming language for the first time. As I was reading about some of the reasons why people like to use it, one adjective kept coming up over and over again: expressive”.

At the time, I was most comfortable with the Python programming language, and if you have any familiarity with Python, you know that its syntax prizes explicitness above all else. As the Zen of Python says, “Explicit is better than implicit”.

For this reason, I was somewhat confused as to what people were referring to when they described Ruby’s syntax as being “expressive.” By comparison to Python, Ruby has a million ways of accomplishing any given task, which (I felt) increases the cognitive load of reading Ruby code and therefore decreases its “expressiveness” (whatever that meant).

However, after bouncing this concept of “expressive code” around in my brain for the past year or so, I have come to the point where I feel as though I understand what people mean when they use this term: Roughly, it has to do with how well the code expresses the intent of the programmer to another human who is reading it.

Although you can often hear people praising particular programming languages for being expressive, I think that it is possible (and important) to write expressive code in any language. That is what I would like to talk about here: Ideas for how we can write expressive code independent of any particular programming language.

In the real world of computer software, code is read much more often than it is written — that is, a professional software developer spends a good deal of her time reading and trying to understand code that was written by other people, so that she can make appropriate modifications and upgrades. This means that when we write code, we are not only writing instructions to the computer, we are also writing for the benefit of whoever the next person to read our code is — even if it is ourselves, far enough in the future that I don’t remember exactly what I was thinking when I wrote the code.

This is, I think, one of the essential ideas behind code that is “expressive.” When we write code, we can write it in a way that not only gives correct instructions to the computer, but that clearly communicates our intent to the next human who reads our code.

To illustrate this idea, let’s suppose that I needed to write a function that would capitalize all of the vowels in a given string (why we would want to do this I’m not sure, but let’s continue). In JavaScript, I might write something that looks like this:

function capitalizeVowels(string) {
const VOWELS = ['a', 'e', 'i', 'o', 'u'];
let result = '';
for (let i = 0; i < string.length; i++) {
let nextChar = string[i];
if (VOWELS.includes(nextChar)) {
nextChar = nextChar.toUpperCase();
}

result += nextChar;
}
return result;
}

However, as I analyzed this function in more detail, I might notice that if I pass anything other than a string into this function as an argument, the function will do strange things — it might throw an error, or it might return something unexpected, depending on the situation.

To resolve this issue, it is common to put an if statement into the function to ensure that the argument passed in is actually a string. Naively, I might do something like this:

function capitalizeVowels(string) {
if (typeof string === 'string') {
const VOWELS = ['a', 'e', 'i', 'o', 'u'];
let result = '';
for (let i = 0; i < string.length; i++) {
let nextChar = string[i];
if (VOWELS.includes(nextChar)) {
nextChar = nextChar.toUpperCase();
}

result += nextChar;
}
return result;
} else {
return undefined;
}
}

Now, this solution will work as intended. However, it doesn’t do a good job of expressing my intent. What do I mean by this? Well, when I see an if-else structure in code, my mind envisions the resulting execution as having two possible paths, which is of course true in this case: Either the input is a string, or it is not. However, writing the code as an if-else structure implies that both of those possibilities are equally significant, which they are not— the case where the input is a string is the “main” purpose of the function, and the case when it is not a string is just an extra piece that allows the function to behave gracefully in the (unlikely) event that the input is not a string.

How can we change this code to better express the intent of the function, putting the focus on the case where the input is a string as it should be? One way to accomplish this is with the use of a guard condition, which allows the function to “bail out early” if something undesirable happens. Here’s what the function would look like if we refactor it to use a guard condition:

function capitalizeVowels(string) {
if (typeof string !== 'string') return undefined;
const VOWELS = ['a', 'e', 'i', 'o', 'u'];
let result = '';
for (let i = 0; i < string.length; i++) {
let nextChar = string[i];
if (VOWELS.includes(nextChar)) {
nextChar = nextChar.toUpperCase();
}

result += nextChar;
}
return result;
}

Notice that the edge case where the input is not a string is handled by the first line in the function, and then it is never given a second thought. The function can proceed with its computation, confident that the input is the correct data type, without having the extra visual clutter of an extra level of indentation.

This may not seem like a very big improvement, but it can make a huge difference in clarity if there are more complicated conditions to check. For example, let’s suppose that we were trying to write a function that takes in an array of numbers and filters out all of the numbers that are not multiples of 3. In the ES2015 version of JavaScript, it can be written very succinctly like this:

function getMultiplesOf3(array) {
return array.filter(number => number % 3 === 0);
}

But now, due to how this function interacts with everything else going on in the application, we want to return undefined if the input value is not an array, and we want to throw an error if any of the items in the array is not of type number. If we do this using if-else structures, it gets kind of messy:

function getMultiplesOf3(array) {
if (!Array.isArray(array)) {
return undefined
} else {
if (!array.every(item => typeof item === 'number')) {
throw new Error('Array has a non-number value');
} else {
return array.filter(number => number % 3 === 0);
}
}
}

Notice that the really important computation, the part where we filter out all of the numbers that aren’t divisible by 3, is completely buried in the code that is checking for the various error conditions. As written, the function does not do a good job of expressing the intent of the function by drawing the reader’s attention to the main computation that it is doing. Instead, we can improve the expressiveness of the function by writing it with guard conditions like this:

function getMultiplesOf3(array) {
if (!Array.isArray(array)) return undefined;
if (!array.every(item => typeof item === 'number') {
throw new Error('Array has a non-number value');
}
return array.filter(number => number % 3 === 0);
}

Notice that, although the error-checking code still takes up more lines of code than the main computation, it is much easier to see the true intent of the function. The main computation stands apart from the error-checking code, emphasizing that it is the most important part of what is happening. This makes it much easier for a human to read the code and build a mental model of what the original programmer was thinking when they wrote the function.

In this article, my goal was to communicate what it means for code to be expressive and to show a couple of examples of how we can go about making our own code more expressive. There are endless ways that we can do this, and I encourage you to use your imagination to think about how— the important thing is that when you write code, you keep the human reader in mind as well as the computer.

Happy coding!

Daniel King is a professional software engineer and educator in the Los Angeles area who is available for software development and coaching on a contract basis.

daniel.oliver.king@gmail.com

--

--

Daniel King

Professional Software Engineer and Educator, amateur Musician, armchair Personal Finance Expert