在控制流使其多余的情况下应使用“其他”吗?


18

有时我会偶然发现类似于以下示例的代码(此函数的确切作用超出了此问题的范围):

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  else if (check2(value)) {
    return value;
  }
  else {
    return false;
  }
}

正如你所看到的,ifelse ifelse语句与配合使用return的语句。对于一个偶然的观察者来说,这似乎很直观,但是我认为(从软件开发人员的角度来看)删除else-s并简化这样的代码会更加优雅:

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  if (check2(value)) {
    return value;
  }
  return false;
}

这是有道理的,因为return语句后的所有内容(在相同范围内)将永远不会执行,从而使上面的代码在语义上与第一个示例相同。

以上哪一项更符合良好的编码习惯?两种方法在代码可读性方面都有缺点吗?

编辑:重复的建议已被提出,该问题仅供参考。我认为我的问题涉及另一个主题,因为我并不是要避免像另一个问题中那样重复陈述。这两个问题都试图减少重复,尽管方式略有不同。


14
else是一个较小的问题,更大的问题是您显然从单个函数中返回了两种数据类型,这使函数的API无法预测。
安迪

3
E_CLEARLY_NOTADUPE
kojiro '16

3
@DavidPacker:至少两个;可能是三个。-1是一个数字,false是一个布尔值,value在此未指定,因此它可以是任何类型的对象。
Yay295

1
@ Yay295我希望value实际上是一个整数。如果可以的话,那就更糟了。
安迪

@DavidPacker这是非常重要的一点,尤其是在提出有关良好做法的问题时。我同意您和Yay295的观点,但是代码示例仅作为示例说明了不相关的编码技术。尽管如此,我谨记这是一件重要的事情,在实际代码中不应忽略。
犀牛

Answers:


23

我喜欢一个没有的人else,这是为什么:

function doSomething(value) {
  //if (check1(value)) {
  //  return -1;
  //}
  if (check2(value)) {
    return value;
  }
  return false;
}

因为这样做并没有破坏任何东西,这是不应该的。

讨厌所有形式的相互依赖(包括命名函数check2())。隔离所有可以隔离的内容。有时您需要,else但我在这里看不到。


由于这里所述的原因,我通常也更喜欢这样做。但是,我通常在每个代码if块的末尾添加一个注释,} //else以便在阅读代码时明确指出,if代码块后唯一打算执行的操作是if语句中的条件是否为false。没有这样的东西,尤其是如果if块中的代码变得越来越复杂,则维护该代码的人可能不清楚ifif条件为真时意图永远不会执行超过该块的人。
Makyen '16

@Makyen,你提出了一个好观点。我试图忽略的那一个。我也认为应该在之后做些事情,if以明确说明这部分代码已完成并且结构已完成。为此,我做了我在这里没有做过的事情(因为我不想通过做其他更改来分散OP的重点)。我会做最简单的事情,而不会分散注意力。我添加一个空白行。
candied_orange

27

我认为这取决于代码的语义。如果您的三种情况相互依赖,请明确说明。这提高了代码的可读性,并使其他任何人都更容易理解。例:

if (x < 0) {
    return false;
} else if (x > 0) {
    return true;
} else {
    throw new IllegalArgumentException();
}

在这里,您显然取决于这x三种情况的价值。如果您省略了last else,那么将不太清楚。

如果您的案例不直接相互依赖,请忽略它:

if (x < 0) {
    return false;
}
if (y < 0) {
    return true;
}
return false;

在没有上下文的简单示例中很难显示这一点,但我希望您能理解我的意思。


6

我更喜欢第二个选项(分离ifs,没有else if和早期returnBUT只要代码块是短

如果代码块很长,最好使用else if,因为否则您可能对代码的几个出口点没有清楚的了解。

例如:

function doSomething(value) {
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    return -1;
  }
  if (check2(value)) {
    // ...
    // imagine 150 lines of code
    // ...
    return value;
  }
  return false;
}

在这种情况下,最好使用else if,将返回码存储在一个变量中,最后只包含一个返回语句。

function doSomething(value) {
  retVal=0;
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    retVal=-1;
  } else if (check2(value)) {
    // ...
    // imagne 150 lines of code
    retVal=value;
  }
  return retVal;
}

但是,由于应该努力使函数简短,因此我想说要使其简短,并在第一个代码段中尽早退出。


1
我同意您的前提,但是只要我们正在讨论代码应该如何,我们是否还不仅仅同意代码块应该简短?我认为,如果我不得不选择,将一长段代码不分解成函数比使用其他代码更糟糕。
kojiro

