开发者

Elegant error checking

开发者 https://www.devze.com 2023-03-25 12:49 出处:网络
Our code (in a simple library implementation) is beginning to look like this: err = callToUnderlyingLibrary1();

Our code (in a simple library implementation) is beginning to look like this:

err = callToUnderlyingLibrary1();
if (err!=0) {
printf ("blah %d\n", err);
...
}

err = callToUnderlyingLibrary2();
if (err!=0) {
printf ("blah %d\n", err);
...
}

err = callToUnderlyingLibrary3();
if (err!=0) {
printf ("blah %d\n", err);
...
}

This is cumbersome and ugly. Is there a better way to do this ? Perhaps using the C preprocessor ? I was thinking something like:

CHECK callToUnderlyingLibrary1();
CHECK callToUnderlyingLibrary2();
CHECK callToUnderlyingLibrary3();

where the CHECK macro invokes the function and does the rudimentary error checking.

Are there pref开发者_如何学Pythonerred idiomatic ways of handling this ?


Another macro-based approach which you can use to mitigate the shortcomings in C fairly easily:

#define CHECK(x) do { \
  int retval = (x); \
  if (retval != 0) { \
    fprintf(stderr, "Runtime error: %s returned %d at %s:%d", #x, retval, __FILE__, __LINE__); \
    return /* or throw or whatever */; \
  } \
} while (0)

Then to invoke it you have:

CHECK(doSomething1());
CHECK(doSomething2());
// etc.

For bonus points you could easily extend the CHECK macro to take a second argument y that is what to do on failure:

#define CHECK(x, y) do { \
  int retval = (x); \
  if (retval != 0) { \
    fprintf(stderr, "Runtime error: %s returned %d at %s:%d", #x, retval, __FILE__, __LINE__); \
    y; \
  } \
} while (0)

// We're returning a different error code
CHECK(someFunction1(foo), return someErrorCode);
// We're actually calling it from C++ and can throw an exception
CHECK(someFunction2(foo), throw SomeException("someFunction2 failed")):


Usually, in C, one uses goto for error handling:

int foo()
{
    if (Function1() == ERROR_CODE) goto error;
    ...
    struct bar *x = acquire_structure;
    ...
    if (Function2() == ERROR_CODE) goto error0;
    ...

    release_structure(x);
    return 0;

error0:
    release_structure(x);

error:
    return -1;
}

This can be improved with macros and more clever instruction flow (to avoid repeating cleanup code), but I hope you see the point.


I think you should look at exceptions and exception handling. http://www.cplusplus.com/doc/tutorial/exceptions/

try{    
    callToUnderlyingLibrary1();
    callToUnderlyingLibrary2();
    callToUnderlyingLibrary3();
}catch(exception& e)
    //Handle exception
}

your library functions can throw exceptions if there is an error


Here is a proposition, you may or may not like it:

  • make your functions return 0 on failure, something else on success
  • if something fails in your functions, have them set a global (or static) variable to the error code (like errno)
  • create a die() function that prints the error depending of the error code (or whatever you want it to do)
  • call your functions with do_something(foo, bar) || die("Argh...");


I prefer a variant of Alexandra C.'s goto-approach:

int foo()
{
    int rv = 0;
    struct bar *x = NULL;
    struct bar *y = NULL;
    rv = Function1();
    if (rv != OK){
      goto error;
    }
    //...
    x = acquire_structure();
    if (x==NULL){
      rv = ERROR_MEMORY;
      goto error;
    }
    //...
    rv = Function2();
    if (rv != OK){
      goto error;
    }
    //...
    y = acquire_structure();
    if (y==NULL){
      rv = ERROR_MEMORY;
      goto error;
    }
    //...

    rv = release_structure(x);
    x = NULL;
    if (rv != OK){
      goto error;
    }
    rv = release_structure(y);
    y = NULL;
    if (rv != OK){
      goto error;
    }
    return OK;

error:
    if (x!=NULL){
      release_structure(x);
    }
    return rv;
}

When you use multiple goto-destinations, it is easy to mix them up. Or perhaps you move the initialization of a variable, but forget to update the gotos. And it can be very difficult to test all ways a C-method can fail.

I prefer having a single goto-destination that performs all the cleanup. I find that makes it easier to avoid mistakes.


You could do what you said, which is some rudimentary macro:

#define CHECK(x) (err = x()); \
                 if (err) { \
                      printf("blah %d on line %d of file %s\n", err, __LINE__, __FILE__); \
                 } \
                 else (void)0

And you could use it like

int err = 0;
CHECK(callToUnderlyingLibrary1); // don't forget the semicolon at the end
CHECK(callToUnderlyingLibrary2);
CHECK(callToUnderlyingLibrary3);


No 'goto', use only 1 'return' in functions. That's the elegant code.

IMHO, OP's question point and all answers are talking about FANCY techniques. Fancy code is just sort of eye candy.

0

精彩评论

暂无评论...
验证码 换一张
取 消