DbC & TDD: Fizzing Fusion or Flat Fizzle? [2/2]

From: Universe (universe_at_tAkEcovadOuT.net)
Date: 03/12/04


Date: Thu, 11 Mar 2004 23:55:20 -0500


                                 gauge <= 1.03 * capacity)
           ... Other clauses ...
Class invariants are consistency constraints characterizing the semantics of
a class. This notion is important for configuration management and
regression testing, since it describes the deeper properties of a class: not
just the characteristics it has at at a certain moment of its evolution, but
the constraints which must also apply to subsequent changes.

Viewed from the contract theory, an invariant is a general clause which
applies to the entire set of contracts defining a class.

6 - Documentation
Another key application of contracts is to provide a standard way to
document software elements -- classes. To provide client programmers with a
proper description of the interface properties of a class, it suffices to
give them a version of the class, known as the short form, which is stripped
of all implementation information but retains the essential usage
information: the contract.

In the EiffelBench environment, you obtain the short form interactively by
clicking on the Short button of the Class Tool. The output can be plain text
or can be converted to any text processing format (Microsoft's RTF, HTML for
Web publishing, MIF or MML for FrameMaker, TEX, troff, Postscript etc.)
through one of the environment's predefined filters -- to which you can add
any of your own filters since the mechanism is completely open.

The short form retains headers and assertions of exported features, as well
as invariants, but discards everything else. For example:

  class interface DICTIONARY [ELEMENT] feature

   put (x: ELEMENT; key: STRING) is
            -- Insert x so that it will be retrievable
                           -- through key.
                  require
                          count <= capacity
                          not key.empty
                   ensure
                          has (x)
                          item (key) = x
                          count = old count + 1

                ... Interface specifications of other features ...

  invariant

  0 <= count
  count <= capacity

  end -- class interface DICTIONARY
This short form serves as the basic tool for documenting libraries and other
software elements. It also serves as a central communication tool between
developers. We have learned from our customers and from our own experience
that emphasis on the short form facilitates software design and project
management, as it encourages developers and managers to discuss the key
issues (interface, specification, inter-module protocols) rather than
internal details.

7 - Testing, debugging, quality assurance
Given a class text equipped with assertions, we should ideally be able to
prove mathematically that the routine implementations are consistent with
the assertions. In the absence of realistic tools to do this, we can settle
for the next best thing, which is to use assertions for testing.

Compilation options enable the developers, class by class, what effect
assertions should have if any: no assertion checking (under which assertions
have no effect at all, serving as a form of standardized comments),
preconditions only (the default), preconditions and postconditions, all of
the above plus class invariants, all assertions.

These mechanisms provide a powerful tool for finding mistakes. Assertion
monitoring is a way to check what the software does against what its author
thinks it does. This yields a productive approach to debugging, testing and
quality assurance, in which the search for errors is not blind but based on
consistency conditions provided by the developers themselves.

The availability of these mechanisms is in my experience one of the most
significant consequences of moving to this technology. It causes a dramatic
drop in the number of bugs, and a new attitude of developers towards
software reliability.

8 - Contracts and inheritance
An important consequence of the Design by Contract theory is to yield a
better understanding of the central object-oriented notions of inheritance,
polymorphism, redefinition and dynamic binding.

A class B which inherits from a class A may provide a new declaration for a
certain inherited feature r of A. For example a specialized implementation
of DICTIONARY might redefine the algorithm for put. Such redefinitions are
potentially dangerous, however, as the redefined version could in principle
have a completely different semantics. This is particularly worrisome in the
presence of polymorphism, which means that in the call

  a.r

the target a of the call, although declared statically of type A, could in
fact be attached at run time to an object of type B. Then dynamic binding
implies that the B version of r will be called in such a case.

This is a form of subcontracting: A subcontracts r to B for targets of the
corresponding type. But a subcontractor must be bound by the original
contract. A client which executes a call under the form

     if a.pre then
             a.r
     end

must be guaranteed the contractually promised result: the call will be
correctly executed since the precondition is satisfied (assuming that pre
implies the precondition of r); and on exit a.post will be true, where post
is the postcondition of r.

The principle of subcontracting follows from these observations: a redefined
version of r may keep or weaken the precondition; it may keep or strengthen
the postcondition. Strengthening the precondition, or weakening the
postcondition, would be a case of "dishonest subcontracting" and could lead
to disaster. The Eiffel language rules for assertion redefinition [3]
support the principle of subcontracting.

