我刚来自Simple Design and Testing Conference。在其中一个会话中,我们讨论的是编程语言中的邪恶关键词。提出这个问题的Corey Haines确信if
陈述是绝对邪恶的。他的另一种选择是使用predicates创建函数。你能否向我解释为什么if
是邪恶的。
我知道您可以编写非常难看的代码滥用if
。但我不相信它那么糟糕。
答案 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
语句已完全“消失”,但是:
if
,而是在整个代码模式中(仅仅因为它是重复的),并且if
仍然存在,但它是在LINQ Where
扩展方法中编写的,该方法已经过测试并已关闭以进行修改。拥有较少的自己的代码总是一件好事:要测试的东西越少,出错的东西越少,代码就越容易遵循,分析和维护。说完这一切之后,你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,那么还需要多少代码更改。
感谢各方都是你的。
结论:如果没有,那就永远不够了。
你去吧。到。