vrm@cathedral.cerc.wvu.wvnet.edu (Vasile R. Montan) (09/27/90)
Here is a general question to all of the expert C programmers, or even novice C programmers, who have a strong opinion on C style. What is the best way for a function to check for errors and return an error code? This sounds like a simple problem, but I don't know which way to go. I would just like to follow a style which is easy to maintain. Below are the methods which I have considered so far. You may add new ones if you like. I apologize for any other style problems in my example code. I was trying to make the examples small. I will post a summary of all replies that I get through email. Method 1: Return Error Code When Error Is Encountered The problems I have with this are first it goes against software engineering principles which say that all functions should have one entry point and one exit point. Secondly some functions do have some standard clean up to do. int function() { int error; if (error = check1()) {cleanup(); return error;} if (error = check2()) {cleanup(); return error;} if (error = check3()) {cleanup(); return error;} cleanup(); return NOERROR; } Method 2: Set Error Code and Return Only at End The problem I have with this is that I don't want to do later work if a previous error occured. I just want to clean up and return. This forces me to continuously check if a previous error occured. int function() { int error; error = check1(); if (!error) error = check2(); if (!error) error = check3(); cleanup(); return error; } Method 3: Use GOTO to Create Exception Handler Of course this breaks the golden rule of software engineering of "absolutely positively no GOTO's in your program." int function() { int error; if (error = check1()) goto exception; if (error = check2()) goto exception; if (error = check3()) goto exception; cleanup(); return NOERROR; exception: cleanup(); return error; } Thanks for your time, -- Vasile
otto@tukki.jyu.fi (Otto J. Makela) (09/28/90)
In article <837@babcock.cerc.wvu.wvnet.edu> vrm@cathedral.cerc.wvu.wvnet.edu (Vasile R. Montan) writes:
[What is the best way for a function to test for errors ?]
Method 1: Return Error Code When Error Is Encountered
The problems I have with this are first it goes against
software engineering principles which say that all functions
should have one entry point and one exit point. Secondly
some functions do have some standard clean up to do.
[...]
Method 2: Set Error Code and Return Only at End
The problem I have with this is that I don't want to do
later work if a previous error occured. I just want to
clean up and return. This forces me to continuously check
if a previous error occured.
[...]
Method 3: Use GOTO to Create Exception Handler
Of course this breaks the golden rule of software
engineering of "absolutely positively no GOTO's in your
program."
I would go for method 3 everytime (if the clean-up is trivial, go for 1).
Remember, rules like "no GOTOs" are inventions of Pascal weenies, and I
wouldn't call them golden :-)
Simplistic rules like this are supposedly a alternative to using common
sense in programming (no smiley here). They are human inventions, they
didn't come down from the mountain engarved in stone tablets.
Seriously, do you think method 2 is more "structured" than method 3 only
because it has no gotos in it ? I have seen awful constructs which are
supposedly structured programming, in which you have a dozen of "break-
out-flags" from nested loops because you can't use gotos or breaks (the
latter being only gotos to the exit point of the loop).
(I have a weird feeling I'm going to be flamed by religiously structured
zealots... what are you doing with that napalm ?)
--
/* * * Otto J. Makela <otto@jyu.fi> * * * * * * * * * * * * * * * * * * */
/* Phone: +358 41 613 847, BBS: +358 41 211 562 (CCITT, Bell 24/12/300) */
/* Mail: Kauppakatu 1 B 18, SF-40100 Jyvaskyla, Finland, EUROPE */
/* * * Computers Rule 01001111 01001011 * * * * * * * * * * * * * * * * */
henry@zoo.toronto.edu (Henry Spencer) (09/28/90)
In article <837@babcock.cerc.wvu.wvnet.edu> vrm@cathedral.cerc.wvu.wvnet.edu (Vasile R. Montan) writes: >What is the best way for a function to check for errors and return an error >code? ... >Method 1: Return Error Code When Error Is Encountered > The problems I have with this are first it goes against > software engineering principles which say that all functions > should have one entry point and one exit point. Secondly > some functions do have some standard clean up to do. What you have encountered is one of the numerous situations where an arbitrary one-entry-one-exit rule just creates too much grief. Such principles should be taken as guidelines, not as Holy Law. Multiple exit points do make it harder to trace precisely where the function exited during a particular run. On the other hand, they can make the function's logic simpler by clearing away exceptional conditions and letting the code focus on the main-line case. My experience is that the latter consideration is usually more important than the former. As for standard cleanup, this *is* a pain of using multiple returns. Sometimes you can solve this by breaking the function up, putting the cleanup in an outer function where it always gets done after return from the inner one. >Method 2: Set Error Code and Return Only at End > The problem I have with this is that I don't want to do > later work if a previous error occured. I just want to > clean up and return. This forces me to continuously check > if a previous error occured. I don't recommend this. It's taking the Holy Law to ridiculous extremes. >Method 3: Use GOTO to Create Exception Handler > Of course this breaks the golden rule of software > engineering of "absolutely positively no GOTO's in your > program." See above comments on Holy Law. :-) That having been said, my own feeling is that such situations do generally represent a failure of structuring. However, I don't mean "there ought to be a way to write this function without a goto" -- that leads to the flag-variable syndrome you allude to in Method 2. What I mean is "there ought to be a way to *design* this function's *interface* that would yield a clean internal implementation". -- Imagine life with OS/360 the standard | Henry Spencer at U of Toronto Zoology operating system. Now think about X. | henry@zoo.toronto.edu utzoo!henry
Richard.Utter@f226.n260.z1.FIDONET.ORG (Richard Utter) (09/29/90)
> Method 1: Return Error Code When Error Is Encountered > Method 2: Set Error Code and Return Only at End > Method 3: Use GOTO to Create Exception Handler Herewith, one vote for #2. There's a sound _pragmatic_ reason for avoiding #1. If you've written a module with enough complexity to generate many errors and associated returns, then you may find yourself in the position of having to set just as many debugger breakpoints in the module if it doesn't work right the first time. IMHO, #3 is hateful. It's Fortran-66-think. GOTOs are no more than taking advantage of an escape clause. "Oops, we broke. Let's get out of here." Some will argue the point, but they may not have to maintain or even understand code riddled with GOTOs. ...Leaving us with #2. Its most significant disadvantage is that it costs you an indent level. Almost all of your productive code will live in conditional blocks like-- if (!error) { error = do_whatever_is_next (); } Still, the flow of the module remains obvious and, should an error occur, the processing time needed to fall through the remaining tests of the status variable will normally be negligible. One final hypercritical thought: at the point where a module tests for more than one error, it may be time to consider whether it deserves to be transformed into more than one module. If a module does but one thing, which can result in but one error, suddenly life becomes simpler. -- *%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%* Richard Utter - via FidoNet node 1:260/230 UUCP: ...!rochester!ur-valhalla!rochgte!226!Richard.Utter INTERNET: Richard.Utter@f226.n260.z1.FIDONET.ORG *%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*%*
peter@ficc.ferranti.com (Peter da Silva) (09/30/90)
"Do you want to try door #1, door #2, or door #3?" "Yes." Door #1 (if(error) {cleanup; return;}) If cleanup is relatively small. Door #2 (if(!error) {next_step;}) If there are relatively few cases, or you have religious reasons not to use #3 (i.e., it's on an exam) Door #3 (if(error) goto cleanup;) If there are lots of cases and cleanup is nasty. I've used all three. -- Peter da Silva. `-_-' +1 713 274 5180. 'U` peter@ferranti.com
peter@ficc.ferranti.com (Peter da Silva) (10/01/90)
foo() { stuff1 if(!error) { stuff2 if(!error) { stuff3 if(!error) { main code return success; } cleanup stuff3 } cleanup stuff2 } cleanup stuff1 return failure; } The main advantage of this form is it guarantees cleanups get performed in the right order. It does get a bit deep, but it's just as efficient and easier to follow than the version with gotos: foo() { stuff1 if(error) goto exception1; stuff2 if(error) goto exception2; stuff3 if(error) goto exception3; main code return success; exception3: cleanup stuff3 exception2: cleanup stuff2 exception1: cleanup stuff1 return failure; } However, if you have a couple of dozen exceptions the latter can easily be seen to be more readable. Just be DAMN careful that the exception recovery ordering is maintained. Oh, you can use a case: foo() { stuff1 state=1; if(error) goto exception; stuff2 state=2; if(error) goto exception; stuff3 state=3; if(error) goto exception; main code return success; exception: switch(state) { case 3: cleanup stuff3 case 2: cleanup stuff2 case 1: cleanup stuff1 } return failure; } But this comes down to the same thing, really. -- Peter da Silva. `-_-' +1 713 274 5180. 'U` peter@ferranti.com
gah@ibm.com (Gary Hoffman) (10/01/90)
This was something I have been building a library for .. ain't portable! I can call a procedure after setting up with my version of setjmp() and then I can hit the button wherever and return to that point. In this way I have centralized error handling. It is handy in that the setup is multi-level. Actually I've been trying to reproduce the function built into another language I use and it has proved difficult. Performance that results from this is very good since the exceptions are infrequent. Nice in that folls user, compiler, and system exceptions into one handler with return codes. -- g
r3jjs@VAX1.CC.UAKRON.EDU (Jeremy J Starcher) (10/02/90)
In article <OTTO.90Sep28090952@tukki.jyu.fi> otto@tukki.jyu.fi (Otto J. Makela) writes: > >Remember, rules like "no GOTOs" are inventions of Pascal weenies, and I >wouldn't call them golden :-) >Simplistic rules like this are supposedly a alternative to using common >sense in programming (no smiley here). They are human inventions, they >didn't come down from the mountain engarved in stone tablets. > I have to agree, those who flame your (or my) use of GOTOs should try to program on an 8088 or a 6502 based machine in assembly. These programmers know how nice GOTOs are (since often no nice alts exist). Although I do not feel that GOTOs should be over used. Since C (and PASCAL) provide reasonable looping controls and if statements, the need for GOTOs declines nicely. ----------------------------------------------------------------------------- r3jjs@vax1.cc.uakron.edu
vrm@blackwater.cerc.wvu.wvnet.edu (Vasile R. Montan) (10/02/90)
Here is a summary of the responses that I recieved on my question on the "best" way to handle errors in C. I have reproduced my original article in its entirety and have cut out all references to it except Method numbers from the responses. I appologize for repetition of some replies, but I would like to recognize all authors who replied. There were also some good responses posted to comp.lang.c in case you missed them. The majority seem to like Method 3 which uses a goto to impliment an exception handler. However, the people who are opposed to it are often very strongly opposed to it. Method 1 with an immediate return when an error is encountered is second, especially if there are only a few errors. Thanks again to everyone who responded. -- Vasile My orginal article: > Here is a general question to all of the expert C programmers, or >even novice C programmers, who have a strong opinion on C style. What >is the best way for a function to check for errors and return an error >code? This sounds like a simple problem, but I don't know which way >to go. I would just like to follow a style which is easy to maintain. >Below are the methods which I have considered so far. You may add new >ones if you like. I apologize for any other style problems in my >example code. I was trying to make the examples small. > > I will post a summary of all replies that I get through email. > >Method 1: Return Error Code When Error Is Encountered > The problems I have with this are first it goes against > software engineering principles which say that all functions > should have one entry point and one exit point. Secondly > some functions do have some standard clean up to do. > > int function() > { > int error; > if (error = check1()) {cleanup(); return error;} > if (error = check2()) {cleanup(); return error;} > if (error = check3()) {cleanup(); return error;} > cleanup(); > return NOERROR; > } > >Method 2: Set Error Code and Return Only at End > The problem I have with this is that I don't want to do > later work if a previous error occured. I just want to > clean up and return. This forces me to continuously check > if a previous error occured. > > int function() > { > int error; > error = check1(); > if (!error) error = check2(); > if (!error) error = check3(); > cleanup(); > return error; > } > >Method 3: Use GOTO to Create Exception Handler > Of course this breaks the golden rule of software > engineering of "absolutely positively no GOTO's in your > program." > > int function() > { > int error; > if (error = check1()) goto exception; > if (error = check2()) goto exception; > if (error = check3()) goto exception; > cleanup(); > return NOERROR; > exception: > cleanup(); > return error; > } > ============================================================================ From raymond@math.berkeley.edu Thu Sep 27 13:56:28 1990 >Method 3: Use GOTO to Create Exception Handler I've never seen that particular rule. GOTOs are bad when they are abused. When used properly, GOTOs can actually make code easier to read. Error handling is, I think, one of those instances. ============================================================================ From kanamori@Neon.Stanford.EDU Thu Sep 27 13:57:51 1990 Tell Wirth to GOTO..., no just kidding. Seriously, I would go with Method 1 (i.e. bail out as soon as you detect an error.) If cleanup is required, use method 3 (goto exception handler). The tricky part here is that depending on how far the computation proceeded before the error manifested itself, different amounts of cleanup may be required. "Absolutely positively no GOTO's in your program" is a bad rule to follow. A better rule is "Make the structure of your code match the structure of your problem." Simply put, error handling is inherently unstructured and is best handled by an unstructured approach. By investing in a certain number of messy, unsightly "if...return" statements at the start of the function, you greatly increase the readability of the rest of the function because it can proceed on the assumption that all is well. Now, the code reads straightforwardly - telling the reader what it does in the normal case, and segregating the exceptional cases off in a corner. It simply isn't natural for a program to say "Do step 1" "Do step 2 unless error occurred in step 1" "Do step 3 unless error occurred in steps 1 or 2" "Do step 4 unless error occurred in steps 1, 2 or 3..." By the way, there are alternatives to having functions that return either a result or an "error code". The problem with that approach is that it cruds up the caller's code because it has to check every return value - and, it's hard to design a consistent error-coding scheme that works for all result types. A better way is to design the function so that it either returns a valid result or doesn't return at all. For maximum flexibility, the caller should be able to specify how it wants the callee to handle errors: does it panic and core-dump, or does it escape to some global handler, or... The most general mechanism would be for the caller to pass in an escape procedure (say created by setjmp) but this is pretty clumsy to do in C. You might, instead, try to list the different error handling strategies required and assign an integer code for each one that the caller can pass in. Again, the advantage is that the caller can assume that the return value is always valid. Drastic reduction in "if" statements. If a caller doesn't want to handle an error, it can pass the buck to its own caller by passing in the same error handler that IT was handed. Reading the code becomes easy for the easy, normal cases, and becomes hard only for the exceptional cases. ============================================================================ From brnstnd@KRAMDEN.ACF.NYU.EDU Thu Sep 27 14:36:15 1990 I think you would program a lot more effectively if you unlearned all of that ``software engineering.'' ---Dan ============================================================================ From sga@redwood.cray.com Thu Sep 27 14:54:42 1990 > Method 1: Return Error Code When Error Is Encountered This is definately my preferred method. There are times when I firmly believe that the software engineering proponents never coded a real application in their life, and this is one of those cases. I have no problem with one NORMAL return in a function, but errors are exceptions and should be treated differently. > Method 2: Set Error Code and Return Only at End Messy and very inefficient. > Method 3: Use GOTO to Create Exception Handler Your last sentence says it all. Dr. Steven Anderson Cray Research, Inc sga@cray.com ============================================================================ From steve@unidata.ucar.edu Thu Sep 27 14:59:07 1990 >Method 2: Set Error Code and Return Only at End My variation on this method (which doesn't require repeated tests) is int function() { int success = 0; /* return status = failure */ if (!check1()) { if (!check2()) { if (!check3()) { success = 1; } } } cleanup(); return success; } which, in your simplified example, could be merged into one statement -- but I presume you're interested in the large-scale structure. -- Steve Emmerson steve@unidata.ucar.edu ...!ncar!unidata!steve ============================================================================ From drh@cs.duke.edu Thu Sep 27 15:33:54 1990 Of the options you present, I would normally use method 3. Exception handling is a legitimate use of a goto -- the only legitimate use. If I were teaching an undergraduate what to do, though, I would tell them to use method 2. Otherwise they would use my suggestion of number 3 as a license to use goto's whereever they wanted. (I know this is what would happen from experience.) There are, of course, other options. I frequently use a nested if: int function() { int error; if( !(error=check1()) ){ if( !(error=check2()) ){ if( !(error=check3()) ){ /* code */ } } } cleanup(); return error; } Another possiblity is to use short-circuit evaluation. int function() { int error; if( !(error=check1()) && !(error=check2()) && !(error=check3()) ); cleanup(); return error; } ============================================================================ From rhl@astro.Princeton.EDU Thu Sep 27 16:37:49 1990 (I've been writing C for 10 years) I use multiple return's to avoid extra flags; I finmd that it makes for clearer code. If the cleanup is extensive and non-trivial I usually define #define RETURN(I) \ cleanup; return(I); Robert ============================================================================ From @po2.andrew.cmu.edu:ghoti+@andrew.cmu.edu Thu Sep 27 16:53:03 1990 I definitely prefer 1 and/or 2 to 3 -- however I think a lot of it has to do with the surrounding situation. For instance - if you are writing a program which is supposed to parse an input file, and for which any error in parsing the input file could be considered a critical error, I ususally put together something like this: ---------------- #define WHERE __FILE__, __LINE__ extern int errno; void reportError(char *fname, int line, int enum, char *msg) { fprintf(stderr, "<%s:%d> ", fname, line); if (enum) { errno = enum; perror(msg); } else { fputs(msg, stderr); } exit(1); } type func(type parm1, type parm2) { int eno; errno = eno = 0; if ((parm1 = some_func(parm2)) == -1) { eno = errno; reportError(WHERE, eno, "some_func failed.\n"); } return (parm1); } ---------------- It tends to keep most of the rest of the code cleaner - especially if each function that tries to perform something that has a chance to fail, reports it in such a way. That way less of the other code needs to actually check for errors. Of course the above is only for a situation where the errors are all considered critical. But one can easily modify the above to understand keywords (macros, or enums perhaps) like ERROR_CRITICAL, ERROR_TEMPORARY, ERROR_IGNORE, etc. and only have the program exit on the first --- or use more than one function for the different types of errors. Or you could use the above like a mediator of sorts -- allowing the called function to produce it's own diagnostic message, and then either exit blindly or return an status value back to the calling function - which then can do what it wants, and not have to worry about putting out a diagnostic message (as it's already been taken care of by the actual function that had an error) (I probably have more to blab about, but I have a meeting to go to, and my mind is getting overloaded -- hope the above is of some use.....) --fish ============================================================================ From morse@cs.unc.edu Thu Sep 27 16:57:38 1990 My $0.02: Option 1: This works and is clean. While most people are familiar with the axiom you refer to, most only look at a part of it. From what I have read, every function should have one entry point and one *NORMAL* exit point. Additional (possibly multiple) abnormal termination points are permitted. This is especially true for small functions where the extra "return" is clearly visible. If it is nested inside three loops and two conditionals, you might want to reconsider. I often code my error checking as return statements as follows: if (precondition 1 not met) return; if (precondition 2 not met) return; the main body of the function; I find this very readable. Option 2: Yuck!!!! I know this is what blind obedience to programming rules might dictate, but I find following if-then-elses MUCH more confusing than multiple exit points. Option 3: I've never tried this, but it has a certain aesthetic quality. While "No GOTOs" is true, the scope of this is limited to the function alone and only under very clearly defined conditions. I'm not quite sure what side-effects you might produce. Here's my single, most overriding, programming maxim: Thou shalt do that which is most readable! If you have to choose between readability and some programming "rule", choose the readability every time. You'll never go wrong. Bryan morse@cs.unc.edu ============================================================================ From ddb@ns.network.com Thu Sep 27 17:34:51 1990 :Method 3: Use GOTO to Create Exception Handler This is a stupid rule, and this sort of error-handling situation is EXACTLY the situation where use of GOTO can make your code significantly easier to understand and maintain. I find Method 2 a major mistake. I usually go with method 1, except when there's standard cleanup work to do in which case I use method 3. -- David Dyer-Bennet, ddb@terrabit.fidonet.org or ddb@network.com or ddb@Lynx.MN.Org, ...{amdahl,hpda}!bungia!viper!ddb or Fidonet 1:282/341.0, (612) 721-8967 9600hst/2400/1200/300 ============================================================================ From ath@prosys.se Fri Sep 28 03:16:15 1990 I don't know about 'the best', but there are some 'reasonably good' wyas. >Method 1: Return Error Code When Error Is Encountered Contrary to Software Engineering principles? No big matter. The only thing that matters is getting it right. It could be difficult to make fundamental changes to the code later, though. >Method 2: Set Error Code and Return Only at End This is close to the method I use in quick-and-dirty code, especially when cleanup isn't expensive. The reason: sometimes a routine can do useful work even *after* an error has occurred (this usually involves more that one 'error' variable, though. I prefer to do the cleanup immediately after the error was detected, though: ... if (!error) { error = ...something that may fail and needs cleanup ... if (error) { ...cleanup... } } Then the error variable is used only to bypass the code. If it can take more than one value, it could also be used for alternative actions: if (error == 1) { ...handle this case specially... } else if (error) { ...all other cases are lumped together... } >Method 3: Use GOTO to Create Exception Handler One question: why is that rule there? If you know, you also know when goto's are useful. The code below does not contain any goto's in the regular control flow. It is not until an error has been detected that one simple and obvious goto is used to transfer to the cleanup code. In my book, this is correct programming. This is actually my preferred way of handling things. I saw it described in a note in comp.lang.c some years back. It works like this: { int state = 0; ...compute... if (...error...) goto error; state = 1; ...compute... if (...error...) goto error; ... etc ... return OK; /* ----- clean-up section ---- */ error: switch (state) { case 2: /* cleanup after state 2 */ case 1: /* cleanup after state 1 */ case 0: /* cleanup after state 0 */ } return FAIL; Note that the switch statement does not contain any 'break' - control enters at the correct state label, and falls through the remaining cases. --- Apart from that, I also have a penchant for providing clear-text error messages when any function returns an error. Something like this: extern char *global_errmsg; ... if (error_detected) { global_errmsg = "Error: out of memory"; return FAIL; } The message would be printed by the routine that gets the FAIL return. The reason: the routine that detects the error is usually the one that knows how to describe it. Rather than leving it to the calling routine to produce a intelligible message from an error return code, it's better to just signal the error by FAIL, and the leave it up to the caller to retrieve the error message. Hope this is of any use, -- Anders Thulin ath@prosys.se {uunet,mcsun}!sunic!prosys!ath Telesoft Europe AB, Teknikringen 2B, S-583 30 Linkoping, Sweden ============================================================================ From kevin@math.lsa.umich.edu Fri Sep 28 11:48:43 1990 How about int error_handler() { int error = NOERROR; if (error = catch1() || error = catch2() || error = catch3()) ; cleanup(); return error; } The ||'s allow you to stop as soon as you detect an error. If you decide later that there is special cleanup to do in the case of errors, you can make the body of the 'if' do something, and you stil have only one entry and exit point. Best, Kevin Coombes <krc@mayme.umd.edu> ============================================================================ From taumet!taumet!steve@uunet.UU.NET Fri Sep 28 12:23:09 1990 >Method 1: Return Error Code When Error Is Encountered The above function has one entry point (the opening left brace) and one exit point (the closing right brace). That is what is meant by that principle. FORTRAN and some other languages allowed a function to be entered in the middle. In assembly language you can insert a return instruction in the middle, which makes maintenance harder, as function epilogue code might be skipped accidently. The return statement in C does not have this problem. >Method 2: Set Error Code and Return Only at End You can use nested if's instead: if( ! (error = check1()) ) if( ! (error = check2()) ) error = check3(); cleanup(); return error; >Method 3: Use GOTO to Create Exception Handler There is no rule that says absolutely positively no goto's. It is just that most progams when carefully designed and clearly written happen not to contain any goto's -- they are seldom necessary. See ACM Computing Surveys, vol 6 no 4, December 1974, expecially the articles by Knuth and by Wirth. Your example is too restricted to show any real style differences. If we assume a more typical model where processing is aborted and an error code returned on error, processing continuing otherwise, we can get two styles of interest: C programmers generally write like this: if( check1() ) return ERR1; process1(); if( check2() ) return ERR2; process2(); if( check3() ) return ERR3; process3(); return OK; Pascal programmers generally write like this (but still using C): int result; /* this should really be initialized to something */ if( check1() ) result = ERR1; else { process1(); if( check2() ) result = ERR2; else { if( check3() ) result = ERR3; else { process3(); result = OK; } } } return result; The code is equivalent, and may well result in identical object code, depending on the compiler. I don't see that it matters which style you use, but consistency in style is a big help to anyone reading your code. -- Steve Clamage, TauMetric Corp, steve@taumet.com ============================================================================ From tetrauk!rick@relay.EU.net Fri Sep 28 15:16:29 1990 Given your examples, I would say either use style 3, or if the number of possible errors is only 1 or 2, something like: int function() { int error; if (!(error = check1())) { error = check2(); } cleanup(); return error; } You can't do this too many times, otherwise the nesting gets totally out of order. I do not subscribe to the anti-goto religion. Goto's have a value if used sensibly, and this is one case where it is sensible. A general maxim I would apply is "goto forward is OK, goto back is not". In other words, never use goto to implement a loop. Software engineers don't really say "never use them" anyway - one proof is to observe that they will discuss in depth the semantics etc. of setjmp/longjmp. This means they consider it important, and setjmp/longjmp is really a Mega-Goto! --- Rick Jones The definition of atomic: Tetra Ltd. from the Greek meaning "indivisible" Maidenhead, Berks, UK So what is: rick@tetrauk.uucp an atomic explosion? ============================================================================ From karl@IMA.ISC.COM Fri Sep 28 16:31:23 1990 I would use Method 3. >Of course this breaks the golden rule of software engineering of "absolutely >positively no GOTO's in your program." That's a misquote. Don't you ever use a GOTO when programming in old Fortran, or minimal BASIC? A better form of the rule is "use a GOTO only to emulate a more structured construct which happens to be missing from the language you're using." Now, let's look at your description again: >Method 3: Use GOTO to Create Exception Handler C doesn't have builtin exception handlers. Thus, this use of GOTO is well within the rules, and in fact is *more* structured than some other methods that do not use GOTO. Karl W. Z. Heuer (karl@kelp.ima.isc.com or ima!kelp!karl), The Walking Lint (Permission granted to repost in a summary.) ============================================================================ From bjornmu@idt.unit.no Fri Sep 28 16:46:51 1990 I have used a method like your #3. I define this macro: --------------------------- /* This macro is used to return an error code from a function. There must be a local integer variable Error, initialized to 0, and a label Return at the end of the function, where clean-up is performed. The function must end with "return Error". */ #define Ret(err) { Error = (err); if (Error)\ { fprintf (stderr,\ "File: %s\tLine:%d\n", __FILE__, __LINE__);\ TrapError (Error); }\ goto Return; } ---------------------------- Error could also be a global variable, maybe that would have been better. I call this macro whenever an error is found: if (! some_pointer) Ret(BAD_POINTER); , and also to return before the end of the function: if (finished_early) Ret(0); TrapError prints a descriptive text to stderr. If the error is of a serious kind (not just a user mistake), TrapError does something that "offends" the debugger, so that it stops. The latter, plus the printf statement, should of course be removed in a finished product. With a few exceptions, my functions return 0 upon success, otherwise an error code, which I always check for, like this: int retcode; .... retcode = SomeFunc (param); if (retcode) Ret(retcode); Bj|rn Munch | Div. of Comp. Science & Telematics, bjornmu@idt.unit.no | Norwegian Institute of Technology (NTH), PhD Student (well, soon...) | Trondheim, Norway (some filler words here) | You can finger me @garm.idt.unit.no ============================================================================ From geovision!pt@uunet.UU.NET Sat Sep 29 00:20:08 1990 Here, we almost always use the 3rd method. Our code looks a lot like ... if (!dbi_act_query(args, &qid)) goto err_exit; if (!dbi_get_1_row(qid)) goto err_exit; ... return TRUE; err_exit: ...cleanup code.. return FALSE; We also issue error messages at the earliest possible opportunity, which some times mean you get cascading messages like: ipc_tcpip_spawn: permission denied. ipc_startup: Could not establish link to program "gfx_dm" on node: "zaphod" gfx_init: Graphics Display manager not started. plot_open_dev: Unable to open device "X Window" which can get kind of confusing. Some of our newer code passes back more status codes, so that higher level code can issue message that are more meaningful than just success or failure. --- Paul Tomblin, Department of Redundancy Department. ! My employer probably I'm not fat..... I'm metabolically challenged. ! does not agree with my I'm not underpaid... I'm financially challenged. ! opinions.... nrcaer!cognos!geovision!pt or uunet!geovision!pt ! Me neither. ============================================================================ From sactoh0!pacbell!jak@PacBell.COM Sat Sep 29 14:58:36 1990 >Method 1: Return Error Code When Error Is Encountered > It depends on the function's complexity. Generally though I don't like to do this though. It can make tracing the code harder. However, it is probably one of the cleaner ways to do it. >Method 2: Set Error Code and Return Only at End This is probably the most "structured" way to do it. But it makes for more complecated code. Still, I don't think I would code like this. >Method 3: Use GOTO to Create Exception Handler Well, golden rule or no, this _will_ be the fastest meathod available. This is the meathod I would use. However, be careful to structure it carefully and DO NOT OVERUSE the longjump. Also, avoid jumping out of a function. That could cause more problems than it solves. The problem with using jumps/goto's is that they are so easy to misuse and make your program unreadable. However, using them for error conditions is a generally acceptable. > >Thanks for your time, > Hope I was of help. -- ------------------------------------------------------------- Jay @ SAC-UNIX, Sacramento, Ca. UUCP=...pacbell!sactoh0!jak If something is worth doing, it's worth doing correctly. ============================================================================ From naitc!cookr@uunet.UU.NET Mon Oct 1 11:21:40 1990 Concerning the testing/returning of error codes, I've found that academia is a bit out of touch when it comes to multiple return points. As you point out, it's a pain to have to check for previous errors before taking the next step. By the same token, using a GOTO for exception handling/cleanup is also poor form. Yet, what's so terrible about an if-then block which quite clearly is cleaning up from an error and returning to the caller? In my experience, this form is the most clearly understood and easiest to maintain. As an aside, one form which was pseudo-missed is somewhat similar to the test-for-previous-error one. Specifically, rather than repeatedly testing an error-state variable, you simply use nested if-thens. Of course, for me, this is the absolute WORST method. I say this because I used to work with a person which did this and 12 levels of nesting weren't all that unusual.
rmj@tcom.stc.co.uk (The Beardless Wonder) (10/03/90)
In article <703@VAX1.CC.UAKRON.EDU> r3jjs@VAX1.CC.UAKRON.EDU (Jeremy J Starcher) writes: > >I have to agree, those who flame your (or my) use of GOTOs should try to >program on an 8088 or a 6502 based machine in assembly. These programmers >know how nice GOTOs are (since often no nice alts exist). With respect, I have spent several months now decoding and documenting a rather large semi-differentiated lump 8086 assembler, and respectfully submit that at least some assembler programmers wouldn't know "nice" features of code if they came up and punched them in the face. The code I have been facing is just barely structured (some of the time), hard to read, badly commented and sometimes written very "creatively". GOTOs, with one major exception, are the least of its problems. >Although I do not feel that GOTOs should be over used. Since C (and PASCAL) >provide reasonable looping controls and if statements, the need for GOTOs >declines nicely. I concur. With that sort of looping control it is usually easy to get some idea of where the code goes at a glance, even though its purpose may be unclear. With the assembler I have haunting my nightmares :-), it is usually impossible to tell where the control flow goes until after the code is flowcharted, and even then it can give you a real headache. I don't say that GOTOs should never be used; occasionally they are the most appropriate thing. Overused, they make your code very hard to read. Rhodri >----------------------------------------------------------------------------- >r3jjs@vax1.cc.uakron.edu -- * Windsinger * "But soft, what light through yonder * rmj@islay.tcom.stc.co.uk * airlock breaks?" * rmj@tcom.stc.co.uk * --RETURN TO THE FORBIDDEN PLANET * rmj10@phx.cam.ac.uk * You've gotta be cruel to be Khund!
mneerach@iiic.ethz.ch (Matthias Ulrich Neeracher) (10/05/90)
In article <OTTO.90Sep28090952@tukki.jyu.fi> otto@tukki.jyu.fi (Otto J. Makela) writes: >Remember, rules like "no GOTOs" are inventions of Pascal weenies, and I >wouldn't call them golden :-) I thought this particular rule was the invention of an Algol weenie ?-) Matthias ----- Matthias Neeracher mneerach@iiic.ethz.ch "These days, though, you have to be pretty technical before you can even aspire to crudeness." -- William Gibson, _Johnny Mnemonic_
scs@adam.mit.edu (Steve Summit) (12/06/90)
While I'm up on an error handling soapbox, I'd like to discuss what else to do with errno values other than call perror to interpret and print them. (This relates to a long discussion which raged on comp.unix. er, "internals" a while ago. I'm crossposting to comp.unix.programmer because this pertains mostly to Unix/Posix programming.) In article <14617@smoke.brl.mil>, Doug Gwyn pointed out that (in Posix), >The allowable values of errno (<errno.h> >macros) are specified for each function. While this is useful information, and helps you figure out how each function might fail, it is often a bad idea to build knowledge of specific errno values into a program. To be sure, some errno values may be interesting, and a program may well wish to handle them specially, but (as evidenced by recent discussion) a lot of people seem to want to believe that the documented vales are guaranteed to be the only only ones that the function will ever "return." This is a dangerous and unnecessary assumption. It leads to code that ignores error returns because the author inspected the list of possible errno's and decided that the program could handle all of them, or that all of them "couldn't happen" (or that it couldn't handle any of them, and "don't check for error conditions you can't handle"). All of these rationalizations are poor. Don't use (void)syscall(...); or if(syscall(...) < 0) { switch(errno) { case ENOENT: /* ...build one... */ break; case EINTR: /* ...retry... */ break; } } Most of the time, use something like if(syscall(...) < 0) { perror("syscall failed"); return ERROR; } (but see my previous article for other useful information which should be included in the error message, and how to do so conveniently.) If there are a few errno values which you know how to "correct" (i.e. handle in some special way), use code like the switch statement above, but include a default case: default: perror("syscall failed"); return ERROR; If the system call "can't fail," or if your program "can't handle it" (i.e. somehow has to keep processing, even though the system call failed), or if a failure truly wouldn't matter (e.g. close fails on a file open for reading), at least use something like if(syscall(...) < 0) { perror("warning: syscall failed"); fprintf(stderr, "(continuing anyway; hope things are okay)\n"); } This lets the user know that something might be amiss. No matter what the documentation says today, errno values are the sort of thing that change all the time (or are implemented slightly incorrectly by a system implementer who didn't read the fine print), and it is useful to add new ones as systems expand or are otherwise improved, so it makes sense for a program that might be around for a while to anticipate the occurrence of errno's it hasn't heard of yet. Steve Summit scs@adam.mit.edu