異常處理

在計算和計算機編程領域中，例外處理（exception handling，也意譯為異常處理，需注意“異常”一般對應英文abnormality），是對出現的例外的響應處理，在程序執行期間，異常或例外情況需要特殊處理。一般而言，例外打斷正常的執行流程並執行預先登記的“例外處理器”；具體如何去做依賴於它是硬件還是軟件例外，還有軟件例外是如何實現的。
例外是由計算機系統的不同層級來定義的，典型的層級有CPU定義的中斷、操作系統（OS）定義的信號和編程語言定義的例外。每個層級都要求不同例外處理方式，但是它們可以是關聯的，比如說CPU中斷可能被轉變成OS信號。一些例外特別是硬件例外，可以被優雅地處理使得程序執行能在它被中斷的地方恢復。

**硬體的例外處理**
硬體的異常處理機制由CPU完成。這種機制支持錯誤檢測，在發生錯誤後會將程序流跳轉到專門的錯誤處理例程中。發生異常前的狀態存儲在棧上。

**操作系統的例外處理**
針對程序中可能發生的例外，操作系統可能通過IPC來提供對應的處理設施。進程執行過程中發生的中斷通常由操作提供的「中斷服務子程序」處理，操作系統可以藉此向該進程發送信號。進程可以通過註冊信號處理器的方式自行處理信號，也可以讓操作系統執行預設行為（比如終止該程序）。
從進程的視角，硬體中斷相當於可恢復異常，雖然中斷一般與程序流本身無關。

**編程語言的例外處理**
在編程語言領域，通常例外（exception）這一術語所描述的是一種資料結構，該資料結構可以存儲例外的相關訊息。例外處理的常見的一種機制是移交控制權。引發（raise）異常，也叫作拋出（throw）異常，通過該方式達到移交控制權的效果。例外拋出後，控制權會被移交至某處的接（catch），並執行處理。
編程語言對例外有著截然不同的定義，而現代語言大致上可分兩類：- 用作於控制流程的例外，如：Ada、Java、Modula-3、ML、OCaml、Python、Ruby 。- 用作於處理異常、無法預測、錯誤性的情況。如：C++、C#、Common Lisp、Eiffel、Modula-2 。
從子程序作者的角度看，如果要表示當前子程序無法正常執行，拋出例外是很好的選擇。無法正常執行的原因可以是輸入參數無效（比如值在函數的定義域之外），也可以是無法獲得所需的資源（比如文件不存在、硬碟出錯、內存不足）等等。在不支持例外的系統中，子程序需要通過返回特殊的錯誤碼實作類似的功能。然而回傳錯誤碼可能導致不完全預測問題，子程序的使用方需要編寫額外的代碼，才能將普通的回傳值與錯誤碼相區別。
Kiniry強調：“語言設計僅僅部分地影響了對例外的使用，從而影響編程者處理系統執行期間部分或所有失敗的方式。其他主要的影響還有在核心庫、技術書籍、雜誌文章、在線研討論壇和特定組織的代碼標準中的使用示例”。 

**歷史**
在1960和1970年代，Lisp語言發展出軟體例外。最初版本是在1962年Lisp 1.5的時候，這時候異常通過ERRSET關鍵詞進行捕捉，並在出錯時候，通過NIL進行回傳，而不是以前的終止程序或者進行調試器。p=31960年代後半，Maclisp語言通過ERR關鍵詞引入“引發”（Raise）錯誤機制。p=3Lisp的這種創新不僅僅被應用於拋出錯誤，還被應用於“非局部控制流”。在在1972年6月，Maclisp語言通過CATCH和THROW兩個新的關鍵詞來實現非局部控制流，並保留ERRSET和ERR專門做錯誤處理。在1970中後，NIL（“新實現的LISP”）衍生出清除操作UNWIND-PROTECT，對應著現今常見的finally。p=194該操作也被Common Lisp使用了。與之同時代，Scheme也誕生了dynamic-wind，用於處理閉包中的異常。1975a和1975b是介紹結構化的異常處理的開創性文章。p=392 1980年後，異常處理被廣泛利用於許多編程語言。
PL/I語言使用的是動態作用域例外，然而稍微現代的編程語言多用詞法作用域的例外。PL/I語言的例外處理包含事件（不是錯誤）、注意（Attention）、EOF、列舉了的變量的修改（Modification of listed variables）。雖然現在的一些編程語言支持不含錯誤信息的例外，但是他們並不常見。
一開始，軟體的例外處理是包含可恢復的例外，它具有恢復語義，就像大部分的硬體例外一樣，以及不恢復的例外，它具有終止語義。但是，在1960和1970時代，在實踐中得出恢復語義是十分低效的（C++標準相關的討論可見loc=16.6 Exception Handling: Resumption vs. Termination, pp. 390–393），因此恢復語義就很少再出現了，通常只能在類似Common Lisp和Dylan這種語言中見到。

