Programing: Why I Hate Exceptions

By Xah Lee. Date: . Last updated: .

Few things you probably don't know about exceptions:

golang no exceptions 2015-07-14 13208
golang does not have exceptions. Screenshot from golang FAQ @ https://golang.org/doc/faq .

i've always hated exceptions. Why? it's hard to explain. (when you can't explain something in a clear and logical way, it also means you do not understand it fully)

basically, exceptions are invisible gotos. I want program languages to be modeled after formal languages, and “exceptions” doesn't fit there. 〔►see Programing Language Syntax Soup

while randomly bitching about programing on Google Plus (https://plus.google.com/+XahLee/posts/GfdSQdMhfnq) , i had a fruitful discussion with Nick Alcock and Vance Morris.

I learned quite a lot of things. Here's the best parts.

The Semipredicate Problem

One of the thing i learned, is the term “Semipredicate problem”. The term captured the problem i had for a long time. Namely, when a function returns a value, but the function might encounter a error (For example, invalid input), how do you signal that error?

In computer programming, a semipredicate problem occurs when a subroutine intended to return a useful value can fail, but the signalling of failure uses an otherwise valid return value.[1] The problem is that the caller of the subroutine cannot tell what the result means in this case.

from Semipredicate problem ()

One simple minded solution is to have the function return a list instead. For example, the first element is the actual result, the second contains error value. But this solution is rather ugly. For one thing, some of your functions will have a unnatural return structure. (languages with multi-return solves this. (multi-return means a function returns multiple values. One of them is the normal result, while others you can query.))

Example

The division operation yields a real number, but fails when the divisor is zero. If we were to write a function that performs division, we might choose to return 0 on this invalid input. However, if the dividend is 0, the result is 0 too. This means there is no number we can return to uniquely signal attempted division by zero, since all real numbers are in the range of division.

Practical implications

Early programmers dealt with potentially exceptional cases, as in the case of division, using a convention that required the calling routine to check the validity of the inputs before calling the division function. This was undesirable for two reasons. First, it greatly encumbers all code that performs division (a very common operation). Second, it violates the important principle of encapsulation in programming, whereby the handling of one issue should be contained in one place in the code. If we imagine a more complicated computation than division, the caller may not even know that invalid input is being handed to the target function; indeed, figuring out that the input is invalid may be as costly as performing the entire computation.

also, note it has this to say about exceptions:

They [exceptions] are an example of out-of-band signalling.

which captured why i hate exceptions. Exception system creates invisible exit points that runs in some magic realm.

Solutions to the Semipredicate Problem

Here's a list of solutions:

  1. Return a list, first element contain value, 2nd contains machine readable error type, 3rd contains human readable error message, etc. (golang.)
  2. Function with multiple return values. For example, Common Lisp function can return multiple values, using values, and using multiple-value-bind to read.
  3. Use “out parameter”. In some language, function can have paramater that is meant to be used by passing variable as argument. When the function is called, results are assigned to the variables. Some of these parameters are designated input parameters, and other as output parameter, or error parameter. (C, C++, C#)
  4. Modify a object. A function can take a object as input, and output the same object. If error occurs, the object may have a property “error” with value.
  5. Use global variable for exit-status. For example, bash shell's $?.
  6. Return a special value. For example, Emacs Lisp nil, other's null, -1, or return the argument unchanged (Wolfram Language).
  7. Return a special type. For example, Haskell.
(multiple-value-bind (val1 val2 val3)
    (myfunc ...)
  ;; here , val1 val2 val3 are values of myfunc
  )

Your Exception is Not My Exception

According to a 2006 comparative paper by Joseph R. Kiniry, programming languages differ substantially in their notion of what is an exception. According to Kiniry, the contemporary languages can be roughly divided in two groups:[7]

How Do People Use Exceptions?

How do people use exceptions, depends on lots things. The language, the documentation, the marketing, the style guide, the community.

Kiniry also notes that “Language design only partially influences the use of exceptions, and consequently, the manner in which one handles partial and total failures during system execution. The other major influence is examples of use, typically in core libraries and code examples in technical books, magazine articles, and online discussion forums, and in an organization's code standards.”[7]

Contemporary applications face many design challenges when considering exception handling strategies. Particularly in modern enterprise level applications, exceptions must often cross process boundaries and machine boundaries. Part of designing a solid exception handling strategy is recognizing when a process has failed to the point where it cannot be economically handled by the software portion of the process.[8]

Exceptions in the Wild, Badly Used

Exception handling is often not handled correctly in software, especially when there are multiple sources of exceptions; data flow analysis of 5 million lines of Java code found over 1300 exception handling defects.[9]

Exception History

Software exception handling developed in Lisp in the 1960s and 1970s. This originated in LISP 1.5 (1962), where exceptions were caught by the ERRSET keyword, which returned NIL in case of an error, instead of terminating the program or entering the debugger.[10] Error raising was introduced in MacLisp in the late 1960s via the ERR keyword.[10] This was rapidly used not only for error raising, but for non-local control flow, and thus was augmented by two new keywords, CATCH and THROW (MacLisp June 1972), reserving ERRSET and ERR for error handling. The cleanup behavior now generally called "finally" was introduced in NIL (New Implementation of LISP) in the mid- to late-1970s as UNWIND-PROTECT.[11] This was then adopted by Common Lisp. Contemporary with this was dynamic-wind in Scheme, which handled exceptions in closures. The first papers on structured exception handling were Goodenough (1975a) and Goodenough (1975b).[12] Exception handling was subsequently widely adopted by many programming languages from the 1980s onward.

Originally software exception handling included both resumable exceptions (resumption semantics), like most hardware exceptions, and non-resumable exceptions (termination semantics). However, resumption semantics proved ineffective in practice[citation needed] in the 1970s and 1980s, and are no longer in common use.

Note: according to the Wikipedia article, one of the early language to use exception is MacLisp. Now, Emacs Lisp is descended from MacLisp.

In elisp, the catch and throw is merely a form of goto. And there's also function error for creating error, and function condition-case for checking error, unwind-protect that is like “finally” for cleaning up. There's no hierachy of exception system. (info "(elisp) Nonlocal Exits")

Problems of Exceptions

A contrasting view on the safety of exception handling was given by C.A.R Hoare in 1980, described the Ada programming language as having “…a plethora of features and notational conventions, many of them unnecessary and some of them, like exception handling, even dangerous. […] Do not allow this language in its present state to be used in applications where reliability is critical[…]. The next rocket to go astray as a result of a programming language error may not be an exploratory space rocket on a harmless trip to Venus: It may be a nuclear warhead exploding over one of our own cities.” [14]

Citing multiple prior studies by others (1999–2004) and their own results, Weimer and Necula wrote that a significant problem with exceptions is that they “create hidden control-flow paths that are difficult for programmers to reason about”.[9]:8:27

some other's opinions.


Exception in Haskell

by Yuri Khan,

OK, let's consider Haskell. The canonical textbook explanation of error handling in Haskell goes like this:

Functions that may not be defined for all possible inputs are called “partial functions”. The simplest way to represent a partial function is the Maybe resultType monad. A successful return with value x is represented as Just x, while an error is represented as Nothing. Monadic functions may be chained; if any function in the chain returns Nothing, the whole chain returns Nothing. (This provides the error autopropagation issue.)

myDivide :: Fractional a => a -> a -> Maybe a
myDivide _ 0 = Nothing
myDivide x y = x / y
But returning just one error return value is insufficient for real world programming; at a minimum, we'd like to return a human-readable errror description. Instead of Maybe resultType, we can return Either String resultType, where a Right x value represents a successful return, and a Left "something went wrong" represents an error and provides specific error details. Either a is also a monad and so a function f :: a -> Either e b can be chained with g :: b -> Either e c, to yield a composite function :: a -> Either e c. A chain of functions returns the earliest error, or if none occur, the successful result of the last function in chain.

But human-readable error messages are not very useful within programs; you could say we don't have the exception type tagging property. To this end, we can use Either with a custom error type instead of String, for example, Either (ErrorCode, String) resultType. This way, error objects are machine-readable; we can switch on the error code and decide which errors to handle and which to propagate. (For example, a function that uses a configuration file might query the configuration file for an option, and if this fails because the option is unconfigured, use a default value; but if it fails because the option value is syntactically incorrect, fail.)

But in order for this approach to work in large, the ErrorCode has to be the same throughout the program, which is hard to achieve if you use libraries. This is because the Either (ErrorCode, String) resultType mechanism lacks extensibility. For that reason, several exception support modules (Control.Exception, Control.Monad.Exception) are devised. I am not prepared to give a good analysis on those right now, but I see that they achieve extensibility by defining a typeclass that can be defined for any client-defined error type.

Note: Haskell's exception is completely different than the common notion of Exception in Java, Python. Haskell's exception's is not invisible goto, nor is it multiple exit point. Rather, it's like returning a multiple value, but without needing to check at each call, as the type system makes it a smooth flow.