From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: fac41,a48e5b99425d742a X-Google-Attributes: gidfac41,public X-Google-Thread: ffc1e,a48e5b99425d742a X-Google-Attributes: gidffc1e,public X-Google-Thread: f43e6,a48e5b99425d742a X-Google-Attributes: gidf43e6,public X-Google-Thread: 1108a1,5da92b52f6784b63 X-Google-Attributes: gid1108a1,public X-Google-Thread: 103376,a48e5b99425d742a X-Google-Attributes: gid103376,public From: milkweed@plainfield.bypass.com (Anders Pytte) Subject: Re: Papers on the Ariane-5 crash and Design by Contract Date: 1997/03/24 Message-ID: X-Deja-AN: 228002282 References: <332B5495.167EB0E7@eiffel.com> <332D113B.4A64@calfp.co.uk> <5gm8a6$2qu$2@news.irisa.fr> <3332BE49.8F9@lmtas.lmco.com> <33330FE5.3F54BC7E@eiffel.com> <333438B5.ABD322C@eiffel.com> <3335D3AC.9B8@pti-us.com.nospam> Organization: Milkweed Software Newsgroups: comp.lang.eiffel,comp.object,comp.software-eng,comp.programming.threads,comp.lang.ada Date: 1997-03-24T00:00:00+00:00 List-Id: In article <3335D3AC.9B8@pti-us.com.nospam>, Steve Furlong wrote: > Anders Pytte wrote: > > > > In article <333438B5.ABD322C@eiffel.com>, Bertrand Meyer > > wrote: > > > > In a few hours my team developed much of the functionality of Eiffel's > > assertions (certainly the most essential functionality) for our C++ > > project by using macros. With minor changes, I could apply them to the > > problem suggested by Bertrand as follows. Show me how this will not work, > > please! > > > > The macros, which can offcourcse be turned on or off according to compile > > switches: > > > > ENTER: Sets up data structures to register requires by name so they can > > override requires of the same name in calls to inherited method. > > > > REQUIRE, ENSURE, INVARIANT: Registers name (check to see if overridden) > > and handles false expression with exception, program break, or other > > means. > > > > EXIT: Cleans up data structures created by ENTER and calls CheckInvariants(). > > <> > > > I would prefer using Eiffel, but I agree with Jon Anthony that the same > > practices (use of assertion for design by contract) are available from > > alternative languages. Getting this concept across to Eiffel language > > fiends can be exasperating! > > > PLEASE make these macros available. I've failed to raise interest in > Eiffel or Ada instead of C++, so now I'm trying to bring some of their > better features to our C++ code. Steve, Our techniques are not original, so don't blame me for inventing all of the following. We actually used REQUIRE and ASSERT, not REQUIRE, ENSURE and INVARIANT. REQUIRE usage was to detect fatal errors like NULL pointers or incorrect run-time type. ASSERT usage was to detect non-fatal runtime errors. Our reason for distinguishing is that we could optionally retire ASSERT and switch REQUIRE from a program break to an exception or user error alert. This distinction also added extra information for the code reader - a trivial advantage over Eiffel. You can define the macros like this (slightly modified from our versions): #define ASSERT(identifier, expression) \ if (!(expression)) \ (AssertionFailed(__FILE__, __LINE__, false, \ #identifier, #expression)); \ #define REQUIRE(identifier, expression) \ if (!(expression)) \ (AssertionFailed(__FILE__, __LINE__, true, \ #identifier, #expression)); \ #define EXIT CheckInvariants(); \ And the function that does the work could be like this: void AssertionFailed(char *file, int line, Boolean fatal, char *identifier, char *expression); Missing from the above illustration are compiler switches to switch assertions on and off (and into intermediate states). In order to reduce code size (methods often have more assertions than behavior!) you may want alternative definitions that will not generate any code, for example: #define ASSERT(ignore) ((void) 0) AssertionFailed can write to a log file, or to the debugger (using DebugStr), and/or if fatal can throw an exception or pose a user alert. The alert would display the file name and line number of the require along with instructions to report the error to the developer, and allowed the user to quit (better than crashing the system). I leave it to you to draft your own version. I think the idea of using separate macros ENSURE and INVARIANT is a good idea - the debug message could express what kind of check is being enforced along with the identifier and expression text. Modify the assertion procedure to accept and express the type of assertion: enum EAssertionType {eRequire, eEnsure, eInvariant, eCheck}; #define REQUIRE(identifier, expression) \ if (!(expression)) \ (AssertionFailed(__FILE__, __LINE__, eRequire, \ #identifier, #expression)); \ #define ENSURE(identifier, expression) \ if (!(expression)) \ (AssertionFailed(__FILE__, __LINE__, eEnsure, \ #identifier, #expression)); \ void AssertionFailed(char *file, int line, EAssertionType assertionType, char *identifier, char *expression); You may not find it necessary to override assertions (other than adding additional ones in subclass methods), but if you really want to follow through on that part of my suggestion, you need to 1) on startup of each process (normally just the application) create a stack of global scope to the process 2) save a local "frame pointer" in ENTER (the macro would declare an index and assign it the size of the stack) 3) check each assertion to see if the identifier is already in the stack and, if so, ignore the local assertion 4) otherwise push the identifier name for the assertion onto the stack and invoke the assertion 5) clear the stack down to the local frame pointer in EXIT (the macro would set the size of the stack to the saved index). For example: #define ENTER \ long assertionStackSize = globalAssertionStack->GetSize(); \ #define EXIT \ CheckInvariants(); \ globalAssertionStack->SetSize(assertionStackSize); \ #define REQUIRE(identifier, expression) \ if (!globalAssertionStack->Contains(identifier) \ { \ globalAssertionStack->Push(identifier); \ if (!(expression)) \ (AssertionFailed(__FILE__, __LINE__, whatever, \ #identifier, #expression)); \ } \ You can either put the identifier's text on the stack, or else enumerate all your assertion identifiers and place the enumerator on the stack (in which case the AssertionFailed parameter lists I suggested would need to be modified). In general, the process of writing code using these assertions is try to state every assumption being made when you write methods and classes, in such a way that the reader can immediately see what assumptions you are making, and thereby get a good idea of how you expect the method or class to be used, and ofcourse also enforce these assumptions when the assertions are turned on. That is "design by contract" in fifty words, more or less. NOTA BENE: in my example there is a danger of missing the ENSURE and INVARIANT if the method has multiple returns. I can think of several rather messy ways to solve this. Or you could just avoid multiple exits when necessary, which may be a better programming style anyway. Please feel free to contact me at my email address if you have any more questions. Anders. -- Anders Pytte Milkweed Software RR 1, Box 227 Voice: (802) 472-5142 Cabot VT 05647 Internet: milkweed@plainfield.bypass.com