Handling Failure in a Function Call
When a function fails to complete the action that was requested of it, there are either two or three ways to handle that failure, depending on if the function was expected to return a value or cause a side effect. If the function was expected to cause a side effect, the only two options are to throw an error or fail silently (i.e. do nothing). If the function was expected to return a value, however, it also has an option to throw an error, but since the calling code expects a value, it cannot fail silently. A function which does not throw an error must then either return a nil value or an inert value, depending on the context of the value and the code that may be calling the function.
Silent failure (side effect)
Thrown Error (return value or side effect)
A thrown error shunts the calling function into an alternative path of execution, such that it may recover from the unexpected error. Throwing an error should be used when the failure is severely problamatic relative to success. Failing to save a value to a database for instance, usually prompts some sort of damage mitigation from the record not being made durable, which may result in the application entering an entirely alternative failure state.
Since a thrown error usually must be caught to prevent severe problems when the thread exits, the ability for a function to throw the error should be considered to be a part of that functions Contract so that the calling function may prepare for such a possibility. In Java the errors that a method is allowed to throw are placed in the signature, but even in languages where this is not the case, it should be considered to be part of the informal argreement between function caller and implementation. The thrown error would then be considered to be an alternative to the return value, if it exists, such that the function may return its value or may throw an error. If the calling function fails to catch the error, then the calling functions of that function must take on responsibility to catch the error, and so on and so forth.
Return a Nil Value
A Nil Value is one which exists outside the space of expected values. In this context, a nil value could be anything that conveys that the returned value is not the greenpath expected result. Often the integer -1 will be used as a nil value when negative numbers would conventionally be impossible. This would still be a nil value as the space of correct results is hypothetically the natural numbers.
A nil value is best when the failure likely does not require explicit failure mitigation, but is still important for the calling function to understand. A nil value must, of course, be part of the functions contract, as the nil return value must be accounted for, but it is less intrusive than a thrown error. If the failure is unconcerning the calling function may choose to replace the nil value with an inert value and continue execution unfazed.
Return an Inert Value
An Inert Value is one which exists in the space of expected values, but is selected so as not to induce meaningful execution. The best example is that of returning an empty array instead of values. The calling function may then choose to continue its greenpath, with the understanding that processing the empty array will not harm anything.
Returning an inert value is best when the failure likely does not need to be handled at all, though it depends on there existing a viable candidate for such a value. Not all value spaces will have a value which could be considered inert.
Inert values are similar to silent failure with a side-effect function, but with a return value. Similar to a silent failure, inert values blur the line between failure and success. If a fuzzy search returns no results, is that the expected result, or an inert value from a failure to find any matches? Just as with silent failure, the distinction doesn’t matter much as long as the agreement between caller and implementation.