What If: How to Write Logic Future You Can Read

Pam-Marie Guzzo
IBM Data Science in Practice
4 min readMar 18, 2021

We’ve all been there. A weird bug comes up, it’s late on Friday, we just want a quick fix and a cool drink. You find the offending piece of code, and it looks like this:

if (thisIsTrue || (!thisIsFalse && (trueVariable || thisIsAlsoTrue)) || somethingIsTrue && someMethod() || SomeClass.someOtherMethod()) {
let fruit = isThisTrue ? 'apple' : (!thisIsAlsoFalse ? 'orange' : 'pineapple');
}
Frustrated child looking at screen
Photo by Kelly Sikkema on Unsplash

Now, instead of relaxing on a patio, we’re spending our evening staring at this code, following methods through files, and sobbing quietly.

While you’re likely to run into code like this (as often as not written by Past You), there are some Do’s and Don’t’s for writing logic that can save Future You more pain.

DO use switch statements

If all your logic is doing is checking a variable for matches, use a switch statement. Switch statements are there to make code more readable, and you’ll thank yourself every time you need to add another check to the logic.

DON’T nest ternary operators

It really doesn’t take long to get lost in ternary operators when they’re nested. A quick example shows the headache it can cause:

let fruit = SomeClass.getTruth('isVegetableAlso') === 'true' ? (isTomato ? 'tomato' : 'pizza') : (isBerry ? 'strawberry' : 'apple')

This type of code is often caused by someone needed to add something quick and being too lazy or too uncertain to refactor the logic. The best way to approach this is to add tests for each possible result (if they don’t already exist) then refactor away. Tomorrow you will thank you!

let fruit = 'apple';if (SomeClass.getTruth('isVegetableAlso') === 'true' && isTomato) {
fruit = 'tomato';
} else if (SomeClass.getTruth('isVegetableAlso') === 'true') {
fruit = 'pizza';
} else if (isBerry) {
fruit = 'strawberry'
}

DO make use of variables

Once upon a time I was told it was wasteful to create variables that are only used once. So I would do things like this:

if (SomeClass.isTrue(SomeOtherClass.someMethodResult(someParameter))) {
doThing();
}
a person holding their head as if in pain
Photo by Ben White on Unsplash

The problem with long, complicated lines like this is that when the next person comes along to update the code, there’s a 50/50 chance it turns into this:

if (SomeClass.isTrue(SomeOtherClass.someMethodResult(someParameter)) || SomeClass.isTrue(SomeOtherClass.someMethodResult(someOtherParameter))) {
doThing();
}

Always keep in mind that the person coming along after you (probably Future You) is at least as lazy and busy as you are. The simpler you make things now, the better chance you can read it in the future. Writing this:

let result = SomeOtherClass.someMethodResult(someParameter);if (SomeClass.isTrue(result)) {
doThing();
}

Makes this more likely:

let result = SomeOtherClass.someMethodResult(someParameter);
let otherResult = SomeOtherClass.someMethodResult(someParameter);
if (SomeClass.isTrue(result) || SomeClass.isTrue(otherResult)) {
doThing();
}

DON’T nest more than 3 levels

I get it. Sometimes logic gets very complex. There are layers of permissions and situations that need to be accounted for. Then you end up with something like this:

if (isTrue || isAlsoTrue) {
if (!isFalse) {
if (class.isTrue()) {
doAThing();
} else if (isAlsoTrue) {
doADifferentThing();
if (!class.isFalse()) {
doSomethingElse();
}
someRandomMethod();
}
return Something();
}
return SomethingElse();
}

By the time you get to SomeRandomMethod() it’s likely you’ve lost the chain of what conditions need to be met for that method to trigger. Once you see something like this going on for 50 to 100 lines, you quickly understand the slippery slope of logic like this.

DO feel free to pull things out into methods

Taking the above example, one of the ways this could be simplified is by pulling some of the logic into its own method.

var methodLogicIsIn = () => {
if (isTrue || isAlsoTrue) {
if (!isFalse) {
if (class.isTrue()) {
doAThing();
} else if (isAlsoTrue) {
methodForMoreLogic();
}
return Something();
}
return SomethingElse();
}
};
var methodForMoreLogic = () => {
doADifferentThing();
if (!class.isFalse()) {
doSomethingElse();
}
someRandomMethod();
}

Ideally every method should only do one thing (a good book to read is Clean Code by Robert C. Martin). When you’re in an older application, you’ll likely have to do some refactoring to make life easier. Overly-complicated logic is a great place to start.

DON’T be afraid to refactor

The truth of the matter is all of us have written pieces of code we’re not proud of. Developers are human — we get tired, or stressed, or decaffeinated. Cleaning up old logic should always be encouraged. If the tests aren’t there for you to refactor comfortably, write them. If you feel pressured by time or other priorities, tell anyone who will listen that readable logic needs to be one of those priorities.

As devs, our goal should be to make everything as clear as possible. After all, our drinks are waiting.

two hands clinking beer bottles in front of a sunset
Photo by Wil Stewart on Unsplash

Pam-Marie Guzzo (she/they) is a Software Developer at IBM who is interested in continuous improvement — both personally and in the code base. Pam-Marie is also a Dungeon Master, mother, and occasional writer of questionable quality.

--

--

Pam-Marie Guzzo
IBM Data Science in Practice

Software Developer | Sometime Writer | Occasional Anthropologist | She/They