You might not need if statements: a better approach to branching logic

David Gilbertson
Jan 22, 2017 · 11 min read

What a ridiculous title, of course you need if statements.

But since you’re here, why don’t you stick around for a while?

I was recently tasked with the, um, task of implementing some rather complex logic in a web site. I got my head around how it all worked, then created a pretty flowchart (in an effort to perpetuate the ruse that I am a mature developer who doesn’t just jump straight into code-writing).

When all stakeholders were pleased, I reached into my Thomas the Tank Engine toolbox and pulled out old faithful: the if statement.

As I typed away — building up my leaning tower of if — a growing unease, um grew. I was taking something perfectly suited to documenting a set of decisions (the flowchart) and turning it into some rather clunky code (if this then this else if switch this then this else this).

Why such a rush to abandon the flowchart way of thinking?

What if, pause, I tried to represent the logic of the flowchart as an object? If the object was generic and simple enough it would be quite readable. If that worked, I’m sure parsing the thing wouldn’t be hard.

Surprise! I have done this and would very much like to share it with you. You don’t have to, but if you print it out and put it on your fridge, I’d love to see a photo of that. It’s totally up to you though.

Let’s run through an example: building this here graphic in code.

Image for post
Image for post
Would you call that green, or teal?

This supremely simple flowchart describes how to decide what to show at the top of some fictional web page.

To start with I had a bit of a think about the key building blocks in a flowchart and what they do, and came up with the following:

  • Some steps ask a question (is the user signed in?)
  • Some steps do something (show the support widget)
  • Some steps do something then ask a question (request ad for kittens, was an ad available?)
  • The lines are paths from one step to the next, based on the answer to a question.

OK none of that is going to be difficult, let’s get started.

If I take the information in this flowchart and put it in JavaScript, it will look something like this:

I am pleased. Gleeful, almost.

Almost.

I decide that when it comes time to parse this tree, I will look at each ‘step’, assess some ‘test’, and move on to the next step based on the result of the test.

But soon dark clouds pass over my gleeful mood, as it dawns on me: if I don’t get organised now about handling asynchronous requests, I’m going to be sad before the sun sets.

Having mulled it over for a while, I decided the best approach was to categorise each step into one of three, um, categories.

This will include steps such as “is the user signed in”. With these steps, I can get an answer immediately, in other words, synchronously.

These steps need to do something asynchronously (attempt to fetch some data) before being able to answer a question (was there any data?).

I will need a way to define an asynchronous action, and then have a test that has access to the results of the action.

In the actual flowchart, you will have noticed that the green or teal blocks were the end of the road, and of course each of these will do something.

Let’s take a gander at some of these steps in more detail.

The word ‘gander’ made me think of geese which I fear/hate, now the red mist has descended and I’m unable to concentrate.

OK I’m back.

Pro-tip: no matter what ails ya, a pygmy marmoset will make it better. They are surprisingly tasty.

The test

The average computer won’t understand “Is the user signed in?”, so I’ll add a test expression that asks the same question in computer words.

To keep things simple, I have decided that the test expression must be synchronous.

Notice how the potential results of the test match the options under the if property (true or false)?

The results of the test doesn’t need to be boolean, it can be anything. One example of anything is a string:

Comprende?

Bueno.

The action

OK let’s look at a step a bit further down. This one is reached if the user is not signed in, has performed a search, and is now viewing the singles category.

Since I have decided that each step must answer a question, this step must do two things:

  • Request ad for kittens
  • Ask if an ad was available

When it comes time to process this data, I will execute the action, expecting it to return a promise. When the promise resolves, I will pass the result to the test.

(If you’re not familiar with promises, you can consider this roughly the same as: “I will call the action requesting data, passing a callback. When the callback is called with the data, I will then pass it on to the test.”)

If test: ads => !!ads is a bit too shorthand for your delicate tastes, you could write it out as a function like so:

Moving on with our journey down the decision tree, we arrive at a ‘terminus step’. The one below is the step that is triggered when there was an ad for kittens available (huzzah!):

