Oops — Craig Sunter (CC BY-ND 2.0)

The Single Biggest Mistake Programmers Make Every Day

I recently taught a master class at a conference. The first half of the day I stood in front of the class and talked about The Two Pillars of JavaScript: Prototypal OO and functional programming.

For the second half, I had the students team up and collaborate on an app project. The assignment was to keep in mind a few key points from the lessons:

  • Don’t export any classes. If you need to instantiate anything, use a factory function.
  • Use pure functions wherever you can. A pure function does not mutate anything outside itself. A pure function does not produce side effects. Given the same inputs, a pure function will always return the same output.
  • Notice how much of the program state can be represented as lists of things.

After beginning the practice assignment, I spent the rest of the day walking around the room and helping the teams progress. I noticed a consistent theme, which inspired a question that I put out on Twitter:

I got some great responses. Here are a few favorites:

And my personal favorite:

Brendan Eich seemed to like it, too:

I believe the single biggest mistake that every programmer makes from time to time is overcomplicating things.

Keep it Stupid Simple

There is a common design principle that (according to Wikipedia) originated in the US Navy:

KISS — Keep It Simple, Stupid

I don’t think that goes far enough, so I say “Keep It Stupid Simple”.

By that, I mean that you should think of the simplest possible way to accomplish the goal, and then make it even simpler.

“Perfection is attained
not when there is nothing more to add,
but when there is nothing more to take away.”
~ Antoine de Saint Exupéry

What is Simple Code?

Simple, from the latin simplus, originally referred to a medicine made from one constituent, especially from one plant. The meaning I refer to here is “not complex or compound; single.” Simple vs complex can be summed up concisely: one thing vs many things.

One thing is simple.

Many things are complex.

What do I mean when I say that you should keep code as simple as possible? I believe that all code should be simple (as in easy):

  • Simple to understand.
  • Simple to use.
  • Simple to reuse.
  • Simple to extend.

The best way to make things easy is to keep your code simple, as in Do One Thing (DOT).

The Process

My first time around the room, I noticed that students were getting stuck in rabbit holes. One group was stuck creating a build process. Another was stuck working out all the data structures. Another was trying to work out how to apply a functional data flow to the problem before there were any functions for data to flow through.

All of the groups would benefit from advice often attributed to Kent Beck, author of “Test Driven Development By Example”:

  1. Make it work.
  2. Make it right.
  3. Make it fast.

Step one should always be to make it work. The best way to do that is to simplify. Do the simplest thing right now that will move you measurably toward the goal.

What does “make it work” mean?

Since I love immediate feedback, I always start with a few simple unit tests that will alert me immediately when I have a solution that works. During the unit tests, I decide what I mean by “make it work”. What are the minimum requirements that the solution needs to satisfy?

The practice assignment was a simple scrum checkin app. I selected this app intentionally because it would allow students to practice some simple functional programming, and avoid `class` in a situation where developers commonly rely on classes and class inheritance: UI code.

In agile development, we strive to implement the minimum effective amount of process to enable high velocity development.

The scrum meeting is a common element of that minimal process. Its purpose is to allow team members to check in with each other. On each work day, each small team gathers to answer three questions:

  • What did you do on the last workday?
  • What are you doing today?
  • Is there anything blocking you?

The checkin app lets you do the scrum checkin without the meeting.

We’ll start with the first step: A user must be able to identify themselves. For this, we may need some type of sign in screen.

In the case of this app, I encouraged students to use React, and I offered the React Pure Component Starter as a starting point for the app.

Using this foundation, step one is to create a React component that displays a text field for the user’s name, and a “Sign In” button.

I believe you should always start with tests, first, and that belief is backed up by research from Microsoft and IBM. You can learn more about that in my blog post, “Five Questions Every Unit Test Must Answer”.

You should start with tests that will answer the question, “does it work?”

I always start my unit tests with simple, human readable descriptions of the requirement I’m testing. In this case, those might look something like this:

`should display a text input field for the user's name.`
`should display a "Sign In" button.`

Once that button is pressed, we should create some app state that represents the current logged in user:

