在检查C子例程的返回值时避免冗余

  • 本文关键字:冗余 返回值 子例程 c
  • 更新时间 :
  • 英文 :


以下代码中存在大量冗余:

int init( )
{
int err;
err = init_a();
if(err)
return -1;
err = init_b();
if(err)
return -1;
err = init_c();
if(err)
return -1;
// etc.
return 0;
}

是否有更好的方法来检查err返回值?

如果你真的有大量的init函数,并且不怕函数指针,

#include <stddef.h>
int init_a(void), init_b(void), init_c(void);
int init(void);
static int (*const func[])(void) = {
&init_a,
&init_b,
&init_c,
// etc.
};
#define ELEMENTS(array) (sizeof(array) / sizeof(array[0]))
int init(void) {
for (size_t i = 0; i < ELEMENTS(func); ++i) {
if (func[i]() != 0) {
return -1;
}
}
return 0;
}

对于另一个init_x((,只需将其插入func[]数组的适当索引即可。交换和删除功能也非常简单,几乎不会带来不便。如果你想知道哪个函数失败了,你甚至可以return 1 + i;

现在,您的init((已成为由func[]数组驱动的数据(而不是由init((中的语句驱动的代码(。

只有当init_*函数具有相同的原型并采用相同的参数(如果有的话(时,这才有效。

使用短路评估:

bool init( )
{
return init_a() || init_b() || init_c();
}

如果您可以接受使用布尔值作为成功或失败的最终指示(在我的示例failure=true(,那么短路运算符可以使事情变得非常紧凑。

C语言采用短路,因此在具有多个操作数的逻辑表达式中,代码将只执行";只要需要";以推导表达式的最终值。因此,如果A在表达式A || B || C中为真,则BC将不被求值。如果init_N((函数都没有返回failure,那么返回值将为success。

如果你更喜欢成功=truereturn A && B && B(但你的代码表明成功是false(,你当然可以否定这个逻辑

此外,如果您想允许将错误的原因(即错误代码(报告给调用者,只需传递一个指向错误代码的指针,并让所有子init将其设置为有用的值:

bool init(int* err)
{
return init_a(err) || init_b(err) || init_c(err);
}

如果必须按顺序调用这些初始化子进程(这在硬件/驱动程序软件中很常见(,则需要在每个步骤后进行状态检查。在您的示例中,返回不同的错误代码更具指示性。

int init(void) {
int err = init_a();
if (0 != err) {
return -1;
}
err = init_b();
if (0 != err) {
return -2;
}
err = init_c();
if (0 != err) {
return -3;
}
// etc.
return 0;
}

以下代码中存在大量冗余:

我不知道。有一个重复的主题,我想这就是你所说的,但没有什么是多余的。如果你正在寻找DRYer代码,那么这类事情可以在预处理器宏的帮助下解决:

#define RETURN_IF_NZ(x, r) do { if (x) return (r); } while (0)
int init(void) {
RETURN_IF_NZ(init_a(), -1);
RETURN_IF_NZ(init_b(), -1);
RETURN_IF_NZ(init_c(), -1);
// ...
return 0;
}

其中的优势包括:

  • 没有(显式(重复错误处理代码
  • 函数的行为更容易跟踪(一旦理解了宏(,因为关键元素较少被错误处理脚手架遮挡

缺点包括:

  • 不像原始版本那样容易调试(但比现有的一些替代方案更容易(
  • 人类必须知道、猜测或查找宏的含义

有些人不太重视宏的使用,因为大多数情况下都应该首选函数。然而,这是宏可以处理但函数不能处理的情况之一。

最新更新