DEV Community

Cover image for How to Get Rid of Annoying IFs Forever

How to Get Rid of Annoying IFs Forever

Maxi Contieri on November 09, 2020

Why the first instruction we learn to program should be the last to use. Nobody uses GOTO instruction anymore and few programming languages still ...
Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️ • Edited

This is an incredibly narrow view on programming.

  1. Ifs are performant¹, precisely because they get translated to conditional gotos, which is how CPUs make decisions.
  2. Normally if-statements are the simplest and easiest way to describe some logic². In most cases, this doesn't change, so it's much more viable to only remove them as a refactoring step or if it's very clear from the start that the logic will have to be extended.
  3. The example of the movie ratings makes the fatal mistake of confusing logic and data, which I will explain in more detail below

Have a look at the following code³:

local ratings = {
   AdultsOnly = {age = 18};
   PG13 = {age = 13};
   -- ...
}
Enter fullscreen mode Exit fullscreen mode

It should become obvious that the mapping from a rating to the minimum age should be thought of as a constant, not as program logic. Encoding this data into the source code is the actual code smell, not the statement used to do so. Building classes around this only hides the real issue under a heap of complexity instead of fixing anything.

This is a very common pattern in naive OO design: Bury obvious flaws under a convoluted class-system and hope to gain some extensibility by breaking up the code.

Here's what the code should really look like:

function moviegoer:watch(movie) -- The : can be read as a . here
   if self.age < ratings[movie.rating].age then
      error "You are not allowed to watch this movie"
   end

   error("watching movies not implemented yet")
end
Enter fullscreen mode Exit fullscreen mode

There is exactly one if, and it encodes business logic. The data is extracted into a data structure that could at any time be trivially moved into a JSON or YAML file for easier configuration.


¹ This isn't always relevant, but it often enough is that it shouldn't be ignored. It always depends on the language and compiler, but if performance is critical, the code will likely be C or something comparable.

² "if this than that" sentences are how we usually communicate conditional actions outside programming, so it's safe to assume it's a very natural way for humans to think about them.

³ Written in Lua because I don't enjoy typing as much as in my Pascal days.

Collapse
 
mcsee profile image
Maxi Contieri • Edited

Hello and thank you for your detailed comments.

1 - Performance and good design are sometimes in tension. In my experience performance issues should be delayed as possible. Unless you are building a time critical software good designs should always be prioritized.
You might be doing Premature Optimization (which is a code smell)

Modern Virtual machines make a lots of inline optimizations so I always suggest making a benchmark before guessing which one will be faster.

2 - I don't know what is normal. This is the way were taught at school and universities. But software has a lot of errors and bad quality and that is the article thesis. Don't trust normal.
Logic extension should be made following SOLID principles. Not adding IFs.

Are you very sure if this than that is common among not developers? I don't really think so on accidental IFs. Try talking with non developers on NULLs and IFs. You might get surprised.

3 - I don't know anything about logic and data. I think they are related to old school structured programming. Which is fine. I write articles con OOP and they only predicate on behaviour.

I think you are confusing accidental concepts (like JSON or YAML) with essential ones (behavior). I don't see a problem on that.
I just think we are talking with different design points of view.

Your comment states data is important and should be configured externally. I say data is accidental and we should never care about it at all. My humble opinion is that we should view objects related to behavior and we should encapsulate what you call data following information hiding guidelines.

This is my point of view on models:

Related to this topic:
This is a very common pattern in naive OO design: Bury obvious flaws under a convoluted class-system and hope to gain some extensibility by breaking up the code.

Extensibility is key when you design very large systems with millions of code lines (as I do). Breaking up the code and refactoring is the only way of evolving the system and IFs are always blockers and defects source.

I Appreciate the time you took to comment on the note.

Regards

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️ • Edited

Performance and good design are sometimes in tension. In my experience performance issues should be delayed as possible. Unless you are building a time critical software good designs should always be prioritized.

