by Contract is a software paradigm that, when used, ensures that your code
will have fewer bugs. It does so by enforcing “contracts.” A contract
is made by a function based on what it expects when you enter it (preconditions)
and what it yields when it exits (postconditions). For example, an int factorial(int n) function expects n to be positive (precondition), and it
will yield a positive value (postcondition). Use the Design by Contract
paradigm, and your code will contain fewer bugs—a lot of programmers believe it,
and their experience proves it.
How it works
A function employs Design by Contract (DbyC) if it has preconditions
and postconditions—conditions that should always be met—and it tests
that those conditions indeed exist.
You can employ DbyC in C++
the assert function (or some
an exception in case a precondition or postcondition fails
- Using assert and also throwing an
Using the assert
function and throwing an exception is the preferred method. In this article, we’ll
discuss why it’s preferred and how to implement it effectively.
Usefulness of designing by contract
DbyC helps you catch
bugs early in two places
your DbyC function, if results are wrong for some arguments
- In client
code, when you call a DbyC function and you mistakenly pass an incorrect
combination of arguments
You should use preconditions when your function does not
handle some input data; for instance, when the data wouldn’t make sense for
that function (e.g., calling a factorial function with a negative value). You
should use postconditions to test the correctness of the function output (e.g.,
the factorial function should return a positive value).
Saying that a contract
is broken (it failed a precondition or postcondition) is equivalent to
declaring a programming error. It
means that either the function or the client code calling the function contains
a semantic error. Usually, when either condition fails, the code that is
supposed to be implemented after that failed condition should not be executed
because the program will most likely crash
(Listing B). In light of that possibility, when a DbyC
precondition or postcondition fails, you’ll want to:
out ASAP that the contract has been broken. This is usually accomplished
by breaking into the offending line of code with a debugger.
sure that code following the broken contract is not executed (since the
program could crash or produce invalid results). This is usually
accomplished by throwing an exception.
If you’re developing an application, you’ll usually prefer
to use the debugger method, because when a bug appears, you’ll want to know
about it right away. This will be very helpful, especially when connecting
different modules from a big application (integration testing).
When you’re developing a library, you never know where it
will be used. For instance, if someone using your library is building a server
application, the debugger option is not viable; therefore, your best bet is
throwing an exception.
The biggest problem with the first option is that your program could
crash at the customer site. Many experts recommend the second option over the debugger
option for this issue alone. I tend to agree more and more with these experts—throwing
an exception will make your program safer
Best of both worlds
The bad thing about throwing an exception, however, is that
when a contract is broken, you’ll find out too late and lose the context in
which the problem occurred
Remember that contract
is broken is equivalent to programming
error. So, before throwing an exception, you can do something extra to
retain the context of the problem:
Debug mode, break into the debugger when a contract is broken. You won’t lose any
context, and you’ll be able to fix the problem very easily.
Release mode, find out where a broken contract occurred and log it
somewhere. The program will then continue, and there will be no crash.
Achieving the above is simple: In your code, instead of
using throw some_exc(args), just use dbyc_throw some_exc(args).
The code for the dbyc_throw macro is
shown in Listing E.
When you want to implement custom handling before an
exception is thrown, you just define the THROW_CUSTOM_HANDLER macro prior to
Then, in a source file, provide the before_throw_exception function.
shows you how to use the dbyc_throw
As you’ve seen, the code in <enhance_throw.h> is quite small and simplistic, but it does
handle most cases. However, if it’s not enough for you, the SMART_ASSERT
library offers you more options and focuses on providing you with as much
information as possible when a contract is broken. In addition, you can specify
multiple assertion levels and set how each should be handled. Finally, you can
use SMART_ASSERT in Release mode as well.
DbyC is a very powerful paradigm, and there are many ways to
implement it in C++.
But you should do it wisely. As soon as a contract is broken, make sure you
know about it firsthand and use <enhance_throw.h>
or the SMART_ASSERT library, or implement your own library based on what you’ve
seen in this article.