I was involved in some discussion about Assertions in general (and Class Contracts
in particular) on the borland.public.delphi.non-tech newsgroup today, kindled by
introduction of Class Contracts into the Pascal language for Chrome (as described
here),
and i figured some of the ideas discussed might be worth mentioning.
Since Assertions and Class Contracts will not be present in Release builds,
how will i diagnose those when things go wrong?
This question reminds me of a pet grievance of mine: people hat confuse (and therefore
mix up) assertions and exceptions. I can remember one particular developer (you
know who you are ;) working for me who really liked assertions – liked them so much,
in fact, that i saw them poping up all over the place in the finished end user application,
whenever anything went wrong (We did Release builds with
Assertions enabled, at the time).
Assertions (and Chrome’s Class Contracts) should be used to ensure that parameters
are within the range that your methiod (or class) can handle.
For example, a List.Remove method might not be expected to handle an index that is
below zero; so checking for this is the right job for an assertion. If you’re method
is reading characters typed on a keyboard then by definition that method
is defined to handle whatever the user will type (and react accordingly of
the input is bad) – so this is not the right job for an assertion. Similarly, if
you’re validating data read from a disk file or received over a network, again your
method should be defined to handle whatever data it will receive.
Another way to look at it is that assertions (and class contracts) are simply a
way to notify you, as the developer/tester/debugger that something is wrong, early
on.
You should write your code with the assumption that the assertions are not there.
If you encounter an unforeseen problem, chances are your assertions will give you
a better picture of what went wrong then the Access Violation you might have gotten
a few lines down the road (or a few calls up the callstack). The assertion is there
to help you locate and debug the error, but not to "catch" it. It will
not "prevent" the failure (like a properly handled exception might do,
and it shouldn’t be expected to.
For example, imagine the following method:
In either case. The error is there and waiting to happen. All the assertion does
for you is catch it "early on" and with a prettier message ("Hey,
you called DoSomething with a nil object" rather then just "Kaboom, have
fun searching for the problem").
Ok, that’s all fine and makes sense, but what if i’m writing a component library
used by third parties? Should i consider calls from outside of my library as "external
input"?
That’s a very good question, and the best answer is a decisive "maybe".
A StringToInt function could very well have a pre-condition or assertion
that the string must be valid. If it does, the surrounding code (ie the caller)
would be responsible for testing – say – the text entered into the edit box, before
passing it into StringToInt. If StringToInt were, on the other hand,
defined to take any input, then the user code doesn’t have to check, and could rely
on StringToInt behaving in some proper way when being fed a bad string).
Iow, for all data coming into your application from an external source, that data
has to be validated on some level (preferably as high up as possible); all
levels below that can then assume (ie require/assert) the data to be valid.
Iagree that for libraries made for external consumption, the lines are blurry. Is
assigning a bad value to a property to be considered "external input"?
or can it be made the user (ie application-developer’s) responsibility to assure
data is valid? In many cases, there’s no black and white answer, and it will boil
down to properly documenting the requirements.
For example, a for method that takes an object parameter to work with (say Print(aDocument:
Document)), it would be perfectly acceptable to require aDocument to be assigned
(and just assert/AV if it isn’t). A lot of code in our libraries is written that
way. On the other hand, a property, especially one that can be set at design-time,
should usually accept any data and handle it correctly (worst case by rejecting
it with an exception).
But again, your mileage may vary, and you should decide this case by case/.
In summary, assertions and contracts are used to protect against your own
programming errors, while normal error handling (such as exceptions) should be applied
to external inputs. Think of them as sort of "sanity checks" while your
application runs in debug mode; just making sure that stuff that couldn’t be wrong
anyway, really isn’t. As the name "assertion" implies, its not really
a check for the right value, it’s a statement: "i know this to
be true. if not, please reboot universe."