【发布时间】:2018-02-16 13:29:20
【问题描述】:
我经常编写代码,这些代码最终是长序列之类的
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;
我正在寻找一种更简洁的方法来编写它,从而在某种程度上避免重复的相同检查。到目前为止,我已经编写了一个 ERROR_OR 宏,其工作原理类似于
#define ERROR_OR(origerr, newerr) \
({ \
int __error_or_origerr = (origerr); \
(__error_or_origerr != 0) \
? __error_or_origerr \
: (newerr); \
})
它允许原始代码变成类似的东西
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;
这(在我看来)更干净一些。它也不太容易理解,因为除非您阅读它的文档和/或实现,否则 ERROR_PRESERVE 宏的功能并不明显。它也没有解决重复的问题,只是更容易将所有(现在是隐式的)检查写在一行上。
我真的很想重写这一切,如下所示:
return 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 不支持递归宏
我研究了一些recursive macro hacks,但我不喜欢使用那种程度的预处理器魔法——太多的空间让某些事情发生微妙的错误。我也考虑过使用真实(可能是可变参数)函数,但这需要
- 放弃短路行为
- 将函数作为指针传递,从而规范化它们的签名
而且这两个似乎都比原始的显式代码更糟糕。
我很想听听有关处理此问题的最佳方法的建议。我对许多不同的方法持开放态度,但我的最终目标是在不影响可读性的情况下避免重复。
(我想很明显,我对 || 运算符在 Ruby 等语言中的行为感到有些羡慕)。
【问题讨论】:
-
我不认为这更好,只是可读性较差。此外,
error-or的行为与您首先显示的代码完全不同。不要太看中宏;许多项目的错误处理代码比其他任何东西都多。如果你的就是其中之一,那就这样吧。专注于可维护和可读的代码。 (旁观:({ … })是 gcc 扩展。 -
我感觉这个问题将被关闭,因为它主要是基于意见的。但是,您可以通过将每个函数调用块放在一行中来使代码更紧凑,例如,
if ((error = do_something())) return error; if ((error = do_something_else(with, some, args))) return error; if ((error = do_something_yet_again())) return error; -
顺便说一句:
if ( error ) return error;是少数几个我不使用大括号并且可能单行的情况之一。 -
就我个人而言,我喜欢将宏用于样板错误处理代码,有点像您的
ERROR_OR()。这种做法使程序逻辑在错误处理中比没有它们时更清楚地突出。我不担心尽量减少宏扩展产生的错误处理代码的数量。 -
@JohnBollinger:问题是第一个 sn-p 做的事情与第二个非常不同。将宏内的代码流更改到外部会很快让您遇到维护问题..
标签: c gcc error-handling c-preprocessor