By using this site, you agree to our updated Privacy Policy and our Terms of Use. Manage your Cookies Settings.
424,687 Members | 2,046 Online
Bytes IT Community
+ Ask a Question
Need help? Post your question and get tips & solutions from a community of 424,687 IT Pros & Developers. It's quick & easy.

An error handling scheme (long)

P: n/a
The main two desirable things I feel error handling should provide are
these:

1) Debugging and diagnostic aid
2) User feedback

One method that is used a fair amount it to 'say' that all functions must
return an error code of a standard type, for example tError. Where tError is
typedefed as a long int. A list of error codes may then be defined in a
global header file, or perhaps upper bits of tError may indicate a module
code, and lower bits a module specific error code.

Example:

tError OpenResource( ... )
{
...
return MAKE_ERROR( MOD_RESOURCE, RESOURCE_INVALID_PATH );
}

Here MAKE_ERROR fuses the error information together to form the error
return code of type tError. Unfortunately there is no room for additional
debugging information, such as __FILE__, or __LINE__ which could be very
useful, so this method fails feature (1) somewhat.
There are other ways that I don't favour e.g: Set a global error code.
either
like C's errno. or using a GetError() / SetError() approach. And there is
setjmp and longjump.
Here is a proposed method, which seems somewhat unwieldy too ( an
abomination really ). It does have some advantages though, namely:

1) Could work with threads
2) Provides line and file number, as well as any other desirable information
to be added to the 'error' structure.

On the down side:

1) It's a bit of a mess
2) Wouldn't work in environments where global variables aren't allowed.
What I was trying to do here is a universal ( well across a single large
project ) error handling mechanism and debugging information to go with
that, namely file and line numbers. Perhaps someone can suggest a good
alternative method to yield these things. Using int return codes to API
functions, and a macro to set fields in a global structure might be more
sensible, but would fall apart in the presence of multi threading.

NB- The UUID is there so that a new set of error codes can be defined
without recompiling any core code.

--------- error.h --------

struct sError
{
char *uuid;
char *name;
int code;
int specific;
char *file;
long line;
} Error;

typedef struct sError *tError;
static tError MakeError( tError mod, int code, int specific, char *file,
long line )
{
mod->code = code;
mod->specific = specific;
mod->file = file;
mod->line = line;
return mod;
}

/* Declare a struct in the C file to hold the error information */
#define ERROR_DECALRE_MOD( mod, uuid, name ) static struct sError mod = {
uuid, name }

/* Set the error information and return a pointer to it - use like this:

return ERROR_GENERAL( ParserModule, eParse_BadlyFormedConstant );

*/
#define ERROR_SPECIFIC( mod, code ) MakeError( &mod, code, 1, __FILE__,
__LINE__ )
#define ERROR_GENERAL( mod, code ) MakeError( &mod, code, 0, __FILE__,
__LINE__ )
#define ERROR_GET_CODE( err ) ((err) ? (err)->code : 0)

/* Each module has some sort of UUID so that a user interface can locate
user error messages and diplay them */
#define ERROR_GET_MOD( err ) ((err) ? (err)->uuid : eMod_Unknown)
#define ERROR_IS_MOD( err, uuid ) ((err) ? !strcmp((err->uuid), (uuid)):
0 );
#define ERROR_IS_MODULE_SPECIFIC( err ) ((err) ? (err)->specific : 0 )
/* Modules */
#define eMod_Unknown "unknown"
/* General(universal) errors */
#define eErr_None 0
#define eErr_NoMem 1
-------- yarm.h ----------

enum
{
eErr_CantCreateEvent,
eErr_CantOpenRegKey,
eErr_RequestRegNotifyChangeFail,
eErr_CouldNotCreateThread,
eErr_RegQueryInfoKeyFail,
};

#define UUID_Yarm "{DACA91AC-EE0C-4e0c-93EC-2332AA21BD0A}"

Nov 14 '05 #1
Share this Question
Share on Google+
7 Replies


P: n/a
"Spacen Jasset" <sp**********@yahoo.co.uk> wrote

The main two desirable things I feel error handling should provide
are these:

