“He reached out and pressed an invitingly large red button on a nearby panel. The panel lit up with the words PLEASE DO NOT PRESS THIS BUTTON AGAIN. He shook himself.”
“The Hitchhiker’s Guide to the Galaxy”
I’ve written elsewhere that to me, the source code itself constitutes the design of a software product. All other forms of (design) documentation fall behind, sooner or later. If you have other documentation in parallel — however legitimate your reasons may be — you have to pay the price that all violations of the DRY principle incur.
If the code is the documentation of the design, it should be easy to read. Good identifier names, short routines that do just one thing, no super-clever hacks and so on. Most importantly, it should be free of surprises such that it can be read (and skimmed) without major effort. Enter the Principle of Least Surprise (PoLS).
A for-loop like this is a surprise:
1 2 3 |
for (int i = 0; i <= len; i++) ... |
Why? If a programmer sees a start index of 0 (s)he assumes an iteration over a half-open range; that is, the upper bound is excluded. Even if it is OK in this case and not a bug (who is to tell without browsing a lot of other code?) it results in quite some head-scratching. Contrast this with this rewrite:
1 2 3 4 5 6 |
const int SPARE_ELEMENT_COUNT = 1; ... int gross_len = len + SPARE_ELEMENT_COUNT; for (int i = 0; i < gross_len; i++) ... |
Even without comments, everyone gets it: “Do something ‘gross_len’ times”.
Recently, I came about code that initialized a state machine:
1 2 3 |
state = STATE_READY | STATE_PERMANENT | STATE_PRIO_HIGH; |
I was hunting down a bug. Since no events had occurred, I expected the state machine to be still in state ‘permanent’ but the behavior of the component made me believe that it wasn’t. So I loaded the code into a debugger and set a write breakpoint on variable ‘state’ to find out which code (actually who) reset the ‘permanent’ flag. But apart from the initialization of ‘state’ I didn’t get a hit. After walking around for some time (it is always a good idea to get away from the computer when solving difficult problems) I had another desperate idea: maybe ‘state’ was never initialized to ‘permanent’. I opened up the header file that defined the flags and what I stared at with horror was this:
1 2 3 |
static const int STATE_READY = 0x01; |
1 2 3 |
static const int STATE_PRIO_HIGH = 0x02; |
1 2 3 |
// static const int STATE_PERMANENT = 0x04; |
1 2 3 4 |
// Our platform doesn't support 'persistent' --> ignore! static const int STATE_PERMANENT = 0x00; |
I guess I must have looked like Steve McConnell’s “Coding Horror” guy.
Now, I consider it OK if a platform doesn’t support all features; but this way of dealing with it is probably the worst. And it is a clear violation of PoLS: when I see code that looks like it sets a flag, I expect that it sets the darn flag. Period.
So the general advice for adhering to PoLS is “Write WYSIWYG Code”. Code that uses well-known idioms consistently, code that can be grasped without debugging and jumping back and forth between files and declarations. Put all cards on the table; say what you mean and mean what you say.