Just a title and an action. Oh did I mention that the titles are superfluous? Yeah, the titles are superfluous.

In this case the action doesn’t need to be a function that returns a promise — we’re all done with the decision tree now so I just fire this last action and walk away.

I do not look back.

The finished tree is a rather portly gentleman, but still plenty readable, right?

Imagine discovering this code — having never seen it before — and trying to work out what happens for non-logged in users that haven’t searched yet. It should be just as easy to read as a flowchart, and easier than the equivalent pile of if statements.

Alrighty, we have our flowchart defined in a fairly simple object (not small, but simple. Like a giant idiot.)

Let’s parse this bad boy

It has come time to parse the decisionTree object and I am, quite rightly I insist, more excited than I have ever been in my life. Skydiving? Just a bunch of air. Birth of first child? Meh, heaps of people have done it. But parsing the decision tree, that’s where it’s at, baby.

The parser is pretty simple really. It starts at the top step, runs the test, works out which step is next. Then it does it again and again and again until it gets to a step with no test — which means the end of the road.

That’s it.

Yet still there’s more…

The syntax of selecting the next step

When calling the next step, the parsing function takes the result of the test and looks for a prop with that name, like step.if[testResult]. If nothing matches, the parser looks for a step.if.default property. If that doesn’t exist, then you’ll get an “unhandled scenario” warning.

If you’re feeling queasy about me using the reserved words if and true and false as property names, relaaaaaax, it’s been valid since ES5. Here’s a support table (you have to expand the Object/array literal extensions row).

If you’re feeling queasy about me confusing the string 'true' with the boolean true, relaaaaaax!! When you call obj[propName], if propName isn’t a string, then toString() will be called on it. And I know that you know that true.toString() === 'true'.

If you were queasy about that you’ll be super queasy about this:

That feels so wrong, but it’s totally legit.

Taking it further

I could make this an npm package, call it flowr or something snazzy. But I don’t really like the “giving back” aspect of open source. The “take take take” seems like much better value to me.

If I were to package this up, here’s a few thing I would do:

If I was interested in knowing when the decision tree had been parsed, and perhaps even what the final result was, I could change my outer function to return a promise, and have the final step resolve the promise.

Something like:

BTW: putting console.log in the then is a trick I only just realised recently. It’s the same as something.then(result => console.log(result)). And to some of you I’m sure not even worthy of the ‘trick’ label.

Half the purpose of this is to create something that’s easier to change next year and the one after that. But that future person might not know that test should be sync and action must return a promise if there is a test.

I should add in some console.warn('test must be be synchronous') and console.warn('action must return a promise if the step contains a test property') and so on.

In my example, I don’t have any steps that are reused; there’s no recursion within the decision tree.

But if there was: because decisionTree is just an object, it can be broken up into smaller objects. For example if I needed to refer to the category branch of the decision tree in multiple places, I could split it out like…

I’m not sure that it’s easier to unit test this method than it is having an if statement labyrinth (holy crap I just spelled labyrinth right first go!).

But it’s certainly easier to make sure you cover all the eventualities, because you can just move your eyes down the screen and count up all the terminus steps.

It doesn’t take much brain power to glance as the below screenshot and see the 10 scenarios I need to cover. I could do it with my eyes closed, using my downstairs brain.

Fun fact: you have two brains, and that is not a dirty joke. 50% of your dopamine and 90% of your serotonin are in your second brain, which is made up of half-a-billion neurons and is not in your head. (This one is true. The one about the movie Se7en was not true.)

Image for post
Image for post

I’ve folded up the 10 terminus steps so they stand out (if you’ve got a decent text editor you’ll see a summary of what’s folded).

Sanity check

Let me take a moment to align with reality.

[wriggles interestingly]

Is all this fancy footwork any better than a bunch of if and switch statements?

Just for you, I have made the if/switch equivalent:

