我是一名学习计算机科学的初级程序员,我多次被告知在循环中使用 return 语句不是一个好的编程习惯,应该避免。如今,我阅读了 Java 库中的大量代码来了解一切是如何工作的,我惊讶地发现循环中的 return 语句无处不在。
有谁可以解释我为什么应该避免在循环中使用 return 语句?
谢谢!
我是一名学习计算机科学的初级程序员,我多次被告知在循环中使用 return 语句不是一个好的编程习惯,应该避免。如今,我阅读了 Java 库中的大量代码来了解一切是如何工作的,我惊讶地发现循环中的 return 语句无处不在。
有谁可以解释我为什么应该避免在循环中使用 return 语句?
谢谢!
大多数人喜欢看一个方法并假设一个单一的出口点。当您在循环中间插入返回时,它会稍微打破流程并破坏这个假设。这就是为什么许多人会提出“不干净”的论点。它确实没有什么脏东西,但它迫使程序的“人类”读者更深入地研究该方法,以便更清楚地了解它的流程。你可以做的任何事情来减少人们为了理解你的逻辑流程而必须做的心理推理,这被认为是一件好事。
如果我可以在不阅读其内部结构的情况下浏览一个方法,同时全面了解它的作用和退出位置,那么作者做得很好。当一种最初意味着好的方法随着时间的推移而发展并迫使维护者在练习中在心理上兼顾多种状态以理解它时,就会出现问题。
一个很好的例子是经典的保护条款,它被一些人认为是好的做法。这是一种在继续之前检查状态或参数有效性的方法。如果一个方法给出了错误的东西,那么我们不想继续。看到这个假代码:
public void doSomethingCool(Object stuff) {
if(weWereNotExpectingThis(stuff)) return; //guard clause
while(somethingHappens()) {
doSomethingCool();
}
return;
}
有人走过来说,“嘿,酷!我们可以在方法中间使用 return 语句!” 他们做出改变,变成:
public void doSomethingCool(Object stuff) {
if(weWereNotExpectingThis(stuff)) return; //guard clause
while(somethingHappens()) {
if(somthingHasChanged()) return; //because my team likes early exits, Yay!
else {
doSomethingCool();
}
}
return;
}
仍然不是太破旧,但越来越多的变化进入,你最终会得到这个:
public void doSomethingCool(Object stuff) {
if(weWereNotExpectingThis(stuff)) return; //guard clause
while(somethingHappens()) {
if(somthingHasChanged()) return;
else if(weDetectNewInput()) {
doSomethingCool();
} else {
doSomethingBoring();
if(weDetectCoolInput()) {
doSomethingCool();
continue;
}
return;
}
}
return;
}
现在你明白这是怎么回事了。很难一目了然地理解我们跳到哪里或发生了什么。
这一切都归结为“干净的代码”。这句话对不同的人意味着不同的东西。在方法的底部有一个 return 语句被认为是干净的;这是主观的。
也就是说,方法可以有多个返回语句并且仍然被认为是干净的。这完全取决于您和您的团队的容忍度,更重要的是,您的高级开发人员认为干净。
当您跨越可接受的返回习惯和不可读/容易出错的代码之间的界限时,很难量化。一种安排如下的方法
method {
if(!x) return;
for(...) {
if (x && y) return
for(...) {
if (z) return;
}
}
if (a) return;
...
return;
}
不管你怎么看,它几乎都不干净。
换句话说,太多的 return 语句是一种代码味道,可能有问题的代码需要重新审视
长期以来不鼓励循环中的 return 语句的原因是因为一种称为结构化编程的范式,这基本上意味着程序中的执行流程应该简单且易于从任何地方遵循。当时,GOTO 仍在广泛使用,并且正在对代码的可读性造成噩梦;作为反对它的一部分,人们常说每个代码块应该只有一个退出点,唯一的跳转应该是条件分支、循环和函数调用。许多人仍然相信这一点,因此不喜欢循环中的 break 语句、异常和 return 语句。
我自己的观点是,能够理解特定代码块的作用比能够遵循其确切的执行流程更重要。(我经常使用函数式语言进行编程,并尝试以函数式风格编写代码,即使我不是。)因此,我认为循环中的 return 语句很好。这最终是一个品味问题。
由于我还不能评论其他答案,我不得不发布一个新答案。我会非常小心地说任何哲学“总是”是正确的。这都是关于权衡的。如果添加第二条 return 语句可以帮助您避免不必要地使用 break 语句和布尔标志来管理您的程序流,那么这可能是正确的做法。
然而,其他评论者认为返回语句、中断和标志的网络是一种“代码味道”是正确的——看看你的方法是否可以分解成更小、更模块化的部分。
那些坚持“单归”宗教的人会竭尽全力干净利落地打破循环,设置旗帜以表明该归来了。
接受这种警告的最好方法是考虑是否有充分的理由过早返回,或者是否表明在考虑如何构建代码时草率。
如果目标是干净的代码和可读性,恕我直言,最好的方法是当场退出一个函数,即return
在任何满足条件的地方,而不是最后只有一个return
。
这在函数的琐碎示例中可能并不明显,但在真正复杂工作的函数中,对我来说 1)更容易理解发生了什么 2)更容易阅读 3)IMO 它不容易出错 - 许多有时检测到错误并需要尽快修复。如果你return
最终保留一个,我已经多次看到“快速而肮脏”的修复可能会破坏代码。
但这是我个人的看法。
在过去,它可能会混淆编译器和虚拟机,但现在不再如此了,所以这通常是一种安全的做法。我认为人们也会提出普遍的“糟糕的实践”论点,即如果你是一个真正优秀的程序员,你会找到一种更优雅的方式来做同样的事情。
干杯
我认为许多人认为它更清洁(和更安全的风格)的原因是;随着函数变得越来越复杂(随着应用程序的增长,它们经常这样做),函数中间的退出点可能更难追踪,可能会变得混乱并可能导致细微的错误。
但是,与所有“规则”一样,在某些例外情况和情况下,函数中间的 return 比等待到结束可能导致的可能的 if ( !error ) 块要干净得多。