为什么'if'陈述被认为是邪恶的?

Vad*_*dim 64 programming-languages if-statement

我刚刚参加了简单设计和测试会议.在其中一个会话中,我们讨论的是编程语言中的邪恶关键词. 科瑞海恩斯,谁提出的问题,确信if说法是绝对的邪恶.他的另一种选择是用谓词创建函数.你能告诉我为什么if是邪恶的.

我知道你可以编写非常难看的代码滥用if.但我不相信它那么糟糕.

fly*_*ire 84

还有另一种if可能是邪恶的感觉:当它来源而不是多态性时.

例如

 if (animal.isFrog()) croak(animal)
 else if (animal.isDog()) bark(animal)
 else if (animal.isLion()) roar(animal)
Run Code Online (Sandbox Code Playgroud)

代替

 animal.emitSound()
Run Code Online (Sandbox Code Playgroud)

但基本上,如果它是一个完全可以接受的工具.它当然可以被滥用和滥用,但它远不及goto的地位.

  • 我同意这一点.这与switch语句有同样的问题.在这种情况下,我们可以采用策略模式. (5认同)
  • 在动态语言中,`if(obj is Foo)`是非常明智的,因为你没有类型参数.大多数情况下,你真的想要鸭子型,但有时候内置类型,只有真实的东西就足够了,在这种情况下,你想为除了'Foo`以外的任何东西引发错误,并且你需要`if(obj is Foo)`. (3认同)
  • 最初,当书籍/人们说“如果”不好时,正是在这种多态性的背景下。我完全同意。后来在某些时候,有些人在不知道原始上下文或原因的情况下重复了这一点,这很可惜。 (2认同)

Gro*_*roo 77

条件子句有时会导致代码更难管理.这不仅包括if语句,更常见的是switch语句,它通常包括比相应的更多的分支if.

有些情况下使用它是完全合理的 if

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

// 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;
}
Run Code Online (Sandbox Code Playgroud)

分支"类型代码"是代码气味

另一方面,如果您遇到测试某种类型代码的代码,或者测试变量是否属于某种类型,那么这很可能是重构的良好候选者,即用多态替换条件.

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

考虑:

// 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);
}
Run Code Online (Sandbox Code Playgroud)

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

// 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();
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以轻松测试您的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());
Run Code Online (Sandbox Code Playgroud)

只能在if条件上不同的模式可以重复使用

关于if在你的问题中用"谓词" 替换的论点,Haines可能想要提到有时候你的代码中存在类似的模式,这些模式仅在条件表达式上有所不同.条件表达式与ifs 一起出现,但整个想法是将重复模式提取到单独的方法中,将表达式作为参数.这就是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;
}
Run Code Online (Sandbox Code Playgroud)

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

// 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);
Run Code Online (Sandbox Code Playgroud)

而且由于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);
Run Code Online (Sandbox Code Playgroud)

在最后一个LINQ版本中,if语句完全"消失",但是:

  1. 说实话,这个问题if本身并不存在,而是整个代码模式(仅仅因为它是重复的),而且
  2. if实际上仍然存在,但它是在LINQ Where扩展方法中编写的,该方法已经过测试并关闭以进行修改.拥有较少的自己的代码总是一件好事:要测试的东西少,出错的东西少,代码更容易遵循,分析和维护.

当你觉得它是代码味道时重构,但不要过度工程

说完这一切之后,你不应该因为现在和那里有几个条件而度过不眠之夜.虽然这些答案可以提供一些一般的经验法则,但能够检测需要重构的构造的最佳方法是通过经验.随着时间的推移,出现了一些模式,导致一遍又一遍地修改相同的条款.

  • 模式匹配的一个很好的例子,结果只有一个小问题:“sum / people.Count”不正确,因为它将部分总和除以所有人的计数。考虑在 15 名程序员组成的团队中只考虑一名女性。女性的实际年龄是 45 岁,但除以 15 后,女性的平均年龄是 3 岁。这不是正确的平均年龄...... (2认同)

Jor*_*mer 32

Code Complete的一句好话:

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