`should save current logged-in user in client state.`

Now we have a pretty good definition of what “make it work” means for one of our app views. Before moving on to the rest of the app, we should make those tests pass.

Lessons:

  • Understand the problem. Know what “make it work” means.
  • Begin at the beginning. If the task is to identify what the current user is doing, the first step is probably to identify who the current user is.
  • Do one thing at a time. Don’t try to squeeze all the app requirements into the first module you write. Chances are your finished app will consist of many modules. Keep each module small and focused, and concentrate on one module at a time.
  • Start small and iterate. Write a test. Make it pass. Refactor. Make it work. Make it right. Make it fast. One step at a time. Keep taking those steps until the job is done.

Simple to Understand

Do one thing per line.

Do one thing per function. For example, if you need to use the value of a query parameter, you should dedicate a function to extracting the value of a query parameter from the URL rather than mix that logic with a function that uses the value.

Use one variable to represent only one thing. Sometimes it’s tempting to create a variable to represent some data and then use that variable as a temporary place to store values in transition from one representation to another. For instance, you may be after a query string parameter value, and start by storing an entire URL, then just the query string, then the value. It’s better to have one variable for the URL, a different one for the query string, and finally, a variable to store the value.

This is why I favor `const` over `let` in ES6. In JavaScript, `const` means that the variable can’t be reassigned. (Not to be confused with immutable values. Unlike true immutable datatypes such as those produced by Immutable.js and Mori, a `const` object can have properties mutated.)

`const` is a signal that the variable won’t be reassigned. `let`, is a signal that the variable may be reassigned, such as a counter in a loop, or a value swap in an algorithm. If I don’t need to reassign, `const` is my default choice over `let` because I want the usage to be as clear as possible in the code. I don’t use `var` in ES6. There is value in block scope for loops, but I can’t think of a situation where I’d prefer `var` over `let`.

Sometimes a function can exhibit hidden complexity, meaning that it does more than one thing, but it isn’t immediately obvious. There are many ways that a function can cause side effects, or create a complex array of possible outcomes. Let’s explore how you can simplify the effects of a function.

Avoid Side Effects

The essence of any program is to take some data as input and produce some data as output.

That’s as simple as a program ever gets.

The essence of a function is to take some data as input and produce some data as output.

Therefore, the simplest implementation possible is a function.

If a function complies with that definition, it is easily understood:

foo(...inputs) => output

The function `foo()` according to the signature above takes any number of arguments as input and returns some output. It’s easy to understand:

const result = foo(a, b, c);

In this case, the arguments `a`, `b`, and `c` go in, and `result` comes out. But what if that’s not all that happens? What if `a` is an array, and `foo()` were to mutate it? Then the effects of `foo()` could not be understood simply by looking at its function signature. You’d also have to trace through the complete life of `a` in order to understand `foo()`’s effects.

In JavaScript, arrays and objects are passed by reference, so if you mutate array or object parameters, it doesn’t just affect the variable inside the function. It also affects any other function that uses a reference to the same variable.

In programming lingo, the mutation of `a` is referred to as a side-effect. Side effects are not obviously visible, so when they cause a bug, it can potentially be hard to trace to the root cause.

Therefore, the simplest functions cause no side effects.

Side effects include any state change that is visible outside the function. That includes logging messages, displaying things to the screen, and throwing exceptions.

If it’s possible for a function to throw an exception, that fact should be clearly documented. If it’s possible to avoid an exception and still make the function safe to use, you should favor that course over throwing. This allows your functions to obey the robustness principle:

“Be liberal in what you accept, and conservative in what you send.”

My interpretation is a bit more general:

“Expect the worst. Behave your best.”

In short, if any change occurs that is not communicated by the function signature, it’s a side-effect.

Wherever possible, we should avoid side effects.

Favor Consistency

If you call the same function several times with the same input, and it returns different outputs, it’s not always obvious why that is. Some functions rely on random input, or differ in output depending upon the time it was called, or the value of some outside variable that isn’t specified in the function signature.

Such functions can be difficult to debug. Whenever possible, ensure that your functions will always return the same output given the same inputs.