I 100% agree. To clarify, my point was just that performane can be a reason to prefer if statements, but only after the code has been found to be a performance bottleneck. Prematurely using low-level constructs where a higher-level abstraction would improve code quality is, as always, bad style.

Are you very sure if this than that is common among not developers? I don't really think so on accidental IFs

Depending on the situation, I am very much convinced. People say "If the weather is good, we can go to the park tomorrow", not "tomorrow we can do a weather-appropriate activity, where going to the park would be appropriate for sunny weather". This obviously isn't necessarily universal, but it does seem like a tendency.

I think you are confusing accidental concepts (like JSON or YAML) with essential ones (behavior).

I did mention those, but only to give a specific example. My main point is independent of data description formats though: The mapping from Rating to minimum age, is (or rather falls on the side of) data, not logic

I say data is accidental and we should never care about it at all

On the contrary: Data is the only thing we should care about. After all, the only purpose of any program is to take input data and turning it into output data.

Extensibility is key when you design very large systems with millions of code lines (as I do). Breaking up the code and refactoring is the only way of evolving the system and IFs are always blockers and defects source.

Our opinions seem to run parallel here. Looking back at my code example, you end up getting the same level of extensibility. It is easy to add another rating by appending an entry to the map. This is certainly also achieved by your method of encoding this relationship in objects, but my main point of criticism is that it encodes what I would consider a constant¹ as an object method.

¹ note that I don't consider a numeric constant like 42 to be any different from a hugely complex constant data-structure. The defining factor is it's immutability during program execution.

What I consider naive with that pattern is that it adds complexity to the code. If you were to draw flow-charts of both implementations, one of them ends up showing the relationships, while the other treats them as additional input data.

Breaking up the code and refactoring is the only way of evolving the system and IFs are always blockers and defects source.

Not in my experience at least. Assuming performance is not critical, refactoring code with many conditionals can be done in many ways and I personally prefer representing the complex decision logic using data structures. This sometimes goes almost full-circle: depending on the language, data-structures can contain code (usually in the form of functions), which then looks surprisingly similar to an object, which is ultimately just a handle for a collection of behaviours, not unlike a data-structure containing functions.

Thread Thread
 
mcsee profile image
Maxi Contieri

Hi again.
We agree to disagree :)

I think the main disagreement comes from this perception of software.

On the contrary: Data is the only thing we should care about. After all, the only purpose of any program is to take input data and turning it into output data.

I don't care about data because I see software as a model and predictor and data is accidental.
If we were to design a weather forecaster I would not see temperature and pressure indicators as data flowing into the system. I'd like to see the model as a simulator and predictor of actual conditions.
So out forecasted data like estimated temperature, wind speed and thus would be accidental. What would be essential (IMHO) is the model behaving like real weather.
If you get too coupled to data you cannot change them as long as requirements change. And I personally think end users don't care about data. It is us, developers, who care too much about that. User wish a system do what they would need to achieve manually.

You prefer representing the complex decision logic using data structures and this is fine. you want to see all the information centralized and this is tradeoff decision. This is the structural programming approach and is very good.
I'm just pointing out there other ways of simulating real world.

Again. All my opinions

Thread Thread
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

We agree to disagree :)

Definitely. But either way, I still find it very interesting to see how someone else thinks about software systems completely differently, as I don't believe neither of us is inherently more "right" or "wrong" than the other.

To end on a positive conclusion: I think we can extrapolate from this that OOP, with its emphasis on systems and behaviours and FP, with its contrasting emphasis on data flow and transformations can both be the right or wrong tool for any individual developer and/or team, depending largely on how they tend to think about computation.

Not that this is a groundbreaking deduction, but it's something that's easily forgotten :D

Thread Thread
 
mcsee profile image
Maxi Contieri

It is very nice learning with you

I think there are no silver bullets.
different projects need different solutions

Collapse
 
fp2sec profile image
cd /