我保持简单.如果通过在特定区域中使用谓词来增强应用程序的可读性,请使用它.否则,使用'if'并继续前进.

  • 啊,但Code Complete的引用包含一个if语句,因此是邪恶的.:-) (19认同)
  • @JasonBaker你的评论也有"那个"陈述,所以你的评论也是邪恶的.让我们在这里停止递归:) (4认同)

Kyl*_*ndo 17

我认为这取决于你所做的事实.

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

如果可以,可以使用switch更大的if替换,然后如果选择将谓词用于大型操作(有意义的话,否则您的代码将成为维护的噩梦),请使用它.

这个家伙似乎对我的喜欢有点迂腐.if用Predicates 取代所有的东西只是疯狂的谈话.

  • 我不喜欢switch语句.我更喜欢使用策略模式. (9认同)
  • 我和Kyle在一起,这只是疯狂的谈话.如果没有GOTO,你就无法在没有条件的情况下进行编程.切换,谓词,调度表等只是IF的语法糖. (7认同)

Kei*_*oom 14

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

我很想看到使用Predicate的例子.这更像功能编程吗?

  • 这看起来和听起来像另一个营利性的子宗教开始,希望让程序员购买关于他们会更好,更完全通过实践学习的东西的书籍. (5认同)
  • 看起来它们不是反 if,而是反 switch-writing-as-if。 (3认同)
  • **叹气**所以,基本上,我必须彻底复杂化我的代码才能摆脱'If'语句?这相当于使用代码混淆来满足代码纯粹主义者.谢谢,但没有. (3认同)
  • 甚至没有听说过反如果运动。似乎有点极端,但我同意他们所说的基本原则。 (2认同)

Pau*_*han 10

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

海恩斯先生很傻,应该被嘲笑.


Kal*_*see 9

就像关于金钱的圣经经文一样,如果陈述不是邪恶的 - 那么陈述的爱就是邪恶的.没有if语句的程序是一个荒谬的想法,必要时使用它们是必不可少的.但是一个程序有100个if-else if连续的块(遗憾的是,我已经看到了)绝对是邪恶的.


Jas*_*ker 8

我不得不说,我最近已经开始将陈述视为代码气味:特别是当你发现自己多次重复相同的情况时.但是你需要了解一些关于代码味道的东西:它们并不一定意味着代码很糟糕.他们只是说,有一个很好的机会的代码是坏的.

例如,评论被Martin Fowler列为代码气味,但我不会认真对待任何人说"评论是邪恶的;不要使用它们".

但一般来说,我更喜欢使用多态而不是if语句.这只会减少错误的空间.我倾向于发现很多时候,使用条件也会导致很多tramp参数(因为你必须将形成条件所需的数据传递给适当的方法).

  • 马丁福勒说评论是一种甜蜜的气味,而不是一种难闻的气味**.让我引用*重构*中的"难闻气味"一章."...评论并不是一种难闻的气味:确实它们是一种甜蜜的气味...... [但]它们通常被用作除臭剂.令人惊讶的是,你经常看到厚厚的注释代码并注意到评论是因为评论很糟糕." (3认同)

Rap*_*Rap 7

我同意你的意见; 他错了.你可以用这样的东西走得太远,为自己的利益而聪明.

用谓词而不是ifs创建的代码对于维护和测试来说是可怕的.


Lee*_*e B 5

谓词来自逻辑/声明性编程语言,例如 PROLOG。对于某些类别的问题,比如约束求解,它们可以说比许多冗长的、逐步的“如果-这个-做-那么-那么-做-这个”废话要好。在命令式语言中解决起来冗长而复杂的问题在 PROLOG 中只需几行即可完成。

还有可扩展编程的问题(由于向多核、网络等的发展)。一般来说,if 语句和命令式编程往往是按步骤顺序进行的,并且不可扩展。然而,逻辑声明和 lambda 演算描述了如何解决问题以及可以将其分解为哪些部分。因此,执行该代码的解释器/处理器可以有效地将代码分解为多个片段,并将其分布在多个 CPU/核心/线程/服务器上。

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