Saturday, June 15, 2013

Styles of handling conditional logic

TLDR: Raising & catching exceptions is a programming style. Designing functions/methods to always return a value to indicate success or error is a programming style. My preference is the latter, as I will explain below.

This post is a response to a Twitter discussion about the use of exceptions as an indicator of a condition that an API caller must either deal with or defer handling to logic at a higher level of a call stack. To read that discussion thread and all of its branches, you can start here.


In the case above, the get_user method communicates a condition where it is not able to return a user object by means of raising an error (RecordNotFoundError). Anything that calls this method can choose to call it and ignore the possible error case as is shown below:


Or it can wrap the call to get_user in a block that guards against the possibility of the call failing and handling that condition in a rescue block:


What should the code do in the rescue block? Whatever is appropriate given the requirements of the system. Frankly, it doesn't matter. The rescue block is a form of conditional logic branch. The logic of the branch above could be rewritten if, instead of blowing up, the call to get_user always returned something. But what? It could return nil (or whatever value represents "nothing" in your language of choice, if such a concept exists in the language). With this design, the calling code would have to test for whether the method returned a value or not.


Is this better? I think it is not better because the un-value value (nil) is being INTERPRETED by the calling code to mean something. The calling code must know that either a useful object will be returned, or else a reference to something that the calling code must make a decision about will be returned (nil). What is nil? In the style above, nil can mean whatever the API designer wants it to mean. At the same time, nil can mean whatever the caller to the API wants it to mean. It is a value open to interpretation, like a work of art. No right answers, and no wrong answers... And no useful information passed between caller and callee. At least with raised exceptions, the caller can have multiple rescue blocks each catching a different sort of error condition and responding in kind. In my opinion, though, it is better to be uniform and explicit about conditional behavior instead of expressing logic using a mixed set of language constructs.

So how is this accomplished? There's more than one way to do it. One approach is the Null Object pattern . This pattern introduces an object that is meant to behave like an "empty" instance of a type, one which has no real values. So given a type Product:


A variation of Product could be a PerishableProduct:


And for cases where some function or method may return a product or may fail to retrieve a product for some reason, a NullProduct may be returned instead so that calling code which wants to operate uniformly on all products to do something qualitative can get an object reference that will behave like a Product but does not actually represent one.


This approach is useful when the caller needs a usable reference to something but doesn't care what kind of thing it is. The caller relies on polymorphic behavior in whatever is returned in the call that yields either a "real" Product or a NullProduct. However, this is different from the original problem. One where calling an API either returns something useful or blows up, and this dichotomy requires the caller to use conditional logic to account for both sets of outcomes. An alternative to NullObject for more accurately representing the return of something usable or something to indicate a failure or error is the "Either" pattern.

Either is a wrapper object that can hold the "ideal" return value of a call or something that is informationally indicative of the failure to return the ideal value.


The Either is an ideal return value from an API along with whatever error indicator may have occurred in the course of creating (and therefore in lieu of) that value. Callers of a method or function that return an Either must ask the Either reference whether it has a real value or an error and behave appropriately. This turns the original example above into the following:


In this style, there is the same amount of conditional behavior as in the begin/rescue approach, but it is more explicit about handling the conditions. I prefer the clarity of this style. Languages (like Java) that distinguish between checked and unchecked exceptions (ones where the compiler either enforces explicit handling of method invocations that could result in a raised exception vs. ones where a RuntimeException may be thrown that the calling code can either handle or allow to leap up the call stack) put the responsibility of imposing the conditional logic via try/catch blocks in code that calls into an occasionally explosive method or function. For these kinds of languages, API designers have (in my opinion) too much influence over the coding style of the callers of their code with respect to handling success or error cases where their APIs are invoked.

I have been intentional in the use of the word "style" in this post. Expressing conditional logic via if/else, switch/case/when, try/catch/begin/rescue, or polymorphic semantics (depending on the language) is a matter of coding style and developer preference, that of the API designer. The outcome of each is effectively the same, a running program goes down one logical branch or another. Stylistically, I think code should be simple and prefer to use constructs like Either instead of designing APIs that may return something useful or blow-up.

I would like to thank Jessica Kerr for first introducing me to the Either style. It is not an uncommon approach for return values in the Scala world.

5 comments:

Amos King said...

I don't like the Either because we are still asking everywhere. Might as well return nil. What if Either kept that if internal to itself? Then it wouldn't leak that out and it could make the decisions.

House Family said...

Interesting post Mario and excellent work explaining your reasoning. Great timing too since I've been considering alternatives to exceptions this weekend while working on a Pluralsight course.

"...API designers have (in my opinion) too much influence over the coding style of the callers of their code with respect to handling success or error cases where their APIs are invoked."

I'd argue both approaches have equal influence on the caller's coding style. With either approach the caller has to write conditional logic to handle the exception. The shape/syntax merely differs, but the magnitude of influence is comparable.

My take: A method answers a question. If the answer returned would tell a lie, the method should throw an exception. With the either pattern, the caller may overlook handling the either properly. This leads to "failing slow". The caller can't "overlook" an exception. It will bubble up the stack if not handled directly. Thus my preference for the latter.

Mario said...

Cory,

About the risk of the caller overlooking handling the either properly... Would you agree that with any return value, the caller may not do "the right thing" with it? It's not different with an either return as with any other composed object. The caller is always responsible for knowing what to do with the return of a call. They assume that responsibility by writing the code that makes the call.

In a language like Ruby, the two return types wrapped by an Either aren't as obvious as they would be in Java or Scala. That's one of the tradeoffs of a dynamically type language.

I accept your style preference. I used to feel that way too.

CraigBuchek said...

I agree with Cory House -- exceptions allow more flexibility regarding how/where you react to the error condition. If the case is actually exceptional/rare, I usually prefer to use an exception.

The null object pattern, the either pattern, and non-null signal return values are worth considering in various cases, but so are exceptions.

Of course, the whole problem here is that the API has been designed for the client to ask instead of tell. That's not always avoidable, of course, but it's advisable to tell instead of ask wherever possible.

Another good way of dealing with the exceptional case is having the API allow you to specify a value to return in the exceptional case. The Ruby Array#fetch() method (http://ruby-doc.org/core-2.0/Array.html#method-i-fetch) is a good example of this. An even better API is one that lets you pass some code to execute only in the non-exceptional case, like Array#fetch() also does.

Lots of good options.

Luis Aquino said...

hi dadin!!!!!!!!
-luis