Why is the statement “if” considered evil?

I just came from a simple conference on design and testing . In one of the sessions, we talked about evil words in programming languages. Corey Haines , who proposed the topic, was convinced that the if an absolute evil. His alternative was to create functions with predicates . Could you explain to me why if is evil.

I understand that you can write very ugly code abusing if . But I do not believe that it is so bad.

+59
programming-languages if-statement
Oct 12 '09 at 12:03
source share
18 answers

Conditional clauses sometimes lead to code that is more difficult to manage. This includes not only the if , but even more often the switch , which usually includes more branches than the corresponding if .

There are times when it is wise to use if

When you write helper methods, extensions, or specific library functions, it is likely that you will not be able to avoid if (and should not). There is no better way to encode this little function and make it more documented than it is:

 // 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; } 

Branching over "type code" is the smell of code

On the other hand, if you come across code that checks some type code or checks to see if a variable is of a particular type, then this is most likely a good candidate for refactoring, namely replacing a conditional expression with polymorphism .

The reason for this is that by allowing your callers to jump to a certain type of code, you are able to end up with a lot of checks scattered throughout the code, which greatly complicates the expansion and maintenance. Polymorphism, on the other hand, allows you to make this decision about branching as close as possible to the root of your program.

To consider:

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

By placing common, but type-specific (that is, class-specific) functionality in separate classes and exposing them through a virtual method (or interface), you allow the internal parts of your program to delegate this solution to someone higher in the call hierarchy (potentially in one place in the code), which greatly simplifies testing (prototyping), extensibility and maintenance:

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

And now you can easily check if your RunVehicle method RunVehicle as follows:

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

Patterns that differ only if conditions can be reused

Regarding the argument of replacing if with a “predicate” in your question, Haines probably wanted to mention that sometimes there are similar patterns in your code that differ only in their conditional expressions. Conditional expressions appear along with if s, but the whole idea is to extract the repeating pattern in a separate method, leaving the expression as a parameter. This is what LINQ is already doing, usually the result is cleaner code than alternative foreach :

Consider these two very similar methods:

 // 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; } 

This means that you can extract the condition into the predicate, leaving you with one method for these two cases (and many other future cases):

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

And since LINQ already has many such convenient extension methods, you don’t even need to write your own methods:

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

In this latest version of LINQ, the if completely “disappeared”, though:

  1. To be honest, the problem was not in the if itself, but in the entire code template (simply because it was duplicated), and
  2. if still really exists, but it is written inside the LINQ Where extension method, which has been tested and closed for modification. Having less code of your own is always good: fewer things to test, fewer errors, and code is easier to track, analyze and maintain.

Refactoring when you smell code, but don't overload

Having said all this, you should not spend sleepless nights on a couple of conventions from time to time. Although these answers may provide some general practical rules, the best way to be able to detect constructs that require refactoring is through experience. Over time, some patterns appear that lead to changes in the same sentences over and over again.

+73
Oct 12 '09 at 13:48
source share

There is another meaning in which if can be evil: when it comes instead of polymorphism.

eg.

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

instead

  animal.emitSound() 

But in principle, if it is a perfectly acceptable tool for what it does. Of course, this can be abused and abused, but it is not anywhere near the status of goto.

+83
12 Oct '09 at 12:17
source share

Good quote from Complete code:

The code, as if the one who supports your program, is a violent psychopath who knows where you live. - Anonymous

IOW, keep it simple. If the readability of your application is improved using a predicate in a specific area, use it. Otherwise use "if" and move on.

+30
Oct 12 '09 at 12:45
source share

I think it depends on what you do to be honest.

If you have a simple if..else , why use predicate ?

If you can, use switch for large if notes, and then if you use a predicate for large operations (where it makes sense, otherwise your code will be a nightmare for support), use it.

This guy seemed a little pedantic to his liking. Replacing all if with Predicates is just crazy talk.

+17
Oct. 12 '09 at 12:09
source share

There is an Anti-If campaign that started earlier this year. The basic premise is that many nested if statements can often be replaced by polymorphism.

I would be interested to see an example using Predicate. Is this more in line with functional programming?

+14
Oct 12 '09 at 12:19
source share

if not evil (I also believe that the purpose of morality for writing code is asinine ...).

Mr. Haines is stupid and has to laugh.

+9
Oct 12 '09 at 15:20
source share

As in the bible verse about money, if statements are not evil - LOVE if statements are evil. A program without claims is a ridiculous idea, and using them as needed is important. But a program that has 100 if-else if blocks in a line (which, unfortunately, I saw) are definitely evil.

+9
Oct 12 '09 at 15:27
source share

I have to say that recently I began to consider statements as a code smell: especially when you repeat the same condition several times. But there is something you need to understand about code smells: they do not necessarily mean that the code is bad. They simply mean that there is a good chance that the code is bad.

For example, comments are listed as the smell of Martin Fowler's code, but I would not take seriously those who say: "Comments are evil, do not use them."

In general, although I prefer to use polymorphism instead of if statements, where possible. It just makes less room for mistakes. As a rule, I find that a lot of time, using conditional expressions, also leads to many tramp arguments (because you need to pass the data necessary to form the condition to the corresponding method).

