【问题标题】:Elegant error checking优雅的错误检查
【发布时间】:2011-10-19 10:20:53
【问题描述】:

我们的代码(在一个简单的库实现中)开始看起来像这样:

err = callToUnderlyingLibrary1();
if (err!=0) {
printf ("blah %d\n", err);
...
}

err = callToUnderlyingLibrary2();
if (err!=0) {
printf ("blah %d\n", err);
...
}

err = callToUnderlyingLibrary3();
if (err!=0) {
printf ("blah %d\n", err);
...
}

这既麻烦又丑陋。有一个更好的方法吗 ?也许使用 C 预处理器?我在想这样的事情:

CHECK callToUnderlyingLibrary1();
CHECK callToUnderlyingLibrary2();
CHECK callToUnderlyingLibrary3();

CHECK 宏调用函数并进行基本错误检查。

是否有首选的惯用方式来处理这个问题?

【问题讨论】:

  • 您应该选择 C ​​或 C++,而不是两者。在 C++ 中,规范答案将涉及异常。在 C 中,它不会。
  • 好主意。让我删除 C++ 标记。
  • 希望我能给更多UP votes!!!
  • 正如您最初将其标记为 C 和 C++,是否可以公平地假设这涉及从 C++ 代码调用的 C 库??
  • + 1 表示希望有更好的方法来做到这一点 :)

标签: c error-handling


【解决方案1】:

我更喜欢 Alexandra C. 的 goto 方法的变体:

int foo()
{
    int rv = 0;
    struct bar *x = NULL;
    struct bar *y = NULL;
    rv = Function1();
    if (rv != OK){
      goto error;
    }
    //...
    x = acquire_structure();
    if (x==NULL){
      rv = ERROR_MEMORY;
      goto error;
    }
    //...
    rv = Function2();
    if (rv != OK){
      goto error;
    }
    //...
    y = acquire_structure();
    if (y==NULL){
      rv = ERROR_MEMORY;
      goto error;
    }
    //...

    rv = release_structure(x);
    x = NULL;
    if (rv != OK){
      goto error;
    }
    rv = release_structure(y);
    y = NULL;
    if (rv != OK){
      goto error;
    }
    return OK;

error:
    if (x!=NULL){
      release_structure(x);
    }
    return rv;
}

当您使用多个 goto-destination 时,很容易混淆它们。或者也许你移动了一个变量的初始化,但忘记更新 goto。并且很难测试 C 方法可能失败的所有种方式。

我更喜欢使用单个 goto-destination 来执行所有清理工作。我发现这样更容易避免错误。