This solution is way cleaner, and easier to understand than the OP. I created an account just for this post. Over engineering a solution just to get rid of some if statements is not an improvement.

Collapse
 
mcsee profile image
Maxi Contieri • Edited

It is just an example.
Replacing one IF with a hierarchy might seem over engineering.
But this is one short example to fit a small article.
In big systems (I will not put tons of code here) the impact is huge.

I'm showing a technique with its benefits and the second example is definitively over design. I also provided a rule to decide (IMHO) when it's worth it and when it isn't

IFs seem to be very easy and more readable to read. This is exactly what happen with all coupling issues like Nulls, Globals, Singletons and so. They are very easy to read on a isolated piece of code. But this coupling brings lots of defects and prevent big systems to evolve.

Collapse
 
codemouse92 profile image
Jason C. McDonald • Edited

While there may be some good ideas here, I'm concerned that this is going to promote "Real Programmers Use Butterflies"-flavored overengineering. I've seen countless atrocities committed in the name of eliminating perfectly reasonable if statements.

But then, one should be wary of anything stated as "<x> Considered Harmful". It's almost always either (1) an overbroad generalization based on a specific problem, or (2) an attempt to promote the latest Clever Solution™.

The author may say, "Well, no, I'm not really advocating getting rid of all if statements", but if that's the case, the title and article structure aren't a responsible or accurate reflection of the point, even to the brink of being guilty of "clickbait".

Collapse
 
mcsee profile image
Maxi Contieri

Thank you, Jason

I'm aware of over engineering and overbroad generalization. That's why the article has a counterexample on it and warns against abuse.

Title is a bit clickbait. But the word 'annoying' has two meanings
1) All the Ifs are annoying and should all be removed.
2) We need to remove JUST the annoying ones.

The article's spirit is the second one.

Collapse
 
codemouse92 profile image
Jason C. McDonald • Edited

I figured as much, but you have to consider how your readers MIGHT perceive it. Skimming is prevalent, and this doesn't skim the way you'd want.

Thread Thread
 
mcsee profile image
Maxi Contieri

Certainly I'll edit the conclusion to stress this out.

Thank you for your feedback!

Collapse
 
gwangjinkim profile image
Gwang-Jin Kim • Edited

Congrats! You created OOP Spaghetti code!
Leaving the If Else would have been more readable and thus more maintainable than this forest of Classes.

As for me, in the FP style - following Wii's answer:

// classes contain only attributes (container classes, structures)
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
}

class Movie {
    constructor(name, rating) {
        this.name = name;
        this.rating = rating;
    }
}

// as Wii pointed out, underlying logic is mapped into a data structure (map)
let rating2minAge = {
    AdultsOnly: 18,
    PG13: 13,
}

// typical for FP: Predicate functions (returning booleans 
// which can then be used for filtering or other decisions) -
// They make the conditions more readable and 
// provide an abstraction layer (more readable and easier refactoring later on).
function is_allowed(person, movie) {
    return rating2minAge[movie.rating] <= person.age;
}


// Call it by:
let movies = [new Movie("The Exorcist", "AdultsOnly"), new Movie("Gremlin", "PG13")];
let persons = [new Person("Jane", 12), new Person("Joe", 16)];

persons.forEach((person) => {
    movies.forEach((movie) => {
        console.log(`${person.name} (${person.age}) watching \"${movie.name}\" allowed: ${is_allowed(person, movie)}`)
    })
})

/* 
Jane (12) watching "The Exorcist" allowed: false
Jane (12) watching "Gremlin" allowed: false
Joe (16) watching "The Exorcist" allowed: false
Joe (16) watching "Gremlin" allowed: true
*/

Enter fullscreen mode Exit fullscreen mode
Collapse
 
mcsee profile image
Maxi Contieri

How about all the other arguments about extensibility, new business rulet etc? Are they also 'Pasta' ?

Collapse
 
mjgs profile image
Mark Smith

I dunno man, your making the if statements look pretty simple.