[comp.lang.c] Error handling

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