The key property of the Design by Contract methodology is that a correct program behaves exactly the same when assertions are monitored and when they are not. In other words, in a correct program assertion monitoring can be turned off without affecting the program behavior.
Assertion monitoring can be turned off after making sure all required tests pass (an unsound approach) or by using a verification tool that checks assertions at compile time rather than at run time (the example is written in Eiffel, the corresponding verification tool is AutoProof; other languages with design-by-contract support have other similar tools).
Whereas the requirement that the argument should be above zero could be documented in the description of the feature, comments (usually) are not consumable by programming language tools like compilers, code analyzers, etc., and could not be used for program verification, automatic test generation and other automated tasks.
Preconditions and postconditions also play a role in responsibility separation. Preconditions allow the supplier to rely on the guarantees provided by the client. Postconditions allow the client to rely on the guarantees provided by the supplier. Note that in both cases there is no need to repeat the checks and no need to catch exceptions or to check the result. All the knowledge is accumulated in the associated assertions. Were the checks done inside the method body, in the implementation part, there would be no glue what was guaranteed, what result was expected, etc.
Use of both preconditions and postconditions enables chaining the calls. Let's consider an expression
get_result ((get_result (n * n + 1).ceiling)
The precondition of the first feature call is satisfied by construction. Its postcondition guarantees that the result is a positive value. The call to ceiling
makes sure the argument to the outer feature satisfies its precondition. So we can prove that the expression never throws an exception without ever looking into the implementation of get_result
(provided that it is correct). In fact, several implementations are possible, but we do not care which implementation is used.
To summarize, preconditions and postconditions
- can be turned off at run-time for correct programs
- are consumable by program analysis tools
- are used to verify programs at compile time (and make this verification modular)
- separate responsibilities between client and supplier (who is guilty of a bug?)
- remove the need for additional checks at the client side thus simplifying feature call chaining