Mechanism to generate annotated "error codes"

D

Don Y

Hi,

[Please take the time to understand what I'm looking for before
replying. I've spent a lot of time describing it; it would
be courteous for you to make a similar investment before
commenting on it.]

I have a suite of projects that utilize a common approach to
error reporting. Instead of a PASS/FAIL result, most functions
return information regarding the nature of the "failure" (which
can also include "partial failures"... "warnings"). These
reports allow upper levels of the application to convey those
details to the user without exposing all of the *mechanism*
involved to those upper levels.

E.g., the application may need a numeric value from the user
with a particular set of constraints (must be positive, less
than 293, no more than two decimals, etc.). Of course, it would
be silly for the upper levels of the application to process
individual characters to assemble such a "value" and enforce
those constraints.

Instead, you'd wrap the behavior in a function that you could
reuse wherever similar needs arise. That function would be
parameterized to allow the caller to specify the constraints
that need to be applied. It would return the value retrieved
from the user or an indication of problems encountered in
the parsing of that value -- constraint violations.

[Note that "from the user" could just as easily be "from a
file"... i.e., how the error is ultimately reported/handled
is something that the upper level routines should decide,
not this lower level function]

For example, the lower level routine might report:
- invalid character encountered
- value below minimum allowed
- value exceeds maximum allowed
- too many significant digits

The "invalid character" report might be embelished:
- only one decimal point allowed
- only one *sign* allowed
- non-digit encountered

Etc. The information conveyed by each of these allows the
application to inform the user of the exact nature of the
"problem" -- instead of just resorting to a "bad value"
report.

Each possible "report" needs a mechanism through which the
caller can ascertain the nature of the error. You *could*
impose a convention that causes reports to be more easily
summarized: e.g., 0 for SUCCESS, <0 for errors, >0 for
warnings (so, if you don't care about warnings the caller
would test for any "not negative" result codes as being
"acceptable" -- as in the case of SUCCESS or "too many
significant digits").

Of course, there's nothing difficult or magical about this.
Create an enumeration or a set of manifest constants to
symbolically represent each of the "results", package them
in a header file and you're all set!

That approach is fine for "small" functions with a limited
number of potential "results". But, consider how you would
scale it to more comprehensive functions. E.g., the complete
UI. Managing these "enumerations" gets to be tedious and
error prone. "Which *values* have been assigned? What is
a good value to pick for this new 'condition'? etc."

One approach to ensuring unique "values" for each of these
"results" is to synthesize them from the function/file name
and line number where they are detected (instantiating a
macro at that particular point). This ensures that values
are unique while hiding the *actual* value from the developer
(*I* surely don't care if TOO_MANY_DECIMAL_POINTS resolves
to 0x27 or 0x7732!).

The problem then becomes one of making that information available
to other *consumers*! I.e., wrapping it in a header file that
others can reference.

[Note this opens the door for a bootstrap problem but I am
willing to live with that -- "I'll deal with it later..."]

A "solution" also needs to address the problem of *explaining*
the "result" (to the ultimate user). The point at which the
result is "signaled" contains the most information about the
nature of the "problem" being reported though it is in a
very narrowly defined context: i.e., you can tell the user
"Multiple decimal points encountered" -- though you can't
indicate the role that the value being entered plays in the
application (is this a person's wages? the weight of a
newborn child? the average lifespan of a male tortoise?).

So, the developer should be able to tie the explanation/commentary
*to* the (symbolic) result code where the result code is defined
instead of having to do so "somewhere else" -- hoping to keep the
explanations in sync with the symbolic names, etc.

A code fragment with such a system might look like:

if (*ptr == '.') {
if (decimalPointEncountered == TRUE) {

MakeErrorCode(Err_TooManyDecimalPoints,
"Multiple decimal points encountered.",
"Only a single decimal point is allowed in numeric
values."
)

result = Err_TooManyDecimalPoints;
} else {
decimalPointEncountered = TRUE;
}
}
...
return (result);

Obviously, a macro could #define Err_TooManyDecimalPoints to a unique
value, here (e.g., __FILE__ ## __LINE__). A script could parse the
output of the preprocessor extracting all lines of the form
"#define Err_" to build a suitable header file. And, another
script could extract the short and long textual explanations of
the error itself.

I currently do this with a mix of ad hoc measures. As such,
it is very fragile (and an annoying drain on my time!). I'm
hoping for suggestions that might lead me to a better/different
approach (WITH THE SAME GOALS). Pointers to tools better suited
to this. *Or*, examples of similar approaches!!

Thx,
--don
 
W

Willem

Don Y wrote:
) A code fragment with such a system might look like:
)
) if (*ptr == '.') {
) if (decimalPointEncountered == TRUE) {
)
) MakeErrorCode(Err_TooManyDecimalPoints,
) "Multiple decimal points encountered.",
) "Only a single decimal point is allowed in numeric
) values."
) )
)
) result = Err_TooManyDecimalPoints;
) } else {
) decimalPointEncountered = TRUE;
) }
) }
) ...
) return (result);
)
) Obviously, a macro could #define Err_TooManyDecimalPoints to a unique
) value, here (e.g., __FILE__ ## __LINE__). A script could parse the
) output of the preprocessor extracting all lines of the form
) "#define Err_" to build a suitable header file. And, another
) script could extract the short and long textual explanations of
) the error itself.
)
) I currently do this with a mix of ad hoc measures. As such,
) it is very fragile (and an annoying drain on my time!). I'm
) hoping for suggestions that might lead me to a better/different
) approach (WITH THE SAME GOALS). Pointers to tools better suited
) to this. *Or*, examples of similar approaches!!

