Error handling

Type of errors by nature

Notice: this classification lacks of formal proof. While first two categories clearly defined, other is more hand-wavy explained and contains all errors, that are not fail in first two categories.

Undefined value

Humankind doesn’t know answers to those questions yet. Generally possible those questions are undecidable.

Example: division by zero, factorial of negative number, etc.

Hardware error

Errors caused by limitations of machine implementation.

Example: integer overflow, loss of significance in floating point arithmetic, not enough memory error, stack overflow, etc.

Programmer level error

Example: network error, disk error, timeout error, interruption.

User level error

Errors caused by interaction of user with a system. Generally expected that system can not be brought in error state with user input. Good system expected to prevent user errors and give user explanations on how to fix the error, but this is more question of usability rather than computability.

Example: wrong file format, text input instead of number.

Type of errors by predictability

Predictable

Errors that will always happen. Think of analogue for notation of function purity for errors.

Example: division by zero will always lead to error.

Unpredictable

Errors that can potentially happen at run time.

Example: network error.

Error handling strategies

Ignore

Consciously or unconsciously error ignored by programmer. In this case system can still be usable or unusable. In some cases it leads to glitches, security holes, hardware damage or even death threat. Not user friendly, can lead to constantly broken state, for example if error occurs inside database transaction.

Panic

The simplest solution to prevent undefined system behavior or hardware damage is to shutdown system at all. Not user friendly, can lead to constantly broken state, for example if error occurs inside database transaction.

Graceful error handling

In case of error system not able to finish desired action, but system will not get in error state, and user will be informed about the error. This method not always guarantee usability, because error messages can be cryptic, or system still need to be restarted.

Other notes

Lack of error logging can lead to difficulties in debugging.

Sometimes showing error messages to user can lead to security holes.

Error handling from syntax point of view

Exception

In some sense exceptions share traits of GOTO - it can jump to unobvious part of code.

Joel Spolsky about exceptions.

From google c++ guide:

Pros:

  • Exceptions allow higher levels of an application to decide how to handle “can’t happen” failures in deeply nested functions, without the obscuring and error-prone bookkeeping of error codes.
  • Exceptions are used by most other modern languages. Using them in C++ would make it more consistent with Python, Java, and the C++ that others are familiar with.
  • Some third-party C++ libraries use exceptions, and turning them off internally makes it harder to integrate with those libraries.
  • Exceptions are the only way for a constructor to fail. We can simulate this with a factory function or an Init() method, but these require heap allocation or a new “invalid” state, respectively.
  • Exceptions are really handy in testing frameworks.

Cons:

  • When you add a throw statement to an existing function, you must examine all of its transitive callers. Either they must make at least the basic exception safety guarantee, or they must never catch the exception and be happy with the program terminating as a result. For instance, if f() calls g() calls h(), and h throws an exception that f catches, g has to be careful or it may not clean up properly.
  • More generally, exceptions make the control flow of programs difficult to evaluate by looking at code: functions may return in places you don’t expect. This causes maintainability and debugging difficulties. You can minimize this cost via some rules on how and where exceptions can be used, but at the cost of more that a developer needs to know and understand.
  • Exception safety requires both RAII and different coding practices. Lots of supporting machinery is needed to make writing correct exception-safe code easy. Further, to avoid requiring readers to understand the entire call graph, exception-safe code must isolate logic that writes to persistent state into a “commit” phase. This will have both benefits and costs (perhaps where you’re forced to obfuscate code to isolate the commit). Allowing exceptions would force us to always pay those costs even when they’re not worth it.
  • Turning on exceptions adds data to each binary produced, increasing compile time (probably slightly) and possibly increasing address space pressure.
  • The availability of exceptions may encourage developers to throw them when they are not appropriate or recover from them when it’s not safe to do so. For example, invalid user input should not cause exceptions to be thrown. We would need to make the style guide even longer to document these restrictions!

Error as return value

Precedence of maybe monad

Null pointer

The inventor, Tony Hoare, has this to say about it:

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

Note: do not confuse with void pointer.

See:

Undefined, Null as no value

See:

Maybe monad

Typical usage: function can return value or one type of error.

Example: first item of a list (head), n-th item of array, position of element in array which in case of element absence will return -1.

In different languages: rust (Option), elm, haskell, other in wikipedia

See also:

Result monad variation

NaN, Infinity

See:

Result-error tuple

See: Go style error handling

RPC

Example: JSON/XML RPC

Result monad

Typical usage: function can return value or more than one type of error. Error object can contain error message, error code or other data to recover from it.

In different languages: elm, rust, haskell (Either - not sure on that), scala (Try)

Promise monad: async error handling

Typical usage: the same as result monad except that data becomes available through time.

In different languages: elm (Task), rust (Future), haskell (Promise)

Errors vs type system

Errors that can be prevented

Assume we have dependent type language, like idris, than we can say that division operation defined for all numeric divisor except zero. So compiler will complain if you would try to pass in all numeric values.

--- example from idris https://github.com/idris-lang/Idris-dev/blob/master/libs/prelude/Prelude/Nat.idr
--- Division where the divisor is not zero.
--- Note: Z stands for Zero, definition follows Peano
divNatNZ : Nat -> (y: Nat) -> Not (y = Z) -> Nat

So if you have some function

someNumber :: a -> Nat
let x = someNumber(123)
1/x
^
| --- This produce type error: expected Nat (Not = Z), got Nat

To make compiler happy:

if (x == 0) then
  --- handle bad case
else
  1/x

Errors that need to be handled

Result monad plus exhaustive checking will spot if you are not handling all error cases

view address model =
    case model of
        Result.Ok i  -> div [] [ text <| toString i ]
        ^
        |-------- Exhaustive check failed: not all cases are handled

To make compiler happy:

view address model =
    case model of
        Result.Ok i  -> div [] [ text <| toString i ]
        Result.Err m -> div [] [ text <| errorMapper m ]

Other resources