怎么应该"这永远不会发生"处理C中的错误风格?

时间:2015-03-17 08:39:25

标签: c error-handling core

我正在与其他团队成员一起在C中开发一个大型项目,我们对如何处理“这种情况永远不会发生”的错误方式存在分歧。我的意思是代码当前永远无法达到的错误情况,但有人可以修改代码,修改后可能会出现错误情况。

例如,假设我有一个函数,在成功时返回0,如果给出NULL参数则返回-EINVAL。我自然会做的是:

int err;
struct myctx ctx;
err = callFunction(&ctx);
if (err != 0)
{
  abort();
}

甚至:

int err;
struct myctx ctx;
err = callFunction(&ctx);
if (err == -EINVAL)
{
  abort();
}
else if (err != 0)
{
  abort();
}

...区分-EINVAL和未知错误代码的两种情况。

这种错误处理方法的好处是编程很简单(几行代码),并留下可用于快速调试情况的核心文件。

然而,一些团队成员不同意这种错误处理方法,因为我正在做一个不干净的退出,而代码当前永远不能到达abort()行,有可能在将来有人修改代码并且它到达然后abort()行。据他们说,我应该尝试做一个受控退出并将原因打印给用户。所以,我想我应该这样做而不是:

int err;
struct myctx ctx;
err = callFunction(&ctx);
if (err == -EINVAL)
{
  fprintf(stderr, "Argument was NULL in file %s line %d\n", __FILE__, __LINE__);
  exit(1);
}
else if (err != 0)
{
  fprintf(stderr, "Error was %d in file %s line %d\n", err, __FILE__, __LINE__);
  exit(1);
}

在我看来,这第二个错误处理策略更糟糕,因为它不会留下可用于调试此情况的核心文件。此外,它会产生具有更多代码行的代码。

所以,问题是,如何在C中处理“这种情况永远不会发生”的错误风格?我应该使用描述性错误消息进行中止或受控退出吗?

2 个答案:

答案 0 :(得分:4)

原则上,您应该始终处理错误并为用户生成最具说明性的描述。这是正确的事情,他们在日本会称之为 honte

然而,在实践中,第一个示例中无法解释的abort()是大多数人所做的,因为写入速度更快。这样做的结果是,大多数应用程序在失败时会惨遭退出。例如,当Microsoft Word崩溃时,向用户发送的消息是" Word遇到问题,现在将退出。"或者那种效果。许多应用程序根本不提供任何消息,只是简单地终止。

正确的编程是完全编码所谓的负路径。每个可能的失败不仅会产生有意义的文本消息,而且当失败返回并通过调用链向后渗透时,每个调用函数都应将其上下文和任何相关参数添加到消息的前面。所以给用户的最终消息应该是这样的:

  

尝试初始化时:尝试加载配置时   文件在[c:\ user \ data \ configuration.ini]:无法读取第453行:   参数值无效' NumRedirects' (超出界限): - 43

在这个错误消息中我们看到的是每个函数,因为它接收错误会在字符串的开头插入一个后跟冒号然后将函数返回到调用它的函数。最终结果显示所有重要信息(正在读取的文件,正在解析的行,正在加载的参数,为参数提供的无效值)。这完全解释了给用户(或开发者)的问题所在。

答案 1 :(得分:2)

我认为这就是断言的用途。除非代码是使用NDEBUG定义的,否则将检查返回值。

#include <assert.h>
...
int err;
struct myctx ctx;
err = callFunction(&ctx);
assert(err != -EINVAL);
assert(err == 0);

这个想法是错误通常会导致意外。此外,断言语句增加了代码的可读性。当断言失败时,自动错误消息有助于程序员识别失败的断言。可以使用-DNDEBUG构建发布版本以减少分支数量。