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

通常我在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规则避免了多个return语句。

哪种方法最好?

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

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

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

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

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

我倾向于使用两种样式的混合,之前使用第二种样式(多次返回),然后(可能)使用第一种样式(稍后要返回的局部变量)。

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

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

我同意Lundin的回答,但我想提供另一个符合单一退出规则的解决方案,并且对第二个示例仍然具有类似的可读性:

 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; } 

我使用的方法是转到error_exit。

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

原因1是非法参数,比如将负数传递给平方根。 因此断言失败,错误是调用者。

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

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

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

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

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