This feature means that such functions are idempotent. That is, the results of calling the function many times are the same as calling the function once.

Idempotent functions without side effects have a feature known as referential transparency. That means that if you have a function call:

const foo = f(a);

You could replace that function call with the result of `f(a)` without changing the meaning of the program. So, for example if the result of `f(a)` were `42`, you could change the code above to:

const foo = 42;

And your program would still work as expected.

Pure Functions are the Simplest Functions

Functions which have no side-effects and exhibit referential transparency are called pure functions.

A pure function:

  • Given the same input, will always return the same output (relies on no shared mutable state or entropy).
  • Produces no side effects.

The simplest functions are pure functions. If a problem can be solved with pure functions, there’s a good chance that pure functions are the simplest solution to the problem.

Simple to Use

Simple to use means that the code should have a clear and focused API. The code does one thing, and it has a simple interface to accomplish that thing.

Avoid Ad-Hoc Polymorphism

It’s possible in JavaScript to use ad-hoc polymorphism to create a single function that does many different things. For example, jQuery accepts many different types of inputs to the jQuery function.

If you pass a function, it gets triggered on page ready. If you pass a string, it gets interpreted as a DOM selector, and so on.

jQuery deserves a little leeway here because it was written before JavaScript had a good module format, but now, wouldn’t it be more clear if you could do this, instead?

import { pageReady, $ } from 'jQuery';
pageReady(() => {
$('.your-selector').on('click', () => {
doStuff();
});
});

Of course, experienced jQuery developers know that `$(fn);` means `$(document).ready(fn);`, but what about new developers? Shouldn’t you be able to glance at some code and guess its meaning based on what things are called?

Ad-hoc polymorphism means that inside your function, you look at the inputs, and based on their types or values, you follow different branches of conditional logic. Ad-hoc polymorphism adds to function complexity, forces you to write unnecessary conditional logic, and removes your ability to write semantic names for each different role you want a function to play.

Our new standard modules mean that it’s easier to create and use smaller, more focussed modules than it is to group a bunch of loosely related tasks together into a single function.

Encapsulate Private Data

I believe that the object you export from a module should also serve as documentation for that module. In other words, it should expose the supported interface, and only the supported interface.

The thing about private implementation details is that they are far more likely to have breaking changes than the public API, and breaking changes should be avoided if you can avoid them.

In JavaScript, there is a common convention to prefix private properties with underscores:

const foo = () => {
return {
_secret: ‘yeah, nobody could possibly find this…’,
getSecret () {
return this._secret;
}
};
};
const bar = foo();
console.log(bar._secret); // yeah, nobody could possibly find this…

The private underscore convention is flawed for two reasons:

  • Newbies don’t know what the underscore means, so they ignore it.
  • Experienced developers think it doesn’t apply to them. “I know what I’m doing. This warning is just for newbies.” So they ignore it, too. Admit it, experienced devs. You’ve done this before. So have I. Apologies to the three of you who have better discipline than the rest of us. ;)

Contrary to the common misconception, JavaScript does support real encapsulation.

Like objects, closures are a mechanism for containing state. In JavaScript, a closure is used whenever a function accesses a variable defined outside the immediate function scope. It’s easy to use closures: Simply define a function inside another function, and expose the inner function, either by returning it, or passing it into another function. The variables used by the inner function will be available to it, even after the outer function has finished running.

You can use closures to create data privacy in JavaScript using a factory function:

const counter = function counter() {
let count = 0;
return {
getCount() {
return count;
},
increment() {
count += 1;
return this;
}
};
};
const myCounter = counter();
console.log(typeof myCounter.count); // undefined -- private!
myCounter.increment().increment().increment();
console.log(myCounter.getCount()); // 3

This also works in constructors, but factories are better.

Pure function > Function > Factory > Class

Notice that these things increase in complexity as you move from left to right. Start on the left side and move to the right only as needed.

“Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function.” ~ John Carmack

It’s worth reading that quote twice. Burn it into your brain so that you think of it every time you move to implement a new feature.