**批評**
1980年Tony Hoare在評論Ada語言時，將異常處理提及為危險特徵。
對於軟件而言，異常處理經常無法正確的處理，尤其是當這裡有多種來自不同源代碼的異常時。在對五百萬行Java代碼進行數據流分析時，我們發現了超過1300個異常處理。這是1999-2004年的前沿報告以及他們的結論，Weimer和Necula寫到，異常是一個十分嚴峻的問題，他們會創造隱藏的控制流途徑，這種途徑是編程人員很難去推理的。
Go語言的初始版本並沒有異常處理，而因此被有的開發者認為控制流十分冗餘。後來，追加了類似的異常處理的語法lang=go/lang=go機制，但是Go語言的作者建立這僅僅在整個程序不可恢復的錯誤時候使用它。
異常，作為一個非結構化的流程，它會增加資源洩露的可能性（如：從鎖住的代碼中逃脫，在打開文件時候逃脫掉），也有可能導致狀態不一致。因此，出現了集中異常處理的資源管理技術，最常見的結合dispose pattern和解除保護（unwind protection）一起使用（如finally語句），會在這段代碼的控制權結束時自動釋放資源。

**編程語言相關支持**
許多常見的程序設計語言支持異常處理，包括：
*Actionscript
*Ada
*BlitzMax
*C++
*C#
*Common Lisp
*D
*ECMAScript
*Eiffel
*Java
*ML
*Modula-2
*Modula-3
*Object Pascal
*Objective-C
*OCaml
*PHP（版本5）
*PL/I
*Prolog
*Python
*REALbasic
*Ruby
*Visual Prolog
*Scheme
*大多數.NET語言
多數語言的異常機制的語法是類似的：用throw或raise拋出一個異常對象（Java或C++等）或一個特殊可擴展的枚舉類型的值（如Ada語言）；異常處理代碼的作用範圍用標記子句（try或begin開始的語言作用域）標示其起始，以第一個異常處理子句（catch, except, rescue等）標示其結束；可連續出現若干個異常處理子句，每個處理特定類型的異常。某些語言允許else子句，用於無例外出現的情況。更多見的是finally, ensure子句，無論是否出現異常它都將執行，用於釋放異常處理所需的一些資源。
C語言沒有try-catch異常處理，而是使用返回碼用於錯誤檢查；setjmp與longjmp標準庫函數可以被用來通過宏實現try-catch處理。一般在異常處理代碼的搜索過程中會逐級完成-{zh-hans:棧捲回;zh-hant:堆疊輾轉開解;}-（stack unwinding）；但Common Lisp中進行異常處理的條件系統，不採取-{zh-hans:棧捲回;zh-hant:堆疊輾轉開解;}-，因此允許異常處理完後在拋出異常的代碼處原地恢復執行。

**C++**
C++異常處理是資源獲取即初始化（RAII）的基礎。異常事件在C++中表示為“異常對象”（exception object）。異常事件發生時，由操作系統為程序設置當前異常對象，然後執行程序的當前異常處理代碼塊，在包含了異常出現點的最內層的try塊，依次匹配同級的catch語句。如果匹配catch語句成功，則在該catch塊內處理異常；然後執行當前try...catch...塊之後的代碼。如果在當前的try...catch...塊沒有能匹配該異常對象的catch語句，則由更外一層的try...catch...塊處理該異常；如果當前函數內的所有try...catch...塊都不能匹配該異常，則遞歸回退到調用棧的上一層函數去處理該異常。如果一直回退到主函數main()都不能處理該異常，則調用系統函數terminate()終止程序。