Have you considered using (pointers to constant) strings?

Like this:

char *result = 0; /* or: char *result = ""; */
if (*ptr == '.') {
if (decimalPointEncountered) {
result = Err("E:Multiple decimal points encountered:"
"Only a single decimal point is allowed in numeric values");
} else {
decimalPointEncountered = TRUE;
}
}
...
return (result);

You can probably use the __FILE__ and __LINE__ macros to add something
extra to the string, to make it unique (if that is needed).

With the first character, you distinguish errors and warnings, and you have
all the necessary info at your fingertips when you encounter the error.

It wasn't in your requirements, but if you need your program to check
against specific errors (EG catch them), I'm sure there's a way to get
the pointer values of all the error codes into a .h file or something.

This way, you're still passing around 'magic values', but the required
info is directly accessible from the value. Downside is that you need
an indirection to distinguish the error/warning, upside is that you can
now distinguish more than that if you want.


SaSW, Willem
--
Disclaimer: I am in no way responsible for any of the statements
made in the above text. For all I know I might be
drugged or something..
No I'm not paranoid. You all think I'm paranoid, don't you !
#EOT
 
S

Shao Miller

Hi,

[Please take the time to understand what I'm looking for before
replying. I've spent a lot of time describing it; it would
be courteous for you to make a similar investment before
commenting on it.]

I read the whole thing.
I have a suite of projects that utilize a common approach to
error reporting. Instead of a PASS/FAIL result, most functions
return information regarding the nature of the "failure" (which
can also include "partial failures"... "warnings"). These
reports allow upper levels of the application to convey those
details to the user without exposing all of the *mechanism*
involved to those upper levels.

[...and more...]

You might take a look at iPXE's error system. If I recall correctly,
there's a range of bits for the error type, a range of bits for the
module (translation unit), and maybe another range or two I'm forgetting
about (maybe major and minor codes). The special gift that iPXE's
primary author grants is the ability to look up the error codes on a
web-page. :)

If you are going to go with a strategy such as "warnings are positive"
and "errors are negative" and "success is 0," consider wrapping those
predicates with macros:

if (WARNING(result = operation(arg1, arg2))) { /* ... */ } /* or */
if (SUCCESS(result = operation(arg1, arg2))) { /* ... */ } /* or */
if (ERROR(result = operation(arg1, arg2))) { /* ... */ } /* etc. */

then the representation of 'result' needn't be considered and becomes an
implementation detail.
That approach is fine for "small" functions with a limited
number of potential "results". But, consider how you would
scale it to more comprehensive functions. E.g., the complete
UI. Managing these "enumerations" gets to be tedious and
error prone. "Which *values* have been assigned? What is
a good value to pick for this new 'condition'? etc."

If you have a single project, I'm not sure why you'd worry about "which
values have been assigned," since if you use macro/enum identifiers, you
can change these arbitrarily and recompile. "The last error number I
made up was 7. I need a new one. I guess I'll make it 8. No wait,
this one should come before the last one as it's alphabetically lower;
no problem."

If you're producing a library, then yeah, I don't think you'd wish to
change the header in an incompatible fashion in future revisions, as the
calling programs would break.
One approach to ensuring unique "values" for each of these
"results" is to synthesize them from the function/file name
and line number where they are detected (instantiating a
macro at that particular point). This ensures that values
are unique while hiding the *actual* value from the developer
(*I* surely don't care if TOO_MANY_DECIMAL_POINTS resolves
to 0x27 or 0x7732!).

The problem then becomes one of making that information available
to other *consumers*! I.e., wrapping it in a header file that
others can reference.

That is something that functions happen to be particularly good at. A
header can declare a function:

int ErrHasTooManyDecimalPoints(errtype_t test);

and the caller need know nothing about the representation of 'errtype_t'
nor even the range of values... The predicate is what's important, and
it's identified symbolically, in this case.

Or if you want the error implementation to be opaque to the lower-level
functions, you could have:

int ErrHasTooManyDecimalPoints(errtype_t * errobj, int get_or_set);


The lower functions can include:

/* ...function body... */
ErrHasTooManyDecimalPoints(&result, 1 /* set */ );

and the caller could:

/* ...function body... */
result = operation(arg1, arg2);
if (ErrHasTooManyDecimalPoints(&result, 0 /* get */ )) {
/* We know that one. Handle */
/* ... */
}

But of course this seems a fair bit of extra work just to make the error
system opaque and symbolically accessible.
[Note this opens the door for a bootstrap problem but I am
willing to live with that -- "I'll deal with it later..."]