1) Debugging and diagnostic aid
2) User feedback
1) is essential. 2) it depends what you mean. If there is an internal
programming error then all the user needs to know is "an error was
triggered". On the other hand if the user has made a mistake, and the
program has caught it, it is not really a error but part of normal program
flow.
One method that is used a fair amount it to 'say' that all functions
must return an error code of a standard type, for example tError.
Where tError is typedefed as a long int. A list of error codes may
then be defined in a global header file, or perhaps upper bits of
tError may indicate a module code, and lower bits a module
specific error code.
It makes everything dependent on the error.h header. Fine if you are only
writing one program, but not OK for portable code.
There are other ways that I don't favour e.g: Set a global error
code. either like C's errno. or using a GetError() / SetError()
approach. And there is setjmp and longjump.
C++ exceptions are a good way of handling errors, but unfortunately C
doesn't support them and there is no way to add exception handling without
doing real damage to the concept of C as a language that just abstracts one
level from assembler.
What I was trying to do here is a universal ( well across a single
large project ) error handling mechanism and debugging information
to go with that, namely file and line numbers. Perhaps someone can
suggest a good alternative method to yield these things. Using int
return codes to API functions, and a macro to set fields in a global
structure might be more sensible, but would fall apart in the
presence of multi threading.
You don't need a universal error system because there are several different
types of error.
1) You genuinely run out of resources. If this is expected (eg you are
processing a huge video file) then you need to clean up and return an
out-of-resource message. If it is totally unexpected (eg you are asking for
a few bytes to duplicate a string, which the OS won't give you) it may be OK
to terminate the program, or it may be that you have to handle it as an
expected error.
2) You run out of resources because you are passed a garbage value. This may
not be easy to distinguish from genuinely out of resources. In debug the
programmer needs to know, at release this has to be handled as genuinely out
of resources.
3) You are passed an invalid parameter (eg asked to calculate the mean of a
zero number of values). This is an error by the calling programmer so at
debug time assert(). What you do in release depends on whehter what you are
doing is critical or not.
4) You are passed bad input. Sometimes, as in the case of an expression
parser, it is very difficult for the caller to validate the input without
rewriting most of the parsing function. Here an assert() is inappropriate,
you need to pass back an "illegal expression" message.
5) You have left a bug in the code. Ideally this shouldn't happen but it is
not always possible to get everything right, also sometimes you have to pass
to someone else code you know is not 100%. Here you need file and line
numbers printed out so that the bug can be squashed

/* Modules */
#define eMod_Unknown "unknown"
/* General(universal) errors */
#define eErr_None 0
#define eErr_NoMem 1
-------- yarm.h ----------

enum
{
eErr_CantCreateEvent,
eErr_CantOpenRegKey,
eErr_RequestRegNotifyChangeFail,
eErr_CouldNotCreateThread,
eErr_RegQueryInfoKeyFail,
};

#define UUID_Yarm "{DACA91AC-EE0C-4e0c-93EC-
2332AA21BD0A}"

This is where everything begins to fall apart. On a big project with a
stable team and no desire to re-use code you can get away with this, but all
the code becomes dependent on the error system.

Nov 14 '05 #2

P: n/a

"Malcolm" <ma*****@55bank.freeserve.co.uk> wrote in message
news:c8*********@newsg3.svr.pol.co.uk...
"Spacen Jasset" <sp**********@yahoo.co.uk> wrote

The main two desirable things I feel error handling should provide
are these:

1) Debugging and diagnostic aid
2) User feedback
1) is essential. 2) it depends what you mean. If there is an internal
programming error then all the user needs to know is "an error was
triggered". On the other hand if the user has made a mistake, and the
program has caught it, it is not really a error but part of normal program
flow.


In general point (2) can be achived by taking an arror code in any given
module and reading a user oriented error string from some database
somewhere. Ofcourse not all the errors do have useful meanings for a user.
But errors returned from a given function can releate directly to and error
printed on screen. ( If there enough fine grained errors )
/* Modules */
#define eMod_Unknown "unknown"
/* General(universal) errors */
#define eErr_None 0
#define eErr_NoMem 1
-------- yarm.h ----------

