Why are corner cases the devil? 😈

Corner cases make for complex code. They multiply with each other. And as they multiply, they reduce the effectiveness of each new line of code.

Transcript

Eric Normand: Why are corner cases the devil? My name is Eric Normand, and these are my thoughts on functional programming, and that is what I want to talk about today. Why corner cases are evil? Let me just define what I'm talking about. A corner case is any valid argument to a function that requires you to do some kind of check.

We can't just use the value directly. You have to do some kind of check, like an IF statement on that value. You might have to check for the NO case. If your language lets you pass a NO, that's a corner case, if you need to check it. Maybe, a corner case, classic one, is you cannot divide by zero.

Dividing by zero is undefined. So, you need to check, if your number is zero before you divide, otherwise it's going to throw an error or something. There are many types of corner cases...I mean one thing in languages that have lists or sequences. What does it mean to take the first element of an empty list?

In Haskell, this is undefined. So, you have to check — Do I have an empty list? — before you can take the first element. I mean the same for the last, for the second element. These are often not thought of as corner cases because they're so common. We deal with them most of the time, but they're corner cases.

Why are they so evil? When you have a corner case, the best-case scenario is you have to do an IF statement. An IF statement implies two branches. There's the normal case and then the corner case. This is the best case scenario. Sometimes you have a corner case that there's like seven corner cases, and each case requires a check.

https://twitter.com/ericnormand/status/1058434377049346049

So, you actually have eight branches. Let's just talk about the best case scenario, a two-branch check, do I have the normal case or the corner case? That might not seem so bad. It's just an IF statement, but the thing is, these things compound.

If you want to compose two functions up, just regular function composition, that means you have the return value from one function flowing into the argument of the other function. Each of those has a corner case. Again, best case scenarios — if two cases in function F and two cases in function G, how many cases do you have?

You multiply them and you have four. There are four possible branches that your code will execute. Now, if you have another one that you compose up, now you have eight possible branches. They just explode from there.

Each time you had a corner case, each time you compose something with a corner case, you're doubling the number of possible branches you're going to go down. This is why I think that corner cases are evil, because for each of those branches, you could have a different result.

The result is going to be in a different case of an answer. What you get is, then you need another IF statement to figure out which branch it went down. This is now the worst case scenario, because very often, things will collapse back down to a fixed small number of cases. If you're not careful, each of those branches could be a different case that you have to handle.

The only way to know is to check each branch. You might do a case analysis. You have 16 different possible cases. This one was the normal case, but this one was the corner case, and this one was the corner case, this one normal case. All four of those together, that's 16 possible different kinds of answers that you're getting.

You need to have another IF statement. Not only did you have the four normal IF statements, now you have this other big hairy IF statement that you have to deal with.

It's not just the IF statement. If you're using a language like Haskell that requires you to define a new type for any of those possibilities, a new type that represents those 16 different cases...I've got to run, I'm crossing the street...if you have to define a new type, that's a new type that you have to define.

It just gets more complicated. Your program becomes more complicated, and you get this coupling where the 16 possible cases which were just implicitly defined by this function composition, are now have to be known in another place, where you have to pick apart this value that you got back, and figure out what branch it went down.

What's the solution? That's actually going to be the topic of my next thought, my next episode, so please stay tuned for that. I'll talk about one possible answer to this problem of corner cases. The best thing is just to avoid the corner case, just not even have it.

https://twitter.com/ericnormand/status/1047501660690558976?ref_src=twsrc%5Etfw

It's not always avoidable, but if you can avoid it, you should. I talked about in the last one, one way of avoiding it is to lift up into a space where that corner case doesn't exit. You might be able to solve the problem without any branches, because you've got everything you need up in that newer space, that richer space.

But, let's say you do have to have corner cases. You can't avoid it. What do you do? How do you deal with this and stop the explosion, the multiplicative explosion of branches that you might have to go down. That's what we are going to talk about next time.

All right, thank you so much for listening. Please, I love hearing questions or comments. You can reach me on Twitter, @ericnormand, one word, or you can email me. Love to hear what you think, if you have any questions or comments, disagreements. Tell me anything. Lie to me. Tell me the truth, whatever. I just want to hear it.

All right, see you later. Thanks. Bye.