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