避免在C错误处理中重复 [英] Avoid repetition in C error handling

查看:63
本文介绍了避免在C错误处理中重复的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我经常写一些以长序列结尾的代码,例如

  int错误; 

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

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


error = do_something_yet_again();
if(error){
返回错误;
}

返回0;

我正在寻找一种更简洁的书写方式,在某种程度上避免了重复相同的检查。到目前为止,我已经编写了 ERROR_OR 宏,该宏的工作原理类似于

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

允许原始代码变成

  int错误= 0; 

错误= ERROR_OR(错误,do_something());
错误= ERROR_OR(错误,do_something_else(带有一些,args));
错误= ERROR_OR(错误,do_something_yet_again());

返回错误;

(在我看来)这更清洁一些。这也不太容易理解,因为 ERROR_PRESERVE 宏的功能并不明显,除非您阅读了其文档和/或实现。它也不能解决重复问题,只是使在一行上写所有(现在是隐式的)检查变得更加容易。



我真正想要的是重新编写所有内容,如下所示:

 返回ERROR_SHORT_CIRCUIT(
do_something(),
do_something_else(with,some,args),
do_something_yet_again()
);

假设的 ERROR_SHORT_CIRCUIT




  • 在其参数列表中使用可变数量的表达式

  • 按顺序评估每个表达式

  • 如果每个表达式的计算结果均为零,则其自身值为零

  • 如果任何表达式的计算结果为非零,请立即终止并求值至最后一个表达式的值



最后一个条件是我的短路与直接使用 | 运算符-因为它将计算为1而不是错误值。 >

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

这有两个明显的问题:




  • 它不处理基本情况(当 __ VA_ARGS __ 是单个值时)

  • C不支持递归宏



我研究了一些递归宏hacks ,但我不喜欢使用这种程度的pre -处理器的魔力-太多的空间可能会导致某些错误。我还考虑过使用真实的(可能是可变参数)函数,但这将需要




  • 放弃短路行为

  • 将这些函数作为指针传递,因此规范了它们的签名



这两者似乎都比



我很想听听有关处理此问题的最佳方法的建议。我接受许多不同的方法,但是我的最终目标是避免重复而不损害可读性。



(我想我显然很羡慕这种行为 ||| 运算符,例如Ruby)。

解决方案

I将使用以下代码:

  if((error = do_something())!= 0 || 
(error = do_something_else(with,some,args))!= 0 ||
(错误= do_something_yet_again())!= 0)
返回错误;
返回0;

它是完全定义的,因为每个 || 运算符。它实际上并不需要宏。它仅在分配资源或在函数调用之间执行其他操作时才会出现问题,但这与示例代码显示的有所不同。至少90%的工作是创建 do_something_or_other()函数的序列,这些序列使处理错误序列更加容易。


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).

解决方案

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.

这篇关于避免在C错误处理中重复的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