enum
{
eErr_CantCreateEvent,
eErr_CantOpenRegKey,
eErr_RequestRegNotifyChangeFail,
eErr_CouldNotCreateThread,
eErr_RegQueryInfoKeyFail,
};

#define UUID_Yarm "{DACA91AC-EE0C-4e0c-93EC-
2332AA21BD0A}"
This is where everything begins to fall apart. On a big project with a
stable team and no desire to re-use code you can get away with this, but

all the code becomes dependent on the error system.


Well yes, and no. Becuase I could change that header file, so that it only
uses int values and there is none of this other stuff to be seen. I think
it's useful to have a "global error statergy" across a project, for example:
Say I have three modules, A, B and C.

Each has specific type of error return - In other words, module A could
handle email, so it might want to indicate Error_WrongPassword for example.

So, if module A defines that error and futhermore module A has a unique
module identifier. Then the user interface module U, can easily locate an
error message given the error code returned from module A.

To return the code you would do something like this:

return MAKE_ERROR( Mod_Email, Error_WrongPassword );

Where the MAKE_ERROR macro yeilds a long int with the module id, and error
code 'stored inside'

This basic approach, I feel is reasonable. It doesn't have any impact on
portability either. Because If another developer comes along they can use
module A and read error codes from module A, and can even define new module
identifiers, but with some risk. So this doesn't mean module A won't work
with other code. [ This is where I digressed and stated using UUID's -- so
that one could add modules after compilcation of the original code ]

The alternative is, as far as I can see are these:

a) Bad, no real information at all

if ( password_wrong )
{
return -1 /* Didn't work */
}

b) Helpful, but then the error defines are either local, and have to be
mapped to other modules errors, or they are in a global error.h and we come
nearly full circle and have defined a common error handling system or sorts.

if ( password_wrong )
{
return MAIL_ERROR_WRONG_PASS;
}
Nov 14 '05 #3

P: n/a

"Spacen Jasset" <sp**********@yahoo.co.uk> wrote in message

The alternative is, as far as I can see are these:

a) Bad, no real information at all

if ( password_wrong )
{
return -1 /* Didn't work */
}

Or this

/*
reademail() - reads the user's email
Params - pw - password
ptr - various gubbins
Returns:
The number of emails read on success
0 - no emails waiting
-1 - bad password
-2 - couldn't connect to server
-3 - random Microsoft evil
-4 - spammed out
etc
*/

This way you don't pollute the namespace with lots of identifiers.
Nov 14 '05 #4

P: n/a

"Malcolm" <ma*****@55bank.freeserve.co.uk> wrote in message
news:c8**********@newsg4.svr.pol.co.uk...

"Spacen Jasset" <sp**********@yahoo.co.uk> wrote in message

The alternative is, as far as I can see are these:

a) Bad, no real information at all

if ( password_wrong )
{
return -1 /* Didn't work */
}

Or this

/*
reademail() - reads the user's email
Params - pw - password
ptr - various gubbins
Returns:
The number of emails read on success
0 - no emails waiting
-1 - bad password
-2 - couldn't connect to server
-3 - random Microsoft evil
-4 - spammed out
etc
*/

This way you don't pollute the namespace with lots of identifiers.


Bare constants in code? Isn't there a law against that in your county.

The problem with that approach is that the UI won't understand errors at a
function level. a higher up function would have to translate the error from
reademail() into some other error code that the UI ( or error reporting
module understands - which makes thins unreadable too. ) Consider:
err = pop3_readmail();
if ( err == -1 )
ret = -5 /* Authentication failure */
else if ( err == -2 )
ret = -9; /* Can't connect */

if ( ret ) return ret;

err = imap_readmail();
if ( err == 49 )
ret = -5; /* Auth */
else if (err== 58 )
ret = -9 /* Can't connect */

return ret;

.... therefore we have to do this "remapping" of error codes because my
general e-mail library has it's own public API which uses different error
returns from the pop3_readmail() and indeed different from imap_readmail().
Isn't this all very bad?

I don't consider namespace pollution a problem, not for well defined things
like a set of error messages. There are a ton and a half defines from
[various] compiler libraries polluting the namespace anyway. I haven't done
a huge amount of coding, but I've ported to a few different platforms and
while there are sometimes name clashes, I haven't found it a serious problem
yet.