A "solution" also needs to address the problem of *explaining*
the "result" (to the ultimate user). The point at which the
result is "signaled" contains the most information about the
nature of the "problem" being reported though it is in a
very narrowly defined context: i.e., you can tell the user
"Multiple decimal points encountered" -- though you can't
indicate the role that the value being entered plays in the
application (is this a person's wages? the weight of a
newborn child? the average lifespan of a male tortoise?).

So, the developer should be able to tie the explanation/commentary
*to* the (symbolic) result code where the result code is defined
instead of having to do so "somewhere else" -- hoping to keep the
explanations in sync with the symbolic names, etc.

An approach to error reporting that I see quite often is very simply:

int operation(param1, param2);

If you can't pack all of the information you wish to have associated
with an exception or error condition or whatever into an 'int', then
perhaps you're better off allocating some status and passing it around:

/* my.h */
/* A simple error code */
typedef int myerr_t;

/* Extended status information */
typedef struct s_status s_status;

struct s_status {
const char * message;
/* ... */
};

/* my.c */
myerr_t operation_foo(
int * param1,
char * param2,
somes_status * status
) {
myerr_t result;
s_status status_internal;

/*
* If the caller hasn't provided storage for status,
* then they're not interested. Use our own for
* called functions
*/
if (!status) {
status = &status_internal;
memset(status, 0, sizeof *status);
}
/* ... */
result = operation_bar(13, param2, status);
if (!MYERR_SUCCESS(result)) {
/* Actually, we have specific info for this case */
SetStatusMessage(status, "Bar failed while blargling");
SetStatusSourcePoint(status, __FILE__, __LINE__);
/* Twiddle the case for out-of-range major error type */
if (MYERR_TEST(result, MyErrOutOfRange))
SetStatusTwiddle(status);
goto err_bar;
}

/* Success! */
result = SetStatusGeneralSuccess(status);

err_bar:

return result;
}
[...and more...]

However, it's possible that you've already considered and dismissed
material akin to this response post's content. :)
 
D

Don Y

Hi Willem,

Don Y wrote:
) A code fragment with such a system might look like:
)
) if (*ptr == '.') {
) if (decimalPointEncountered == TRUE) {
)
) MakeErrorCode(Err_TooManyDecimalPoints,
) "Multiple decimal points encountered.",
) "Only a single decimal point is allowed in numeric
) values."
) )
)
) result = Err_TooManyDecimalPoints;
) } else {
) decimalPointEncountered = TRUE;
) }
) }
) ...
) return (result);
)
) Obviously, a macro could #define Err_TooManyDecimalPoints to a unique
) value, here (e.g., __FILE__ ## __LINE__). A script could parse the
) output of the preprocessor extracting all lines of the form
) "#define Err_" to build a suitable header file. And, another
) script could extract the short and long textual explanations of
) the error itself.
)
) I currently do this with a mix of ad hoc measures. As such,
) it is very fragile (and an annoying drain on my time!). I'm
) hoping for suggestions that might lead me to a better/different
) approach (WITH THE SAME GOALS). Pointers to tools better suited
) to this. *Or*, examples of similar approaches!!

Have you considered using (pointers to constant) strings?

Yes. My first implementation allowed the called function to
return a pointer to a string (that the caller could embellish
to communicate with the user).

Then, the problem of differentiating "warning" explanations from
"error" explanations arose (I returned only the char* vs. NULL
for SUCCESS).

And, it didn't let me extract the text of the explanation from
the module in which it was "implemented" -- hence the move to
a separate process step to extract this information from the
source files.
Like this:

char *result = 0; /* or: char *result = ""; */
if (*ptr == '.') {
if (decimalPointEncountered) {
result = Err("E:Multiple decimal points encountered:"
"Only a single decimal point is allowed in numeric values");
} else {
decimalPointEncountered = TRUE;
}
}
...
return (result);

You can probably use the __FILE__ and __LINE__ macros to add something
extra to the string, to make it unique (if that is needed).

I am currently relying on FILE/LINE to generate a *unique* marker
for each error "source". For (*contrived*) example:

if (*ptr == '+') {
if (signEncountered == TRUE) {
MakeErrorCode(Err_TooManySignsPos,
"Multiple signs encountered.",
"At most, a single sign can preface a numeric
value."
)

result = Err_TooManySignsPos;
}
} else if (*ptr == '-') {
MakeErrorCode(Err_TooManySignsNeg,
"Multiple signs encountered.",
"At most, a single sign can preface a numeric
value."
)
result = Err_TooManySignsNeg;
}
}

[Sorry, it was really a lousy example!]

All I want from the *code* is an indication of where in the code
a test "failed". The caller can use the error code in a given
"context" to lookup the error message along with potential
remedial actions in a database that maps error codes to explanations
and other documentation in an on-line manual.

What I need is a good "hook" to let the developer indicate points
where tests can "fail" (i.e., yield results that seem to be
contrary to what we HOPE the user was intending).

