
“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.”
–Brian Kernighan
This sounds nice, and can (maybe) be good advice in some cases. But it can’t possibly be true; in fact, it rests on a bit of a logically flawed syllogism:
- A: Debugging is twice as hard as writing code.
- B: Writing code is hard.
- C: Ergo, if you write the hardest code you possibly can, you are not smart enough to debug your own code.
The problem sits in both in assumption A, and in an unspoken assumption about what it means for something to be “hard”.
Regarding A, there’s no backing behind this “debugging is twice as hard” assumption; it’s simply stated. Regarding the idea of relative hardness of a problem; the conclusion to the above syllogism assumes that there is a ceiling of “hardness” which you are capable of comprehending. If your code is at that ceiling, it argues, then debugging it will be impossible for you, because debugging it will be harder than writing it, which was the limit of how “hard” a problem you can solve. I reality, I don’t believe this to be the case; something may be “harder” than anything I’ve yet worked through, but that only means it might take me more time and more effort to understand it; not that I cannot possibly comprehend it.
Now, we all know that bugs can be hard to find and fix, so we tend to simply accept, even encourage, statements like this. But are they really true?
Think back to your Algebra, Trig, or Calculus classes; if you took these in the same sort of classes, with the same sort of textbooks, that I did, you were routinely given assignments whose answers were in the back of the book. The idea was, you would work through the problems, and then check your answer to see how you did. If you had an incorrect answer, then you would look at the steps you took to solve the equation until you found the point at which you had made a mistake.
This is, to my mind, completely analogous to programming. You write a piece of code to do a certain thing; then you either write a test, or you simply run it with some sample input, to ensure that it does what you think it does. If you test it thoroughly (note: this is actually hard, and is the reason that bugs often slip into production code), then you will either:
- a) discover that your code successfully does what you intended, or
- b) discover a case where your code returns an incorrect result; in other words, a bug, or a place where you made a mistake.
In case b, you’ll do the same thing you used to do with your math homework; you’ll start going through it until you find the place where you made a mistake, and you’ll fix it, and (ideally), you’ll write a test that mocks this scenario, and be satisfied that this bug is squashed.
Debugging is indeed hard; there are several things which make it harder than just solving a math equation. For one thing, in your programs, you may not have total control over what inputs (if applicable) are sent to your function. You might intend that factorial(num) always receives an integer as its argument. What if some malevolent person calls it and sends a String? An octal value? An object? An array? Do you care? Does your function try to detect this and either raise an error or return false? Is returning false or nil really what you want to do in that case? What if a number is sent that is larger than MAX_INT? Does it fail silently, interpret the number as whatever your programming language sees it, raise an error… ? In comparison, you can pretty much ensure that x, whatever it may be, in your Algebra exercise, is a number. It’s probably a Real number. And you can probably find it without too much trouble.
Another thing that I like about this example is that it basically shows that the process of learning math is essentially the process of locating and fixing your mistakes. The instructor, the course, assumes that you will not always get the correct answer. That’s the whole reason there are answers in the back of the book, and that you are assigned those questions which actually have answers. The basic premise is that you will get some questions wrong, and that through the process of realizing you are wrong and going back to find where you are wrong (and fixing it), you are doing something which we call learning math.
Programming, I’d argue, works the same way. You learn to program, not by consistently writing correct programs, but by writing bugs. Then, once you realize you have written a bug, you locate it, attempt to understand why it’s a bug, why you erroneously believed that it would work, what you should do instead, and how to do that thing.
We call this “learning to program”.
If you are not smart enough to find and fix bugs, you are actually not smart enough to learn to program. The good news is, you are smart enough to find and fix bugs, and you are smart enough to program.
The logical error comes in the assumption that writing the hardest/most complex/most clever code that you can is the theoretical limit of your intelligence, and that, since “debugging is harder than coding”, you are not clever enough to see the bugs in your own code.
That’s similar to saying that if you are doing the hardest Calculus question you are currently capable (or barely capable) of doing, if you make a mistake, you are unable to find and fix that mistake, because finding the mistake is (somehow) harder than solving the problem.
Put that way, it seems absurd. Of course finding your mistake isn’t “harder” than solving the problem; finding the mistake is part of the problem. In fact, since generally every step in solving an equation or working out an integral or a derivative involves smaller steps based on things you have already learned and mastered (again… theoretically), you almost definitely can, for sure, find your error if you go through each of your steps carefully, thinking about what you are doing and why you’re doing it.
Sure, finding bugs is hard. That’s because if it was easy, you wouldn’t have written the bug in the first place. The bug represents that place in your algebra homework where you’d make that tiny arithmetic error, or forget to flip the sign when a number was/wasn’t negative, or where you’d mis-apply some other mathematical rule. Spotting where you made that error is simply the process of learning math.
That’s not the same as it being too hard for you to accomplish. I would guess (though I can’t know this) that Mr. Kernighan intended his little aphorism to be taken with a rather liberal grain of salt, that it was actually a bit of the subtle humor engineers and geeks are so fond of. The fact that we’ve taken it at face value and treated it as though it were actually true is our fault, not Brian Kernighan’s.
Now, the actual quote (according to wikiquote) seems to be from Elements of Programming Style, and goes:
“Everyone knows that debugging is twice as hard as writing a program in the first place. So if you’re as clever as you can be when you write it, how will you ever debug it?”
This still has that logical leap embedded in it (”Everyone knows that debugging is twice as hard”, etc.), but it does have a good, valid point, depending on how you define “as clever as you can be”. (If being clever is actually being smart, then “as clever as you can be” would be to only write code which you are capable of debugging… but I digress.)
What I think this really is intended to mean is: if you write code that you do not completely understand, you will (potentially) introduce bugs which you are not capable of identifying or resolving.
The key is code that you don’t understand. The reason you won’t be able to find the bugs is that you don’t understand what your code is doing in the first place. If that’s true, then yes, of course you have written un-debuggable software (for you, at least; someone who understands what you wrote can certainly find your bugs). The other side of this coin is that, if you take the time and effort to understand the code that you wrote, you can find the bugs. And you can remove them.
This is called programming. You’ve probably been doing it for a long time. The fact that we’re still creating and fixing bugs only means that we haven’t finished learning to do it yet.