+8
Oct 12 '09 at 12:23
source share

I will agree with you; he was wrong . You can go too far with things too smart for your own good.

Code created using predicates instead of ifs will be terrifying to maintain and test.

+7
12 Oct '09 at 12:07
source share

Perhaps with quantum computing, it would be a smart strategy not to use IF statements, but to allow each note of the calculation to continue and only the function “collapse” at the end to a useful result.

+4
Oct. 12 '09 at 12:26
source share

Predicates come from logical / declarative programming languages ​​such as PROLOG. For some classes of problems, such as resolving a constraint, they are likely to outperform a lot of those pulled out step by step, if you do this-do-do-this-shit. Problems that are long and difficult to solve in imperative languages ​​can be accomplished with just a few lines in PROLOG.

There is also the problem of scalable programming (due to the transition to a multi-core network, network, etc.). If statements and imperative programming as a whole tend to be phased and not scalable. Logical declarations and lambda calculus, however, describe how a problem can be solved, and what parts it can be divided into. As a result, the interpreter / processor executing this code can effectively break the code into pieces and distribute it to several processors / cores / threads / servers.

Definitely not useful everywhere; I would really like to try writing a device driver with predicates instead of operators. But yes, I think that the main point probably sounds and is worth at least meeting, if not using it all the time.

+4
Oct 12 '09 at 12:54
source share

The only problem with predicates (in terms of replacing if ) is that you still need to test them:

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

Of course, you can use the terniary ( ?: Operator, but this is just an if in disguise ...

+3
Oct 12 '09 at 12:06
source share

This is probably due to the desire to reduce the cyclic complexity of the code and reduce the number of branch points in the function. If you simply decompose a function into several smaller functions, each of which can be tested, you can reduce complexity and make the code more easily verifiable.

+2
12 Oct '09 at 12:58
source share

MMO: I suspect he was trying to provoke a debate and make people think about the misuse of the if. No one seriously suggested that such a fundamental construction of programming syntax should have been completely eliminated if they were?

+2
Oct 12 '09 at 13:05
source share

Sometimes it is necessary to make an extreme position in order to make one’s thought. I'm sure this person uses if - but every time you use if , you should consider whether another template will make it more understandable.

Alleged polymorphism before if underlies this. Instead:

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

... use:

 animal.makeSound(); 

But this assumes that you have an Animal class / interface - so if tells you that you need to create this interface.

So, in the real world, what kind of if we see that leads us to the solution of polymorphism?

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

It is really annoying to see all your code. What about, instead, having two (or more) Logger implementations?

 this.logger = new NullLogger(); // logger.log() does nothing this.logger = new StdOutLogger(); // logger.log() writes to stdout 

This leads us to a strategy template.

Instead:

 if(user.getCreditRisk() > 50) { decision = thoroughCreditCheck(); } else if(user.getCreditRisk() > 20) { decision = mediumCreditCheck(); } else { decision = cursoryCreditCheck(); } 

... could you...

 decision = getCreditCheckStrategy(user.getCreditRisk()).decide(); 

Of course, getCreditCheckStrategy() may contain if - and this may be appropriate. You pushed him into the neat place where it belongs.

+2
03 feb. '15 at 12:05
source share

I think if statements are evil, but if expressions are not. What I mean by the if statement in this case may be something like a C # ternary operator (condition? TrueExpression: falseExpression). This is not evil, because it is a pure function (in a mathematical sense). It evaluates a new meaning, but it does not affect anything else. Because of this, it works in a substitution model.

Imperative If statements are evil because they force you to create side effects when you don't need them. In order for the If statement to make sense, you must create different “effects” depending on the condition expression. These effects can be like input-output operations, image rendering, or databases that change things outside the program. Or it could be assignment operators that mutate the state of existing variables. It is usually best to minimize these effects and separate them from real logic. But due to If statements, we are free to add these “conditionally executable effects” everywhere in the code. I think this is bad.

+1
Jun 25 2018-12-18T00:
source share

Good thing in ruby we have if ;)

But seriously, it’s possible if this is the next goto , even if most people consider this evil to simplify / speed up work in some cases (and in some cases, such as low-level highly optimized code, this is a must).

0
Oct 12 '09 at 17:04
source share

If it is not evil! Consider ...

 int sum(int a, int b) { return a + b; } 

Bored, huh? Now with the added if ...

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

... the performance of your code generation (measured in LOC) doubles.

The readability of the code has also improved, since now you can see in the blink of an eye that the result is when both arguments are zero. You could not do this in the code above, could you?

You also supported testteam, as they can now promote their test tools for code coverage using more restrictions.

In addition, the code is now better prepared for future improvements. Suppose, for example, that the sum should be zero if one of the arguments is zero (do not laugh and do not blame me, silly demands of customers, you know, and the customer is always right). Due to the fact that if in the first place a small code change is required.

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

How many more code changes would be needed if you had not invented the law from the very beginning.

Gratitude will be yours from all sides.

Conclusion: never happens, if any.

There you go. TO.

-2
Feb 07 '13 at 15:45
source share



All Articles