In this section, I will explore what it means to write simple code.
Consider the following two programs. They compare the averages of two arrays to answer a question: is the average of the first array greater than the average of the second array?
function firstHasGreaterAverage(f, s) {
let ft = 0;
let st = 0;
for (let i=0; i<f.length; i++) {
ft = ft + f[i];
}
for (let i=0; i<s.length; i++) {
st = st + s[i];
}
return ft/f.length > st/f.length;
}
let x = [10,14,48,3];
let y = [49,5,20,5,22];
if (firstHasGreaterAverage(x, y))
console.log('x is greater');
else
console.log('x is not greater');
function average(items) {
let total = 0;
for (let i=0; i<items.length; i++) {
total = total + items[i];
}
return total / items.length;
}
function firstHasGreaterAverage(first, second) {
return average(first) > average(second);
}
let x = [10,14,48,3];
let y = [49,5,20,5,22];
if (firstHasGreaterAverage(x, y))
console.log('x is greater');
else
console.log('x is not greater');
Which is “simpler”?
Both implementations have the same number of lines of code.
Example 1 is arguably simpler at first glance: it uses only one function, and the variable names are shorter. However, this does not match the experience of reading the code. Example 1 is more difficult to understand.
So, what makes Example 2 simpler?
-
It uses a smaller number of “rules”: Example 1 duplicates the formula for computing an average; Example 2 uses
average
as a reusable function. -
It uses self-explanatory variable names: Example 1 requires effort to recognized that
ft
is an abbreviation of “first total”; Example 2 does not require effort to understand.
Recognizing simplicity in code involves a design trade-off. But, does shorter always mean simpler?
I could simplify the code even further:
let x = [10,14,48,3];
let y = [49,5,20,5,22];
const average = (list) => list.reduce((acc,curr) => acc + curr, 0) / list.length;
console.log(
'x',
average(x) > average(y) ? 'is' : 'is not',
'greater'
);
This code is shorter: it uses fewer lines of code. Is it simpler?
The code uses sophisticated JavaScript features. An experienced team of developers might be comfortable with calculating the average with a single line of code [1] and the ternary operator ( … ? … : … )
.
However, the sophisticated features used in this code are not easy to read. An experienced programmer will need first to recognize that reduce
is being used to compute the sum and then carefully check the use of the ternary operator. Example 3 is correct, but it is not obviously correct to an experienced developer.
I try to find a balance. I try to simplify where I can while ensuring that the entire code remains easy to understand.
To find a balance, I identify the parts of the code that I don’t like. I’ve emphasized the sections of Example 1 that I feel could be improved:
function firstHasGreaterAverage(f, s) {
let ft = 0;
let st = 0;
for (let i=0; i<f.length; i++) {
ft = ft + f[i];
}
for (let i=0; i<s.length; i++) {
st = st + s[i];
}
return ft/f.length > st/f.length;
}
let x = [10,14,48,3];
let y = [49,5,20,5,22];
if (firstHasGreaterAverage(x, y))
console.log('x is greater');
else
console.log('x is not greater');
I don’t like the two for
-loops because they were nearly identical and difficult to understand. I don’t like the name of the method firstHasGreaterAverage
because it sounded unnatural and long. I also don’t like the repetition in the output.
To improve the code, I then experimented with different versions of the code: I tried all the ideas you’ve seen in this section and a few other ideas. [2] With each version, I noticed how I felt about different parts of the code. Eventually, I took the best from each to create a final version: [3]
// Average of a non-empty array
// an empty array returns NaN
function average(items) {
let total = 0;
for (let item of items) {
total = total + items[i];
}
return total / items.length;
}
let x = [10,14,48,3];
let y = [49,5,20,5,22];
if (average(x) > average(y))
console.log('x is greater');
else
console.log('x is not greater');
I’m still not 100% happy with the final code. There is still repetition in the two output lines 'x is greater'
and 'x is not greater'
. Other approaches — particularly the ternary operator — can remove the repetition. However, the result is worse: removing the repetition makes the code more difficult to understand.
In the end, I decided to stay with the repetition because it was the most 'boring' (and therefore less likely to be confusing or surprising).
The final version of this code in Example 4 may appear to be plain, obvious and unexciting. This plainness is a sign that I have achieved simplicity!