代码之家  ›  专栏  ›  技术社区  ›  Lea Cohen mark winkle

反转“if”语句以减少嵌套

  •  243
  • Lea Cohen mark winkle  · 技术社区  · 16 年前

    当我跑的时候 ReSharper 例如,在我的代码上:

        if (some condition)
        {
            Some code...            
        }
    

    ReSharper给了我上述警告(反转“if”语句以减少嵌套),并建议进行以下更正:

       if (!some condition) return;
       Some code...
    

    我想知道为什么这样更好。我一直认为使用“返回”的方法中间有问题,有点像“GOTO”。

    24 回复  |  直到 10 年前
        1
  •  347
  •   hdoghmen Neil Knight    9 年前

    这不仅仅是审美 ,但它也降低了成本 maximum nesting level many static analysis tools 提供这方面的度量,作为代码质量的指标之一)。

    另一方面,它也使您的方法具有多个退出点,而另一组人认为这是不允许的。

    就个人而言,我同意ReSharper和第一组(在一种有例外的语言中,我发现讨论“多个退出点”是愚蠢的;几乎任何东西都可能抛出,因此所有方法中都有许多潜在的退出点)。

    关于性能 应该 在每种语言中都是等效的(如果不是在IL级别,那么肯定是在抖动通过代码之后)。理论上这取决于编译器,但实际上今天任何广泛使用的编译器都能够处理比这更高级的代码优化情况。

        2
  •  320
  •   Joshua Pinter    10 年前

    方法中间的回报并不一定是坏事。如果能让代码的意图更清楚,那么最好立即返回。例如:

    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();
    };   
    

    我从数据库中选择了这个代码 refactoring catalog

        3
  •  104
  •   Peter Mortensen icecrime    10 年前

    这是一个有点宗教色彩的论点,但我同意ReSharper的观点,你应该选择更少的筑巢。我相信这比从一个函数获得多个返回路径的负面影响更大。

    代码可读性和可维护性 . 请记住,许多其他开发人员将来需要阅读您的代码,缩进较少的代码通常更易于阅读。

    前提条件 是一个很好的例子,说明在函数开始时可以提前返回。为什么函数其余部分的可读性会受到前提条件检查的影响?

    在一个函数中有多个返回不会影响维护程序员的工作。

    代码可读性差将导致错误。

        4
  •  70
  •   Michael McGowan    13 年前

    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 可能不是。

        5
  •  54
  •   Scott Langham    10 年前

    只在函数结束时返回的想法是从语言支持异常的前几天开始的。它使程序能够依赖于能够将清理代码放在方法的末尾,然后确保它将被调用,并且其他程序员不会在方法中隐藏导致跳过清理代码的返回。跳过的清理代码可能导致内存或资源泄漏。

    但是,在支持异常的语言中,它不提供此类保证。在支持异常的语言中,任何语句或表达式的执行都可能导致导致方法结束的控制流。这意味着必须通过使用finally或关键字来完成清理。

    无论如何,我想说的是,我认为很多人引用了“方法末尾的唯一返回”准则,却不理解为什么这样做是一件好事,减少嵌套以提高可读性可能是一个更好的目标。

        6
  •  34
  •   Piotr Perak    13 年前

    我想补充一点,倒排的if-s-Guard子句是有名字的。只要我能,我就用它。

    我讨厌在一开始有if的地方阅读代码,两屏代码,没有其他。只要反转if并返回。这样就没有人会浪费时间滚动了。

    http://c2.com/cgi/wiki?GuardClause

        7
  •  22
  •   Rion Williams    11 年前

    它不仅影响美观,还防止代码嵌套。

        8
  •  18
  •   Deestan    16 年前

    这当然是主观的,但我认为它在两点上有很大的改进:

    • 现在很明显,如果 condition 持有。
    • 它可以降低筑巢的水平。嵌套对可读性的伤害比你想象的要大。
        9
  •  15
  •   Richard Poole MatzFan    16 年前

    在C语言中,多个返回点是一个问题(在较小程度上是C++),因为它们迫使您在每个返回点之前复制清理代码。通过垃圾收集 try finally 构造与 using 积木,你真的没有理由害怕它们。

    归根结底,这取决于你和你的同事觉得什么更容易阅读。

        10
  •  12
  •   Oli    16 年前

    保护子句或前置条件(如您可能看到的)检查是否满足某个条件,然后中断程序流。它们非常适合那些你只对一个测试结果感兴趣的地方 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();
        }
    }
    

    if (!something) return;
    do-something();
    
    if (!something-else) return do-something-else();
    do-another-thing();
    

    我一刻也不会建议precons会改变你的生活或者让你上床,但是你可能会发现你的代码只是稍微容易一点。

        11
  •  12
  •   Jeffrey Sax    13 年前

    就性能而言,这两种方法之间没有明显的区别。

    对于哪种方法更可取,存在着相互竞争的思想流派。

    另一种观点是,从功能性更强的风格来看,一个方法应该只有一个出口点。函数式语言中的一切都是表达式。所以if语句必须总是有else子句。否则,if表达式并不总是有值。因此,在功能性风格中,第一种方法更自然。

        12
  •  9
  •   Jon Limjap    16 年前

    可能无法阅读 同样,如果方法非常冗长。也就是说,如果要使用多个返回点,请确保方法简短,否则多个返回点的可读性优势可能会丢失。

        13
  •  9
  •   Peter Mortensen icecrime    13 年前

    问题中的情况很清楚,ReSharper是正确的。而不是筑巢 if 语句,并在代码中创建新的作用域,您在方法的开头设置了一个明确的规则。它增加了可读性,更易于维护,并且减少了需要筛选才能找到目标的规则数量。

        14
  •  7
  •   ilitirit    16 年前

    就我个人而言,我只喜欢一个出口点。如果您保持方法简短、切中要害,那么很容易实现,并且它为下一个处理代码的人提供了一个可预测的模式。

    如。

     bool PerformDefaultOperation()
     {
          bool succeeded = false;
    
          DataStructure defaultParameters;
          if ((defaultParameters = this.GetApplicationDefaults()) != null)
          {
               succeeded = this.DoSomething(defaultParameters);
          }
    
          return succeeded;
     }
    

        15
  •  6
  •   Nikita    4 年前

    有很多很好的理由 代码是什么样子的 . 但是呢 ?

    让我们来看看一些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!!!");
        }
    }
    

    这个简单的代码片段可以编译。您可以打开生成的 .exe 存档 ildasm 然后检查结果是什么。我不会发布所有汇编程序的东西,但我会描述结果。

    1. 如果第一个条件是 false ,跳到第二个字符所在的代码。
    2. 如果是 true
    3. 在第二种情况下,计算结果后也会发生同样的情况。比较和:到达 Console.WriteLine 如果 错误的 或者,如果这是真的 符合事实的 .
    4. 打印消息并返回。

    因此,代码似乎会跳到最后。如果我们使用嵌套代码执行正常的if呢?

    using System;
    
    public class Test {
        public static void Main(string[] args) {
            if (args.Length != 0 && (args.Length+2)/3 != 5) 
            {
                Console.WriteLine("hey!!!");
            }
        }
    }
    

    结果在IL指令中非常相似。不同之处在于,之前每个条件有两次跳跃:如果 如果需要,请转到下一段代码 符合事实的

    1. 第二种:在第二种情况下避免一种指令。
    2. 第三:如果第二个条件是 错误的

    无论如何,程序计数器将始终跳转。

        16
  •  5
  •   nevyn jfg956    6 年前

    从理论上讲,倒转 if 如果增加分支预测命中率,则可能会导致更好的性能。在实践中,我认为很难确切地知道分支预测的行为,尤其是在编译之后,所以我不会在日常开发中这样做,除非我正在编写汇编代码。

    更多关于分支预测的信息 here

        17
  •  4
  •   unwind    16 年前

    这完全是有争议的。在提前返回的问题上没有“程序员之间的协议”。据我所知,这总是主观的。

    我认为你不会得到这个问题的最终答案。

        18
  •  4
  •   shibumi    13 年前

    避免多个出口点 可以

    但是在99%的情况下,保存额外的构造函数和析构函数调用并不值得失去可读性 if 块引入(正如其他人所指出的)。

        19
  •  3
  •   Ingo Schalk-Schupp    13 年前

    这里已经有很多有见地的答案,但我还是想指出一个稍微不同的情况:与其先决条件,不如先把它放在函数的顶部。实际上,可以考虑一步一步的初始化,在初始化过程中,您必须检查每一步是否成功,然后继续下一步。在这种情况下,您无法检查顶部的所有内容。

    在使用Steinberg的ASIOSDK编写ASIO主机应用程序时,我发现我的代码真的不可读,因为我遵循了嵌套范式。就像上面安德鲁·布洛克提到的那样,它有八层深,我看不到设计上的缺陷。当然,我可以将一些内部代码打包到另一个函数中,然后将剩余的级别嵌套在其中以使其更具可读性,但对我来说这似乎是随机的。

    通过用guard子句替换嵌套,我甚至发现了我的一个错误观念,那就是清理代码的一部分应该在函数中更早地出现,而不是在最后出现。对于嵌套的分支,我永远不会看到,你甚至可以说它们导致了我的误解。

    因此,这可能是另一种情况,在这种情况下,反转的ifs有助于生成更清晰的代码。

        20
  •  2
  •   Colin Pickard    16 年前

    这是意见的问题。

    我通常的方法是避免单行IFS,并在方法的中间返回。

        21
  •  2
  •   JohnIdol    16 年前

    在我看来,如果您只是返回void(或者一些您永远不会检查的无用返回代码),那么提前返回是可以的,并且它可能会提高可读性,因为您避免嵌套,同时明确表示您的函数已经完成。

    如果您实际返回的是returnValue,嵌套通常是一种更好的方法,因为您只在一个位置返回returnValue(在末尾-duh),并且它可能会使您的代码在很多情况下更易于维护。

        22
  •  1
  •   Marcin Gołembiewski    5 年前

    我不确定,但我认为R#会尽量避免跳远。当您使用IF-ELSE时,编译器会执行如下操作:

    真实条件标签: 说明1 说明

    错误的条件标签: ... 说明

    端块

    若条件为true,则不会跳转,也不会卷展一级缓存,但跳转到false_条件_标签可能非常远,处理器必须卷展自己的缓存。同步缓存很昂贵。R#尝试将远跳转替换为短跳转,在这种情况下,所有指令都已在缓存中的可能性更大。

        23
  •  0
  •   Bluenuance    16 年前

    我想这取决于你喜欢什么,如前所述,目前还没有达成一致意见。

        24
  •  0
  •   Joshi Spawnbrood    16 年前

    我的想法是,“在函数中间”的返回不应该如此“主观”。 原因很简单,以下面的代码为例:

        function do_something( data ){
    
          if (!is_valid_data( data )) 
                return false;
    
    
           do_something_that_take_an_hour( data );
    
           istance = new object_with_very_painful_constructor( data );
    
              if ( istance is not valid ) {
                   error_message( );
                    return ;
    
              }
           connect_to_database ( );
           get_some_other_data( );
           return;
        }
    

    也许第一次“返回”不是那么直观,但这真的节省了。 关于干净代码的“想法”太多了,需要更多的练习才能丢掉“主观”的坏想法。

        25
  •  0
  •   David Allan Finch    16 年前

    这种编码有几个优点,但对我来说最大的好处是,如果你能快速返回,你可以提高应用程序的速度。IE我知道,因为前提条件X,我可以很快返回错误。这将首先消除错误情况,并降低代码的复杂性。在很多情况下,由于cpu管道现在可以更干净,它可以阻止管道崩溃或交换机。其次,如果您处于循环中,快速中断或返回可以节省大量cpu。一些程序员使用循环不变量来实现这种快速退出,但这样会破坏cpu管道,甚至产生内存寻道问题,这意味着cpu需要从外部缓存加载。但基本上我认为你应该做你想做的,那就是结束循环或函数,而不是仅仅为了实现正确代码的抽象概念而创建复杂的代码路径。如果你唯一的工具是锤子,那么一切看起来都像钉子。