当我在我的代码上运行ReSharper时,例如:
if (some condition) { Some code... }
ReSharper给了我上面的警告(反转"if"声明以减少嵌套),并提出了以下更正:
if (!some condition) return; Some code...
我想明白为什么那样更好.我一直认为在方法中间使用"返回"有问题,有点像"goto".
它不仅美观,而且还降低了方法内的最大嵌套水平.这通常被认为是一个优点,因为它使方法更容易理解(实际上,许多 静态 分析 工具提供了一种衡量代码质量指标的方法).
另一方面,它也使你的方法有多个退出点,这是另一群人认为是禁止的.
就我个人而言,我同意ReSharper和第一组(用一种例外的语言我发现讨论"多个退出点"很愚蠢;几乎任何东西都可以抛出,因此所有方法都有很多潜在的退出点).
关于性能:在每种语言中,两个版本应该是等效的(如果不是在IL级别,那么肯定在抖动通过代码之后).从理论上讲,这取决于编译器,但实际上今天任何广泛使用的编译器都能够处理比这更高级的代码优化案例.
方法中间的返回不一定是坏的.如果它使代码的意图更清晰,那么立即返回可能会更好.例如:
double getPayAmount() { double result; if (_isDead) result = deadAmount(); else { if (_isSeparated) result = separatedAmount(); else { if (_isRetired) result = retiredAmount(); else result = normalPayAmount(); }; } return result; };
在这种情况下,如果_isDead
是,我们可以立即退出该方法.以这种方式构造它可能更好:
double getPayAmount() { if (_isDead) return deadAmount(); if (_isSeparated) return separatedAmount(); if (_isRetired) return retiredAmount(); return normalPayAmount(); };
我从重构目录中选择了这段代码.这种特定的重构称为:使用Guard子句替换嵌套的条件.
这有点宗教争论,但我同意ReSharper的观点,你应该更喜欢筑巢.我相信这超过了从函数中获得多个返回路径的负面影响.
嵌套较少的关键原因是提高代码可读性和可维护性.请记住,许多其他开发人员将来需要阅读您的代码,而缩进较少的代码通常更容易阅读.
前提条件是在函数开始时可以提前返回的一个很好的例子.为什么函数的其余部分的可读性会受到前置条件检查的影响?
至于从方法中多次返回的负面影响 - 现在调试器非常强大,并且很容易找到特定函数返回的确切位置和时间.
函数中有多个返回值不会影响维护程序员的工作.
代码可读性差.
正如其他人所提到的,不应该有性能损失,但还有其他考虑因素.除了那些有效的顾虑之外,在某些情况下,这也可能会让你陷入困境.假设你正在处理一个double
代替:
public void myfunction(double exampleParam){ if(exampleParam > 0){ //Body will *not* be executed if Double.IsNan(exampleParam) } }
与看似等效的反演形成鲜明对比:
public void myfunction(double exampleParam){ if(exampleParam <= 0) return; //Body *will* be executed if Double.IsNan(exampleParam) }
因此,在某些情况下,似乎是正确倒置的if
可能不是.
仅在函数结束时返回的想法从语言支持异常之前的几天开始回归.它使程序能够依赖于能够在方法结束时放置清理代码,然后确保它将被调用,并且其他程序员不会隐藏导致清理代码被跳过的方法中的返回.跳过的清理代码可能导致内存或资源泄漏.
但是,在支持异常的语言中,它不提供此类保证.在支持异常的语言中,任何语句或表达式的执行都可能导致控制流导致该方法结束.这意味着必须通过使用finally或using关键字来进行清理.
无论如何,我说我认为很多人引用"方法结束时的唯一回报"指南而不理解为什么这是一件好事,并且减少嵌套以提高可读性可能是一个更好的目标.
我想补充一点,那些倒置的名字是 - Guard Clause.我尽可能地使用它.
我讨厌阅读代码,如果在开头有两个代码屏幕而没有别的.只需反转if并返回.这样,没有人会浪费时间滚动.
http://c2.com/cgi/wiki?GuardClause
它不仅影响美学,还会阻止代码嵌套.
它实际上可以作为确保数据有效的前提条件.
这当然是主观的,但我认为它在两点上有很大改进:
现在很明显,如果condition
保持,你的功能还有什么可做的.
它保持嵌套水平.嵌套会比您想象的更容易损害可读性.
多个返回点是C中的问题(在较小程度上是C++),因为它们强制您在每个返回点之前复制清理代码.垃圾收集,try
| finally
构建和using
阻止,你真的没有理由害怕它们.
归根结底,它归结为您和您的同事更容易阅读的内容.
在性能方面,两种方法之间没有明显的区别.
但编码不仅仅是性能.清晰度和可维护性也非常重要.而且,在这种不影响性能的情况下,这是唯一重要的事情.
关于哪种方法更可取,有各种各样的思想流派.
一种观点是其他人提到的观点:第二种方法降低了嵌套级别,从而提高了代码清晰度.这在命令式的风格中是很自然的:当你没有什么可做的时候,你也可以尽早回归.
从功能性更强的角度来看,另一种观点是方法应该只有一个出口点.功能语言中的一切都是表达.所以如果语句必须总是有一个else子句.否则if表达式不会总是有值.所以在功能风格上,第一种方法更自然.
保护条款或前提条件(您可能会看到)检查是否满足某个条件,然后打破程序流程.它们非常适合那些你真正只对一个if
陈述的结果感兴趣的地方.所以不要说:
if (something) { // a lot of indented code }
如果满足相反的条件,您可以反转条件并中断
if (!something) return false; // or another value to show your other code the function did not execute // all the code from before, save a lot of tabs
return
远不如肮脏goto
.它允许您传递一个值来显示函数无法运行的其余代码.
您将看到可以在嵌套条件中应用此位置的最佳示例:
if (something) { do-something(); if (something-else) { do-another-thing(); } else { do-something-else(); } }
VS:
if (!something) return; do-something(); if (!something-else) return do-something-else(); do-another-thing();
你会发现很少有人认为第一个是更清洁,但当然,这是完全主观的.一些程序员喜欢知道什么条件是通过缩进操作的,而我宁愿保持方法流线性.
我暂时不会建议precons会改变你的生活或让你得到你的生活,但你可能会发现你的代码更容易阅读.
这里有几个好点,但如果方法非常冗长,多个返回点也是不可读的.话虽如此,如果您要使用多个返回点,请确保您的方法很短,否则多个返回点的可读性加值可能会丢失.
表演分为两部分.当软件处于生产阶段时,您可以获得性能,但您也希望在开发和调试时获得性能.开发人员想要的最后一件事是"等待"一些微不足道的事情.最后,通过启用优化来编译它将导致类似的代码.因此,了解这些在两种情况下都能获得回报的小技巧是很好的.
问题的案例很清楚,ReSharper是正确的.if
您可以在方法的开头设置一个明确的规则,而不是嵌套语句,并在代码中创建新的范围.它增加了可读性,更易于维护,并且减少了必须筛选以找到他们想要去的地方的规则数量.
我个人更喜欢只有1个出口点.如果你保持方法的简洁和重点,它很容易实现,它为下一个处理代码的人提供了一个可预测的模式.
例如.
bool PerformDefaultOperation() { bool succeeded = false; DataStructure defaultParameters; if ((defaultParameters = this.GetApplicationDefaults()) != null) { succeeded = this.DoSomething(defaultParameters); } return succeeded; }
如果您只想在函数退出之前检查函数中某些局部变量的值,这也非常有用.您需要做的就是在最终返回时放置一个断点,并保证您可以点击它(除非抛出异常).
关于代码如何看起来很多很好的理由.但结果怎么样?
让我们来看看一些C#代码及其IL编译形式:
using System;
public class Test {
public static void Main(string[] args) {
if (args.Length == 0) return;
if ((args.Length+2)/3 == 5) return;
Console.WriteLine("hey!!!");
}
}
这个简单的代码片段可以编译.您可以使用ildasm打开生成的.exe文件,并检查结果是什么.我不会发布所有汇编程序的东西,但我会描述结果.
生成的IL代码执行以下操作:
如果第一个条件为false,则跳转到第二个条件的代码.
如果它是真的跳转到最后一条指令.(注意:最后一条指令是退货).
在第二个条件下,在计算结果后也会发生相同的情况.比较和:如果为false则转到Console.WriteLine,如果是,则转到最后.
打印邮件并返回.
所以似乎代码将跳到最后.如果我们使用嵌套代码执行常规操作会怎么样?
using System; public class Test { public static void Main(string[] args) { if (args.Length != 0 && (args.Length+2)/3 != 5) { Console.WriteLine("hey!!!"); } } }
IL指令的结果非常相似.区别在于每个条件跳转之前:如果为false则转到下一段代码,如果为true则转到结束.现在IL代码更好地流动并且有3次跳转(编译器对此进行了优化):1.第一次跳转:当Length为0时,代码再次跳转(第三次跳转)到结束.2.第二:在第二个条件中间避免一条指令.3.第三:如果第二个条件是假的,跳到最后.
无论如何,程序计数器总是会跳转.