Nov 14 '05 #5

P: n/a

"Spacen Jasset" <sp**********@yahoo.co.uk> wrote in message

Bare constants in code? Isn't there a law against that in your
county.
There's no ideal solution. Defining a list of errors makes the return more
understandable but if you define that negative numbers are errors, there's
always the risk that someone will break the scheme by changing a definition
to a positive. You also have the problem that if foo() uses -4 ==
OUTOFMEMORY, and bar() uses -5 == MALLOCFAILURE then the scope for mixups is
very large.
The problem with that approach is that the UI won't understand
errors at a function level. a higher up function would have to
translate the error from reademail() into some other error code that
the UI ( or error reporting module understands - which makes thins > unreadable too. ) Consider:

err = pop3_readmail();
if ( err == -1 )
ret = -5 /* Authentication failure */
else if ( err == -2 )
ret = -9; /* Can't connect */

if ( ret ) return ret;

err = imap_readmail();
if ( err == 49 )
ret = -5; /* Auth */
else if (err== 58 )
ret = -9 /* Can't connect */

return ret;

... therefore we have to do this "remapping" of error codes because > my general e-mail library has it's own public API which uses different error returns from the pop3_readmail() and indeed
different from imap_readmail().
Isn't this all very bad?
If the number of errors is large and they are going up a large hierarchy
then the scheme could break down. On the other hand pop3_readmail() and
imap_readmail() may not return identical error lists (maybe pop3 has an
address too long error whilst imap allows arbitrary-length addresses or
something) so you would need to do some processing to make things
consistent. And if you impose the same error values on the two functions
then they have to be written by peole who work in close collaboration with
each other.
I don't consider namespace pollution a problem, while there are
sometimes name clashes, I haven't found it a serious problem
yet.

Is your symbol really necessary? Name clashes are a mechanical problem and
only a minor issue. A more serious problem is that every symbol has to be
documented and memorised by the human programmers. If the symbol is defined
in a lower-level library then it has to be used by the calling programmer.
(The classic case is declarations of a boolean type. You very quickly get
into a situation where calling code is juggling bools, BOOLs and bool_ts
beacuse of ill-considered definitions by libraries). Another problem is
that to disambiguate programmers often put prefixes like
aalib_errorhandler(). The extent to which this policy can be pursued without
making identifiers unreadable is strictly limited.
Nov 14 '05 #6

P: n/a

"Malcolm" <ma*****@55bank.freeserve.co.uk> wrote in message
news:c8**********@news7.svr.pol.co.uk...
There's no ideal solution. Defining a list of errors makes the return more
understandable but if you define that negative numbers are errors, there's
always the risk that someone will break the scheme by changing a definition to a positive. You also have the problem that if foo() uses -4 ==
OUTOFMEMORY, and bar() uses -5 == MALLOCFAILURE then the scope for mixups is very large.


No idea solution no. Buy then if a module defines it own error codes (
within some range ) and each module has an ID. Then it doesn't matter what
error values are returned - so long as they comply to the scheme of, say
negative means error.

I know this won't work at all for different code by different software
houses or anything like that. But it seems reasonable for a large project. I
won't say any more on this. I had hoped someone might have pointed out some
other methods. But as you say, there is no golden way; and I shan't attempt
to use the method in my first posting what was recently described by a
friend of mine as "ikky".

Symbian OS uses setjmp/longjmp in C++ in preference to the standard
exception handling. Which boggles me somewhat. I digress...


Nov 14 '05 #7

P: n/a

"Spacen Jasset" <sp**********@yahoo.co.uk> wrote in message
No idea solution no. Buy then if a module defines it own error
codes ( within some range ) and each module has an ID. Then it
doesn't matter what error values are returned - so long as they
comply to the scheme of, say negative means error.
Think of it as building a model house out of Lego, or out of balsa wood. If
you take the Lego route, the bits will all snap together, but you are
restricted to bricks made by one manufacturer. If you choose glue and balsa
wood, then you can use a wide variety of components from different sources,
however they don't stick together by themselves.

Nov 14 '05 #8

This discussion thread is closed

Replies have been disabled for this discussion.