That’s 62 lines (including going overboard with the comments), vs my brilliantly simple 130 lines.

Oh no.

I seem to have strayed from reality in my excitement. Maybe all of this was my subconscious not wanting to do any real work and so I convinced myself that this particular wheel needed reinventing. I do find the desire to delay certain unpleasantries a powerful force.

Story time: many moons ago, in an effort to defer updating react-router (to 1.0.0), I tidied up my desk, adjusted the paintings on my wall, and made a coffee table. Five react-router versions later, I’m running out of room for coffee tables.

Bringing it back in

This is quite the emotional rollercoaster, I have my gut saying this is a good idea, but the line count saying otherwise. How about I do a pros and cons list: doin’ it flowchart-style instead of the more traditional if/switch approach.

Pros:

  • It’s (arguably) more readable
  • It enforces thinking about (and handling) all scenarios
  • If multiple decision trees are used throughout a codebase and all developers are aware of it, it offers nice consistency

Cons:

  • By separating the definition of the flow from the execution logic, a third thing (understanding how one works with the other) has been introduced. This needs documentation/understanding.
  • Similar to the above, if this is implemented by one dev on a team but no one else uses it, it becomes yet another over-engineered approach to handling a problem.
  • For a simple tree, it’s more lines of code. Maybe even for a complex tree.

Comment away if there’s something I’m missing. Like I even need to say that.

OK I just took a break and made dinner and had a surprise nap then ate cold dinner and I have come to a conclusion.

I do think this works as an idea — despite seeming convoluted — because it offers rigidity.

Something I just realised (in a dream, via a talking pony called Bruce) is that the natural state of any codebase is complete chaos. And given time, all codebases will return to their natural state.

Image for post
Image for post
Sometimes I miss the outside

The problem with a nest of if statements is that it’s easy to add an extra bit in here, and a little negative condition there, and oh I’ll just stick in an early return here, and so on.

As it gets messier over time, not only is it more likely to harbour bugs, but it becomes less readable. This increases the time it takes to make any future changes.

And readability of code should count for something. If your code clearly communicates what it’s supposed to do, then it is more easily maintained. At the end of the day, the same information is there, whether it’s in the shape of a ‘decision tree’ or a bunch of nested if statements. But if one method better demonstrates your intent, it is the better form of communication.

For example, I wanted to communicate to my upstairs neighbour that he should play World of Warcraft less loudly. When I communicated this information via a typed letter in his letterbox, it had no effect. But when I wrote the exact same words in lipstick on his bathroom mirror? — boom, no more World of Warcraft.

My point, if there are two ways of writing some piece of code, I’ll prefer the one that more clearly communicates the intent of that code.

For those that asked, here’s the full thing as a fiddle.

Conclusion

I think this is a bit like writing CSS in JavaScript and getting rid of frameworks entirely. It’s interesting, it’s something I might use in a personal project (obviously not for a decision tree this small), but not something I would impose upon other developers in a larger project.

If you think this is a good idea, click the little heart once. If you think it’s a shit idea, click the little heart three times. Every vote counts.

Did you notice, not a single mention of know-it-all.io in this whole post!

Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.

If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!

Image for post
Image for post

HackerNoon.com

#BlackLivesMatter

Sign up for Get Better Tech Emails via HackerNoon.com

By HackerNoon.com

how hackers start their afternoons. the real shit is on hackernoon.com. Take a look

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

David Gilbertson

Written by

I like web stuff.

HackerNoon.com

Elijah McClain, George Floyd, Eric Garner, Breonna Taylor, Ahmaud Arbery, Michael Brown, Oscar Grant, Atatiana Jefferson, Tamir Rice, Bettie Jones, Botham Jean

David Gilbertson

Written by

I like web stuff.

HackerNoon.com

Elijah McClain, George Floyd, Eric Garner, Breonna Taylor, Ahmaud Arbery, Michael Brown, Oscar Grant, Atatiana Jefferson, Tamir Rice, Bettie Jones, Botham Jean

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store