我只是来自简单设计和测试会议。在一个会议中,我们讨论了编程语言中的邪恶关键字。 提出该主题的科里·海恩斯(Corey Haines)坚信,这种if
说法绝对是邪恶的。他的替代方法是使用谓词创建函数。你能告诉我为什么if
是邪恶的。
我了解您可以编写非常丑陋的代码if
。但是我不相信那有那么糟糕。
我只是来自简单设计和测试会议。在一个会议中,我们讨论了编程语言中的邪恶关键字。 提出该主题的科里·海恩斯(Corey Haines)坚信,这种if
说法绝对是邪恶的。他的替代方法是使用谓词创建函数。你能告诉我为什么if
是邪恶的。
我了解您可以编写非常丑陋的代码if
。但是我不相信那有那么糟糕。
Answers:
该if
语句很少被视为“邪恶的”goto
或可变的全局变量-甚至后者实际上也并非普遍且绝对不是邪恶的。我建议把这个说法有点夸张。
它还在很大程度上取决于您的编程语言和环境。在支持模式匹配的语言中,您将拥有强大的替换工具供if
您使用。但是,如果您正在用C编程低级微控制器,则用if
函数指针替换s将是朝错误方向迈出的一步。因此,我将主要考虑if
在OOP编程中替换s,因为在功能语言中,if
反正不是习惯用法,而在纯过程语言中,您没有很多其他选择。
但是,条件子句有时会导致难以管理的代码。这不仅包括该if
语句,而且更常见的是该switch
语句,该语句通常包含比相应语句更多的分支if
。
if
当您编写实用程序方法,扩展或特定的库函数时,很可能将无法避免if
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;
}
另一方面,如果遇到测试某种类型代码的代码,或者测试某个变量是否为某种类型的代码,则这很可能是重构的好选择,即用多态替换条件。
这样做的原因是,通过允许调用者在某个特定类型的代码上分支,您将有可能最终在代码中散布大量检查,从而使扩展和维护变得更加复杂。另一方面,多态使您可以使此分支决策尽可能接近程序的根。
考虑:
// 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
仍实际存在,但它写在里面LINQWhere
扩展方法,它已经过测试和修改关闭。减少自己的代码总是一件好事:减少测试的事情,减少出错的事情,并且使代码易于遵循,分析和维护。if
/else
语句当您看到一个跨越1000行并包含数十个嵌套if
块的函数时,很有可能将其重写为
switch
在适当情况下以声明代替。说了这么多,您不应该在现在和那里有条件的情况下度过不眠之夜。虽然这些答案可以提供一些一般的经验法则,但能够通过经验检测出需要重构的最佳方法。随着时间的流逝,出现了一些模式,导致一遍又一遍地修改相同的子句。
sum / people.Count
是不正确的,因为它将部分和除以所有人的总数。15个程序员的团队中只考虑一个女性。女性的实际年龄是45岁,但是如果除以15,则女性的平均年龄是3。这不是正确的平均年龄...
还有另一种if
可能是邪恶的感觉:当它出现而不是多态时。
例如
if (animal.isFrog()) croak(animal)
else if (animal.isDog()) bark(animal)
else if (animal.isLion()) roar(animal)
代替
animal.emitSound()
但基本上,对于它的工作而言,if是否是一个完全可以接受的工具。当然,它可能会被滥用和滥用,但是goto的地位远远不及它。
if
s。他甚至警告不要使用is(obj是Foo),因为这种反映暗示了设计缺陷。
if(obj is Foo)
是非常明智的,因为您没有键入的参数。在大多数情况下,您确实想使用鸭子类型,但有时使用内置类型,除了真实的东西之外什么都没有,在这种情况下,除了之外Foo
,您想对其他任何东西都引发错误。if(obj is Foo)
。
Code Complete的一个好报价:
就像维护程序的人一样,编写代码时会知道自己住的地方是一个暴力的精神病患者。
-匿名
IOW,保持简单。如果通过在特定区域中使用谓词来增强应用程序的可读性,请使用它。否则,请使用“ if”继续。
ifs
在完全损害KISS原则的情况下回避。
我认为这取决于您的诚实行为。
如果您有一个简单的if..else
陈述,为什么要使用predicate
?
如果可以的话,请使用aswitch
代替较大的if
替换项,然后再选择对大型操作使用谓词(在这种情况下有意义,否则您的代码将成为维护的噩梦),请使用它。
这个家伙似乎有点像我喜欢的书呆子。if
用谓词代替所有只是疯狂的谈话。
有 今年早些时候开始反假冒运动。主要前提是,许多嵌套的if语句经常可以用多态替换。
我希望看到一个使用谓词的示例。这更像是函数式编程吗?
if
不是邪恶的(我也认为,将道德赋予代码编写实践是愚蠢的……)。
海恩斯先生很傻,应该被嘲笑。
就像圣经中关于金钱的经文一样,if陈述不是邪恶的-if陈述是邪恶的。没有if语句的程序是一个荒谬的想法,并且在必要时使用它们是必不可少的。但是一个连续有100个if-else if块的程序(不幸的是,我已经看到)绝对是邪恶的。
我不得不说,我最近开始将语句是否视为代码气味:尤其是当您发现自己多次重复相同的条件时。但是您需要了解一些有关代码气味的知识:它们不一定意味着代码不好。它们只是意味着代码很可能是错误的。
例如,马丁·福勒(Martin Fowler)将注释列为代码气味,但是我不会认真对待任何说“注释是邪恶的;不要使用注释”的人。
通常,尽管如此,我更倾向于使用多态性而不是if语句。这样只会减少错误的余地。我倾向于发现很多时候,使用条件也会导致很多流氓参数(因为必须将形成条件的数据传递给适当的方法)。
谓词来自逻辑/声明性编程语言,例如PROLOG。对于某些类型的问题,例如约束解决方案,可以说,它们比许多分步提出的“先做然后再做”废话要好。用命令语言解决的问题很长而且很复杂,可以在PROLOG中仅用几行就可以解决。
还存在可伸缩编程的问题(由于转向多核,Web等)。一般而言,if语句和命令式编程往往是逐步的,并且不可扩展。逻辑声明和lambda演算描述了如何解决问题以及将问题分解为哪些部分。结果,执行该代码的解释器/处理器可以有效地将代码分解为多个片段,并将其分布在多个CPU /内核/线程/服务器之间。
绝对不是到处有用;我不想尝试用谓词而不是if语句编写设备驱动程序。但是,是的,我认为重点可能是合理的,如果不是一直使用,至少值得熟悉。
谓词的唯一问题(就替换if
语句而言)是您仍然需要测试它们:
function void Test(Predicate<int> pr, int num)
{
if (pr(num))
{ /* do something */ }
else
{ /* do something else */ }
}
您当然可以使用三级运算符(?:
),但这只是if
变相的声明...
true = (a,b) => a
和false = (a,b) => b
,但这也只是变相的“ if”。
IMO:我怀疑他是在试图引发一场辩论,并让人们思考对“ if”的滥用。没有人会认真建议完全避免这种编程语法的基本结构吗?
有时有必要采取极端立场来表达自己的观点。我确定这个人会使用if
-但是每次您使用if
,都值得考虑一下是否使用其他模式可以使代码更清晰。
优先考虑多态if
是其核心。而不是:
if(animaltype = bird) {
squawk();
} else if(animaltype = dog) {
bark();
}
... 采用:
animal.makeSound();
但这假设您有Animal类/接口-那么实际上 if
告诉您的是您需要创建该接口。
因此,在现实世界中,if
我们看到什么样的s导致我们找到了多态性解决方案?
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
-可能很合适。您已将其推入其所属的整洁地方。
很好,在红宝石中,除非;)
但严重的可能,如果是下一个跳转,即使大多数人认为这是邪恶在某些情况下简化/加速的东西(在某些情况下,如低的水平高度优化的代码,这是一个必须)。
我认为If语句是邪恶的,但是If表达式不是。在这种情况下,if表达式的含义可以类似于C#三元运算符(条件?trueExpression:falseExpression)。这不是邪恶的,因为它是一个纯函数(在数学意义上)。它评估为一个新值,但对其他没有任何影响。因此,它适用于替代模型。
命令式If陈述式是邪恶的,因为它们在不需要时会迫使您产生副作用。为了使If语句有意义,您必须根据条件表达式产生不同的“效果”。这些影响可能是诸如IO,图形渲染或数据库事务之类的东西,它们会在程序外部进行更改。或者,可能是赋值语句改变了现有变量的状态。通常最好将这些影响最小化,并将其与实际逻辑分开。但是,由于有了If语句,我们可以在代码中的任意位置自由添加这些“有条件执行的效果”。我认为这很糟糕。
if(socket) socket.connect else say "Socket done goofed"
例如
如果不是邪恶的!考虑...
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;
}
...您的代码创建效率(以LOC衡量)提高了一倍。
代码的可读性也有了很大的提高,现在您可以眨眼看到两个参数均为零时的结果。您无法在上面的代码中这样做,对吗?
此外,您还支持了测试团队,因为他们现在可以推动其代码覆盖率测试工具的使用达到极限。
此外,现在为将来的增强功能更好地准备了代码。例如,让我们猜测,如果参数之一为零,则总和应为零(不要笑,不要怪我,愚蠢的客户要求,您知道,客户永远是对的)。因为首先是if,所以只需要稍作更改即可。
int sum(int a, int b) {
if (a == 0 || b == 0) {
return 0;
}
return a + b;
}
如果您从一开始就没有发明if,那么还需要多少代码更改。
感恩将在所有方面都属于您。
结论:如果是,那就永远不够。
妳去 至。
if
陈述就像锤子一样是邪恶的。有些人可能会滥用它们,但是它们是必不可少的工具。