These observations shed light on the true significance of inheritance: not
just a reuse, subtyping and classification mechanism, but a way to ensure
compatible semantics by other means. They also provide useful guidance as to
how to use inheritance properly.

9 - Exception handling
Among the many other applications of the contract theory we may note that
the theory leads naturally to a systematic approach to the thorny problem of
exception handling -- handling abnormal cases.

A software element is always a way to fulfil a certain contract, explicit or
not. An exception is the element's inability to fulfil its contract, for any
reason: a hardware failure has occurred, a called routine has failed, a
software bug makes it impossible to satisfy the contract.

In such cases only three responses make sense:

 1. Retrying: an alternative strategy is available. The routine will restore
the invariant and and make another attempt, using the new strategy.

 2. Organized panic: no such alternative is available. Restore the
invariant, terminate, and report failure to the caller by triggering a new
exception. (The caller will itself have to choose between the same three
responses.)

 3. False alarm: it is in fact possible to continue, perhaps after taking
some corrective measures. This case seldom occurs (regrettably, since it is
the easiest to implement!).

The exception mechanism follows directly from this analysis. It is based on
the notion of "rescue clause" associated with a routine, and of "retry
instruction", which implements retrying. This is similar to clauses that
occur in human contracts, to allow for exceptional, unplanned circumstances.
If there is a Rescue clause, any exception occurring during the routine's
execution will interrupt the execution of the body (the do clause) and start
execution of the Rescue clause. The clause contains one or more
instructions; one of them is a retry, which will cause re-execution of the
routine's body (the do clause). An integer local entity such as failure is
always initialized to zero on routine entry (but not, of course, after a
retry).

Here is an example illustrating the mechanism (see [2, 3] for details). We
assume a low-level procedure unsafe_transmit for transmitting a message over
a network. We have no control over that procedure but know that it may fail,
in which case we want to try again, although after 100 unsuccessful attempts
we will give up, passing on the exception to our caller. The Rescue/Retry
mechanism supports this simply and directly:

     attempt_transmission (message: STRING) is
                   -- Attempt to transmit message over a communication line
                   -- using the low-level (e.g. C) procedure
unsafe_transmit,
                   -- which may fail, triggering an exception.
                   -- After 100 unsuccessful attempts, give up (triggering
                   -- an exception in the caller).
           local
                   failures: INTEGER
           do
                   unsafe_transmit (message)

           rescue
                   failures := failures + 1
                   if failures < 100 then
                      retry
                   end
           end

10 - Further developments
This article has provided an overview of the basic ideas of Design by
Contract. This is a very active area of application and further research,
with several books in preparation. Two areas of development are:

 Concurrency and distribution: the principles of Design by Contract yield a
fascinating solution, described elsewhere in these Web pages, to the problem
of concurrent and distribution object-oriented programming (avoiding the
so-called "inheritance anomaly" and other non-issues of O-O concurrent
computation, resulting from a misunderstanding of object technology). An
article [4] describes in detail the Eiffel approach to concurrent
computation, based on the Design by Contract concepts and currently being
implemented for ISE Eiffel 4.2. (See the new edition of [1] for the most
up-to-date description.)

 An extended specification language, allowing the expression of a richer set
of assertions.

Design by Contract has already been widely applied; the theory provides a
powerful thread throughout the object-oriented method, and addresses many of
the issues that many people are encountering as they start applying O-O
techniques and languages seriously: what kind of "methodology" to apply, on
what concepts to base the analysis step, how to specify components, how to
document object-oriented software, how to guide the testing process and,
most importantly, how to build software so that bugs do not show up in the
first place.

In software development, reliability should be built-in, not an
afterthought.

Bibliography
[1] Bertrand Meyer: Object-Oriented Software Construction, Prentice Hall,
1988. Extensively revised second edition now out.

[2] Bertrand Meyer: Applying "Design by Contract, in Computer (IEEE), vol.
25, no. 10, October 1992, pages 40-51.

[3] Bertrand Meyer: Eiffel: The Language, Prentice Hall, 1992.

[4] Bertrand Meyer: Systematic Concurrent Object-Oriented Programming, in
Communications of the ACM, vol. 36, no. 9, September 1993, pages 56-80.

[5] Kim Waldén and Jean-Marc Nerson, Seamless Object-Oriented Software
Architecture: Analysis and Design of Reliable Systems, Prentice Hall, 1995.

Note: an earlier version of the present article appeared in the Hotline on
Object Technology.

"Design by Contract" is a trademark of Interactive Software Engineering.
 ---------------------------------------------------------------------------
-----
© 2004 Eiffel Software Inc. All rights reserved. -- Privacy Policy