[Note that I haven't even discussed I18N/L10N]

By maintaining the isolation of individual "conditional" failures
(e.g., instead of lumping both of the above into "Err_TooManySigns"),
more information can be provided to allow diagnosing any problems
that the user encounters.

For example, Err_TooManySignsNeg being signaled ALWAYS (or, whenever
this particular branch of code happens to be executed) could cause
you to examine the:
} else if (*ptr == '-') {
statement. Perhaps it was really written as:
} else if (*ptr = '-') {

If you allow multiple "condition failures" (sorry, this is a lousy
phrase) to map to the same error, then you have to know where
*every* case of that error is signaled and explore each path
individually. E.g., "Err_BadValue" could be signaled anywhere
*anything* wrong is detected in the value being provided.
 
S

Stefan Ram

Don Y said:
Managing these "enumerations" gets to be tedious and error
prone.
Which *values* have been assigned?

That's easy. Just keep a single table with two rows:
number and meaning. There, you can always find the
answer to this question. (You can use the include
file with the define-directives.)
What is a good value to pick for this new 'condition'?

The next consecutive number, like: 1, 2, 3, ...

Feel free to ask more questions!
The problem then becomes one of making that information available
to other *consumers*! I.e., wrapping it in a header file that
others can reference.

Where's the problem? You create a header file and put
the definitions in it:

#define ERROR_NO_MORE_MEMORY_AVAILABLE 1
#define ERROR_CANT_OPEN_AT_LEAST_ONE_FILE_FOR_READ_ACCESS 2
....
#define ERROR_AT_LEAST_ONE_CLIENT_SUPPLIED_NUMERIC_VALIDITY_CONDITION_IS_NOT_FULFILLED 2351
A "solution" also needs to address the problem of *explaining*
the "result" (to the ultimate user).

That's a separate problem of the application author.
very narrowly defined context: i.e., you can tell the user
"Multiple decimal points encountered" -- though you can't
indicate the role that the value being entered plays in the
application (is this a person's wages? the weight of a
newborn child? the average lifespan of a male tortoise?).

This is known to the author of the application.
I currently do this with a mix of ad hoc measures.

I use numeric values with meanings relative to a function
and transform them when passing them upward to the next
caller.

For example,

int filemove( LPTSTR const target, LPTSTR const source )
{ BOOL const success = MoveFile( source, target );
int const terminate = success ? 1 : filecall_error();
return terminate; }

»MoveFile« returns »success« to describe its success,
the semantics of which are being described in the
»MoveFile« documentation.

»filemove« uses an int value with semantics being
described in the »filemove« documentation. This int
value is being determined based on the result of two
other functions called in »filemove«: »MoveFile«
and »filecall_error«.

There are no global semantics for result codes in
this case.

For a slightly larger example, see list0.c and list1.c in

http://www.purl.org/stefan_ram/pub/listenverarbeitung_in_c
 
S

Stefan Ram

Supersedes: <[email protected]>
[removed "list0.c" with Supersedes]

Don Y said:
Managing these "enumerations" gets to be tedious and error
prone.
Which *values* have been assigned?

That's easy. Just keep a single table with two rows:
number and meaning. There, you can always find the
answer to this question. (You can use the include
file with the define-directives.)
What is a good value to pick for this new 'condition'?

The next consecutive number, like: 1, 2, 3, ...

Feel free to ask more questions!
The problem then becomes one of making that information available
to other *consumers*! I.e., wrapping it in a header file that
others can reference.

Where's the problem? You create a header file and put
the definitions in it:

#define ERROR_NO_MORE_MEMORY_AVAILABLE 1
#define ERROR_CANT_OPEN_AT_LEAST_ONE_FILE_FOR_READ_ACCESS 2
....
#define ERROR_AT_LEAST_ONE_CLIENT_SUPPLIED_NUMERIC_VALIDITY_CONDITION_IS_NOT_FULFILLED 2351
A "solution" also needs to address the problem of *explaining*
the "result" (to the ultimate user).

That's a separate problem of the application author.
very narrowly defined context: i.e., you can tell the user
"Multiple decimal points encountered" -- though you can't
indicate the role that the value being entered plays in the
application (is this a person's wages? the weight of a
newborn child? the average lifespan of a male tortoise?).

This is known to the author of the application.
I currently do this with a mix of ad hoc measures.

I use numeric values with meanings relative to a function
and transform them when passing them upward to the next
caller.

For example,

int filemove( LPTSTR const target, LPTSTR const source )
{ BOOL const success = MoveFile( source, target );
int const terminate = success ? 1 : filecall_error();
return terminate; }

»MoveFile« returns »success« to describe its success,
the semantics of which are being described in the
»MoveFile« documentation.

»filemove« uses an int value with semantics being
described in the »filemove« documentation. This int
value is being determined based on the result of two
other functions called in »filemove«: »MoveFile«
and »filecall_error«.

There are no global semantics for result codes in
this case.

For a slightly larger example, see list1.c in

http://www.purl.org/stefan_ram/pub/listenverarbeitung_in_c
 
D

Don Y

Hi William,

With POSIX, at least, all errno values are positive. The few defined by C
are also positive. So I utilize the entire negative range for my error
identifiers.

I don't have to cooperate with errno as any function interested in errno
will examine it and fold it's *interpretation* of that error into the
error that *it* reports.
I partition that space using a 3- or 4-character tag. So, for example, XML
library errors might start like

enum {
XML_EBASE = -(('X'<< 24) | ('M'<< 16) | ('L'<< 8) | 32),
...
XML_ELAST
};

Oh, OK. But, you now have to keep track of which ranges ("tags") you've
assigned. I am trying to eliminate all the housekeeping while still
ensuring error codes are unique *and* easy to locate where they
originate.

E.g., where is XML_FOO thrown? Are you sure you have accounted for
all sources of XML_FOO? (e.g., you have to grep the set of source
files)
Minimal coordination is needed by different components, and you maintain
maximum composability going forward. (Not just in C, either.)

I also always use an int instead of an enum to store and return errors,
otherwise things get ugly when, for instance, you have an interface that
takes a pointer to the error object.
Understood.

By itself this doesn't solve all of the issues with error reporting, but the
above scheme is the best common denominator I've come up with. Every
component or library I write these days uses this scheme. It's simple enough
to then fold it into whatever broader error handling scheme is being used.
For me that usually means a simple strerror-type interface, with forwarding
from containers' to subcomponents' _strerror.

But it makes the housekeeping something that you have to explicitly
manage (and, potentially, "get wrong").

I'm looking for an approach where the error sources and explanations
are defined where they make most sense -- where they are *raised*.
Then, extracted so that other agents can use them.

Sort of like Literate Programming but only confined to error events.
 
D

Don Y

Hi Shao,

On 3/1/2012 14:48, Don Y wrote:

You might take a look at iPXE's error system. If I recall correctly,
OK.

there's a range of bits for the error type, a range of bits for the
module (translation unit), and maybe another range or two I'm forgetting
about (maybe major and minor codes).

This forces error "codes" to fit in a defined structure/form.
And, still requires management of that "namespace" -- something
has to ensure that this particular bit pattern is used ONLY
for this module; this other pattern for this OTHER module, etc.

Everything that you do "manually" is suspect. Are you *sure*
there is no conflict or overlap between codes, etc.?

I tried to work around that with the __FILE__, __LINE__ approach
(I actually tie in __func__ as well). Ideally, I would use
the value of the program counter associated with the "failed
conditional" but that gets to be problematic for PIC.
(consider how you would even know how to bind error descriptions
to those codes without SOME a priori knowledge)
The special gift that iPXE's
primary author grants is the ability to look up the error codes on a
web-page. :)

