【问题标题】:How to reduce code bloat in C from error handling & debugs [duplicate]如何通过错误处理和调试减少 C 中的代码膨胀 [重复]
【发布时间】:2015-08-22 16:43:05
【问题描述】:

考虑这个函数:

int get_result(int *result) {
     int err = 0;
     int number = 0;         

     if (result == NULL) {
         printf("error: null input\n");
         return -1;
     }

     err = get_number(&number);

     if (err != 0) {
         printf("error calling get_number: err = %d\n", err);
         return err;
     }

     err = calculate_result(number, result);

     if (err != 0) {
        printf("error calling get_result: err = %d\n", err);
        return err;
     }

     return err;
}

这个函数的真正工作只需要3行(声明数字变量,调用get_number(),然后调用calculate_result())。但是,错误检查/处理代码将此函数膨胀到 17 行(取决于您如何计算行数)。

在更大的范围内,有很多调用和多次错误检查,我们使函数完全膨胀,使其不可读且难以理解。

有什么方法可以解决 C 代码中的这种臃肿问题并保持函数核心操作的可读性(不牺牲基本的错误处理代码)?

【问题讨论】:

  • 为什么使用errno 作为类型名?
  • 好吧。如果您不打印到标准错误并剪断大括号,则可以节省 4-6 行。 c 是这样的。实际上所有的语言都是这样的,你可以在java中争论你也可以扩展一个对try-catch块的简单调用。所以,你编写你需要的代码。
  • 你可以,实际上使用宏可以节省一些工作,比如#define SAFE_CALL(expr) { errno err = expr; if (err != 0) return err; }。如果您将代码中的此类使用限制在合理的范围内,这不会太糟糕。
  • @HuStmpHrrr:不符合 C 标准。只有 POSIX 标准(此处可能适用也可能不适用)。
  • 如果代码有效,那么您可能会在 codereview.stackexchange.com 上得到更好的答案

标签: c readability code-readability


【解决方案1】:

欢迎来到生产质量代码的世界。有一些宏和分解技巧可以使错误检查不那么冗长。例外是另一种机制。但要做出的主要调整是观点。您将“真正的工作”视为与错误处理分开的东西。它不是。处理所有可能的情况是软件工程的本质。要将其从您的系统中移除,请在 cmets 中解释“主要工作”,然后编写具有外部强加条件处理前端和中心的真实算法。

【讨论】:

    【解决方案2】:

    这是出现异常的主要原因,但我不得不承认我不喜欢在使用显式内存管理的语言中引入异常,所以这个答案可能有偏见。也就是说, 中有一些常见的策略可以将业务逻辑与错误处理分开。

    1. 使用异常——查看longjmp()/setjmp() 以自己在 中实现它们。我建议不要这样做。
    2. 如果发生错误时需要进行清理,请将其放在函数的末尾并在此处goto。 (是的,真的!)
    3. 为了检查返回值并将消息打印到stderr,请尝试排除常见情况并定义宏,例如在你的情况下:

      #define CHECK_RETVAL(val, action) do { \
          if (val < 0) { \
              fprintf(stderr, "error calling " action ": %s\n", strerror((val))); \
              goto error_cleanup; \
          }} while (0)
      

    【讨论】:

      【解决方案3】:

      如果你不太关心行的长度,你可以编写如下函数:

      int checkforError(int errorCode, const char* message)
      {
          if (errorCode != 0)
          {
              printf("%s: err = %d\n", err", message, errorCode);
              return 0;
          }
      
          return 1;
      }
      

      并使用它来缩小最后两个 if:

      checkforError(err = get_number(&number), "error calling get_number") && checkforError(err = calculate_result(number, result), "error calling get_result"); 
      return err;
      

      由于第一个if 与其他情况几乎没有共同之处,因此没有理由将checkforError 适应它。

      短路保证了评估的顺序,所以它不是未定义的行为。见Logical comparisons: Is left-to-right evaluation guaranteed?

      【讨论】:

        【解决方案4】:

        唯一不需要错误处理代码的情况是它是多余的。避免冗余错误处理代码与通常避免冗余代码完全相同。唯一的问题是错误处理代码的范围是什么。通常将尽可能多的常见行为纳入尽可能大的范围内。

        例如,malloc 失败通常是致命的,因此您可以包装函数而不是检查每个返回值...

        void* fmalloc(size_t n) {
        
          void* const m = malloc(n);
        
          if (!m)
            on_fatal("out of memory");
        
          return m;
        }
        

        如果行为只能作用于调用函数,则可以使用goto...

        int on_file(fd, rm, wm) {
        
          if (read(fd, rm, 8) < 0)
            goto err;
        
          if (write(fd, wm, 8) < 0)
            goto err;
        
          return 0;
        
          err:
            on_error("on_file error");
            return -1;
        
        }
        

        对于可以参数化的东西,把它们参数化。

        这是我使用的一些example 代码。一般来说,减少错误处理代码与仅对常见行为进行分组没有什么不同。

        【讨论】:

          猜你喜欢
          • 2010-09-05
          • 2011-06-27
          • 2011-02-03
          • 1970-01-01
          • 2011-03-03
          • 2014-03-24
          • 2021-08-28
          • 2020-02-14
          • 1970-01-01
          相关资源
          最近更新 更多