Path: utzoo!utgpu!news-server.csri.toronto.edu!rutgers!tut.cis.ohio-state.edu!nisca.ircc.ohio-state.edu!zaphod.mps.ohio-state.edu!samsung!uunet!mcsun!ukc!pyrltd!tetrauk!rick From: rick@tetrauk.UUCP (Rick Jones) Newsgroups: comp.lang.eiffel Subject: Exceptions and Assertions Message-ID: <723@tetrauk.UUCP> Date: 3 Sep 90 15:13:09 GMT Reply-To: rick@tetrauk.UUCP (Rick Jones) Organization: Tetra Ltd., Maidenhead, UK Lines: 151 I apologise for the length of this posting, but I want to express my ideas clearly as the basis for a discussion (I hope I've done so). Please read on... One of Eiffel's most powerful features is its exception handling, and in my current work I am experimenting with a programming style which places heavy reliance on this system. The general concept is that _all_ "non-normal" conditions are treated as exceptions, including those which may be expected to occur quite frequently. The application is interactive transaction processing, and this means that there are a lot of non-normal conditions which can arise. These can include screen input validation errors, database record-not-found errors, invalid transaction data, etc. The rationale for this approach is that in conventional programming methods, typically over 50% of the code is concerned with handling such non-normal conditions. If the program logic is reduced to what is required if all input is correct and all conditions are as expected, a large amount of complexity can be eliminated. The Eiffel concept of raising an exception where it is detected and letting it ripple back to a point where it can be handled offers a very attractive solution to this problem. This raises a philosophical issue: Eiffel's assertions are geared towards the concept of ensuring program correctness, with just the "raise" statement available as an intended run-time exception condition. The concept presented by the manuals and the book "Object Oriented Software Construction" is that no program should violate its assertions at run-time; if it does it is incorrect. The problem is that assertions are a much clearer way of specifying what conditions are required to exist than using raise. E.g. using "raise": if not condition_1 or not condition_2 or condition_3 then raise ("failed") end using an assertion clause: check need_cond_1: condition_1 ; need_cond_2: condition_2 ; not_cond_3: not condition_3 ; end The assertion clause needs no IF logic and very little AND/OR logic, and provides for an explicit label for each condition. The conditions are also stated positively (i.e. what is required) as opposed to negatively (i.e. what will cause failure). Using "raise" also requires a class to inherit from EXCEPTIONS (this is only a minor inconvenience), but more significantly assertions are an inherent part of the language, while "raise" is not. A second problem is redundancy. A feature may be written with a precondition such as: doit (param:CLASS) is require not param.Void do ... A client of this feature should therefore test the proposed argument before calling doit as in: (c is already declared as "c:CLASS") if not arg.Void then c.doit (arg) end However, if a void arg is considered a non-normal condition, the general requirement is to raise an exception. This leads to: if not arg.Void then c.doit (arg) else raise ("arg void") end This of course begs the question: why not leave the precondition checks active and save the redundancy of checking for the same thing twice in different ways and different places? A precondition violation will effectively raise an exception in the same place. This starts to place a rather different emphasis on assertions, or at least some of them. I agree completely that invariants, postconditions, and loop variants/invariants are issues of program correctness, since the principle of contracting is that a routine _guarantees_ the postcondition and invariant if the preconditions are met. It is the preconditions which may arguably be used as a run-time exception check rather than purely a correctness test. Eiffel's other assertion construct is "check", and this is also quite interesting. The first example compares its use to "raise", where I suggested it is more elegant. I don't know to what extent Eiffel programmers use the check construct in practice to ensure the correctness of their programs, but I find it hard to envisage many circumstances where it would be really useful. It seems naturally more suited to producing real run-time exceptions. The general situation is where a routine is called with valid preconditions, but due to subsequent events not checkable at precondition time, is still unable to meet its postcondition and invariant. Run-time reliance on preconditions can be used in practice simply by ensuring that all classes are compiled with at least precondition checking on. As standard, check assertions are not enabled in this mode, but I have argued above that they should be. It turns out that, since the assertion level which is actually compiled is a C compile-time option controlled by macros in "_eiffel.h", a small change to "_eiffel.h" enables check assertions whenever precondition checking is on. We are currently working in this mode in our development environment. Having presented this concept and the rationale behind it, I would like to know how other Eiffel users, and those as ISE, feel about this as a style of programming. Some practical considerations are: Efficiency - how does precondition checking throughout a program (which includes void reference checks on all feature calls) compare to programmed condition checks and calls to "raise"? Check - to what extent does anyone use "check" assertions at present, and is it more appropriate that they be compiled in if precondition checking is enabled? Identity - what is the best way for a rescue clause to analyse the exception which occured when there are a large number of possible exceptions (this applies to both assertions and use of "raise")? Specification - how can a class interface specify clearly and reliably what exceptions may be raised apart from invariants and pre/post-conditions? (this seems a further argument in favour of check over raise, since such information could be extracted by "short") Those who were present at Bertrand Meyer's presentation at TOOLS '90 on concurrent and distributed processing support in Eiffel will recall that the proposed extension required some change to the concept of precondition checking, so that it did become a run-time issue. How do the ideas above fit into that scenario (perhaps this is one Bertrand should answer)? I look forward to some constructive discussion ... -- Rick Jones You gotta stand for something Tetra Ltd. Maidenhead, Berks Or you'll fall for anything rick@tetrauk.uucp (...!ukc!tetrauk.uucp!rick) - John Cougar Mellencamp