I plan on using the error codes and context to look up error
messages from a genuine DBMS embedded in the devices. Hence
the need to be able to pull the error messages out of the source
files and use them to populate the tables, there.
If you are going to go with a strategy such as "warnings are positive"
and "errors are negative" and "success is 0," consider wrapping those
predicates with macros:

if (WARNING(result = operation(arg1, arg2))) { /* ... */ } /* or */
if (SUCCESS(result = operation(arg1, arg2))) { /* ... */ } /* or */
if (ERROR(result = operation(arg1, arg2))) { /* ... */ } /* etc. */

then the representation of 'result' needn't be considered and becomes an
implementation detail.

Yes. The point was to be able to easily categorize a "code"
as one of these. It would be problematic to separate warnings
from errors if there was no EFFICIENT "scheme" involved (I
don't want to have to lookup each "code" just to determine if
the function succeeded, failed or was a "qualified success".)
If you have a single project, I'm not sure why you'd worry about "which
values have been assigned," since if you use macro/enum identifiers, you
can change these arbitrarily and recompile. "The last error number I
made up was 7. I need a new one. I guess I'll make it 8. No wait, this
one should come before the last one as it's alphabetically lower; no
problem."

Note the "get value" example already uses that many different error
categories.
If you're producing a library, then yeah, I don't think you'd wish to
change the header in an incompatible fashion in future revisions, as the
calling programs would break.

Exactly. Even a tiny library is ~1KLoC. A small "subsystem" is about
an order of magnitude larger than that.

The scheme I outlined scales well -- albeit at the expense of a
"make world" if you want to be sure that all dependencies are
handled correctly (this can be done in just two passes over the
sources *if* you are pedantic about where you allow error codes
to be referenced in those sources -- e.g., NOT in conditional
compilation)
That is something that functions happen to be particularly good at. A
header can declare a function:

int ErrHasTooManyDecimalPoints(errtype_t test);

and the caller need know nothing about the representation of 'errtype_t'
nor even the range of values... The predicate is what's important, and
it's identified symbolically, in this case.

But you don't want the consumer to have to try applying each
potential "ErrWHATEVER" to an error code that it encounters.

Rather, give it an efficient means of testing if *ANY* error
has occurred. Then, a separate means of resolving (and reporting)
what the nature of that error actually is.
Or if you want the error implementation to be opaque to the lower-level
functions, you could have:

int ErrHasTooManyDecimalPoints(errtype_t * errobj, int get_or_set);


The lower functions can include:

/* ...function body... */
ErrHasTooManyDecimalPoints(&result, 1 /* set */ );

and the caller could:

/* ...function body... */
result = operation(arg1, arg2);
if (ErrHasTooManyDecimalPoints(&result, 0 /* get */ )) {
/* We know that one. Handle */
/* ... */
}

But of course this seems a fair bit of extra work just to make the error
system opaque and symbolically accessible.

I use the error code and context in a direct mapping to
descriptions/remedies. The only symbolic references to
error codes necessary are those *in* the actual source
code -- assuming that the code wants to react to specific
errors (e.g., ErrInsufficientMemory might cause the
code to free up some resources and try, again)
[Note this opens the door for a bootstrap problem but I am
willing to live with that -- "I'll deal with it later..."]

A "solution" also needs to address the problem of *explaining*
the "result" (to the ultimate user). The point at which the
result is "signaled" contains the most information about the
nature of the "problem" being reported though it is in a
very narrowly defined context: i.e., you can tell the user
"Multiple decimal points encountered" -- though you can't
indicate the role that the value being entered plays in the
application (is this a person's wages? the weight of a
newborn child? the average lifespan of a male tortoise?).

So, the developer should be able to tie the explanation/commentary
*to* the (symbolic) result code where the result code is defined
instead of having to do so "somewhere else" -- hoping to keep the
explanations in sync with the symbolic names, etc.
However, it's possible that you've already considered and dismissed
material akin to this response post's content. :)

<grin> I've tried to think hard about the sorts of housekeeping
to which any *manual* system would be vulnerable. Since I am
operating in a rather "flush" environment, I am planning on
using those resources to make the code and user interfaces
more robust and full-featured.

I'm, currently, just trying to come up with a more robust
(less kludgey) toolset for integrating these mechanisms into
the development process -- so others can take advantage of
them instead of ignoring this aspect of the process
("Just throw a 'BAD_VALUE' error and generate a lengthy
error message that lists ALL of the possible reasons that
this message could be signaled -- and hope there are no
bugs in the code that tests for each of them! Let the
user sort out the problem...")
 
S

Shao Miller

<grin> I've tried to think hard about the sorts of housekeeping
to which any *manual* system would be vulnerable. Since I am
operating in a rather "flush" environment, I am planning on
using those resources to make the code and user interfaces
more robust and full-featured.

Just out of curiosity, did you skip over the 's_status' part of my
previous response, or dismiss it as Not The Right Strategy For You?
 
D

Don Y

Hi Stefan,

Supersedes:<[email protected]>
[removed "list0.c" with Supersedes]

Don Y said:
Managing these "enumerations" gets to be tedious and error
prone. Which *values* have been assigned?

That's easy. Just keep a single table with two rows:
number and meaning. There, you can always find the
answer to this question. (You can use the include
file with the define-directives.)

You need to maintain this "by hand". There is nothing that
prevents you from:
#define YES (2)
#define NO (2)

Nor:
#define YES (2)
...
#define ABSOLUTELY (27)
(assuming yes and absolutely are synonyms)

There is nothing that ensures:
#define TOO_MANY_DIGITS (375)
is actually *used* anywhere! Does its absence mean that some
test has been forgotten in the code? Or, that the test has
been made unnecessary (and, thus, the error code associated
with it) due to a change in the implementation?

You have to maintain one such table for each library/subsystem
(having one for the entire application is just silly -- if it
has to be maintained by hand!)

And, if you have multiple such tables, you have to ensure the
"number spaces" for each do not overlap.

I.e., it forces all of the housekeeping onto the developer.
The next consecutive number, like: 1, 2, 3, ...

This implies you have *all* of the numbers in one place.
And, that none of the subsystems/libraries are ever enhanced.
("Gee, I need to verify the value entered doesn't overflow
a double. Let me create a new error code DOUBLE_OVERFLOW
and I'll just assign it the value after MOTOR_OVERHEATED...")

How do you find which errors are thrown by each module/library/etc.?
How do you ensure you don't already have "OVERFLOW_DOUBLE" hidden
away in that SINGLE long list of error codes?
Feel free to ask more questions!


Where's the problem? You create a header file and put
the definitions in it:

#define ERROR_NO_MORE_MEMORY_AVAILABLE 1
#define ERROR_CANT_OPEN_AT_LEAST_ONE_FILE_FOR_READ_ACCESS 2
...
#define ERROR_AT_LEAST_ONE_CLIENT_SUPPLIED_NUMERIC_VALIDITY_CONDITION_IS_NOT_FULFILLED 2351

See above.
That's a separate problem of the application author.

No! That's what leads to "bad value" type error messages!
The application doesn't want to be concerned with the details of
"what makes a particular input string a valid numeric indicator".
It wants to *bury* that information and those criteria inside
a tool that it then *uses* to perform that operation for it.

A given layer in an application should only be concerned with
explaining the issues that *it* enforces. Should the UI be
responsible for identifying the reason why keystrokes are
not available from the "keyboard device"? That would imply
that the UI would need to know *how* the keyboard device
works and the sorts of things that could go wrong with it.

Likewise, should a function that gets the setpoint for a
temperature controller from the user know what the *actual*
syntax of a "valid temperature specification" is? And,
*how* the user erred in specifying that value?
This is known to the author of the application.

The application *layer* that is responsible for this is
where the error can best be explained. Anything below that
doesn't have sufficient knowledge of *context* to explain
it meaningfully. Anything *above* that doesn't want to be
concerned with this level of detail (see above).
I use numeric values with meanings relative to a function
and transform them when passing them upward to the next
caller.

For example,

int filemove( LPTSTR const target, LPTSTR const source )
{ BOOL const success = MoveFile( source, target );
int const terminate = success ? 1 : filecall_error();
return terminate; }

»MoveFile« returns »success« to describe its success,
the semantics of which are being described in the
»MoveFile« documentation.

»filemove« uses an int value with semantics being
described in the »filemove« documentation. This int
value is being determined based on the result of two
other functions called in »filemove«: »MoveFile«
and »filecall_error«.

And how does the person *using* the application that encapsulates
this code know what's gone wrong with his *use* of the application?
There are no global semantics for result codes in
this case.

filemove exposes the error from MoveFile to upper levels.
I.e., the error codes returned by MoveFile() *include*
(and, thus, have the potential to conflict with) those
of filemove()).

How do you explain the error to the user -- unless you require
MoveFile to be aware of all of the things that can happen
*inside* filemove? When filemove changes (possibly recognizing
NEW error conditions or *different* error conditions), then
MoveFile needs to be able to explain those new "problems"
for the user.

You haven't isolated the responsibilities.

(Does main() have code in it to explain errors that are essentially
percolated upwards from filemove() -- regardless of how *deep* in
the application the error was detected?)
 
D

Don Y

Hi Shao,

Just out of curiosity, did you skip over the 's_status' part of my
previous response, or dismiss it as Not The Right Strategy For You?

I'm trying to avoid complicating the discussion with the introduction
of mechanisms of passing "qualifying information" (what you call
"extended status information") up to the caller. This is a whole
'nother can of worms as each potential error/result could potentially
have its own idea as to "what's important/pertinent".

My current approach just focuses on indicating *where* the "test
failed" and in which context that was encountered.

E.g., imagine someone says the application wasn't accepting their
"input" (cf the "get value" example). People are notoriously
inaccurate at telling you the *exact* message that they are
receiving:
"I typed in my age but it said the number I typed was bad"
"No, I'm sure it didn't say that (because I *know* what all of
the error messages are and none of them are 'the number was bad')"
"Well, that's what it *meant*! I can't remember the actual WORDING..."
"Could you do whatever it was you were doing and provide me with
the error identifier located at the end of the message?"
"OK, it says _______"
"Ah, you've just typed in a '-' sign but you did so after you
started typing in the numeric value/digits. If you want the value
to be negative, you need to type the '-' sign first. OTOH, if you
are trying to type '2012-1965' and hoping the machine will interpret
that as 47, I'm sorry but the machine doesn't have that capability..."

