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.