For module interfaces, I favor pure functions over functions with state & or side-effects, regular functions over factories, and I never export a `class`.

Note: You never need to export a `class` in JavaScript.

Simple to Reuse

Simple to reuse means that you should be able to extract your code into its own module, import it somewhere else, and use it without breaking a bunch of things or committing to a time consuming refactor. Ideally, this should be one cut, one paste, and a couple `import` statements.

If a function relies on any external state, it won’t be that simple. You’ll need to change the function signature, find all the call sites, and pass in all the required parameters.

If the function has side-effects and depends on the timing (when its run, relative to clock time, or relative to other operations or function calls), that can be even more problematic and difficult to unravel.

Due to their independence from shared state, timing, and similar factors, pure functions are obviously more simple to reuse than impure functions.

Simple to Extend

For stability, API’s should be closed to breaking changes, but open to extension. For functions, this could mean passing named options into a function rather than a list of arguments so that you avoid passing a bunch of values that may or may not be semantically named at the function call site.

Using an options object also allows you to avoid passing `null` for optional parameters.

What reads better?

// Individual arguments
const item = createFilmItem(title, null, null, recommendedPrice);
// Options object
const item = createFilmItem({ title, recommendedPrice });

The first example uses the function signature: `(title: String, releaseDate?: Date, runningTime?: Number, recommendedPrice: Number) => Film`

Using separate arguments instead of an options object forces you to pass `null` value placeholders for optional values that you don’t need to pass (or can’t pass, because the information is not available).

Compare with the second function signature: `createFilmItem({ title: String, releaseDate?: Date, runningTime?: Number, recommendedPrice: Number }) => Film`

Using an options object leaves a lot of flexibility for future API extensions.

Trouble reading the function signatures? Check out the docs in Rtype: reading function signatures for an explanation.

Export Factories Instead of Classes

As I’ve mentioned many times before, `class` affords `extends` like balls afford throwing and chairs afford sitting, but `extends` leads unwary developers down the path to the fragile base class problem, the gorilla banana problem, etc…

Don’t lure your users to crash on the rocks of poor OO design (given enough time and evolution, all class taxonomy designs are wrong for new use-cases).

Instead, set them up for success by exporting something that’s easy to extend or compose into new objects. If you don’t need instance state, export a pure or normal function. If you do need instance state, try a stamp: See The Stamp Specification.

In JavaScript, any function can instantiate and return objects. When you do so without a constructor (e.g., ES6 `class`), it’s called a factory function. `class` can’t compete with the power and flexibility of factories — specifically stamps.

Because of the added flexibility of factories, it’s often desirable to refactor from a class implementation to a factory implementation. For example, to add polymorphism (example), or to switch to object pools to avoid garbage collectors.

But because ES6 classes throw errors if you omit `new`, and code outside your control may use constructor-enabled features such as `instanceof`, switching from a class to a factory is a breaking change.

Save yourself the trouble. Export a factory from the start.

Factory composition with stamps gives you tremendous flexibility for API extension; more than `class` — even when compared to experimental features such as class decorators.

Conclusion

Simplify your code. Start with the least complicated implementation and work your way toward more complex solutions only when the problem demands it.

Remember:

  • Keep It Stupid Simple (KISS)
  • Make it work, make it right, make it fast.
  • Understand the problem. (Know what “make it work” means.)
  • Begin at the beginning.
  • Start with tests.
  • Do One Thing (DOT).
  • Start small and iterate.
  • Pure function > Function > Factory > Class

Simple beats clever every day of the week.


Learn JavaScript with Eric Elliott

  • Online courses + regular webcasts
  • Software testing
  • The Two Pillars of JavaScript (prototypal OO + functional programming)
  • Universal JavaScript
  • Node
  • React

Eric Elliott is the author of “Programming JavaScript Applications” (O’Reilly), and “Learn JavaScript Universal App Development with Node, ES6, & React”. He has contributed to software experiences for Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC, and top recording artists including Usher, Frank Ocean, Metallica, and many more.

He spends most of his time in the San Francisco Bay Area with the most beautiful woman in the world.