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

时间:2009-10-12 12:03:55

标签: programming-languages if-statement

我刚来自Simple Design and Testing Conference。在其中一个会话中,我们讨论的是编程语言中的邪恶关键词。提出这个问题的Corey Haines确信if陈述是绝对邪恶的。他的另一种选择是使用predicates创建函数。你能否向我解释为什么if是邪恶的。

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

18 个答案:

答案 0 :(得分:83)

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

E.g。

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

而不是

 animal.emitSound()

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

答案 1 :(得分:74)

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

在某些情况下,使用if

是完全合理的

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

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

只有if条件不同的模式才能重复使用

关于在你的问题中用“谓词”替换if的论点,Haines可能想提一下,有时代码中存在类似的模式,这些模式仅在条件表达式上有所不同。条件表达式与if 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版本中,if语句已完全“消失”,但是:

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

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

答案 2 :(得分:32)

Code Complete的一句好话:

  

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

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

答案 3 :(得分:17)

我认为这取决于你所做的事情是诚实的。

如果您有简单的if..else声明,为什么要使用predicate

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

这个家伙似乎对我的喜欢有点迂腐。用Predicates替换所有if只是疯狂的谈话。

答案 4 :(得分:14)

今年早些时候开始了Anti-If广告系列。主要前提是许多嵌套的if语句通常可以用多态替换。

我很想看到使用谓词的例子。这更像是函数式编程吗?

答案 5 :(得分:10)

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

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

答案 6 :(得分:9)

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

答案 7 :(得分:8)

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

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

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

答案 8 :(得分:7)

我会同意你的意见; 他错了。你可以用这样的东西走得太远,太聪明了。

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

答案 9 :(得分:4)

也许对于量子计算来说,不使用IF语句是一种明智的策略,但要让计算的每一段都继续进行,并且在终止时只有函数“崩溃”才能获得有用的结果。

答案 10 :(得分:4)

谓词来自逻辑/声明性编程语言,如PROLOG。对于某些类型的问题,例如约束求解,它们可以说是优于大量逐步抽出的 - 如果这样做 - 然后做 - 这个废话。在命令式语言中解决的问题很长很复杂,可以在PROLOG中用几行完成。

还存在可扩展编程问题(由于向多核,网络等方向发展)。如果语句和命令式编程通常是逐步的,而不是可扩展的。然而,逻辑声明和lambda演算描述了如何解决问题,以及可以分解哪些部分。因此,执行该代码的解释器/处理器可以有效地将代码分解成碎片,并将其分布在多个CPU /核心/线程/服务器上。

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

答案 11 :(得分:3)

谓词的唯一问题(在替换if语句方面)是你仍然需要测试它们:

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

你当然可以使用三元运算符(?:),但这只是伪装的if语句......

答案 12 :(得分:2)

有时候采取极端立场来表达你的观点是必要的。我确定此人使用的是if - 但每次使用if时,都值得考虑一下不同的模式是否会使代码更清晰。< / p>

将多态性倾向于if是其核心。而不是:

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

...使用:

animal.makeSound();

但是假设你有一个Animal类/接口 - 所以if告诉你的是,你需要创建那个接口。

所以在现实世界中,我们看到哪种if导致我们采用多态解决方案?

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

在整个代码中看到它真的很烦人。相反,有两个(或更多)Logger的实现呢?

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()可能包含if - 这可能是合适的。你把它推到了一个整洁的地方。

答案 13 :(得分:2)

IMO: 我怀疑他试图挑起辩论,让人们想到滥用“如果”。没有人会认真地建议他们完全避免编程语法的这种基本结构吗?

答案 14 :(得分:2)

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

答案 15 :(得分:1)

我认为If语句是邪恶的,但If表达式不是。在这种情况下,if表达式的含义可能类似于C#三元运算符(condition?trueExpression:falseExpression)。这不是邪恶的,因为它是一种纯粹的功能(在数学意义上)。它评估为一个新值,但它对其他任何东西都没有影响。因此,它适用于替代模型。

势在必行如果陈述是邪恶的,因为它们迫使你在不需要时创造副作用。要使If语句有意义,必须根据条件表达式生成不同的“效果”。这些效果可以是IO,图形渲染或数据库事务,它们会改变程序之外的内容。或者,它可以是赋值语句,用于改变现有变量的状态。通常最好将这些影响降至最低并将它们与实际逻辑分开。但是,由于If语句,我们可以在代码中的任何地方自由添加这些“有条件执行的效果”。我认为这很糟糕。

答案 16 :(得分:0)

好在 ruby​​ 我们除非;)

但严重可能如果是下一个 goto ,即使大多数人认为在某些情况下它是邪恶的,也可以简化/加速事情(并且在某些情况下,例如低级高度优化的代码是必须的。

答案 17 :(得分:-2)

如果不是邪恶的话!考虑......

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为单位)加倍。

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

此外,您支持测试团队,因为他们现在可以将他们的代码覆盖测试工具用于更多的限制。

此外,代码现在可以更好地为将来的增强做好​​准备。让我们猜测,例如,如果其中一个参数为零,则总和应为零(不要笑,不要怪我,愚蠢的客户要求,你知道,客户永远是对的)。 因为if首先只需要轻微的代码更改。

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

如果你从一开始就没有发明if if,那么还需要多少代码更改。

感谢各方都是你的。

结论:如果没有,那就永远不够了。

你去吧。到。