iT邦幫忙

2022 iThome 鐵人賽

DAY 28
0
Software Development

程式基礎概念討論系列 第 28

[DAY 28] 不怕一萬只怕萬一的例外處理

  • 分享至 

  • xImage
  •  

一般來說,程式在被開發至一定規模後,我們便很難把該程式在執行時所有可能發生的錯誤都找出來並將其修掉,尤其是當我們的程式需要與外部連動的時候,例如透過網路來連接伺服器等,不可控的因素增加後便會使錯誤出現的可能性大幅提高。在理想的狀況下,我們會希望可以把所有的錯誤都修掉,但實際上有些狀況是沒辦法在程式內進行解決的,例如網路連接出現異常等,因此我們便需要一些機制來應對這些預期以外的、難以處理的錯誤,那個機制便是今天我們要討論的例外處理 (Exception handling)

把問題拋出去

如上面所述,有時侯我們可能會在程式執行時遇到一些預期以外的、難以處理的錯誤或問題,它們會被稱為例外 (Exception)。當我們判斷程式中的某部分可能會遇到例外時,可以選擇不在該部分中處理這些例外而是中斷該部分的執行,並把例外送到外部讓程式的其他部分來處理,這行為便被稱為拋/引發 (Throw/Raise) 例外。透過拋例外的行為,我們可以讓該部分的程式碼更集中於它原本的目的而不用處理一些相對關連較少的問題,亦可避免該部分一直被難以解決的問題卡住。在一些大型的項目或程式架構中,例如大部分的程式語言的網路連接功能,也會像這樣把無法輕易在程式內部解決的問題拋出來,如上面提到的網路連接出現異常等,讓程式的其他部分進行處理。

把例外接回來

既然我們有把例外拋出去的行為,自然便會有把例外接 (Catch) 回來的行為。通常來說,我們會把這部分作為可能出現預期以外的問題的函式的外部,並準備一些應對例外的措施。舉例來說,現在我們有一個程式是用來檢查使用者輸入的數字是否正數:[Python]

def check_int(str):
    num = int(str) # 程式會嘗試把文字轉換為整數,如果文字本身沒辦法被轉換為整數的話,便會中斷整個函式並拋出 ValueError 這個例外
    if num >= 0:
        print('輸入的整數為正數!')
    else:
        print('輸入的整數為負數!')
    
str = input('請輸入整數:') # 接收使用者輸入的交字
try:
    check_int(str) # 把使用者輸入的交字傳到函式 check_int 中
except ValueError: # 我們把從函式 check_num 中拋出的例外 ValueError 接回來並進行處理
    print(str + ' 並非整數!')

在上面的程式中,我們把轉換文字為整數及檢查是否正數的部分都放在了函式中,可是當我們實際執行程式的時候,便會發現使用者不一定會按照我們的預想輸入整數。因此,為了避免程式在轉換非整數的文字而陷入崩潰,Python 便在遇到這種無法處理的狀況時中斷程式拋出例外,來提示我們出現了例外並需要我們在轉換文字為整數的函式外協助處理。而為了程式可以順利的繼續執行下去,我們便需要在程式中加上接回例外的部分來進行處理:

主函式 ->
呼叫函式 check_int() ->
呼叫轉換函式 int() ->
轉換函式 int() 中止並拋出例外到呼叫它的函式 check_int() ->
函式 check_int() 沒辦法處理例外 ->
函式 check_int() 中止並拋出例外到呼叫它的主函式 ->
主函式接回例外並進行處理/主函式沒辦法處理例外而中止程式

另外,上面的程式中亦為我們示範了把例外接回來的行為的基本架構:

try: [嘗試執行]
    可能會拋出例外的陳述式
catch/except (例外狀況 1): [接回例外]
    處理例外狀況 1 的陳述式
catch/except (例外狀況 2): [接回例外]
    處理例外狀況 2 的陳述式
...
catch/except (例外狀況 N): [接回例外]
    處理例外狀況 N 的陳述式

在使用接回例外時,需要注意的是每種程式語言所支援的例外狀況跟它們分別的名稱都不一樣,也有一些程式語言允許我們自訂新的例外狀況,因此在實際使用前需要先去查詢一下該程式語言有關例外處理的相關文檔。

無論如何都要執行的 finally 區塊

當我們在執行程式時,如果遇到了例外,雖然可以透過 catch 來避免程式中止,可是 try 區塊中的部分還是被強制中止了,那如果我們在該部分中有些必須執行的程式碼又該怎麼辦呢?這時候我們便需要使用 finally 區塊了,放在 finally 區塊的程式碼會在 try 區塊的程式碼沒有拋出任何例外順利結束後、或是拋出例外並在 catch 區塊中處理例外後自動執行,也就是說我們可以透過 finally 區塊來確保一些程式碼無論有沒有出現例外都一定會被執行

基本的架構如下:

try: [嘗試執行]
    可能會拋出例外的陳述式
catch/except (例外狀況 1): [接回例外]
    處理例外狀況 1 的陳述式
catch/except (例外狀況 2): [接回例外]
    處理例外狀況 2 的陳述式
...
catch/except (例外狀況 N): [接回例外]
    處理例外狀況 N 的陳述式
finally:
    必須執行的陳述式

以下是加上了 finally 區塊的例子:[C#]

using System;

public class ExceptionExample
{
    
    public static void checkInt(string str) {
        int input;
        bool isInt = int.TryParse(str, out input); // 嘗試把文字轉換為整數並儲存到變數 input 中,如果文字本身沒辦法被轉換為整數的話,便回傳 false
        if (!isInt) { // 把文字轉換為整數的函式回傳 false 時
            throw new Exception(); // 把例外 Exception 拋出去
        }
        if (input >= 0) {
            Console.WriteLine("輸入的整數為正數!");
        }
        else {
            Console.WriteLine("輸入的整數為負數!");
        }
    }
    
    public static void Main(string[] args)
    {
        Console.WriteLine("請輸入整數:");
        string str = Console.ReadLine();
        try { // 嘗試執行
            checkInt(str);
        }
        catch (Exception ex) { // 接回例外 Exception
            Console.WriteLine(input + " 並非整數!");
        }
        finally { // 在 try 區塊或 catch 區塊結束後執行
            Console.WriteLine("程式完結!");
        }
    }
}
/*
當我們輸入 10 時:
輸入的整數為正數! <-- try 區塊中函式 checkInt 的 if (input >= 0) 陳述式結果為 True
程式完結! <-- try 區塊結束後執行 finally 區塊

當我們輸入 -10 時:
輸入的整數為負數! <-- try 區塊中函式 checkInt 的 if (input >= 0) 陳述式結果為 False
程式完結! <-- try 區塊結束後執行 finally 區塊

當我們輸入 a 時:
a 並非整數! <-- catch 區塊處理從 try 區塊中函式 checkInt 所拋出的例外 Exception
程式完結! <-- catch 區塊結束後執行 finally 區塊
*/

上一篇
[DAY 27] 突破作用域限制的靜態成員
下一篇
[DAY 29] 模板/泛型幫助我們可以使用同樣的內容應對更多的狀況
系列文
程式基礎概念討論30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言