计算函数返回值的最佳实践

时间:2017-03-03 09:38:38

标签: c return misra

我经常在C中构建函数来检查一些参数并返回错误代码。

当我发现错误时停止值检查的最佳方法是什么?

第一个例子:

ErrorCode_e myCheckFunction( some params )
{
  ErrorCode_e error = CHECK_FAILED;

  if( foo == bar )
  {
     if( foo_1 == bar_1 )
     {
        if( foo_2 == bar_2 )
        {
           error = CHECK_SUCCESS;
        }
     }
  }

  return error;
}

第二个例子:

ErrorCode_e myCheckFunction( some params )
{
  if( foo != bar )
  {
     return CHECK_FAILED;
  }

  if( foo_1 != bar_1 )
  {
     return CHECK_FAILED;
  }

  if( foo_2 != bar_2 )
  {
     return CHECK_SUCCESS;
  }
}

我更喜欢第一种方法,因为我读到MISRA规则避免多次返回语句。

哪种方法最好?

5 个答案:

答案 0 :(得分:17)

第二个是最好的,因为它更容易阅读,随着复杂性的增加而很好地扩展,并且在出错时立即停止执行该功能。当您在函数内部进行大量错误处理时,这是编写此类函数的唯一合理方法,例如,如果函数是解析器或协议解码器。

MISRA-C不允许函数中的多个return语句是MISRA-C的缺陷。据说这意味着不允许从各处返回的意大利面条代码,但是教条地禁止多个返回语句实际上可以使代码的可读性大大降低,正如我们从您的示例中看到的那样。想象一下,如果你需要检查10个不同的错误。然后你会有10个复合if语句,这将是一个难以理解的混乱。

我已经多次向MISRA委员会报告此缺陷,但他们没有听过。相反,MISRA-C只是盲目引用IEC 61508作为规则的来源。这反过来只列出了这个规则的一个可疑来源(IEC 61508:7 C.2.9),它是1979年的恐龙编程书。

这不是专业的,也不是科学的 - MISRA-C和IEC 61508(和ISO 26262)都应该感到羞耻(直接或间接地)将1979年的主观无意义列为其唯一的来源和理由。

只需使用第二种形式,并针对此缺陷MISRA规则提出永久性偏差。

答案 1 :(得分:2)

我倾向于使用两种风格的混合,之前是第二种风格(多次返回),然后(或许)第一种风格(稍后要返回的局部变量)。

理由是:"多次退货"是权威。当传递的参数或其他一些不可恢复的条件存在绝对错误时,可以/应该使用它 "局部变量"相反,style允许编写可以多次修改返回值的代码。它倾向于生成代码,意味着让我们假设失败;但如果一切正常,那么我会将结果重写为OK"。或者相反:"假设好;如果出现任何问题,请将结果设置为 failure "。在这些步骤之间,仍然可以有其他回报!

正如最后的想法......我会说正确的风格取决于具体情况,永远不要假设一个总是对的,另一个总是错的。

答案 2 :(得分:2)

我同意Lundin’s answer但我想提供另一个符合单一退出规则的解决方案,并且仍然可以与第二个示例相同:

ErrorCode_e myCheckFunction( some params )
{
  ErrorCode_e error = CHECK_FAILED;

  if( foo != bar )
  {
     error = CHECK_FAILED;
  }
  else if( foo_1 != bar_1 )
  {
     error = CHECK_FAILED;
  }
  else if( foo_2 != bar_2 )
  {
     error = CHECK_SUCCESS;
  }
  else
  {
     // else (even empty) is required by MISRA after else-if
  }
  return error;
}

由于示例中只有两个选项,我们只能使用一个条件:

ErrorCode_e myCheckFunction( some params )
{
  ErrorCode_e error = CHECK_FAILED;

  if( (foo == bar) && (foo_1 == bar_1) && (foo_2 != bar_2) )
  {
     error = CHECK_SUCCESS;
  }

  return error;
}

这种情况可以更加简化,我们不需要任何局部变量:

ErrorCode_e myCheckFunction( some params )
{
  return ( (foo == bar) && (foo_1 == bar_1) && (foo_2 != bar_2) )
      ? CHECK_SUCCESS : CHECK_FAILED;
}

答案 3 :(得分:1)

我使用的方法是转到error_exit。

您必须考虑函数可能失败的原因。

原因1是非法参数,例如将否定值传递给平方根。因此断言失败,错误是来电者。

原因2内存不足 - 这是缩放功能的固有问题。你需要分解故障,虽然通常如果一个程序没有给你少量的内存来保存,比如一个文件路径,那么它已经死了。

原因3是语法错误。这是非法争论的特例。如果参数是平方根的两倍,则可以合理地期望调用者检查否定值。如果参数是基本程序,则调用者无法通过有效编写自己的解析器来检查正确性。如此糟糕的语法需要作为正常的流量控制来处理。

原因4是硬件故障。除非您熟悉特定设备,否则除了分流错误之外,您无能为力。

原因5是内部编程错误。根据定义,没有正确的行为,因为您自己的代码不正确。但是,例如,你经常需要在几何中捏造或抛弃退化的情况。

然而,goto error_exit方法是我喜欢的方法。它保留了一个入口点。退出原则基本上是完整的,没有引入人为嵌套的内存分配错误,这种错误比计算机破坏的可能性更小。

答案 4 :(得分:0)

有趣的是,没有一个人注意到上面的第二个示例演示了为什么MISRA规则首先存在:对于if子句不匹配的所有情况,它都保留了默认的返回值。

那么如果(foo == bar) && (foo1 == bar1) && (foo2 == bar2)会发生什么呢?

此外,在我的第一个示例中,它很容易理解,在特殊情况下,存在非默认返回值。