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.

def get_user(user_id)
user = UserDataModel.find(user_id)
raise RecordNotFoundError unless user
user
end
view raw get_user1.rb hosted with ❤ by GitHub

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:

#boom!!! a RecordNotFoundError is thrown out of this call
my_user = get_user("user id that will fail")
view raw boom.rb hosted with ❤ by GitHub

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:

begin
my_user = get_user("bogus user id")
rescue RecordNotFoundError
log.info("Didn't find the user...")
#return nil? return a "new" user object?
#re-raise the error wrapped in a different one?
#something else?
end

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.

my_user = get_user('some_id_that_may_or_may_not_exist')
if my_user
#do some stuff
else
#do the equivalent of what the rescue block above did
end

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:

class Product
def cost
#some value
end
end
view raw product.rb hosted with ❤ by GitHub

A variation of Product could be a PerishableProduct:

class PerishableProduct < Product
def shelf_life
#something
end
end

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.

class NullProduct < Product
def cost
'$0.00'
end
end
view raw null_product.rb hosted with ❤ by GitHub

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.

class Either
attr_reader :value, :error
def initialize(value: :none, error: :none)
raise ArgumentError.new("Either a value or an error please, not both") if (value != :none and error != :none)
raise ArgumentError.new("Either a value or an error please, please give me one") if (value == :none and error == :none)
@value = value
@error = error
end
def invalid?
error != :none
end
def value?
value != :none
end
end
view raw either.rb hosted with ❤ by GitHub

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:

my_user_either = get_user("bogus user id")
if my_user_either.value?
#do whatever with my_user_either.value
else if my_user_either.invalid?
log.info("Didn't find the user...")
#do something with my_user_either.error
end
view raw user_either.rb hosted with ❤ by GitHub

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.

6 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.

Cory House 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.

Anonymous 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.

Unknown said...

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

Cameron said...

Thhanks for a great read