Why I Hate Exceptions

By Xah Lee. Date: . Last updated: .

Exceptions, they are one of those most complex and ridiculous thing ever devised. — Jonathan Blow.

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 At 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 https://plus.google.com/115849739354666812574 and Vance Morris https://plus.google.com/+VanceMorris

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. e.g. the first item is the actual result, the second contains error value. But this solution is ugly. For one thing, some of your functions will have a unnatural return structure. (languages with multi-value-returns solve this. (In golang, basically it's like returning a list, but not actual list. You get the values by assigning it to multiple variables.)) [see Golang: Function Multi-Value Returns]

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 is a list of solutions:

  1. Return a list, first element contain value, 2nd contains info about the result such as error.
  2. Function with multiple return values. First element contain value, 2nd contains error info, etc. You get these multiple values by assigning to multiple variables. It works like returning a list but transparent. Golang and Common Lisp are like this.
  3. Return a object. The object may have a property “error” with value. This is again similar to returning a list.
  4. Use “out parameter”. In some language, function argument can be variables, and some of the variable are used to store function's return value, and some store error/success value. (C, C++, C#)
  5. Use global variable for exit-status. For example, bash shell's $?.
  6. Return a special value. For example, Emacs Lisp nil. Other lang's null, -1, or return the whole symbolic expression unchanged (e.g. Wolfram Language).
  7. Return a special type. For example, Haskell.

[see Golang: Function Multi-Value Returns]

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

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. Nonlocal Exits (ELISP Manual)

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 to 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.

Joel on Software Exceptions 2023-11-10
[Joel on Software: Exceptions By Joel Spolsky. At http://www.joelonsoftware.com/items/2003/10/13.html , accessed on 2015-07-14 ]

see also, this video, starting at 38:40, about exceptions being the worst invention in programing language.

Ideas about a new programming language for games. by Jonathan Blow. Published on Sep 19, 2014

Exception in Haskell

by Yuri Khan,

(Yuri Khan https://yurikhan.dreamwidth.org/)

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.

Programing Language Design