September 29, 2014
Code Inspections: A Powerful Testing ToolQuick post about code inspections.
Now, when I say "code inspections" to most developers, it conjures up images of teams sitting in airless meeting rooms staring at someone's code and raising issues about style, about formatting, about naming conventions, about architectural patterns and so on.
But this is not what I really mean by "code inspections". For me, first and foremost, inspections are a king of testing. In fact, statistically speaking, inspections are still the most effective testing technique we know of.
Armed with a clear understanding of what is required of a program (and in this context, "program" means a unit of executable code), we inspect that code and ask if it does what we think it should - "at this point, is what we think should be true actually true?" - and look for possibilities of when it could go wrong and ask "could that ever happen?"
For example, there's a bug in this code. Can you see it?
Instead of writing a shitload of unit tests, hoping the bug will reveal itself, we can do what is often referred to as a bit of whitebox testing. That is, we test the code in full knowledge of what the code is internally. The economics of inspections become clear when we realise the full advantage of not going into testing blind, like we do with blackbox testing. We can see where the problems might be.
There are numerous ways we can approach code inspections, but I find a combination of two specific techniques most effective:
1. Guided Inspection - specifically, inspections guided by test cases. We choose an interesting test case, perhaps by looking at the code and seeing where interesting boundaries might occur. Then we execute the code in our heads, or on paper, showing how program state is affected with each executable step and asking "what should be true now?" at key points in execution. Think of this as pre-emptive debugging.
2. Program Decomposition - break the code down into the smallest executable units, and reason about the contracts that apply to each unit. (Yes, everything in code that does something has pre- and post-conditions.)
In practice, these two techniques work hand in hand. Reasoning about the contracts that apply to the smallest units of executable code can reveal interesting test cases we didn't think of. It also helps us to "fast-forward" to an interesting part of a wider test scenario without having to work through the whole process up to that point.
For example, let's focus on the program statement:
sequence[i] = sequence[i - 1] + sequence[i - 2]
What happens if the result of evaluating sequence[i - 1] + sequence[i - 2] is a number too large to fit in a Java int? The maximum value of an int in Java is 2,147,483,647. This code might only work when the sum of the previous two numbers is less than or equal to that max value.
Fibonacci numbers get pretty big pretty fast. In fact, the sum of the 48th and 49th numbers in the sequence is greater than 2,147,483,647.
And there's our bug. This code will only work up to the 49th Fibonacci number. To make it correct, we have choices; we could add a guard condition that blocks any sequenceLength greater than 49. (Or, for fans of Design By Contract, simply make it a pre-condition that sequenceLength < 50.)
Or we could change the data type of the sequence array to something that will hold much larger numbers (e.g., a long integer - although that, too, has its limits, so we may still need to guard against sequence lengths that go too high eventually.)
Now, of course, unit tests might have revealed this bug. But without exception, when teams tackle the Fibonacci sequence TDD exercise in training, if they spot it all, they spot it by reading the code. And, in this example, it would be extraordinary luck to choose exactly the right input - sequenceLength = 50 - to tell us where the boundary lies. So we'd probably still have to debug the code after seeing the test fail.
Reading the code, you may well spy other potential bugs.
And, yes, I know. This is a very trivial example. but that only reinforces my point: it's a very trivial example with bugs in it!
Try it today on some of your code. Are there any bugs lurking in there that nobody's found yet? I'll bet you a shiny penny there are.
Posted 3 years, 4 months ago on September 29, 2014