It's a truism that good code is boring. At best, that advice is badly worded; and at worst, it's catastrophic. Nobody actually likes boring code: What they like is code whose behavior is easy to predict.
If you’re writing something fundamentally new, your code never has to be boring. In fact, if your code is boring, that’s a pretty strong signal that you’re not writing anything new, and ought to refactor or use an existing library.
One way to make code terribly boring is to use too much boilerplate. A little boilerplate gives the reader signposts by which to navigate, but too much only makes them miserable. For example, if a reviewer’s eyes glaze over, they're likely to miss bugs; sometimes even important ones, like Apple’s infamous goto fail in the midst of (one of many) if/else chains.
Even ordinary text, when surrounded by noise, can be awfully hard to read:
c b u g s v t q h n y e
c p b k p r e n e d f m
c q i n p r i e e n q j
v p b w a y i k s o d h
t p r o d u c t i o n j
s p x d c o i n x t d z
y r d u m a r e f n o t
w y m j g n o z t u z b
k b j m u c h t q d e b
w k t p j g l h l o j u
a l f u n c a b x r r g
m v w a s m j p d p m s
The meaning is clearer without all that junk:
b u g s
i n
p r o d u c t i o n
a r e n o t
m u c h
f u n
You may not consider boilerplate “noise,” but it can obscure the meaning of code every bit as effectively.
Hand-rolled imperative loops are surely the worst offenders. There’s a kind of macho “So you think you’re better than me?” pseudo-wisdom that good, clean, honest loops are better than fancy Higher Order Functions (HOFs). If you’re currently in that camp, please consider the following.
Probably half of all small functions ever written look something like this:
def do_something(items): | |
result = initial_value | |
for item in items: | |
result = update(result, item) | |
return result | |
result = do_something(items) |
That pattern is neatly encapsulated by the standard reduce function:
from functools import reduce | |
result = reduce(update, items, initial_value) |
The “initial value” is optional, and often unnecessary in practice. Almost all modern programming languages have the reduce function. In JavaScript, it’s a method of Array:
items.reduce(update, initialValue) |
Experienced programmers know the behavior of standard HOFs, but can’t know the meaning of hand-rolled loops without examining each one closely. Loops are horribly boring.
The most popular HOFs are map and filter. They're standard functions in Python, JavaScript, and most other languages:
jenny = (8, 6, 7, 5, 3, 0, 9) | |
is_even = lambda n: n % 2 == 0 | |
square = lambda n: n * n | |
squared_evens = tuple(map(square, filter(is_even, jenny))) |
const jenny = [8, 6, 7, 5, 3, 0, 9]; | |
const isEven = n => n % 2 === 0; | |
const square = n => n * n; | |
const squaredEvens = jenny.filter(isEven).map(square); |
Python, like Haskell and Scala, even has language-level syntax for map and filter in the form of list comprehensions and generator expressions:
squared_evens = [square(n) for n in jenny if is_even(n)] |
That one-liner's intent and behavior are as clear as daylight. It’s shorter, less noisy, less likely to contain a bug, and probably faster than a functionally equivalent hand-rolled loop.
squared_evens = [] | |
for n in jenny: | |
if is_even(n): | |
squared_evens.append(n) |
For a masterclass in replacing boring code with higher order functions, watch this presentation by Sean Parent, Senior Principal Scientist at Adobe; or as it’s sometimes called, “That’s a rotate.”
You don't have to quit writing raw loops cold turkey, but you should probably cut down. Your code (and its reviewers and maintainers) will thank you.
Nice article, my personal favorite is the ternary operator!