**Python**
在Python中只存在語法錯誤和例外。語法錯誤是在運行之前發生的。而例外是在運行時發生的錯誤，除非進行捕捉處理，否則它將無條件停止程序。可以書寫代碼來處理選定的例外。
Python語言中對例外處理機制的採用是非常普遍深入的，這種編碼風格被稱為EAFP（請求原諒比得到許可更容易），它假定有效的鍵或特性存在，並在這個假定證明失敗時捕獲例外。Python社區認為這種風格是清晰而快速的，它的特徵是會出現很多try和except語句。這種技術對立於常見於很多其他語言比如C語言中的LBYL（看好再跳）風格。

**Java**
在Java中異常是異常事件（exceptional event）的縮寫。異常是一個事件，它發生在程序運行時並會打亂程序指示的正常流程。當方法出現了錯誤時，方法會創建一個對象並將它交給運行時系統，所創建的對象叫“異常對象”，該對象包含了錯誤的信息（描述了出錯時的程序的類型和狀態）。創建錯誤對象和轉交給運行時系統的過程，叫拋出異常。
class RuntimeException 和class Error均是不檢查的異常（Unchecked Exceptions）。錯誤不等於錯誤類（class Error），錯誤類代表著不應該被捕捉的嚴重的問題。class RuntimeException 意味著程序出現問題了。

**Go**
Go語言提倡的是錯誤處理（error handling）。Go語言設計者系統希望使用者在錯誤出時，顯式地檢查錯誤。 Go雖然不提供與Java語言的try..catch同等的功能語句，但是取而代之，提供了輕型的異常處理機制panic...recover。

**.NET語言**
大多數.NET程序設計語言，內建的異常機制都是沿著函數-{zh-hans:調用棧;zh-hant:呼叫堆疊}-的函數調用逆向搜索，直到遇到異常處理代碼為止。而 Visual Basic（尤其是在其早於 .net 的版本，例如 6.0 中）走得更遠：on error 語句可輕易指定發生異常後是重試（resume）還是跳過（resume next）還是執行程序員定義的錯誤處理程序（goto ***）。

**錯誤處理**
錯誤處理（error handling）是通過處理函數的返回值的形式從而處理錯誤的一種編程方式。在Go等返回值可為複數的語言中，可通過將其中一個值設為錯誤值，從而達到錯誤處理的效果。
f, err := os.Open("filename.ext")
if err != nil {
log.Fatal(err)
}
// do something with the open *File f
在僅僅支持返回狀態碼的語言裡，可通過處理錯誤碼，達到錯誤處理的效果。shell語言可通過$?獲得函數執行的退出碼，從而判斷是否出錯。
在其他語言中，可以通過判斷結果的某一個特徵，從而達到錯誤處理部分的效果，但不意味著這些語言自身支持錯誤處理。如，Java等面向對象的語言往往會通過null值判斷是否執行失敗，但有時候也會通過異常處理判斷是否執行失敗。

**技術問題**

**未捕捉異常**
如果一個異常拋出後，沒有被捕捉，那麼未捕捉異常（uncaught exception）將會在運行時被處理。進行該處理的例程叫“未捕捉異常處理器”（uncaught exception handler）。大部分的處理是終止程序並將錯誤信息打印至控制檯，該信息通常包含調試用的信息，如：異常的描述信息、棧追蹤。通常處於最高級（應用級別）的處理器，即便捕捉到異常也會避免終止自身（如：線程出現異常，主線程也不會終止）。
值得了解的是，在即便未捕捉異常導致了程序異常中斷（如：異常沒被捕捉、滾動未完成、沒釋放資源），程序仍舊能正常地順序性地關閉。只要確保運行時系統能正常地運行，因為運行時系統控制著整個程序的執行。
作為默認的未捕捉異常處理器是可以被替換的，不管是全局還是單線程的，新的未捕捉異常處理器可以嘗試做這些事情：未捕捉異常導致關閉了的線程，使之重啟；提供另一種方式記錄日誌；讓用戶報告未捕捉異常等等。在Java中，單一線程可以使用Thread.setUncaughtExceptionHandler，全局可以用Thread.setDefaultUncaughtExceptionHandler；在python中，可通過修改sys.excepthook。