I'm not worried (in this discussion) about providing the extra
details (e.g., "2012-1965") to better explain/understand the
nature of the error to that finer degree.
 
K

Keith Thompson

Don Y said:
You need to maintain this "by hand". There is nothing that
prevents you from:
#define YES (2)
#define NO (2)

Nor:
#define YES (2)
...
#define ABSOLUTELY (27)
(assuming yes and absolutely are synonyms)
[...]

A minor point: parenthesizing macro definitions is a good habit, but
it's not necessary when the macro expands to a single token. This:

#define ABSOLUTELY 27

is just as safe as

#define ABSOLUTELY (27)
 
K

Kaz Kylheku

Don Y said:
You need to maintain this "by hand". There is nothing that
prevents you from:
#define YES (2)
#define NO (2)

Nor:
#define YES (2)
...
#define ABSOLUTELY (27)
(assuming yes and absolutely are synonyms)
[...]

A minor point: parenthesizing macro definitions is a good habit, but
it's not necessary when the macro expands to a single token. This:

#define ABSOLUTELY 27

is just as safe as

#define ABSOLUTELY (27)

Safer. The former ABSOLUTELY above can be subject to token pasting.

#define XPASTE(X, Y) X ## Y
#define PASTE(X, Y) XPASTE(X, Y)

PASTE(FOO_, ABSOLUTELY) /* result: FOO_27 */

