I’ll never forget this lesson I learned from a friend in 6th or 7th grade.

We were building our own Windows clone. In Turbo Pascal, running in DOS, using a textual user interface because we didn’t know how to do graphics.

If you’re too young to know about textual user interfaces, they looked like this:

It’s got windows, buttons, a mouse cursor, and everything else you’d want in a modern user interface. Except graphics. It’s all made with ASCII characters.

I used to know the ASCII table by heart by back then ๐Ÿค“

Now, when I say we were building a new Windows, I don’t mean working together on a single project. We didn’t know how to do that. There was no Git or CVS or SVN back then. At least not that we knew of. The best we could do back then was to use diskettes to borrow each other’s code.

Oh, we also didn’t know about putting code in multiple files, so that was fun.

Anyway, the lesson he taught me about business logic being data came when we were both implementing our top row navigation. Like a menu, but for your whole system.

Oh yes, we built dropdown menus and everything. It’s amazing what a 13 year old with infinite free time can build in a few months.

Avoid encoding business logic in the shape of your program

Let’s say you have 6 items. File, Edit, Format, View, Window, and Help. That’s what iA Writer has.

Now let’s say you want to have a user action that goes through these items. My system used left and right arrow keys, my friend liked j and k, while MacOS relies on mouse alone.

You could build navigation like this:

onLeftKeyPress() {
    if (currentMenu === "File") {
        goTo("Help")
    }else if (currentMenu === "Edit") {
        goTo("File")
    }else if (currentMenu === "Format") {
        goTo("Edit")
    }else if (currentMenu === "View") {
        goTo("Format")
    }else if (currentMenu === "Window") {
        goTo("View")
    }else if (currentMenu === "Help") {
        goTo("Window")
    }
}
 
onRightKeyPress() {
    if (currentMenu === "File") {
        goTo("Edit")
    }else if (currentMenu === "Edit") {
        goTo("Format")
    }else if (currentMenu === "Format") {
        goTo("View")
    }else if (currentMenu === "View") {
        goTo("Window")
    }else if (currentMenu === "Window") {
        goTo("Help")
    }else if (currentMenu === "Help") {
        goTo("File")
    }
}

Nothing wrong with that. It’s readable, gets the job done, everything’s perfect. You congratulate yourself on an engineering problem well solved.

Then the PM comes in and says, “We’ve been running tests, and this sequence doesn’t really work. Let’s change the order.โ€

Uh-oh.

“Oh, and we want different apps to have different menus.”

๐Ÿ’ฉ

Now you have to rewrite that whole logic for every app. Every time the PM decides to change the order. That’s not fun at all!

So here’s what you can do instead.

items = ["File", "Edit", "Format", "View", "Window", "Help"]
currentItem = 0
 
onLeftKeyPress() {
    currentItem -= 1;
    if (currentItem < 0) currentItem = items.length-1;
    goTo(items[currentItem]);
}
onRightKeyPress() {
    currentItem += 1;
    if (currentItem >= items.length) currentItem = 0;
    goTo(items[currentItem]);
}

Array of options, pointer to current selection, move pointer when user does something. Now we’re talking!

With this approach, your system is flexible. You can change ordering, add elements, remove elements, even have different lists for different apps or users. It’s great.

No difficult refactoring every time your PM changes their mind, just a data change.

But Swizec, nobody builds keyboard menus anymore!

That’s true! People don’t use keyboards as much as they used to. My whole thing above is pointless when a user can directly tap or click whatever they want.

But we still live in a world of sequental flows. More than ever, things are designed as user flows that go backwards or forwards. Onboarding flows are a good example.

Screen1: “Hai”, Screen2: “Our app does X”, Screen3: “Sign up”, Screen4: “Gives us your phone number”, Screen5: “Credit card maybe?”, Screen6: “Do The Thing”

I simplify, but many apps have that flow.

You can build it as a hard-coded sequence of steps where each step must know who to call next. When your PM experiments, you’re in for a world of heavy refactoring.

I had fun like that just this week. Not my fault even! Others on the team decided to build it the not-data way, and now we all suffer. I’m sure they will learn their lesson soon ๐Ÿ™‚

But Swiz, my flow has branches and shit

So?

You’re going to go into your super fancy logic and insert a bunch of if statements all the time?

if goNext and currentScreen is ‘phone’ and userSubscribed andโ€ฆ

Fuck.That.

Here’s how you do it:

[() => userSubscribed ? <CreditCard /> : <DoTheThing />, ...]

Put functions in your previous/next logic! Core logic stays simple, end result for user is a complex tree of options.

That can get tricky too, I know. But you know what always works? Tree traversal. You can model your flow as a tree and build a simple walker that goes into different branches of the tree based on stuff. Perhaps based on functions in the tree nodes themselves.

๐Ÿ˜‰

Save time. Make business logic your data. If a Turing machine can do it, so can your app.

Happy hacking.

Learned something new? ๐Ÿ’Œ

Join 8,400+ people becoming better Frontend Engineers!

Here's the deal: leave your email and I'll send you an Interactive ES6 Cheatsheet ๐Ÿ“– right away.ย  After that you'll get an email once a week with my writings aboutย React, JavaScript,ย  andย life as an engineer.


You should follow me on twitter, here.