I just came from Simple Design and Testing Conference. In one of the session we were talking about evil keywords in programming languages. Corey Haines, who proposed the subject, was convinced that if
statement is absolute evil. His alternative was to create functions with predicates. Can you please explain to me why if
is evil.
I understand that you can write very ugly code abusing if
. But I don't believe that it's that bad.
There is nothing wrong with using if-statements, but avoiding them can sometimes make the code a bit more readable to humans. This is definitely not a general rule as sometimes avoiding if-statements will make the code a lot less readable. You be the judge. Avoiding if-statements is not just about readability.
If…else is not bad — excessive usage is It's a simple, easy-to-understand, and flexible logic control structure. But in reality, it's often used excessively. They can be bad in the following cases: Nested if-else or multiple level nesting (worse)
This is good practice to handle error condition first. Overall if else can be used in small blocks based on requirement that will remove code complexity and code will be easy to use/debug/maintain/extend.
Deeply nested conditionals make it just about impossible to tell what code will run, or when. The big problem with nested conditionals is that they muddy up code's control flow: in other words, they make it just about impossible to tell what code will run, or when.
The if
statement is rarely considered as "evil" as goto
or mutable global variables -- and even the latter are actually not universally and absolutely evil. I would suggest taking the claim as a bit hyperbolic.
It also largely depends on your programming language and environment. In languages which support pattern matching, you will have great tools for replacing if
at your disposal. But if you're programming a low-level microcontroller in C, replacing if
s with function pointers will be a step in the wrong direction. So, I will mostly consider replacing if
s in OOP programming, because in functional languages, if
is not idiomatic anyway, while in purely procedural languages you don't have many other options to begin with.
Nevertheless, conditional clauses sometimes result in code which is harder to manage. This does not only include the if
statement, but even more commonly the switch
statement, which usually includes more branches than a corresponding if
would.
if
When you are writing utility methods, extensions or specific library functions, it's likely that you won't be able to avoid if
s (and you shouldn't). There isn't a better way to code this little function, nor make it more self-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; }
On the other hand, if you encounter code which tests for some sort of a type code, or tests if a variable is of a certain type, then this is most likely a good candidate for refactoring, namely replacing the conditional with polymorphism.
The reason for this is that by allowing your callers to branch on a certain type code, you are creating a possibility to end up with numerous checks scattered all over your code, making extensions and maintenance much more complex. Polymorphism on the other hand allows you to bring this branching decision as closer to the root of your program as possible.
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 (i.e. class-specific) functionality into separate classes and exposing it through a virtual method (or an interface), you allow the internal parts of your program to delegate this decision to someone higher in the call hierarchy (potentially at a single place in code), allowing much easier testing (mocking), 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 you can now easily test if your RunVehicle
method works as it should:
// 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
conditions can be reusedRegarding the argument about replacing if
with a "predicate" in your question, Haines probably wanted to mention that sometimes similar patterns exist over your code, which differ only in their conditional expressions. Conditional expressions do emerge in conjunction with if
s, but the whole idea is to extract a repeating pattern into a separate method, leaving the expression as a parameter. This is what LINQ already does, usually resulting in cleaner code compared to an 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 indicates that you can extract the condition into a predicate, leaving you with a single 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 a bunch of handy extension methods like this, you actually 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 last LINQ version the if
statement has "disappeared" completely, although:
if
by itself, but in the entire code pattern (simply because it was duplicated), andif
still actually exists, but it's written inside the LINQ Where
extension method, which has been tested and closed for modification. Having less of your own code is always a good thing: less things to test, less things to go wrong, and the code is simpler to follow, analyze and maintain.if
/else
statementsWhen you see a function spanning 1000 lines and having dozens of nested if
blocks, there is an enormous chance it can be rewritten to
switch
statement where appropriate.Having said all this, you should not spend sleepless nights over having a couple of conditionals now and there. While these answers can provide some general rules of thumb, the best way to be able to detect constructs which need refactoring is through experience. Over time, some patterns emerge that result in modifying the same clauses over and over again.
There is another sense in which if
can be evil: when it comes instead of polymorphism.
E.g.
if (animal.isFrog()) croak(animal) else if (animal.isDog()) bark(animal) else if (animal.isLion()) roar(animal)
instead of
animal.emitSound()
But basically if is a perfectly acceptable tool for what it does. It can be abused and misused of course, but it is nowhere near the status of goto.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With