Avoid repetition in C error handling

纵然是瞬间 提交于 2019-12-12 11:16:29

问题


I often write code which ends up being long sequences something like

int error;

error = do_something();
if (error) {
    return error;
}

error = do_something_else(with, some, args);
if (error) {
    return error;
}


error = do_something_yet_again();
if (error) {
    return error;
}

return 0;

I'm searching for a cleaner way to write this that to some extent avoids the repeated identical checks. So far, I've written an ERROR_OR macro, which works something like

#define ERROR_OR(origerr, newerr)           \
    ({                                      \
        int __error_or_origerr = (origerr); \
        (__error_or_origerr != 0)           \
                ? __error_or_origerr        \
                : (newerr);                 \
    })

which allows the original code to become something like

int error = 0;

error = ERROR_OR(error, do_something());
error = ERROR_OR(error, do_something_else(with, some, args));
error = ERROR_OR(error, do_something_yet_again());

return error;

This is (in my opinion) a little cleaner. It's also less understandable, since the function of the ERROR_PRESERVE macro isn't apparent unless you read its documentation and/or implementation. It also doesn't solve the problem of repetition, just makes it easier to write all the (now implicit) checks on a single line.

What I'd really like to re-write this all as would be the following:

return ERROR_SHORT_CIRCUIT(
    do_something(),
    do_something_else(with, some, args),
    do_something_yet_again()
);

The hypothetical ERROR_SHORT_CIRCUIT macro would

  • Take a variable number of expressions in its argument list
  • Evaluate each expression in order
  • If every expression evaluates to zero, evaluate to zero itself
  • If any expression evaluates to nonzero, immediately terminate and evaluate to the value of that last expression

This last condition is where my short-circuit diverges from a straightforward use of the || operator -- since this will evaluate to 1 instead of the error value.

My initial attempt at writing this is the following:

#define ERROR_SHORT_CIRCUIT(firsterr, ...)          \
    ({                                              \
        int __error_ss_firsterr = (firsterr);       \
        (__error_ss_firsterr != ERROR_NONE)         \
                ? __error_ss_firsterr               \
                : ERROR_SHORT_CIRCUIT(__VA_ARGS__); \
    })

This has two obvious problems:

  • It doesn't handle its base-case (when __VA_ARGS__ is a single value)
  • C doesn't support recursive macros

I've looked into some recursive macro hacks, but I dislike using that degree of pre-processor magic -- too much room for something to be subtly wrong. I've also considered using real (possibly variadic) functions, but this would require either

  • giving up the short-circuit behavior
  • passing the functions in as pointers, and therefore normalizing their signatures

and both of these seem worse than the original, explicit code.

I'm interested to hear advice on the best way to handle this. I'm open to many different approaches, but my ultimate goal is to avoid repetition without hurting readability.

(I suppose it's obvious I'm suffering some envy of the behavior of the || operator in languages like Ruby).


回答1:


I'd use code like:

if ((error = do_something()) != 0 ||
    (error = do_something_else(with, some, args)) != 0 ||
    (error = do_something_yet_again()) != 0)
    return error;
return 0;

It's fully defined because there are sequence points before each || operator. It doesn't really need a macro. It only runs into problems when you allocate resources or do other operations between function calls, but that is different from what your example code shows. At least 90% of the battle was creating the sequence of do_something_or_other() functions that make it easy to handle the error sequencing.




回答2:


Another option:

int error = 0;
do {
    // Note: extra parens suppress assignment-as-conditional warning

    if ((error = do_something())) break;
    if ((error = do_something_else())) break;
    if ((error = do_yet_another_thing())) break;
    error = do_a_final_thing();
} while (0);
return error;


来源:https://stackoverflow.com/questions/46102618/avoid-repetition-in-c-error-handling

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!