2017-03-03 47 views
4

通常我使用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语句。

哪种方法最好?

+1

如果你更喜欢第一个,这是最好的(对你):) –

+0

这实际上不是一个基于意见的问题。 OP更喜欢一个版本,因为MISRA-C告诉他,没有任何理由说明。这里真正的问题是为什么MISRA-C发表这样的声明。在过去,我已经为甚至存在这条规则的底部,下面回答。 – Lundin

+0

可能的重复模式,以防止不断检查错误?(http://stackoverflow.com/questions/24658258/pattern-to-prevent-continually-checking-error) – Toby

回答

11

第二个是最好的,因为它更容易阅读,可以随着复杂度的增加而良好地扩展,并在出现错误时立即停止执行该功能。这是在函数内部进行大量错误处理时编写这些函数的唯一明智方式,例如,如果函数是解析器或协议解码器。

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年的主观废话列为他们唯一的来源和理由。

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

+0

我同意你的意见。当需要检查几个参数时,第二种方法是最好的。特别为了代码可读性。 – Federico

+0

这不是*缺陷*,Lundin :-) ...简单,结构良好的代码总比保存gotos,多出口等代码更容易。 – Andrew

+1

@Andrew缺陷=可疑来源。这就是说,任何C程序员都很清楚上面问题中的第二个版本比第一个版本更可读。尤其是当错误检查数量增加时。这里实际上有3个选择:1)嵌套复合语句疯狂,2)错误返回,或3)“错误转到”BASIC。第一个是不可读的,很难维护,第二个是非常可读和坚固的代码,第三个是可以接受的,但不如第二个可读。 MISRA-C:2004只允许1)。 MISRA-C:2012只允许1)和3)。它如何不是一个缺陷? – Lundin

1

我使用的方法是goto error_exit。

你必须考虑为什么一个函数可能会失败。

原因1是非法论点,例如将负数传递给平方根。所以声明失败,错误是来电者的。

原因2内存不足 - 这是扩展功能的固有问题。你需要将故障分流,尽管通常如果一个程序不会给你少量的内存来保存文件路径,那么它已经死了。

原因3是错误的语法。这是一个非法论点的特例。如果参数是平方根的两倍,则可以合理地预期呼叫者检查负值。如果参数是一个基本程序,除非有效地编写他自己的解析器,否则调用者无法检查正确性。所以坏语法需要像正常流量控制一样处理。

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

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

但是,goto error_exit方法是我喜欢的方法。它保持了一个入口点。和退出原则本质上完好无损,而不会引入人为嵌套,从而不会发生比计算机崩溃更难以发生的内存分配错误。

+0

在我看来,比“on error goto”更好的版本是让函数成为实际函数的包装。保留包装中的所有资源分配/取消分配。它本质上与“错误转到”相同,但是更加面向对象,将分配从算法中分离出来。作为奖励,你不必忍受无休止的“goto认为有害的”辩论。 – Lundin

+0

另一个答案是将malloc包装在xmalloc中,这是保证不会失败。如果失败,它会出现一个对话框,要求用户释放一些内存或终止。 –

+0

尽管“goto失败”总是一个选项(尽管需要针对早期版本的MISRA C的偏差,请记住Apple使用该方法遭受了一个相当令人尴尬的SSL错误! – Andrew

2

我倾向于混合使用两种风格,第二种风格(多重返回),以及(可能)之后的第一种风格(稍后返回的本地变量)。

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

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

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

一个满足要求并且可扩展的维护! – Andrew

相关问题