为什么'如果'的声明被认为是邪恶的?
我刚从简单的devise和testing会议 。 在会议的其中一节中,我们讨论了编程语言中的邪恶关键字。 谁提出这个问题的科里海恩斯相信, if
说是绝对的邪恶。 他的select是用谓词来创build函数。 你能向我解释为什么是邪恶的。
我知道你可以编写非常丑陋的代码滥用, if
。 但我不相信那是坏事。
有条件的子句有时会导致难以pipe理的代码。 这不仅包括if
语句,还更常见的是switch
语句,它通常包含比相应的更多的分支。
有些情况下使用if
是完全合理的
在编写实用程序方法,扩展或特定的库函数时, if
s(而且不应该),那么很可能无法避免。 没有更好的方法来编写这个小函数,也没有比它更自我logging的方式:
// 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; }
分支在“types代码”是一种代码味道
另一方面,如果遇到代码testing某种types的代码,或者testing某个variables是否属于某种types,那么这很可能是重构的一个很好的候选者,即用多态性代替条件 。
原因在于,通过允许呼叫者在特定的types代码上进行分支,可能会导致在代码中散布大量的检查,从而使扩展和维护变得更为复杂。 另一方面,多态性可以让你尽可能接近你程序的根源。
考虑:
// 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); }
通过将通用但特定于types的(即特定于类的)function放入单独的类中,并通过虚拟方法(或接口)进行公开,允许程序的内部部分将此决定委托给调用层次结构中较高层的某个人可能在代码中的一个地方),允许更简单的testing(模拟),可扩展性和维护:
// 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(); }
现在,您可以轻松testingRunVehicle
方法是否正常工作:
// 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
用“谓词”来取代这个论点,Haines可能想提一下,你的代码有时候会有类似的模式存在,它们的区别只在于它们的条件expression式。 条件expression式与if
s一起出现,但是整个想法是将重复模式提取到单独的方法中,而将expression式作为参数。 这就是LINQ已经做的事情,与其他foreach
相比, 通常会产生更清晰的代码:
考虑这两个非常相似的方法:
// average male age public double AverageMaleAge(List<Person> people) { double sum = 0.0; foreach (var person in people) { if (person.Gender == Gender.Male) sum += person.Age; } return sum / people.Count; } // average female age public double AverageFemaleAge(List<Person> people) { double sum = 0.0; foreach (var person in people) { if (person.Gender == Gender.Female) // <-- only the expression sum += person.Age; // is different } return sum / people.Count; }
这表明您可以将条件提取到谓词中,而对于这两种情况(以及其他许多未来情况),您将只使用一种方法:
// average age for all people matched by the predicate public double AverageAge(List<Person> people, Predicate<Person> match) { double sum = 0.0; foreach (var person in people) { if (match(person)) // <-- the decision to match sum += person.Age; // is now delegated to callers } return sum / people.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
语句完全“消失”,但是:
- 说实话,这个问题本身并不在其中,而是在整个代码模式中(仅仅是因为它被复制了)
-
if
仍然存在,但是它写在LINQWhere
扩展方法里面,这个方法已经被testing并且被修改closures了。 less用自己的代码总是一件好事:testing的东西less,出错less,代码更易于跟踪,分析和维护。
重构,当你觉得这是一个代码气味,但不要过度工程
说完这一切之后,你不应该不眠之夜地花费几个条件。 虽然这些答案可以提供一些一般的经验规则,但是能够检测需要重构的构造的最好方法是通过经验。 随着时间的推移,一些模式会出现,导致一遍又一遍地修改同一个子句。
还有另一种意思, if
可以是邪恶的:何时来代替多态。
例如
if (animal.isFrog()) croak(animal) else if (animal.isDog()) bark(animal) else if (animal.isLion()) roar(animal)
代替
animal.emitSound()
但基本上,如果它是一个完全可以接受的工具。 它当然可以被滥用和滥用,但是它远不及goto的地位。
代码完整的一个很好的引用:
就好像谁维护你的程序是一个暴力的精神病患者谁知道你住在哪里。
– 匿名
IOW,保持简单。 如果通过在特定区域使用谓词来增强应用程序的可读性,请使用它。 否则,使用“如果”,继续前进。
我认为这取决于你在做什么说实话。
如果你有一个简单的if..else
语句,为什么使用一个predicate
?
如果可以的话,使用一个更大的switch
来replace,然后如果select使用谓词来进行大的操作(这是有道理的,否则你的代码将成为噩梦来维护),那么就使用它。
这个人似乎对我的喜好有些迂腐。 用Predicates取代所有if
只是疯狂的说法。
今年早些时候开始的反“如果”运动。 主要的前提是许多嵌套的if语句经常可以用多态性replace。
我会有兴趣看到一个使用Predicate的例子。 这是更多的function编程?
我不得不说,我最近已经开始把语句看作代码味道了,特别是当你发现自己多次重复相同的条件时。 但是你需要了解一些关于代码异味的东西:它们并不一定意味着代码是不好的。 他们只是说代码很糟糕。
例如,评论被马丁·福勒列为代码味道,但我不会认真对待谁说“评论是邪恶的,不要使用它们”。
一般来说,我更喜欢在可能的情况下使用多态而不是if语句。 这只是减less了错误的空间。 我倾向于发现很多时候,使用条件导致大量的stream量参数(因为您必须将形成条件的数据传递给适当的方法)。
if
不是邪恶的话(我也认为把道德分配给代码写作实践就像是……)。
海恩斯先生是愚蠢的,应该嘲笑。
就像在关于金钱的圣经经文中一样,如果陈述不是邪恶的 – 如果陈述的爱是邪恶的话。 没有if语句的程序是一个荒谬的想法,必要时使用它们是必要的。 但是,如果一个程序中有100个if-else,那么这个程序肯定是邪恶的。
我会同意你的; 他错了 。 对于这样的事情,你可以做得太过分,为了自己的利益,这太聪明了。
使用谓词而不是ifs创build的代码将是可怕的维护和testing。
谓词来自逻辑/声明式编程语言,如PROLOG。 对于某些types的问题,比如约束求解,如果这样做,那么它们可以说是优于大量的一步一步抽出来的东西。 在PROLOG中,只需几行就可以完成命令式语言中要解决的问题。
还有可扩展的编程问题(由于走向多核,networking等)。 如果陈述和命令编程一般都是按照一步一步的顺序进行的,而不是可扩展的。 尽pipe逻辑声明和lambda微积分描述了一个问题是如何解决的,它可以分解成多less个部分。 因此,执行该代码的解释器/处理器可以有效地将代码分解成多个部分,并将其分布到多个CPU /内核/线程/服务器上。
无处不在; 我讨厌尝试用谓词而不是if语句来编写设备驱动程序。 但是,是的,我认为主要的观点可能是合理的,如果不是全部使用,至less值得熟悉。
也许在量子计算中,不使用IF语句是明智的策略,而是让计算的每一步都进行下去,只有在终止时才有“崩溃”function,才能达到有用的结果。
谓词(replaceif
语句)唯一的问题是你仍然需要testing它们:
function void Test(Predicate<int> pr, int num) { if (pr(num)) { /* do something */ } else { /* do something else */ } }
你当然可以使用三元操作符( ?:
:),但这只是一个伪装语句。
这可能归结为希望保持代码圈复杂性,并减less一个函数中的分支点的数量。 如果一个函数很容易分解成多个小函数,每个小函数都可以被testing,那么可以减less复杂性并使代码更易于testing。
国际海事组织:我怀疑他是在挑起一场辩论,让人们想到滥用“如果”。 没有人会认真地提出这样一个基本的编程语法的构造是完全可以避免的吗?
我认为如果陈述是邪恶的,但如果expression不是。 在这种情况下,ifexpression式的含义可以是C#三元运算符(condition?trueExpression:falseExpression)。 这不是邪恶的,因为它是一个纯粹的function(在math意义上)。 它评估到一个新的价值,但它没有任何其他影响。 正因为如此,它以替代模式运作。
命令如果陈述是邪恶的,因为他们强迫你在不需要的时候产生副作用。 If语句是有意义的,你必须根据条件expression式产生不同的“效果”。 这些影响可能是IO,graphics渲染或数据库事务,这些都会改变程序之外的东西。 或者,它可以是改变现有variables状态的赋值语句。 将这些影响最小化并将其与实际逻辑分开通常会更好。 但是,由于If语句,我们可以在代码中随处添加这些“条件执行的效果”。 我觉得这很糟糕。
有时候需要采取极端的立场来expression你的观点。 我相信这个人使用if
– 但是每次使用if
,值得稍微考虑一下不同的模式是否会使代码更清晰。
首选多态性if
是这个的核心。 而不是:
if(animaltype = bird) { squawk(); } else if(animaltype = dog) { bark(); }
… 使用:
animal.makeSound();
但是,假设你有一个动物类/界面 – if
真的告诉你,那么你需要创build一个界面。
所以在现实世界中,我们看到了什么样的结果导致了多态性解决scheme?
if(logging) { log.write("Did something"); }
在整个代码中看到这真是令人烦恼。 相反,如何有两个(或更多)的logging器的实现?
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
– 这可能是合适的。 你把它推到了一个整洁的地方。
好, 除非我们有ruby ;)
但是, 如果是下一个goto ,那么即使大多数人认为它在某些情况下是邪恶的,也正在简化/加速这些事情(在某些情况下,像低级高度优化的代码,这是必须的)。
如果不是邪恶的! 考虑…
int sum(int a, int b) { return a + b; }
无聊,呃? 现在添加如果…
int sum(int a, int b) { if (a == 0 && b == 0) { return 0; } return a + b; }
…您的代码创build效率(在LOC中测量)增加了一倍。
另外,代码的可读性已经提高了很多,现在你可以在眨眼之间看到两个参数都为零的结果。 你不能在上面的代码中做到这一点,可以吗?
此外,您支持testteam,因为他们现在可以推动他们的代码覆盖testing工具使用更多的限制。
此外,代码现在更好地为将来的增强做好准备。 让我们猜测,例如,如果其中一个参数为零(不要笑,不要怪我,愚蠢的客户要求,你知道,客户永远是对的),总和应该是零。 由于如果在第一个地方只需要轻微的代码更改。
int sum(int a, int b) { if (a == 0 || b == 0) { return 0; } return a + b; }
如果你从一开始就没有发明如果是正确的,那么需要更多的代码改变。
感恩将是你的四面八方。
结论:如果没有足够的话。
你走了 至。