Design
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++
by:

  • Using
    the assert function (or some
    similar macro)
  • Throwing
    an exception in case a precondition or postcondition fails
  • Using assert and also throwing an
    exception

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
(Listing A):

  • In
    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:

  • Find
    out ASAP that the contract has been broken. This is usually accomplished
    by breaking into the offending line of code with a debugger.
  • Make
    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
(Listing C).

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
(Listing D).

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:

  • In
    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.
  • In
    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
including <enhance_throw.h>.
Then, in a source file, provide the before_throw_exception function.

Listing F
shows you how to use the dbyc_throw
method.

Another way

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.

Choose wisely

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.