Software development is fraught with peril. For any development task, there are a million ways to do it wrong, and the one correct way is a small, narrow trail that winds quietly through the forest. It takes experience to know the path and to avoid the dangerous pitfalls along the way.
The following is a list of seven hard-won lessons from many trips into the dark forest. It is an opinionated look at what to do and what not do when navigating the treacherous trail of coding an application. It’s far too easy to stray from the correct path, and these notions will help you stay on course and arrive safely at your destination with a minimum of snags, missteps, and misadventures.
Follow this advice and avoid the brambles.
Look both ways before crossing a one-way street
In the United Kingdom, they drive on the “wrong” side of the road. The Brits realize that visitors are not always aware of that, because on the crosswalks in London, there is text painted on the ground as you step off the curb that says “Look right.” Being American, my habit is to look left, so I appreciated the advice.
But I couldn’t shake the habit, so I ended up looking both ways.
And I realized that looking both ways was a good metaphor for coding: Protect against the impossible, because it just might happen. Code has a way of surprising you, and it definitely changes. Right now you might think there is no way that a given integer variable would be less than zero, but you have no idea what some crazed future developer might do.
Go ahead and guard against the impossible, and you’ll never have to worry about it becoming possible.
Never make one thing do two things
Oh man, this is my personal pet peeve. There is no end to the trouble caused by having one thing do more than one thing.
If you’re ever tempted to reuse a variable within a routine for something completely different, don’t do it. Just declare another variable. If you’re ever tempted to have a function do two things depending on a “flag” that you passed in as a parameter, write two different functions. If you have a switch statement that is going to pick from five different queries for a class to execute, write a class for each query and use a factory to produce the right class for the job.
It might take a bit more coding, but please know that the complexity created by giving one thing more than one responsibility (as Uncle Bob Martin would put it) is just begging for trouble down the road.
Code against interfaces, not implementations
It’s all too easy—and all to common—to couple your code to specific implementations. You start out with an application that does all its logging to a text file. That works great in the early stages, but pretty soon your application is running on multiple machines, and now those text files are everywhere and hard to track and manage. You decide to log to a central database and soon realize that it is a very large job to change from writing to a text file to writing to a database.
If only you had coded your logging system against an interface. Then, when presented with the problem above, you could just write a new implementation of the logging interface, plug that in, and voilà! Problem solved!
It’s always easier to plug a new implementation into an interface than it is to pull out a deeply coupled implementation, the tentacles of which are entangled deeply within your application.
Fear not the explaining variable
Consider the following code.
if (
(user.age > 18 && user.hasValidID) &&
(!user.isBanned || user.hasAppealPending) &&
(currentDate >= event.start && currentDate <= event.end) &&
(user.ticketsOwned < event.maxTickets)
) {
grantEntry(user);
}
This code is complex and really hard for my brain to parse. It requires you have to mentally unpack each boolean expression, keeping track of what is going on in the parentheses. It’s quite a bother and really hard to figure out.
This code is much easier to understand:
const isAdultWithID = user.age > 18 && user.hasValidID;
const isAllowedDespiteBanStatus = !user.isBanned || user.hasAppealPending;
const isEventOngoing = currentDate >= event.start && currentDate <= event.end;
const hasRemainingTicketCapacity = user.ticketsOwned < event.maxTickets;
const qualifiesForEntry =
isAdultWithID &&
isAllowedDespiteBanStatus &&
isEventOngoing &&
hasRemainingTicketCapacity;
if (qualifiesForEntry) {
grantEntry(user);
}
Each of the complex Boolean variables is given a good, explanatory name so that the notion of whether a given user qualifies for entry or not can be easily understood. Don’t be afraid to do that.
Ruthlessly root out the smallest of mistakes
I follow this rule religiously when I code. I don’t allow typos in comments. I don’t allow myself even the smallest of formatting inconsistencies. I remove any unused variables. I don’t allow commented code to remain in the code base. If your language of choice is case-insensitive, refuse to allow inconsistent casing in your code.
Take care of these kinds of small things and the bigger problems will take care of themselves.
Implicitness is evil
This is something that I have never understood—why some developers view implicitness as a virtue. I find it amazing that anyone would choose to be less clear rather than more clear.
Implicitness increases cognitive load. When code does things implicitly, the developer has to stop and guess what the compiler is going to do. Default variables, hidden conversions, and hidden side effects all make code hard to reason about.
Because one can’t always guess right, eliminating the need to guess by making things explicit makes for better code.
Limit scope as much as possible
Everyone knows — or everyone should know at least — that global variables are to be strictly avoided. They can too easily get out of control, be changed in places you least expect, and mysteriously have strange values.
Or, put another way, their scope is too large. Thus, the principle of limiting scope says that global variables are a no-no.
But that principle can apply more broadly. Limit the visibility of fields in your class as much as possible. Use private if you can. Declare all your variables at the last minute if your language supports the notion of inline variables. Don’t let those pesky variables get out of hand by exposing them to places they have no business being available.
So there are a few ways to make sure that your code stays under control. These lessons are hard won through both experience of doing it wrong, and even more prevalent, from having to maintain code that was written by folks who hadn’t learned these lessons.
Follow these habits, and that bramble-filled trail gets a lot easier to walk.



