[vtk-developers] Use of assert [LONG]
François Bertel
francois.bertel at kitware.com
Fri Jul 22 09:50:57 EDT 2005
Ok, here is how I use assertions in VTK.
I use them in the context of Design By Contract. The term "Design By
Contract"
was introduced by Bertrand Meyer in my favorite object oriented language:
Eiffel [1,3].
Basically, when you define the interface of a method,
you can see it as defining a *contract* between the "client" code and the
"supplier" method.
The contract consists of obligations and benefits for both parts.
First, the client must ensure some *pre-conditions*. Then, the supplier may
assume those pre-conditions and do its job. Once the job is done,
the supplier must ensure some *post-conditions*. Finally, the client may
benefit from those post-conditions.
In the documentation of the method, those assertions appear after the
doxygen
tags \pre and \post. Once those tags are parsed by doxygen,
the words Pre-conditions and Post-conditions are generated.
Let's deal with a class InfiniteDoubleStack: it is a stack of infinite size
of double values. Adding an element at the top of the stack implies that
at the end, the stack is not empty and that the value in argument is
actually the value on the top. The method may look like:
// Description:
// Add `value' to the top of the stack.
// \post not_empty: !IsEmpty()
// \post is_set: GetTop()==value
void Push(double value);
Also, we can only get the value on the top of the stack if the stack is
not empty:
// Description:
// Return the value at the top of the stack.
// \pre not_empty: !IsEmpty()
double GetTop();
As you can see, assertion is not restricted to check input argument of
the method but conditions in a more general sense.
In the implementation of the method, the body looks like:
{
assert("pre: comment1" && some_expression1);
assert("pre: comment2" && some_expression2);
<body>
assert("post: comment3" && some_expression3);
assert("post: comment4" && some_expression4);
return result;
}
The trick is a string always returns true, so only the expression on the
right side can make the assert() call to fail.
=What the meaning of an assertion that fails?=
==============================================
* If it is a pre-condition, it means the "client" code broke the contract:
there is a *bug* in the client code.
* If it is a post-condition, the pre-conditions passed so the client code
respected the contract but the "supplier" method broke the contract:
there is
a *bug* in the method.
Assertions deal with unexpected conditions that are bugs somewhere.
I never involved any user here. This has nothing to do with
user input validation. The job a Graphical User Interface
(GUI) is to check the validity of some user input in some widget,
without crashing.
The job of a file loader is to load a file and to detect if it is
corrupted or
not and to report it to the other part of the application, without crashing.
Maybe the best way to remember when to use assertion or not is to
remember this:
*bugs are only in programs, not in data.*
There is no bug in a corrupted file.
There is no bug in an user input. Ok there might be a bug in a user, but
it is
out of the scope of computer science, it has to do with medical sciences :-)
=Bug or feature?=
=================
However, sometimes it is difficult to design a method and find the limit
between a valid argument and a unvalid one. Correctness is a relative
notion.
Imagine a class with an Angle ivar. The angle is expected to be between 0
and 90 degrees. Let's write the method to set this ivar.
void SetAngle(double value);
There are at least 3 ways to design the method:
1. we can ask the client code to provide a value between 0 and 90
2. we can take any value and clamp it between 0 and 90
3. we can ignore any value out of the range and not change the value
of the ivar in this case.
So what the best solution? It is up to you! It is the choice of the designer
*as long as* this choice appears in the documentation of the method:
#1.
// Description:
// Set the angle with `value'.
// \pre valid_range: value>=0 && value<=90
// \post is_set: GetAngle()==value
void SetAngle(double value);
#2.
// Description:
// Set the angle from `value'. If `value' is less than 0, it is clamped to
// 0. If `value' is greater than 90, it is clamped to 90.
// \post set_and_clamp_less: value<0 implies GetValue()==0
// \post set_and_clamp_greater: value>90 implies GetValue()==90
// \post set_and_not_clamp: (value>=0 && value<=90) implies
value==GetValue()
void SetAngle(double value);
#3.
// Description:
// Set the angle with `value' only if `value' is in [0,90]. Otherwise,
// the angle is not changed.
// \post set_if_in_range: (value>=0 && value<=90) equivalent
value==GetValue()
void SetAngle(double value);
=Inheritance=
=============
As Pre and post-conditions are part of the interface of a method, they
are *inherited*: you can define the interface of an abstract method with
its assertions in some superclass. Any implementation in a subclass will
have
to check the assertions as well.
Pre-conditions and post-conditions have more to do with the interface than
with the implementation of a method.
=Valid expression in assertions=
================================
* There are some restrictions on what you can put in a precondition: the
only
constraint is it should involve only code available to the client code.
If the
method is public, all the pre-conditions have to involve public access
methods
or variables, if the method is protected, the pre-conditions can involve
protected variable or methods.
* On the other side, there is *no restriction* on what to put in
postconditions: the client can benefits of public statements or not public
statements. In both pre and post-conditions, you can use pure virtual
methods.
=Benefits=
==========
There are 5 benefits of contracts:
1. Help to design and specify classes by stating the constraints
(preconditions) and benefits (postconditions)
2. Automatic documentation: favors reuse (easy to learn how to use the
routine)
3. Systematic debugging/testing: it is easy to interpret failures:
a. failure on a pre-condition: the client did not respect the contract;
the bug is in the client code
b. failure on a post-condition: the client respected its part of the
contract but the routine dit not: there is a bug in the routine.
4. If you are not using assertions:
a. and your program crashes, usually it crashes a way after the real
location
of the bug. With assertions, it will crash clause to the location of the
bug.
b. you can face a bug that does not crash your application but just implies
a weird behavior, it is even worse to track down; again, with
assertions, some
crash may happen close the location of the bug.
5. Assertions can be disabled in release mode (if macro NDEBUG is defined),
so they have no CPU cost in this case.
=Additional notes=
==================
1. Assertions are necessary conditions not sufficient conditions.
2.As it is also useful to check the validity of some expression inside
the body of a method (like a loop invariant), I also use consistency
check assertion, it looks like:
assert("check: comment5" && some_expression5);
3. Some issue with Cmake under Linux: by default the release mode of
CMake does not set -DNDEBUG. It means if you forget to add it manually,
the release mode with not remove assertions...
4. With Visual Studio in Debug mode, when an assertion failed a dialog box
will show up and you may ignore the assertion or debug the program.
5. As any part of a program, an assertion itself can be wrong...
My two cents.
[1] http://archive.eiffel.com/doc/manuals/technology/contract/
[2]
http://www.eventhelix.com/RealtimeMantra/Object_Oriented/design_by_contract.htm
[3] Bertrand Meyer. Object-Oriented Software Construction. Prentice Hall,
second edition, 1997.
Mathieu Malaterre wrote:
>>Perhaps there should be a "VTK_ASSERT" wrapper that gets used within VTK
>>proper...? That way we can control whether an abort happens or not, at
>>least for the ones that are in VTK source.
>>
>>Although, grepping on assert underneath VTK yields quite a large number
>>of "assert" hits within the various utility libs : vtktiff and now the
>>recently added vtknetcdf. So users of those may still be subject to the
>>occasional abort...
>>
>>It would be my expectation that an assert is usually a severe
>>programming mistake, something "impossible," followed shortly by a crash
>>of some sort. A crash is preferable to an abort call nowadays with the
>>"crash reporting" technologies on Windows and Mac.
>>
>>For any end-user application (like ParaView) there ought to be no chance
>>of an unexpected abort call - assert code should certainly be a no-op in
>>a "Release" build...
>
>
> This is done properly by cmake since 2.0 I believe (NDEBUG is properly passed).
>
>
>>An excellent treatment of this subject appears in the book Writing Solid
>>Code, by Steve Maguire. I definitely recommend reading the section on
>>"assert philosophy" in there.
>
>
>
> I don't have the book right now. If you have read it I guess you should be able answer my questions:
> Even if you put an assert deep inside a function that is not called after user input, how can I *guarantee* that broken/illegal user input won't raise the assert ?
> The only guarantee I can think of is that the developer has written the assert just after an if statement which duplicate the condition...
>
> Since duplication of code make the maintenance more complicated, I believe we should go for the option you proposed: the crash system. The VTK_ASSERT macro should then call any OS specific system (better than abort).
> And we could even controll completely this macro, in my case if I want to debug my code I could always redefine it to my beloved 'assert'.
>
> The only trick in this macro is that we should somehow be able to control it if the 'impossible' happen. For instance in the XML-IO classes there is a mechanism where developer has to specify an error occur by turning a flag ON (member variable of the class).
>
> Therefore the MACRO should take into account:
> - the condition,
> - the error message,
> - the post treatment to do (portion of code to execute) in a 'Production' product (even compiled in Debug mode).
>
> Comments very welcome
> Mathieu
>
> _______________________________________________
> vtk-developers mailing list
> vtk-developers at vtk.org
> http://www.vtk.org/mailman/listinfo/vtk-developers
>
--
François Bertel, PhD | Kitware Inc. Suite 204
1 (518) 371 3971 x113 | 28 Corporate Drive
| Clifton Park NY 12065, USA
More information about the vtk-developers
mailing list