5

We are designing an error-handling framework for a cryptographic library written in C.

The approach we are taking is that relatively few errors are propagated back to the user since on most occasions he cannot do anything about them (notable exception: parameter validation errors. Of course we tell the user if a call succeeded. We'd also create log entries for fault investigation.) In short, just because we have an error condition somewhere inside the library it does not mean that we need a public (user-facing) error code for it.

Also, from past experience we think that a significant portion of error conditions that require error codes pertain to very narrow areas of code, it is often the case that a particular error is only returned from (or set by) one function.

Therefore we would like to define error codes at the lowest possible level. For example, if only one function can return/set a particular error code we would define that code in the .h file where the function prototype is declared.

However, in such case we might get error code conflicts - the same error code value for different errors. Can someone please propose an elegant approach to avoid such conflicts? Or at the very least an automated mechanism to check for such conflicts (which can then be fixed by hand)?

4

we would define that code in the .h file where the function prototype is declared.

I would recommend against this as you don't have anything to reduce the space for potential numbering conflicts.

Instead, use a common header file providing error codes for both your client and server applications. As an alternative, the client file can simply be a subset of the server error code file.

Within the common header file, each major area of your application will get a sequence of error numbers. And from within each major area, you can assign subsets of error code ranges to the modules within that layer.

For example, say you have a session manager, a business logic, and a database access layers in your server application. Your header file might be broken down like this:

/*
 * our error.h file 
 */
/* Common Errors 0 - 99 */
#define SUCCESS 0
#define GENERIC_ERROR 1
/* Session Manager errors 100 - 199 */
    /* Login issues 100 - 110 */
#define BAD_PASSWORD 101
    /* session issues 120 - 125 */
    /* other session issues 150 - 162 */
#define SOMETHING_ELSE 150
/* Business logic errors 200 - 299 */

/* Database errors 300 - 399 */
#define INVALID_CONNECTION 300

The advantage of this approach is it cuts down the overall space where conflicts in numbering can occur. Potential conflict is limited to the areas where two or more developers are working simultaneously.

By having a common error header file, your developers can check out the file as needed to create a new error and make sure they are claiming an unused number at that point in time.


Potential pitfalls with this approach are when developers don't frequently commit their local copy of the error header. But that's a process issue that will usually work its way out once those developers have had to deal with it a few times. And since they are referring to the error number through the #define it's not that big of a change.

Another pitfall is when you misjudge the range required for a layer or sub-module. The easiest way to avoid this is to pre-allocate really large ranges for each layer and sub-module. And from there, you should be able to trim from one sub-module in order to give greater range to another. And there's nothing that says the ranges have to be contiguous.

  • Thank you. Up-vote, but no accept as you do not answer my question (it's quite possible that what I'm asking for is not feasible). – malenkiy_scot Sep 10 '14 at 7:21
2

I would recommend to use a common error code file, exactly like @GlenH7, but I would also recommend to write self-contained functions or modules which don't need the global error file at all. For example, if a `connect´ function can just fail (or not), return a boolean indicator. The caller of that function may decide to map the boolean return value to a global constant, but not the connect function itself:

  success = connect(parameters);
  if(!success)
      return INVALID_CONNECTION;

  success = check_password(paramaters);
  if(!success)
      return BAD_PASSWORD;

  //...
  return SUCCESS;

For bigger software consisting of many different modules or libs, maybe developed by different teams, it is often better to avoid any global error codes at all, and instead use only local per-module or per-library error codes. To distingiush those, for example, in a central log file, you always have to add the module name or some kind of module ID to the error code. You can enforce the usage of these two values by designing your logger API right that way - with a "log" function expecting a module ID and an error code as parameters, not just one error code. If you really have to pass the error codes around your program in a globally distinguishable manner, you may create a struct "ErrorCode", with two attributes "ModuleID" and "LocalErrorCode". That way you can avoid the need for having global error codes at all.

  • We already decided to do per-module error codes (I omitted it from the question for the sake of brevity) and are thinking of implementing it practically exactly as you are describing. Thanks you for your answer. +1; but no accept as it does not answer my question. – malenkiy_scot Sep 10 '14 at 7:19
  • @malenkiy_scot: AFAIK you asked for "an elegant approach to avoid such conflicts" - and that is what my answer is about. If you wanted to know something different, rephrase your question. – Doc Brown Sep 10 '14 at 7:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.