Wyjątki ewoluowały jako uogólnienie błędów. Pierwszy język programowania do włączenia mechanizmu wyjątków był Lisp na początku 1970 roku. Jest dobre podsumowanie w A Pattern of Language Evolution autorstwa Gabriela i Steele. Wyjątki (które nie były jeszcze nazywane wyjątkami) wynikały z potrzeby określenia zachowania programu w przypadku wystąpienia błędu. Jedną z możliwości jest zatrzymanie programu, ale nie zawsze jest to pomocne. Implementacje Lisp tradycyjnie miały sposób na wejście do debuggera w przypadku błędu, ale czasami programiści chcieli włączyć obsługę błędów w swoim programie. Tak więc implementacje Lisp z lat 60. XX wieku miały sposób powiedzieć „zrób to, a jeśli wystąpi błąd, zrób to zamiast tego”. Początkowo błędy pochodziły z prymitywnych funkcji, ale programiści uznali za celowe wyzwolenie błędu, aby pominąć część programu i przejść do procedury obsługi błędów.
W 1972 roku nowoczesna forma obsługi wyjątków w Lisp pojawiła się w MacLisp: throw
i catch
. Software Preservation Grupa wymienia wiele materiałów na wczesnych implementacjach Lisp, w tym The maclisp Reference Manual Wersja 0 Davida Księżyca . Prymitywy catch
i throw
są opisane w §5.3 str.43.
catch
to funkcja LISP do wykonywania ustrukturyzowanych wyjść nielokalnych. (catch x)
ocenia x
i zwraca swoje wartości, z wyjątkiem tego, że jeśli podczas oceny x
(throw y)
powinien zostać oceniony, catch
natychmiast wraca y
bez dalszej oceny x
.
catch
może być również użyty z argumentem econd, bez oceny, który służy jako znacznik do rozróżnienia zagnieżdżonych połowów. (…)
throw
jest używany catch
jako ustrukturyzowany nielokalny mechanizm wyjścia.
(throw x)
ocenia x
i zwraca wartość z powrotem do najnowszej catch
.
(throw x <tag>)
zwraca wartość x
powrotu do najnowszego catch
oznaczonego <tag>
lub nieoznaczonego.
Nacisk kładziony jest na nielokalny przepływ kontroli. Jest to forma goto (goto tylko w górę), która jest również nazywana skokiem . Metafora jest taka, że jedna część programu wyrzuca wartość, aby zwrócić ją do procedury obsługi wyjątków, a procedura obsługi wyjątków przechwytuje tę wartość i zwraca ją.
Obecnie większość języków programowania pakuje znacznik i wartość w obiekt wyjątku i łączy mechanizm przechwytywania z mechanizmem obsługi.
Wyjątki niekoniecznie są błędami. Są sposobem na wyjście z bloku kodu i otaczających go bloków, uciekając, dopóki nie zostanie osiągnięty moduł obsługi wyjątku. To, czy taka rzecz jest uważana za „błąd” w sensie intuicyjnym, jest subiektywne.
W niektórych językach rozróżnia się terminy „błąd” i „wyjątek”. Na przykład niektóre dialekty Lisp muszą zarówno throw
zgłosić wyjątek (przepływ sterowania dla użytkowników, mający na celu wykonanie nielokalnego wyjścia w sposób, który nie oznacza, że coś poszło „nie tak”), a signal
także zgłoszenie błędu (co oznacza, że coś poszło „nie tak” i może spowodować zdarzenie debugowania).