What an amusing coincidence. The dust has barely settled after the Apple’s mishap with extraneous
goto fail; in OpenSSL (used widely by OSX and iOS), that made it ripe for exploits, before we’ve heard another similar story. This time it was about multiple
goto cleanup; statements that comprised a vulnerability in GnuTLS, a rough equivalent of OpenSSL on various Linux distributions.
goto! Everyone warns it cannot be trusted, it shouldn’t be used, it’s basically the work of the devil. Yet, some people just don’t listen, and we can witness firsthand all the woes that befall them. Will it serve as a warning for the others? Can we hope for at least this much?…
The answer is no. Not only we can’t, but also shouldn’t. Despite what many would proclaim, these two fiascoes are hardly about the
goto statement. It’s very tempting, of course, to pigeonhole them into that box, but the truth is just more complex than that.
goto statement is only one example, drawn from a certain class of programming techniques and language features. What they all have in common is that they overstep the safety guarantees a language offers, however elaborate or scarce they are. In return, those techniques offer additional capabilities, exceeding the usual “power level” available to developers.
Pretty much every language hides something of this kind. It may be
goto in C. It could be reflection in Java. It might be metaclasses in Python, or import hooks. Even the pure and graceful Haskell has the infamous
unsafePerformIO, the use of which potentially throws all the normal guarantees of correctness out of the window.
What all those features have in common is their inevitability. You may try to ignore them, or write off as bad practices at best. But come a sufficiently complex program, an amply convoluted system, a problem hairy enough, and they suddenly become adequate, acceptable – or even necessary. The malevolent
goto, to stick with the most prominent example, becomes basically mandatory whenever C code mixes error checking with handling resources that require proper cleanup.
Therefore, the only reasonable solution is to learn to cope with it all, and just be extra careful. Translated to software engineering practices, this means taking every precaution we can reasonably afford to minimize the risk of bugs. Most of this stuff is pretty simple, really. It doesn’t necessarily involve any gymnastics with automatic testing, too, which is every developer’s “favorite” discipline. The OpenSSL’s fiasco would have been prevented by any tool that looks for unreachable code; that’s not really asking a lot.
So if we could learn anything from those high profile
failures, it’s that basic stuff, low hanging fruit – like code reviews and static analyzers – actually help, for little to no cost. I’m sure such conclusion is much more valuable than yet another tirade, coming from a You Should Not Code Like That department.
At work I’ve got a colleague who displays unusual aptitude in coming up with amusing terminology for everyday (coding) things. Among those, the Cake Pattern is always certain to provoke few laughs.
Recently, though, I heard him mention a great common sense rule for any development decision, from the grand architecture down to single line of code. It can be phrased like this:
One hack is fine. But if you need another one on top of the first, it’s probably high time to really consider what you are doing.
For good measure, I’ll call it a Principle of Two Hacks, and I’m pretty convinced that it’s a very beneficial rule to apply in programming – especially when creating any non-trivial, not throw-away programs.
At first it may sound rather vague, however. It’s concept of a “hack” is not easily explicable, or put into indisputable definitions. But that’s what makes it powerful: we don’t need to invoke elaborate (and often controversial) notions of design patterns or abstraction to be able to discuss them.
At best, the ideas of accidental complexity or technical debt might be somewhat close to what developers typically deem as a hack. In practice, this is mostly an opaque intuition that stems from experience or skill, and it’s usually very hard to express in words. Yet, it’s always apparent whenever we encounter it, even though the exact sensation may vary considerably: from a dim feeling that something is maybe a bit off, up to severe intellectual nausea caused by looking at really bad code.
I also like how extremely pragmatic this principle is. Quick-and-dirty fixes making it into production code are just a fact of life, and we are not prohibited from letting them slip. What we are strongly advised here is to maintain integrity of the software we’re writing, trying not to stack one hack upon another.
But even that is not absolute, nonnegotiable gospel; there might still be valid reasons to loosen up its structure. The important part, however, is to notice when we’re doing something fishy and consciously decide whether or not it is a good idea. It is much better than just plunging forward with total disregard of sanity of future maintainers.
Ultimately, this principle is just subtly telling us to think, and that is never a bad advice.