0

我与另一位程序员在如何编写具有大量错误检查的方法方面存在分歧:

public void performAction() {
  if (test1) {
    if (test2) {
      if (test3) {
        // DO STUFF
      } else {
        return "error 3";
      }
    } else {
      return "error 2";
    }
  } else {
    return "error 1";
  }
}

-

public void performAction() {     
  if (!test1) {
    return "error 1";
  }
  if (!test2) {
    return "error 1";
  }
  if (!test3) {
    return "error 1";
  }
  // DO STUFF
}

对我来说, if语句的深度嵌套使得第一个示例难以阅读。
第二个,尽管有三个return ,但更具可读性。

我好奇地检查了 Code Complete 是怎么说的,这让我不太确定如何处理这个问题:

嵌套底部的错误条件堆栈是编写良好的错误处理代码的标志。

但是之后

在四个 if 语句中缩进例程的主体在美学上是丑陋的,尤其是在最里面的 if 语句中有很多代码的情况下。

并考虑使用保护子句,如第二个示例

尽量减少每个例程中的返回次数。当您在底部阅读例程时,您没有意识到它可能会返回到上面的某个位置时,就更难理解例程了。

您如何编写方法的错误检查部分以使其可读且容易出错?

4

3 回答 3

1

没有什么比风格辩论更快地让程序员陷入战斗(在支持循环和函数的语言中使用“goto”是否有利?如果是,为什么?)。所以简短的回答是“你和你的团队决定的任何风格最适合你的项目/语言。”

话虽如此,我想将我的 2 美分加到 Code Complete 关于多次退货的评论中。您应该区分多次成功退货和多次退货。如果我需要调查 5 个不是由于生成错误而导致的返回,则可能需要重写该函数。如果您在检测到错误后立即优雅地退出您的函数,那么维护程序员(即 6 个月后的您)在遵循函数的主要逻辑时应该不会比嵌套所有这些错误检查更麻烦。

所以,我个人认为你的第二个代码示例是更好的选择。

于 2014-10-08T19:34:42.943 回答
0

这是我的意见。

“尽量减少每个例程中的返回次数”的古老口头禅似乎有点过时了。当您有更长的 8-10 行代码的方法时,它非常适用,其中执行大量操作。

较新的思想流派,强调单一职责和非常短的方法,似乎使这有点不必要。当您的整个方法不直接执行任何操作,而只是处理错误处理时,最好以干净的格式多次返回。

在任何一种情况下,只要您嵌套了 if,可读性都会受到很大影响。

我要做的一个优化是使用 if-else-if 结构,以清楚地指示逻辑流程。

示例代码:

public void Execute() 
{     
    if (test1)
    {
        return;
    }
    else if (test2)
    {
        return;
    }
    PerformAction();
}

private void PerformAction()
{
    //DO STUFF
}
于 2014-10-08T19:42:50.353 回答
0

如果您使用的语言具有异常处理和自动化资源管理功能,您的同事可能应该习惯您喜欢的风格,在遇到外部输入错误的情况下会提前退出。

在异常处理和自动化资源管理(例如:没有析构函数或像 C 之类的 GC 的语言)出现之前,尝试将函数出口移至范围底部的想法很有用,因为错误恢复通常需要手动清理

在那些手动清理的情况下,将出口移到函数的底部通常很有用,这样您就可以查看函数顶部的逻辑创建函数所需的临时资源并移到函数底部以看到这些资源的对称清理。

在像汇编这样的情况下jumps/branches,在函数底部看到一个错误标签是很常见的,在该标签会发生清理。gotos即使在 C 中用于此目的,它也并不少见。

此外,如前所述,深度嵌套会带来很多心理开销。你的大脑必须像一个深度嵌套的堆栈一样运作,试图记住你在哪里,就像死硬的 C 编码器 Linus Torvalds 喜欢说的那样:如果你需要在一个函数中像 4 个嵌套级别的缩进,您的代码已经损坏,应该重构(我不确定我是否同意他的确切数字,但他对它如何混淆逻辑有一定的看法)。

当您使用更现代的语言(如 C++)时,您现在可以通过析构函数进行自动资源管理。函数应该不再提及清理细节,因为资源应该通过符合所谓的资源获取是初始化习语(不完全是最好的名称)来自动处理清理。这消除了支持一种努力将错误处理逻辑置于底层的风格的重要原因之一。

最重要的是,当您使用像 C++ 这样的语言时,它可能会抛出异常并且到处都是。因此,其他每一行代码都有隐藏的、隐式退出的效果并不少见,如下所示:

if an exception occurs:
    automatically cleanup resources and propagate the error

所以到处都有隐藏的、过早的出口。所以如果你使用这样的语言,你不仅应该习惯在异常情况下过早退出,而且你有点被迫进入它并且别无选择。就这些语言的可读性/可追溯性而言,没有比这更简单的了:

if something bad happened:
     return error

我建议的一个例外是静态分支预测。如果您正在编写对性能非常关键的代码,其中最小的微效率比可读性更重要,那么您希望您的分支按照英特尔的建议偏向于支持常见的执行情况。所以而不是:

if something exceptional happened:
    return error

...为了提高性能,您可能会颠倒逻辑并改为这样做:

if something normal happened:
     ...
     return success
return error
于 2015-05-25T09:06:34.077 回答