31

我有时想知道的一件事是下面显示的两种风格中哪一种更好(如果有的话)?如果没有满足保护条件,是立即返回更好,还是只在满足保护条件时才做其他事情?

为了论证,请假设保护条件是一个返回布尔值的简单测试,例如检查元素是否在集合中,而不是通过抛出异常可能影响控制流的东西。还假设方法/函数足够短,不需要编辑器滚动。

// Style 1
public SomeType aMethod() {
  SomeType result = null;

  if (!guardCondition()) {
    return result;
  }

  doStuffToResult(result);
  doMoreStuffToResult(result);

  return result;
}

// Style 2
public SomeType aMethod() {
  SomeType result = null;

  if (guardCondition()) {
    doStuffToResult(result);
    doMoreStuffToResult(result);
  }

  return result;
}
4

12 回答 12

39

我更喜欢第一种样式,只是在不需要时我不会创建变量。我会这样做:

// Style 3
public SomeType aMethod() {

  if (!guardCondition()) {
    return null;
  }

  SomeType result = new SomeType();
  doStuffToResult(result);
  doMoreStuffToResult(result);

  return result;
}
于 2010-05-28T11:34:28.267 回答
29

在 80 年代后期接受 Jackson 结构化编程培训后,我根深蒂固的理念始终是“一个函数应该有一个入口点和一个出口点”;这意味着我根据样式 2 编写代码。

在过去的几年里,我开始意识到以这种风格编写的代码通常过于复杂且难以阅读/维护,因此我已切换到风格 1。

谁说老狗学不会新把戏?;)

于 2010-05-28T12:55:21.747 回答
16

样式 1 是 Linux 内核间接推荐的。

来自https://www.kernel.org/doc/Documentation/process/coding-style.rst,第 1 章:

现在,有些人会声称使用 8 个字符的缩进会使代码向右移动太远,并且在 80 个字符的终端屏幕上难以阅读。答案是,如果你需要超过 3 级的缩进,无论如何你都搞砸了,应该修复你的程序。

样式 2 增加了缩进级别,因此不鼓励。

就个人而言,我也喜欢风格1。样式 2 使得在具有多个保护测试的函数中匹配右大括号变得更加困难。

于 2010-05-28T11:44:41.967 回答
6

我不知道守卫在这里是否合适。通常,不满意的守卫会导致异常或断言。
但除此之外,我会选择 style 1,因为在我看来它使代码更干净。您有一个只有一个条件的简单示例。但是,许多条件和样式 2 会发生什么?它会导致很多嵌套if的 s 或巨大的 if 条件(带有||, &&)。我认为最好尽快从方法返回。
但这当然是非常主观的^^

于 2010-05-28T11:37:37.650 回答
6

Martin Fowler 将此重构称为: “用保护子句替换嵌套条件”

if/else 语句也带来了圈复杂度。因此更难测试用例。为了测试所有 if/else 块,您可能需要输入很多选项。

如果有任何保护子句,您可以先对其进行测试,并以更清晰的方式处理 if/else 子句中的真实逻辑。

于 2014-11-18T14:27:39.513 回答
5

如果您使用 .net-Reflector 深入研究 .net-Framework,您会看到 .net 程序员使用样式 1(或者可能是 unbeli 已经提到的样式 3)。上面的答案已经提到了原因。也许还有一个原因是为了让代码更易读、更简洁、更清晰。这种风格最常使用的是在检查输入参数时,如果你编写一种框架/库/dll,你总是必须这样做。首先检查所有输入参数而不是使用它们。

于 2010-05-28T12:03:07.000 回答
4

它有时取决于语言和您使用的“资源”类型(例如打开的文件句柄)。

在 C 中,样式 2 绝对更安全、更方便,因为函数必须关闭和/或释放它在执行期间获得的任何资源。这包括分配的内存块、文件句柄、操作系统资源的句柄(如线程或绘图上下文)、互斥锁以及任何数量的其他事物。延迟return到最后或以其他方式限制函数的退出次数允许程序员更轻松地确保他/她正确清理,有助于防止内存泄漏、处理泄漏、死锁和其他问题。

在使用RAII风格编程的C++ 中,两种风格同样安全,因此您可以选择更方便的一种。我个人使用风格 1 和 RAII 风格的 C++。没有 RAII 的 C++ 就像 C 一样,所以同样,在这种情况下,样式 2 可能更好。

在像 Java 这样带有垃圾收集的语言中,运行时有助于消除两种风格之间的差异,因为它会自行清理。但是,如果您没有明确地“关闭”某些类型的对象,这些语言也可能存在微妙的问题。例如,如果您构造一个新的java.io.FileOutputStream并且在返回之前不关闭它,那么相关的操作系统句柄将保持打开状态,直到运行时垃圾收集FileOutputStream已超出范围的实例。FileOutputStream这可能意味着在收集实例之前,需要打开文件进行写入的另一个进程或线程可能无法打开。

于 2010-05-28T11:55:44.050 回答
3

虽然它违背了我所学过的最佳实践,但我发现当我遇到这种情况时减少 if 语句的嵌套要好得多。我认为它更容易阅读,虽然它存在多个地方,但仍然很容易调试。

于 2010-05-28T11:36:20.710 回答
1

我会说 Style1 变得更常用,因为如果你将它与小方法结合起来,它是最佳实践。

当你有大方法时,Style2 看起来是一个更好的解决方案。当你拥有它们时......无论你如何退出,你都有一些想要执行的通用代码。但正确的解决方案不是强制单个退出点,而是使方法更小。

例如,如果你想从一个大方法中提取一段代码,而这个方法有两个退出点,你开始遇到问题,很难自动完成。当我有一个用 style1 编写的大方法时,我通常将其转换为 style2,然后我提取方法,然后在每个方法中我应该有 Style1 代码。

所以 Style1 是最好的,但与小方法兼容。Style2 不太好,但如果你有不想要的大方法,有时间拆分。

于 2011-10-19T18:00:26.880 回答
0

我更喜欢自己使用方法#1,它在逻辑上更容易阅读,并且在逻辑上也更类似于我们正在尝试做的事情。(如果发生不好的事情,立即退出功能,不要通过 go,不要收取 200 美元)

此外,大多数情况下,您可能希望返回一个在逻辑上不可能的结果(即 -1),以向调用该函数的用户表明该函数未能正确执行并采取适当的措施。这也更适合方法#1。

于 2010-05-28T12:53:06.607 回答
0

我会说“这取决于……”

在离开函数/方法之前我必须执行超过 2 或 3 行的清理序列的情况下,我更喜欢样式 2,因为清理序列只需编写和修改一次。这意味着可维护性更容易。

在所有其他情况下,我更喜欢样式 1。

于 2010-05-28T13:06:28.620 回答
-3

1 号通常是简单、懒惰和草率的方式。数字 2 清晰地表达了逻辑。其他人指出的是,是的,它会变得很麻烦。这种趋势虽然有一个重要的好处。样式 #1 可以隐藏您的功能可能做得太多。它并没有很好地直观地展示正在发生的事情的复杂性。即它阻止代码对你说“嘿,这对于这个功能来说有点太复杂了”。它还使其他不知道您的代码的开发人员更容易错过那些散布在各处的回报,无论如何,乍一看。

所以让代码说话。当您看到长条件出现或嵌套 if 语句时,它表示将这些东西分解为多个函数可能会更好,或者需要更优雅地重写它。

于 2010-05-28T13:35:38.390 回答