Refining Absolute Rules
In the book Code Complete, the author asserts that functions which were expected to compute a value may only fail in one of three ways: They may throw an exception, they may return null, or they may return an [[Inert Value]]. An inert value being a value of the expected type, but which is not considered to result in meaningful execution. If the function was expected to return an array of values, an empty array might be considered inert.
Drive home the idea of an "absolute" rule—a rule without padding.
Most absolute rules like this start from a simple realization. In this case, the author might have realized that failure is only handled in a few distinct ways. From here, a question should be formed to define the boundaries of the realization. In this case, the question would be "how many distinct ways are there to handle failure in a function that is expected to compute a value?"
It's important that your questioned be formed in a way such that the answer will be limited in scope. Otherwise you can't be confident in an absolute rule. If a question is too broad, like "how many different ways are there to produce a pseudo-random value," (hint: infinitely many ways) then you cannot form a sure answer. Only a partial answer, and those are less helpful.
A question that is limited in scope allows for the next step: exhaustive discovery. It is likely that you will already have an answer in mind, but does that answer cover the entire scope of the question? Let's look at our working rule. If a function fails, the only way that it is allowed to exit without returning a value is to throw an exception (or an equivalent action in a language that doesn't have exceptions).
If the function doesn't throw an exception, then it must return a value, but if the function failed to find the correct result, then it must come up with an alternative. Null is an obvious option, but we would need to extend it to include other null-like values, same as we extended "exceptions" to include any halt in execution. The term "null value" could be extended to include any result that exists outside the solution space. If the function was expected to return a number, NaN, short for Not a Number, is a null value. In fact, this is often the result of undefined mathematical operations, like division by zero.
The last option is to select an inert value, a value which is in the solution space, but is not expected to result in meaningful execution. For numbers, this might be zero, negative one, or infinity depending on the context of the caller. This third option might have been missed if we were relying on our intuition alone, but our question and answer approach allows us to be exhaustive. Once we've considered exceptions—any halt in execution—and null values—any return value outside the solution space—we could ask ourselves a more specific question: Is there an incorrect result we could return which is inside the solution space? The answer is yes—inert values.
Go back and generalize Exceptions to cover any halt in execution and null values to cover any return value outside of the solution space.
At this point, we could try our scope trick one more time to see if we could spot a fourth solution. Is there an incorrect result we could return which is inside the solution space. The answer is, yet again, yes. Imagine a video game routine which is expected to return an item list for a chest that the player just opened. The item selection might be based on the context of the dungeon, the difficulty of the boss, the characters level, and even the mods the character has installed. If this function fails to find a competent item list, it could rely on a fallback of gems and gold. This isn't an inert value, as the result is as meaningful to execution as any other item, which implies that we might have found a new failure option.
At this point, we need to decide whether or not to extend the scope of Inert Values, as we did with Null values and NaN. Or if we should add a new entry to our rule, and just say that a simpler but more meaningful Fallback Value is one way of handling failure. Or we could see this as not a failure at all. Even if simple gems and gold isn't an optimal result, it is still a result. If that result isn't considered a failure then it doesn't meet our original question, which only referred to handling failure.
For the sake of being conclusive, we're not going to include Fallback values. Otherwise we'd need to keep this process going, and there's no end to fallback computation.
The author presented his rule very confidently, as he should. How else could a computation fail? Halting execution in response to failure is, by definition, an exception (or at least equivalent to an exception). If the function fails to throw an exception, then it must complete execution, which requires that it return some value. The only two types of values that a failed function could return are a null value or an inert value.
- Invent a question with appears to have an answer bounded by some scope.
- Attempt to answer the question, at least partially.
- Consider how effectively your answer covers all possible answers to the question.
- Extend the boundaries of your answer, either by coming up with new answers, or further broadening existing ones.
- Try to invent a space for your answers. Do they cover all possible vectors for an answer? Are there exceptions?
A thoughtful reader might realize an alternative, and an antagonistic reader would definitely be able to invent an alternative. A function could always return a random value on failure, after all, but it's difficult to think of an actual scenario where that would be desirable. There are, of course, exceptions to every rule, but the rule is still very useful, and is even more useful when treated as an absolute.
Attempting to generalize my rule enough for it to be absolute has forced me to abstract my concept of those three options. An "Exception" now means any halt in execution, "Null values" now includes NaN, and "Inert Values" includes any value which doesn't result in meaningful execution. Say I now wanted to learn Haskell, and I learned that Maybe Monad's—which are allowed to exist in an Empty state—are often used to handle failure. I could recognize the Maybe Monad as a built-in Inert Value. (Or, rather, the Empty state is Inert and the Monad is what makes it Inert.) It's not a perfect analogy to the languages that I've been considering up to this point, but it fits, and it allows me to reuse my understanding of Inert Values from other, more imperative languages when learning Haskell.
Attempting to be absolute also gives me the motivation to exhaustively search for answers. Even now, while writing this article, I thought of new ways to handle failure. If I were to invent this rule myself, as apposed to just ripping it from a book, I might not think of Inert Values right away, despite being familiar with the concept of using empty arrays to avoid meaningful execution without null checks. This process is rigid and exhaustive—define a question with a limited scope of answers and attempt to cover that scope through a combination of creating new answers and generalizing old ones. You will discover new ideas that you otherwise never would have considered.
Finally, using absolute rules opens you up to conflicting ideas. We all like to be right, but we especially like to avoid being wrong. The most effective way to do this is to just pad all of your statements. For example, I could have started my last sentence with "often times." I didn't, because I truly can't think of a more effective way to avoid being wrong. Maybe you can, and maybe you can think of another way to handle failure. In either case I invite you to tell me. By forming my rules as absolutes, I'm daring the world to find alternatives, and I have been corrected many times in my job on many different statements, but each time I've learned something, which is often something I was incapable of considering on my own. I'd rather be decidedly wrong today and less wrong tomorrow than technically right today and just as technically right tomorrow.
Maybe if we all stopped padding our statements we could have a few more rules of thumb on our hands. If we talked with more confidence we might get more utility out of our convictions. At the very least we'll learn more on the path to being wrong.