[comp.software-eng] Tolerance and reusability

bertrand@eiffel.UUCP (Bertrand Meyer) (02/09/91)

From <6249@stpstn.UUCP> by cox@stpstn.UUCP (Brad Cox):
> 
> The whole question of tolerance for software is fascinating, but seeing
> its richness requires dropping the academic notion that software is either
> right or wrong (i.e. provable correctness), but an evolving product of
> human labor.

What about the opposite argument (even if it means accepting
being labeled as ``academic''): that a necessary, and perhaps even
sufficient condition for a successful integration
of the notion of tolerance into software design is precisely
the application of formal specification techniques, without which
the scope of permissible tolerance cannot be defined?

Any software *is* either right or wrong. More precisely, the first
idea of program correctness (day one, lesson one of Program Proving 101) 
is that correctness is a relative notion: a system is correct or
incorrect not per se but with respect to a certain specification.
At one extreme the specification will define just one externally
observable behavior; this is probably the kind of situation that
Brad Cox rejects. But it is perfectly possible, and often desirable,
to write specifications that allow for many observable behaviors
(many possible results). At the other extreme, if I write
a specification as

	require
		true
	deferred
	ensure
		true

then any implementation is acceptable (provided, however, it
terminates). Interesting specifications, of course, will have
weaker preconditions (require) and/or stronger postconditions
(ensure).

Integrating tolerance, then, is a matter of style in the
specification. The more room the specification leaves
for diverse observable behaviors, the more tolerance it integrates.

As an example, here is an intolerant routine:


	process_calls is
		require
			waiting.count <= max_processable_calls
		do
			...
		ensure
			all_processed (waiting)
		end -- process_calls
			
which assumes the following declarations:

	waiting: LIST [CALL]

	all_processed (cl: LIST [CALL]): BOOLEAN is
			-- Have all calls in cl been processed?
		do ... end

(`waiting.count' is the size of list `waiting'.)

Here now is a more tolerant version of the routine:

	process_calls is
		require
			waiting.count <= max_processable_calls
		do
			...
		ensure
			(not_processed (waiting) / waiting.count) <= 0.00003
		end -- process_calls

One interesting version of this kind of scheme is the way it works
with inheritance. Rules on assertion adaptation imply that if an early
version of a routine (an abstract, or ``deferred'' one) is tolerant,
later redefinitions in descendant classes may be progressively more
and more specific, as a result of more and more concrete implementation
choices. The preconditions become weaker and the postconditions become
stronger.
			
> I haven't tried to enumerate the full breadth of the tolerance issue, but
> here are a couple of starting points: Is this component tolerably fast?
> Tolerably small? Tolerably compliant with the previous release? Tolerably
> functional? Tolerably close to what the customer wants? Tolerably priced?
> Etc, etc, etc.

Granted, the above only address tolerance with respect to functional
specification, not with respect to cost, efficiency, informal user
satisfaction etc. But it seems hard to solve all of these problems at
once.
-- 
-- Bertrand Meyer
bertrand@eiffel.uucp