代码之家  ›  专栏  ›  技术社区  ›  Vadim

为什么“如果”的说法被认为是邪恶的?

  •  59
  • Vadim  · 技术社区  · 16 年前

    我刚从北京来 Simple Design and Testing Conference . 在其中一节课中,我们讨论了编程语言中有害的关键字。 Corey Haines 提出这个问题的,确信 if 声明是绝对邪恶的。他的替代方案是使用 predicates . 你能解释一下为什么吗 如果 这是邪恶的。

    我知道你可以写非常丑陋的代码滥用 如果

    18 回复  |  直到 14 年前
        1
  •  105
  •   vgru    5 年前

    这个 if 这种说法很少被认为是“邪恶的” goto

    它还很大程度上取决于您的编程语言和环境。在支持模式匹配的语言中,您将有很好的替换工具 如果 如果 带有函数指针的s将是错误方向的一步。因此,我将主要考虑更换。 在面向对象编程中,因为在函数式语言中, 不管怎么说,它不是惯用语言,而在纯过程语言中,您没有很多其他选项可供选择。

    然而,条件从句有时会导致代码更难管理。这不仅包括 如果 switch

    在某些情况下,使用 如果

    当您编写实用程序方法、扩展或特定的库函数时,很可能无法避免 s(你不应该)。没有更好的方法来编写这个小函数,也没有更好的方法让它更自我记录:

    // this is a good "if" use-case
    int Min(int a, int b)
    {
        if (a < b) 
           return a;
        else
           return b;
    }
    
    // or, if you prefer the ternary operator
    int Min(int a, int b)
    {
        return (a < b) ? a : b;
    }
    

    在“类型代码”上分支是一种代码气味

    另一方面,如果您遇到测试某种类型代码的代码,或者测试某个变量是否属于某种类型,那么这很可能是重构的一个很好的候选者,即 replacing the conditional with polymorphism .

    这样做的原因是,通过允许调用方在特定类型代码上进行分支,您可能会在代码中分散大量检查,从而使扩展和维护变得更加复杂。另一方面,多态性允许您使这个分支决策尽可能接近程序的根。

    // this is called branching on a "type code",
    // and screams for refactoring
    void RunVehicle(Vehicle vehicle)
    {
        // how the hell do I even test this?
        if (vehicle.Type == CAR)
            Drive(vehicle);
        else if (vehicle.Type == PLANE)
            Fly(vehicle);
        else
            Sail(vehicle);
    }
    

    通过将常见但特定于类型(即特定于类)的功能放在单独的类中,并通过虚拟方法(或接口)将其公开,您可以让程序的内部部分将此决策委托给调用层次结构中更高的人(可能在代码中的单个位置),从而更容易进行测试(模拟),扩展性和维护:

    // adding a new vehicle is gonna be a piece of cake
    interface IVehicle
    {
        void Run();
    }
    
    // your method now doesn't care about which vehicle 
    // it got as a parameter
    void RunVehicle(IVehicle vehicle)
    {
        vehicle.Run();
    }
    

    现在,您可以轻松测试您的 RunVehicle 方法的工作原理如下所示:

    // you can now create test (mock) implementations
    // since you're passing it as an interface
    var mock = new Mock<IVehicle>();
    
    // run the client method
    something.RunVehicle(mock.Object);
    
    // check if Run() was invoked
    mock.Verify(m => m.Run(), Times.Once());
    

    如果 条件可以重复使用

    关于替换的争论 如果 在您的问题中有一个“谓词”,Haines可能想提到,有时代码中存在类似的模式,它们只在条件表达式中有所不同。条件表达式确实与 如果 s、 但整个想法是将重复模式提取到单独的方法中,将表达式作为参数。这是LINQ已经做过的事, 通常 与替代方案相比,产生更干净的代码 foreach :

    考虑这两种非常相似的方法:

    // average male age
    public double AverageMaleAge(List<Person> people)
    {
        double sum = 0.0;
        int count = 0;
        foreach (var person in people)
        {
           if (person.Gender == Gender.Male)
           {
               sum += person.Age;
               count++;
           }
        }
        return sum / count; // not checking for zero div. for simplicity
    }
    
    // average female age
    public double AverageFemaleAge(List<Person> people)
    {
        double sum = 0.0;
        int count = 0;
        foreach (var person in people)
        {
           if (person.Gender == Gender.Female) // <-- only the expression
           {                                   //     is different
               sum += person.Age;
               count++;
           }
        }
        return sum / count;
    }
    

    这表明您可以将条件提取到谓词中,为这两种情况(以及许多其他未来情况)留下一个方法:

    // average age for all people matched by the predicate
    public double AverageAge(List<Person> people, Predicate<Person> match)
    {
        double sum = 0.0;
        int count = 0;
        foreach (var person in people)
        {
           if (match(person))       // <-- the decision to match
           {                        //     is now delegated to callers
               sum += person.Age;
               count++;
           }
        }
        return sum / count;
    }
    
    var males = AverageAge(people, p => p.Gender == Gender.Male);
    var females = AverageAge(people, p => p.Gender == Gender.Female);
    

    由于LINQ已经有了一系列像这样方便的扩展方法,实际上您甚至不需要编写自己的方法:

    // replace everything we've written above with these two lines
    var males = list.Where(p => p.Gender == Gender.Male).Average(p => p.Age);
    var females = list.Where(p => p.Gender == Gender.Female).Average(p => p.Age);
    

    在最后一个LINQ版本中 如果

    1. 老实说,问题不在 如果 它本身,但在整个代码模式中(仅仅因为它是重复的),以及
    2. 这个 如果 实际上仍然存在,但它写在LINQ中 Where 扩展方法,已测试并关闭以进行修改。拥有更少的自己的代码总是一件好事:需要测试的事情更少,出错的事情更少,代码更易于遵循、分析和维护。

    大量嵌套的 如果 / else 声明

    当你看到一个函数跨越1000行并且有几十个嵌套的 如果

    1. 使用更好的数据结构,并以更合适的方式组织输入数据(例如,哈希表,它将在一次调用中将一个输入值映射到另一个输入值),
    2. 使用一个公式、一个循环,或者有时只使用一个在10行或更少行中执行相同逻辑的现有函数(例如。 this notorious example 我想到了,但总的想法适用于其他情况),
    3. 使用保护子句防止嵌套(保护子句使整个函数中变量的状态更加可靠,因为它们会尽快消除异常情况),
    4. 至少用一个 转换 在适当情况下发表声明。

    当你觉得这是一种代码味道时重构,但不要过度设计

    说了这么多,你 should not spend sleepless nights

        2
  •  89
  •   flybywire    16 年前

    还有另一种意义,即 if 可能是邪恶的:当它出现而不是多态性时。

    例如。

     if (animal.isFrog()) croak(animal)
     else if (animal.isDog()) bark(animal)
     else if (animal.isLion()) roar(animal)
    

     animal.emitSound()
    

    但基本上,if是一个完全可以接受的工具。当然,它可以被滥用和滥用,但它与后藤的地位相差甚远。

        3
  •  36
  •   Nathan    13 年前

    完整代码中的良好引用:

    编写代码,就好像维护你的程序的人是一个暴力的精神病患者 知道你住在哪里。
    -匿名的

    瞧,简单点。如果应用程序的可读性将通过在特定区域使用谓词得到增强,请使用它。否则,请使用“如果”并继续。

        4
  •  18
  •   Kyle Rosendo    16 年前

    如果你有一个简单的 if..else 声明,为什么要使用 predicate ?

    如果可以,请使用 switch 更大的 if 替换,然后如果选择对大型操作使用谓词(在有意义的地方,否则您的代码将是维护的噩梦),请使用它。

    这家伙对我来说似乎有点迂腐。全部替换 与谓词的关系只是一个疯狂的话题。

        5
  •  15
  •   Keith Bloom    16 年前

    有一个 Anti-If 今年早些时候开始的运动。主要前提是许多嵌套的if语句通常可以用多态性替换。

        6
  •  10
  •   Paul Nathan    16 年前

    if 不是邪恶的(我也认为将道德赋予代码编写实践是愚蠢的…)。

        7
  •  10
  •   Kaleb Brasee    16 年前

    就像圣经中关于金钱的诗句,如果陈述不是邪恶的——爱如果陈述是邪恶的。一个没有if语句的程序是一个荒谬的想法,在必要时使用它们是必要的。但是一个连续有100个if-else-if块的程序(不幸的是,我已经看到)绝对是邪恶的。

        8
  •  8
  •   Jason Baker    16 年前

    我必须说,我最近开始将if语句视为一种代码气味:特别是当您发现自己多次重复相同的条件时。但是关于代码气味,您需要了解一些事情:它们并不一定意味着代码是坏的。他们只是说有一个好的 代码不好。

    例如,Martin Fowler将注释列为代码气味,但我不会认真对待那些说“注释是邪恶的,不要使用它们”的人。

    一般来说,如果可能的话,我更喜欢使用多态性而不是if语句。这只会减少犯错误的空间。我倾向于发现,很多时候,使用条件也会导致大量的tramp参数(因为您必须将形成条件的数据传递给适当的方法)。

        9
  •  7
  •   Rap    16 年前

    他错了 . 这样的事情你可以做得太过分,太聪明了,不利于你自己。

    用谓词而不是ifs创建的代码将难以维护和测试。

        10
  •  4
  •   Lee B    16 年前

    谓词来自逻辑/声明性编程语言,如PROLOG。对于某些类型的问题,比如约束求解,它们可以说比很多冗长的一步一步的解决方法优越——如果这样做了,那么就做这些废话。用命令式语言解决的冗长而复杂的问题可以在PROLOG中用几行代码完成。

    还有可伸缩编程的问题(由于向多核、web等的发展)。If语句和命令式编程通常倾向于循序渐进,不可伸缩。不过,逻辑声明和lambda演算描述了如何解决问题,以及问题可以分解成哪些部分。因此,执行该代码的解释器/处理器可以有效地将代码分解为多个片段,并将其分发到多个CPU/内核/线程/服务器。

    绝对不是到处都有用;我不想尝试用谓词而不是if语句编写设备驱动程序。但是,是的,我认为要点可能是合理的,至少值得熟悉,如果不是一直使用的话。

        11
  •  4
  •   user229044    13 年前

    也许在量子计算中,不使用IF语句是一种明智的策略,而是让计算的每一个分支继续进行,只让函数在结束时“崩溃”以得到有用的结果。

        12
  •  3
  •   Matthew Scharley    16 年前

    谓词的唯一问题(在替换方面) if

    function void Test(Predicate<int> pr, int num) 
    {
        if (pr(num))
        { /* do something */ }
        else
        { /* do something else */ }
    }
    

    你当然可以使用三元运算符( ?: ),但这只是一个例子 变相声明。。。

        13
  •  3
  •   madlep    16 年前

    这可能归结为希望降低代码圈复杂度,并减少函数中分支点的数量。如果一个函数很容易分解成许多较小的函数,并且每个函数都可以测试,那么您可以降低复杂性并使代码更易于测试。

        14
  •  2
  •   Gordon Mackie JoanMiro    16 年前

    海事组织: 我怀疑他是想挑起一场辩论,让人们思考“如果”的滥用。没有人会认真地建议完全避免这种编程语法的基本构造,是吗?

        15
  •  2
  •   slim    11 年前

    有时有必要采取极端的立场来表明你的观点。我相信这个人用的是 if --但是每次你使用 如果 ,值得考虑一下不同的模式是否会使代码更清晰。

    偏好多态性而不是 是这个问题的核心。而不是:

    if(animaltype = bird) {
         squawk();
    } else if(animaltype = dog) {
         bark();
    }
    

    ... 使用:

    animal.makeSound();
    

    如果 告诉你的是,你需要创建这个接口。

    那么在现实世界中,什么样的 我们看到了什么导致了多态性解决方案?

    if(logging) {
          log.write("Did something");
    }
    

    this.logger = new NullLogger();    // logger.log() does nothing
    this.logger = new StdOutLogger();  // logger.log() writes to stdout
    

    这就把我们带到了 战略模式 .

    而不是:

    if(user.getCreditRisk() > 50) {
         decision = thoroughCreditCheck();
    } else if(user.getCreditRisk() > 20) {
         decision = mediumCreditCheck();
    } else {
         decision = cursoryCreditCheck();
    }
    

    decision = getCreditCheckStrategy(user.getCreditRisk()).decide();
    

    当然 getCreditCheckStrategy() 可能包含 如果 --这很可能是合适的。你把它推到了一个整洁的地方。

        16
  •  1
  •   Jakub Troszok    16 年前

    很好 红宝石 我们有 ;)

    但很可能是真的 如果 下一个是什么 后藤 ,即使大多数人认为它是邪恶的,但在某些情况下,它会简化/加速事情(在某些情况下,如低级别高度优化的代码,它是必须的)。

        17
  •  1
  •   VB Guy    13 年前

    我认为如果陈述是邪恶的,但如果表达不是。在这种情况下,我所说的if表达式可以是类似于C#三元运算符(condition?trueExpression:false表达式)的东西。这不是坏事,因为它是一个纯函数(在数学意义上)。它计算为一个新值,但对其他任何内容都没有影响。因此,它在替代模型中工作。

        18
  •  -2
  •   Volker G.    13 年前

    如果不是邪恶!考虑

    int sum(int a, int b) {
        return a + b;
    }
    

    无聊,嗯?现在添加了一个if。。。

    int sum(int a, int b) {
        if (a == 0 && b == 0) {
            return 0;
        }
        return a + b;
    }
    

    ... 您的代码创建效率(以LOC为单位)提高了一倍。

    代码可读性也有了很大的提高,因为现在你可以在眨眼之间看到当两个参数都为零时的结果。你不能在上面的代码中这样做,是吗?

    此外,您还支持testteam,因为他们现在可以将代码覆盖率测试工具的使用推向极限。

    由于if的存在,首先只需要对代码进行轻微的更改。

    int sum(int a, int b) {
        if (a == 0 || b == 0) {
            return 0;
        }
        return a + b;
    }
    

    如果不是从一开始就发明了if,那么还需要多少代码更改。

    感谢将是你的所有方面。

    结论:如果是,就永远不够了。

    好了。到