1
一个奇怪的争论。return在3行以内,if因此else if即使经过200行代码也没有什么不同。您在此处介绍的单返回样式是c语言和其他语言的传统,这些语言不会报告异常。关键是要创建一个清理资源的地方。异常语言finally为此使用了一个块。我想如果调试器不让您在关闭花括号的函数中放置一个断点,那么这也会创建一个放置断点的地方。
candied_orange

4
我根本不喜欢这份作业retVal。如果可以安全地退出函数,请立即这样做,而无需将要返回的安全值分配给该函数一次退出的变量。当您在一个很长的函数中使用单个返回点时,我通常不信任其他程序员,并且最终还会在该函数的其余部分中查找返回值的其他修改。快速失败和早日返回是我赖以生存的两个规则。
安迪

2
在我看来,长块甚至更糟。无论外部环境如何,我都尝试尽早退出函数。
安迪

2
我在这里与DavidPacker在一起。单一返回样式迫使我们研究所有分配点以及分配点之后的代码。无论长短,研究早期返回样式的退出点对我来说都是更可取的。只要语言允许finally块。
candied_orange 2016年

4

我在不同情况下都使用。在验证检查中,我省略了其他内容。在控制流中,我使用else。

bool doWork(string name) {
  if(name.empty()) {
    return false;
  }

  //...do work
  return true;
}

bool doWork(int value) {
  if(value % 2 == 0) {
    doWorkWithEvenNumber(value);
  }
  else {
    doWorkWithOddNumber(value);
  }
}

第一种情况读起来更多,就像您先检查所有先决条件,将其排除在外,然后进行到要运行的实际代码一样。因此,您真正想要运行的多汁代码直接属于该函数的范围;这才是真正的功能。
第二种情况读起来更像两个路径都是要运行的有效代码,它们在某种条件下与另一个函数同等重要。因此,它们彼此属于相似的范围级别。


0

我见过的唯一真正重要的情况是这样的代码:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    }
    if (left != null) {
        return Arrays.asList(left);
    }
    if (right != null) {
        return Arrays.asList(right);
    }
    return Arrays.asList();
}

显然这里有问题。问题是,尚不清楚是否return应添加或Arrays.asList删除。如果不深入检查相关方法,就无法解决此问题。您可以通过始终使用完全穷举的if-else块来避免这种歧义。这个:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    } else if (left != null) {
        return Arrays.asList(left);
    } else if (right != null) {
        return Arrays.asList(right);
    } else {
        return Arrays.asList();
    }
}

除非您在first中添加显式return,否则不会编译(使用静态检查的语言)if

有些语言甚至不允许第一种样式。我尝试仅在严格命令性上下文中或长方法的前提下使用它:

List<?> toList() {
    if (left == null || right == null) {
        throw new IllegalStateException()
    }
    // ...
    // long method
    // ...
}

-2

如果您试图按照代码进行操作并且是错误的做法,那么从这样的函数中退出三个功能确实会造成混乱。

如果该函数有一个出口,则需要其他出口。

var result;
if(check1(value)
{
    result = -1;
}
else if(check2(value))
{
    result = value;
}
else 
{
    result = 0;
}
return result;

实际上,我们走得更远。

var result = 0;
var c1 = check1(value);
var c2 = check2(value);

if(c1)
{
    result = -1;
}
else if(c2)
{
    result = value;
}

return result;

公平地说,您可以为输入验证争取一个早期回报。只是避免将整个逻辑包装在if中。


3
此样式来自具有显式资源管理的语言。需要单点来释放分配的资源。在具有自动资源管理的语言中,单个返回点并不那么重要。您应该集中精力减少变量的数量和范围。
班塔尔16/12/11

答:问题中未指定语言。b:我认为你在这方面是错的。单身返乡有很多充分的理由。它降低了复杂性并提高了可读性
Ewan

1
有时它降低了复杂性,有时却增加了它。参见wiki.c2.com/?ArrowAntiPattern
罗伯·约翰逊

不,我认为它总是会降低圈复杂度,请参见第二个示例。check2 始终运行
Ewan

尽早归还是一种优化,它通过将一些代码移入条件代码来使代码复杂化
Ewan 2016年

-3

我宁愿省略多余的else语句。每当我看到它时(在代码审查或重构中),我想知道作者是否理解控制流以及代码中可能存在错误。

如果作者认为else语句是必需的,因此肯定不能理解return语句对控制流的影响,那么在此函数中以及一般而言,这种缺乏理解是错误的主要来源。


3
尽管我碰巧同意您的意见,但这并不是民意测验。请备份您的主张,否则选民不会对您友好。
candied_orange
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.