【讨论】:

    【解决方案2】:

    没有'goto',在函数中只使用1个'return'。这就是优雅的代码。

    恕我直言,OP 的问题点和所有答案都在谈论 FANCY 技术。花哨的代码只是一种养眼。

    【讨论】:

    【解决方案3】:

    另一种基于宏的方法,您可以使用它来相当轻松地缓解 C 中的缺点:

    #define CHECK(x) do { \
      int retval = (x); \
      if (retval != 0) { \
        fprintf(stderr, "Runtime error: %s returned %d at %s:%d", #x, retval, __FILE__, __LINE__); \
        return /* or throw or whatever */; \
      } \
    } while (0)
    

    然后调用它你有:

    CHECK(doSomething1());
    CHECK(doSomething2());
    // etc.
    

    对于加分,您可以轻松地扩展 CHECK 宏以采用第二个参数 y,这是在失败时执行的操作:

    #define CHECK(x, y) do { \
      int retval = (x); \
      if (retval != 0) { \
        fprintf(stderr, "Runtime error: %s returned %d at %s:%d", #x, retval, __FILE__, __LINE__); \
        y; \
      } \
    } while (0)
    
    // We're returning a different error code
    CHECK(someFunction1(foo), return someErrorCode);
    // We're actually calling it from C++ and can throw an exception
    CHECK(someFunction2(foo), throw SomeException("someFunction2 failed")):
    

    【讨论】:

    • 为什么选择使用宏,而不是函数/内联函数?
    • @alexs973 因为在这种情况下,事物需要能够从调用它的函数返回,而且问题是针对 C 语言的,因此不支持异常。
    【解决方案4】:

    这是一个提议,你可能喜欢也可能不喜欢:

    • 让您的函数在失败时返回 0,在成功时返回其他值
    • 如果您的函数出现问题,请让它们为错误代码设置一个全局(或静态)变量(如 errno
    • 创建一个 die() 函数,该函数根据错误代码(或您希望它执行的任何操作)打印错误
    • do_something(foo, bar) || die("Argh...");调用你的函数

    【讨论】:

    • 如果您的函数在成功时返回 0,do_something(foo, bar) || die("Argh..."); 将在成功时调用 die
    • 这种技术往往会生成关于“未使用计算的值”的虚假编译器警告,除非您将其包装(void)(...)
    【解决方案5】:

    通常,在 C 中,使用goto 进行错误处理:

    int foo()
    {
        if (Function1() == ERROR_CODE) goto error;
        ...
        struct bar *x = acquire_structure;
        ...
        if (Function2() == ERROR_CODE) goto error0;
        ...
    
        release_structure(x);
        return 0;
    
    error0:
        release_structure(x);
    
    error:
        return -1;
    }
    

    这可以通过宏和更聪明的指令流来改进(以避免重复清理代码),但我希望你明白这一点。

    【讨论】:

    • Gotos in Macros :-) 感谢您提醒我为什么尽可能使用 C++ 编程!
    • @Roddy:如果您保持功能简短并且有一定的纪律,那么这种方法没有任何问题。 Goto 和宏在这里有它们的用处。
    • 这些gotos 不在宏中! :)
    • @Roddy:当然,你是对的。我尽可能远离 C。但有时您无法选择语言。
    • 宏中的Gotos 可能非常好,但是总是 将要跳转的标签作为参数传递给宏。这使得宏更加灵活,并且不会隐藏执行可能会在下一行以外的地方继续。
    【解决方案6】:

    你可以照你说的做,这是一些基本的宏:

    #define CHECK(x) (err = x()); \
                     if (err) { \
                          printf("blah %d on line %d of file %s\n", err, __LINE__, __FILE__); \
                     } \
                     else (void)0
    

    你可以像这样使用它

    int err = 0;
    CHECK(callToUnderlyingLibrary1); // don't forget the semicolon at the end
    CHECK(callToUnderlyingLibrary2);
    CHECK(callToUnderlyingLibrary3);
    

    【讨论】:

    • 那是非常糟糕的宏样式,会导致各种奇怪的解析问题,更不用说你有一个宏突然期待封闭范围内的变量。
    • @fluffy 有哪些奇怪的解析问题示例?
    • if (someCondition) CHECK(blah);否则检查(其他); - 出于这个原因,最好将多语句宏包装在 do { ... } while (0) 中,因为它几乎可以解决所有这些情况。
    • @fluffy 为什么if (someCondition) CHECK(blah); else CHECK(otherblah); 会引起问题?
    • 因为是多行表达式,但是 CHECK 没有用大括号括起来。自己扩展宏;你最终得到:if (someCondition) (err=x()); if(err){...}else (void)0; else (etc.),它甚至没有解析。
    【解决方案7】:

    我认为你应该看看异常和异常处理。 http://www.cplusplus.com/doc/tutorial/exceptions/

    try{    
        callToUnderlyingLibrary1();
        callToUnderlyingLibrary2();
        callToUnderlyingLibrary3();
    }catch(exception& e)
        //Handle exception
    }
    

    如果出现错误,您的库函数可能会引发异常

    【讨论】:

    • 是的,那会很好,但不幸的是,这不是我们的选择。我们依赖于使用某个不会引发异常的底层库。
    • 我不会投反对票,因为在您回答后 C++ 标记已被删除。但问题涉及 C,它没有 try/catch 构造。
    • 对。一开始就包含 C++ 标记是我的错。请不要对这个答案投反对票,这将正确适用于 C++。
    • @Ren:那你能为这个库制作一个 C++ 包装器吗?对于 C++,大多数 C 库的可用性可以通过包装类和少量的 RAII 和异常得到极大的改进。
    • @Roddy:注意。我认为这将是对代码库的一个很好的长期增强。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-20
    • 2015-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多