Enterprise Iffing 3.0 for 10x Rockstar Programmers

I’ve had a chance to get back to coding after a couple of months’ hiatus so I thought I’d write about something a bit more fun and way down in the details.

Conditionals add complexity — they seem like a tiny bit of complexity in the singular case, but they really do add up quickly to make code unpredictable and difficult to follow.

Your debugger and your unit tests are probably great at managing that complexity, but they’re really kind of a crutch. You want to remove complexity, not manage it.

As a result I have a bunch of rules around conditionals that I generally always try to follow.

Early-Return is Simpler

Probably my most important personal rule is to use early-returns wherever possible. In general, it’s not simpler to just have a single return at the bottom of a function when you can return earlier in some circumstances. If you know a value shouldn’t be changed after a certain point in a function you should just return right there. There are two reasons it’s simpler: * You don’t have to try to reason about what might still happen later * Subsequent reading of the code (by you or others) can be much faster because you can stop reading the code for a function as soon as the case you’re investigating hits a return.

1
2
3
4
5
6
7
8
9
10
11
12
13
function fizzMyBuzz(i) {
  var output;
  if (i % 15 == 0) {
    output = "FizzBuzz";
  } else if (i % 3 == 0) {
    output = "Fizz";
  } else if (i % 5 == 0) {
    output = "Buzz";
  } else {
    output = i;
  }
  return output;
}

VS

1
2
3
4
5
6
7
8
9
10
11
12
function fizzMyBuzz(i) {
  if (i % 15 == 0) {
    return "FizzBuzz";
  }
  if (i % 3 == 0) {
    return "Fizz";
  }
  if (i % 5 == 0) {
    return "Buzz";
  }
  return i;
}

There’s a visible difference in density here. The second example just has less stuff, even though the number of lines are pretty similar. Early returns mean that subsequent code has to worry a lot less about the effects of previous code.

There’s definitely a trade-off here. Now when you’re looking for where the function returns, it’s not just at the end — it could be in a bunch of places. I think the trade-off makes sense though because even when I’m tracing through function calls in a backward-fashion, I’m usually reading functions from top to bottom.

Try to NOT use negation

Negation is complexity. In my experience it probably even beats off-by-one errors in the category of “things that are too simple to possibly go wrong that go wrong all the time”.

Example:

Don’t allow additional vegetables in this salad but avoid forbidding the addition of non-orange vegetables

So can this salad have carrots or not?!?

VS

No orange vegetables

Oh

Something like…

1
2
3
4
5
if (!specialCase){
  someStuff();
} else {
  someOtherStuff();
}

…should at the very least be changed to:

1
2
3
4
5
if (specialCase){
  someOtherStuff();
} else {
  someStuff();
}

One exception to this rule: If negation will let you early-return, definitely do that! Hopefully this non-non-negation exception is not too complex. ;)

Now you can pretty much deprecate else.

Often when I can’t return early, I just move the entire if..else block to a method where I can return early. Then I don’t need else.

Trying to restrict my use of else is a great forcing function for creating smaller functions and using early-return more often.

1
2
3
4
5
6
7
8
9
10
11
for (var i=1; i <= 20; i++){
  if (i % 15 == 0) {
    console.log("FizzBuzz");
  } else if (i % 3 == 0) {
    console.log("Fizz");
  } else if (i % 5 == 0) {
    console.log("Buzz");
  } else {
    console.log(i);
  }
}

VS

1
2
3
for (var i=1; i <= 20; i++){
  console.log(fizzMyBuzz(i));  // We wrote this earlier in the post!
}

Comments