**異常的靜態檢查**

**= 檢查性異常 =**
Java的設計者設計了 檢查性異常（Checked exceptions）。當方法引發“檢查性異常”時，“檢查性異常”將成為方法符號的一部分。例如：如果方法拋出了IOException ，我們必須顯式地使用方法符號（在Java中是try...catch），如果不這樣做的話將會導致編譯時錯誤。

**異常安全**
一段代碼是“異常安全的”，如果這段代碼運行時的失敗不會產生有害後果，如內存洩露、存儲數據混淆、或無效的輸出。異常安全可分成不同層次：
#“失敗透明”，也稱作“不拋出保證”：代碼的運行保證能成功並滿足所有的約束條件，即使存在異常情況。如果出現了異常，將不會對外進一步拋出該異常。（異常安全的最好的層次）
#“提交或捲回的語義”，或稱作“強異常安全”或“無變化保證”：運行可以是失敗，但失敗的運行保證不會有負效應，因此所有涉及的數據都保持代碼運行前的初始值。
#“基本異常安全”：失敗運行的已執行的操作可能引起了副作用，但會保證狀態不變。所有存儲數據保持有效值，即使這些數據與異常發生前的值有所不同。
#“最小異常安全”，也稱作“無洩漏保證”：失敗運行的已執行的操作可能在存儲數據中保存了無效的值，但不會引起崩潰，資源不會洩漏。
# “沒有異常安全”：沒有保證（最差的異常安全層次）。
例如，考慮一個smart vector類型，如C++的 std::vector或Java的 ArrayList。當一個數據項x插入vector v，必須實際增加x的值到vector的內部對象列表中並且修改vector的計數域以正確表示v中保存了多少數據項；此時如果已有的存儲空間不夠大，就需要分配新的內存。內存分配可能會失敗並拋出異常。因此，vector數據類型如果是“失敗透明”保證將會非常困難甚至不可能實現。但vector類型提供“強異常安全”保證卻是相當容易的；在這種情況下，x插入v或者成功，或者v保持不變。如果vector類型僅提供“基本異常安全”保證，如果數據插入失敗，v可能包含也可能不包含x的值，但至少v的內部表示是一致的。但如果vector數據類型是“最小異常安全”保證，v可能會是無效的，例如v的計數域被增加了，但x並未實際插入，使得內部狀態不一致。對於“異常不安全”的實現，程序可能會崩潰，例如寫入數據到無效的內存。
通常至少需要基本異常安全。失敗透明是難於實現的，特別是在編寫庫函數時，因為對應用程序的複雜知識缺少獲知。

**引用**

**參考文獻**
 oclc=123311492-  dead-url = no-  first = John B.- edition=1st- dead-url=no 

**外部鏈接**
  A Crash Course on the Depths of Win32 Structured Exception Handling by Matt Pietrek - Microsoft Systems Journal (1997)- Article " C++ Exception Handling date=20110717205658 " by Christophe de Dinechin- Article " Exceptional practices date=20110604072934 " by Brian Goetz- Article " Object Oriented Exception Handling in Perl date=20100715182059 " by Arun Udaya Shankar- Article " Programming with Exceptions in C++ date=20091004221159 " by Kyle Loudon- Article " Unchecked Exceptions - The Controversy date=20090805054419 "- Conference slides  Floating-Point Exception-Handling policies (pdf p. 46)  date=20130620140729  by William Kahan-  Descriptions from Portland Pattern Repository date=20110514181448 -  Does Java Need Checked Exceptions?