Professional software development consists of more than feature development. There are also meetings, time reports, compliance activities, and ... defects.
You run into errors and problems all the time. Your code doesn’t compile, the software doesn’t do what it’s supposed to, it runs too slowly, et cetera.
The better you get at solving problems, the more productive you are. Most of your troubleshooting skills may be based on “the shifting sands of individual experience” [4], but there are techniques that you can apply.
This chapter presents some of them.
12.1 Understanding
The best advice I can think of is this:
If you don’t understand why something doesn’t work1, then make understanding it a priority. I’ve witnessed a fair amount of ‘programming by coincidence’ [50]: throw enough code at the wall to see what sticks. When it looks as though the code works, developers move on to the next task. Either they don’t understand why the code works, or they may fail to understand that it doesn’t, really.
If you understand the code from the beginning, chances are that it’ll be easier to troubleshoot.
12.1.1 Scientific Method
When a problem manifests, most people jump straight into troubleshooting mode. They want to address the problem. For people who program by coincidence [50], addressing a problem typically involves trying various incantations that may have worked before on a similar problem. If the first magic spell doesn’t work, they move on to the next. This can involve restarting a service, rebooting a computer, running a tool with elevated privileges, changing small pieces of code, calling poorly-understood routines, etc. When it looks like the problem has disappeared, they call it a day without trying to understand why [50].
Needless to say, this isn’t an effective way to deal with problems.
Your first reaction to a problem should be to understand why it’s happening. If you have absolutely no idea, ask for help. Usually, though, you already have some inclination of what the problem may be. In that case, adopt a variation of the scientific method [82]:
Make a prediction. This is called a hypothesis.
Perform the experiment.
Compare outcome to prediction. Repeat until you understand what’s going on.
Don’t be intimidated by the term ‘scientific method’. You don’t have to don a lab coat or design a randomised controlled double-blind trial. But do try to come up with a falsifiable hypothesis. This might simply be a prediction, such as “if I reboot the machine, the problem goes away,” or “if I call this function, the return value will be 42.”
The difference between this technique and ‘programming by coincidence’ is that the goal of going through these motions isn’t to address the problem. The goal is to understand it.
A typical experiment could be a unit test, with a hypothesis that if you run it, it’ll fail. See subsection 12.2.1 for more details.
12.1.2 Simplify
Consider if removing some code can make a problem go away.
The most common reaction to a problem is to add more code to address it. The unspoken line of reasoning seems to be that the system ‘works’, and the problem is just an aberration. Thus, the reasoning goes, if the problem is a special case, it should be solved with more code to handle that special case.
This may occasionally be the case, but it’s more likely that the problem is a manifestation of an underlying implementation error. You’d be surprised how often you can solve problems by simplifying the code.
I’ve seen plenty of examples of such an ‘action bias’ in our industry. People who solve problems I never have because I work hard to keep my code simple:
People develop complex Dependency Injection Containers [25] instead of just composing object graphs in code.
People develop complicated ‘mock object libraries’ instead of writing mostly pure functions.
People create elaborate package restore schemes instead of just checking dependencies into source control.
People use advanced diff tools instead of merging more frequently.
People use convoluted object-relational mappers (ORMs) instead of learning (and maintaining) a bit of SQL.
I could go on.
To be fair, coming up with a simpler solution is hard. For example, it took me a decade of erecting increasingly more elaborate contraptions in object-oriented code before I found simpler solutions. It turns out that many things that are difficult in traditional object-oriented programming are simple in functional programming. Once I learned about some of these concepts, I found ways to use them in object-oriented contexts, too.
The point is that a catchphrase like KISS2 is useless in itself, because how does one keep things simple?
Youoftenhavetobe smart to keep it simple3, but look for simplicity anyway. Consider if there’s a way you can solve the problem by deleting code.
12.1.3 Rubber Ducking
Before we discuss some specific problem-solving practices, I want to share some general techniques. It’s not unusual to be stuck on a problem. How do you get unstuck?
You may be staring at a problem with no clue as to how to proceed. As the above advice goes, your first priority should be to understand the problem. What do you do if you’re drawing a blank?
If you don’t manage your time, you can be stuck with a problem for a long time, so do manage your time. Time-box the process. For example, set aside 25 minutes to look at the problem. If, after the time is up, you’ve made no progress, take a break.
When you take a break, physically remove yourself from the computer. Go get a cup of coffee. Something happens in your brain when you get out of your chair and away from the screen. After a couple of minutes away from the problem, you’ll likely begin to think about something else. Perhaps you meet a colleague as you’re moving about. Perhaps you discover that the coffee machine needs a refill. Whatever it is, it temporarily takes your mind off the problem. That’s often enough to give you a fresh perspective.
I’ve lost count of the number of times I return to a problem after a stroll, only to realise that I’ve been thinking about it the wrong way.
If walking about for a few minutes isn’t enough, try asking for help. If you have a colleague to bother, do that.
I’ve experienced this often enough: I start explaining the problem, but halfway in, I break off in mid-sentence: “Never mind, I’ve just gotten an idea!”
The mere act of explaining a problem tends to produce new insight.
If you don’t have a colleague, you may try explaining the problem to a rubber duck, such as the one shown in figure 12.1.
Figure 12.1 A rubber duck. Talk to it. It’ll solve your problems.
It doesn’t really have to be a rubber duck, but the technique is known as rubber ducking because one programmer actually did use one [50].
Instead of using a rubber duck, I typically begin writing a question on the Stack Overflow Q&A site. More often than not, I realise what the problem is before I’m done formulating the question4.
And if realisation doesn’t come, I have a written question that I can publish.