When defining a function, the developer needs to decide if a failure will be signaled by an exception or a null return, but the developer of the function is often not in a position to know which approach will be more convenient to the users of the function. How can we leave this decision to the function caller?
Let’s consider this problem in the context of a concrete example, a function that finds a purchase order by id.
One possible solution is to add a boolean flag to function arguments.
PurchaseOrder findPurchaseOrder( String id, boolean failIfNotFound )
{
PurchaseOrder po = this.orders.get( id );
if( po == null && failIfNotFound )
{
throw new IllegalArgumentException();
}
return po;
}
The problem with boolean flags is that they tend to obfuscate the calling code. A developer reading the code will not know how the function behaves without researching the semantics of the flag. The problem is magnified if the function already has many arguments.
Another possible solution is to create two variants of the function, with function name signaling its semantics.
PurchaseOrder findOptionalPurchaseOrder( String id )
{
return this.orders.get( id );
}
PurchaseOrder findRequiredPurchaseOrder( String id )
{
PurchaseOrder po = this.orders.get( id );
if( po == null )
{
throw new IllegalArgumentException();
}
return po;
}
That should make the calling code more clear, but now we've doubled the number of functions, contributing to API bloat and higher implementation costs.
Perhaps we should look for inspiration in fluent interface design principles. Maybe the function can return an intermediary object that has optional() and required() methods. That has the clarity of encoding the semantics in the function name without doubling the number of functions.
public final class Result<T>
{
private final T object;
private final RuntimeException exception;
private Result( final T object )
{
this.object = object;
this.exception = null;
}
private Result( final RuntimeException exception )
{
this.object = null;
this.exception = exception;
}
public static <T> Result<T> success( final T object )
{
return new Result<T>( object );
}
public static <T> Result<T> failure( final RuntimeException exception )
{
return new Result<T>( exception );
}
public T required()
{
if( this.exception != null )
{
throw this.exception;
}
return this.object;
}
public T optional()
{
return this.object;
}
}
Result<PurchaseOrder> findPurchaseOrder( String id )
{
PurchaseOrder po = this.orders.get( id );
if( po == null )
{
return Result.failure( new IllegalArgumentException() );
}
return Result.success( po );
}
Notice that when the purchase order is not found, an exception is created, but not thrown. To throw or not to throw decision has been deferred to the caller.
A function caller that prefers a null return would call optional() on the function result and never see the exception.
PurchaseOrder po = findPurchaseOrder( id ).optional();
Similarly, a function caller that prefers an exception would call required() to get the desired behavior.
PurchaseOrder po = findPurchaseOrder( id ).required();