If ABSOLUTELY is (27) then the above is undefined behavior, since
pasting FOO_ with ( is an invalid token.

I.e. #define ABSOLUTELY 27 is, effectively, not only a constant expression
evaluating to 27, but it is lexically a numeric token, which can be desireable.
 
K

Kenny McCormack

Don Y said:
You need to maintain this "by hand". There is nothing that
prevents you from:
#define YES (2)
#define NO (2)

Nor:
#define YES (2)
...
#define ABSOLUTELY (27)
(assuming yes and absolutely are synonyms)
[...]

A minor point: parenthesizing macro definitions is a good habit, but
it's not necessary when the macro expands to a single token. This:

#define ABSOLUTELY 27

is just as safe as

#define ABSOLUTELY (27)

Much as I hate to disagree with Leader Kiki, I have to take exception to
this. If there is a corporate requirement that all macro definitions be
parenthesized, as is probably the case in many workplaces, then not
parenthesizing exposes the coder to the risk of ending up on the
unemployment line.

Hardly safe by my lights...
 
D

Don Y

Hi Keith,

A minor point: parenthesizing macro definitions is a good habit, but
it's not necessary when the macro expands to a single token. This:

Keeping a gun's safety "on" is "a good habit". Do you often
leave it *off* just because you don't *think* you need it
"on" at the present time? Or, do you wait until you
really *need* it "off" (i.e., just prior to discharge)?

Good habits are kept -- as a matter of HABIT! :>
 
J

James Kuyper

Hi Keith,



Keeping a gun's safety "on" is "a good habit". Do you often
leave it *off* just because you don't *think* you need it
"on" at the present time? Or, do you wait until you
really *need* it "off" (i.e., just prior to discharge)?

Good habits are kept -- as a matter of HABIT! :>

I check my keys every time I go out the door of my house, as a matter of
habit, even if I don't intend to lock the door. That's a good habit.
People with OCD might check their keys each time they go out the bedroom
door, even if it doesn't have a lock. That's not a good habit, that's a
waste of time. In extreme cases, people with OCD waste so much time
checking things that don't need to be checked, that they never get
anything useful done with their life.

Parentheses convert other kinds of expressions into primary expressions.
Macro expansions that are already primary expressions (identifiers,
constants, string literals, and generic selections* - 6.5.1) - don't
need parentheses. Parenthesizing them is closer to the OCD end of the
spectrum, than to the good habit end.

*Generic selections are a new feature of C2011.
 
D

Don Y

Hi James,

I check my keys every time I go out the door of my house, as a matter of
habit, even if I don't intend to lock the door. That's a good habit.
People with OCD might check their keys each time they go out the bedroom
door, even if it doesn't have a lock. That's not a good habit, that's a
waste of time. In extreme cases, people with OCD waste so much time
checking things that don't need to be checked, that they never get
anything useful done with their life.

OCD interferes with your goal -- "living".
Parentheses convert other kinds of expressions into primary expressions.
Macro expansions that are already primary expressions (identifiers,
constants, string literals, and generic selections* - 6.5.1) - don't
need parentheses. Parenthesizing them is closer to the OCD end of the
spectrum, than to the good habit end.

I guess I don't see two extra keystrokes on a #define as interfering
with my goal (of writing reliable code that others can maintain).
I've already invested 10 keystrokes ("#define " the whitespace between
the symbol and its definition and the trailing '\n') and will invest
at least 2 more (assuming the symbol and definition are each just 1).
Increasing that "overhead" from 12 to 14 is pocket change.

OTOH, I can't count the number of times I've had to step in and
"fix" someone's "minor tweek" of a running program when they
changed:
#define PHONE_NUMBER_DIGITS 7
to:
#define PHONE_NUMBER_DIGITS 3+7
And then have to *explain* the reason behind the *need* for the parens:
"So, if I just said '10', instead, I wouldn't have had the problem
AND wouldn't have had to type the parens? I guess I should just
type '10' in the future..."
"No, the problem with *that*, is..."

I can't speak for you but *I* sure don't like wasting my time on
that sort of after-the-fact annoyance.

Of course, you're free to live with whatever coding style you
(and your clients/employers) find suitable for the projects
and personnel available to you.

You could also build giant tables of #define's to represent all
of the possible error codes that routines in your project can
throw! :>

[I'd really prefer to address the subject of my OP. Missing the
forest for the trees is a sure-fire "waste of time" :>]
 
K

Kaz Kylheku

Hi Keith,



Keeping a gun's safety "on" is "a good habit". Do you often
leave it *off* just because you don't *think* you need it
"on" at the present time? Or, do you wait until you
really *need* it "off" (i.e., just prior to discharge)?

Do you often modify your gun to a completely different model,
and without touching the safety switch?
 
I

Ian Collins

Don Y said:
You need to maintain this "by hand". There is nothing that
prevents you from:
#define YES (2)
#define NO (2)

Nor:
#define YES (2)
...
#define ABSOLUTELY (27)
(assuming yes and absolutely are synonyms)
[...]

A minor point: parenthesizing macro definitions is a good habit, but
it's not necessary when the macro expands to a single token. This:

#define ABSOLUTELY 27

is just as safe as

#define ABSOLUTELY (27)

Or simply don't use macros for constants!
 
B

Ben Pfaff

Keith Thompson said:
A minor point: parenthesizing macro definitions is a good habit, but
it's not necessary when the macro expands to a single token. This:

#define ABSOLUTELY 27

is just as safe as

#define ABSOLUTELY (27)

Furthermore,

#define PRIx64 "llx"

is far more useful than

#define PRIx64 ("llx")
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top