簡體   English   中英

何時捕獲異常與何時拋出異常?

[英]When to catch the Exception vs When to throw the Exceptions?

我已經用 Java 編碼有一段時間了。 但有時,我不明白什么時候應該拋出異常,什么時候應該捕獲異常。 我正在做一個有很多方法的項目。 等級制度是這樣的——

Method A will call Method B and Method B will call some Method C and Method C will call Method D and Method E.

所以目前我正在做的是 - 我在所有方法中拋出異常並在方法 A 中捕獲它,然后將其記錄為錯誤。

但我不確定這是否是正確的做法? 或者我應該開始在所有方法中捕獲異常。 所以這就是為什么這種混亂開始於我的 - 我什么時候應該捕獲異常與什么時候應該拋出異常。 我知道這是一個愚蠢的問題,但不知何故我很難理解這個主要概念。

有人可以給我一個詳細的例子,說明When to catch the Exception vs When to throw the Exceptions以便我的概念得到澄清? 就我而言,我應該繼續拋出異常,然后在主調用方法 A 中捕獲它嗎?

當您在知道要做什么的方法中時,您應該捕獲異常。

例如,暫時忘記它的實際工作原理,假設您正在編寫一個用於打開和讀取文件的庫。

所以你有一堂課,說:

public class FileInputStream extends InputStream {
    public FileInputStream(String filename) { }
}

現在,假設該文件不存在。 你該怎么辦? 如果您正在努力思考答案,那是因為沒有答案…… FileInputStream不知道如何處理該問題。 所以它把它扔到鏈上,即:

public class FileInputStream extends InputStream {
    public FileInputStream(String filename) throws FileNotFoundException { }
}

現在,假設有人在使用您的圖書館。 他們可能有如下所示的代碼:

public class Main {
    public static void main(String... args) {
        String filename = "foo.txt";
        try {
            FileInputStream fs = new FileInputStream(filename);

            // The rest of the code
        } catch (FileNotFoundException e) {
            System.err.println("Unable to find input file: " + filename);
            System.err.println("Terminating...");
            System.exit(3);
        }
    }
}

在這里,程序員知道該做什么,所以他們捕獲異常並處理它。

有兩種情況您應該捕獲異常。

1. 在盡可能低的水平

這是您與第三方代碼集成的級別,例如 ORM 工具或任何執行 IO 操作的庫(通過 HTTP 訪問資源、讀取文件、保存到數據庫,等等)。 也就是說,您讓應用程序的本機代碼與其他組件交互的級別

在此級別,可能會出現您無法控制的意外問題,例如連接失敗和鎖定文件。

您可能希望通過捕獲TimeoutException來處理數據庫連接失敗,以便您可以在幾秒鍾后重試 訪問文件時的異常也是如此,該文件可能在當前被進程鎖定,但在下一瞬間可用。

這種情況下的指導方針是:

  • 僅處理特定異常,例如SqlTimeoutExceptionIOException 永遠不要處理通用異常(類型Exception
  • 僅當您有一些有意義的事情要做時才處理它,例如重試、觸發補償操作或向異常添加更多數據(例如上下文變量),然后重新拋出它
  • 不要在此處執行日志記錄
  • 讓所有其他異常冒泡,因為它們將由第二種情況處理

2. 盡可能高的水平

在將異常直接拋出給用戶之前,這將是您可以處理異常的最后一個地方。

您在這里的目標是記錄錯誤並將詳細信息轉發給程序員,以便他們能夠識別和糾正錯誤。 添加盡可能多的信息,記錄下來,然后向用戶顯示一條道歉信息,因為他們可能對此無能為力,尤其是如果它是軟件中的錯誤。

第二種情況的指導方針是:

  • 處理通用 Exception 類
  • 從當前執行上下文添加更多信息
  • 記錄錯誤並通知程序員
  • 向用戶道歉
  • 盡快解決

這些指南背后的原因

首先,異常代表不可逆轉的錯誤 它們代表系統中的錯誤、程序員犯的錯誤或應用程序無法控制的情況。

在這些情況下,用戶通常無能為力或無能為力 因此,您唯一能做的就是記錄錯誤,采取必要的補償措施,並向用戶道歉。 如果這是程序員犯的錯誤,最好讓他們知道並修復它,朝着更穩定的版本努力。

其次, try catch塊可以根據它們的使用方式屏蔽應用程序執行流程 try catch塊具有與label及其goto同伴類似的功能,它會導致應用程序執行流程從一個點跳轉到另一個點。


何時拋出異常

在開發庫的上下文中更容易解釋。 您應該在遇到錯誤時拋出異常,除了讓 API 的使用者知道並讓他們做出決定之外,您無能為力

假設您是某個數據訪問庫的開發人員。 當您遇到網絡錯誤時,除了拋出異常之外,您無能為力。 從數據訪問庫的角度來看,這是一個不可逆轉的錯誤。

這在您開發網站時有所不同。 您可能會捕獲此類異常以便重試,但如果您從外層接收到無效參數,則希望拋出異常,因為它們應該在那里得到驗證。

這在表示層中又有所不同,您希望用戶提供無效參數。 在這種情況下,您只需顯示一條友好消息而不是拋出異常。


正如https://roaddd.com/the-only-two-cases-when-you-should-handle-exceptions/

當函數遇到失敗,即錯誤時,應該拋出異常。

功能是一個工作單元,故障應被視為錯誤或基於它們對功能的影響。 在函數f 中,失敗是一個錯誤,當且僅當它阻止f滿足其被調用者的任何先決條件,實現任何f自己的后置條件,或重新建立f分擔維護責任的任何不變量

存在三種不同類型的錯誤:

  • 阻止函數滿足另一個必須調用的函數的先決條件(例如,參數限制)的條件;
  • 阻止函數建立其自己的后置條件之一的條件(例如,產生有效的返回值是后置條件);
  • 阻止函數重新建立它負責維護的不變量的條件。 這是一種特殊的后置條件,特別適用於成員函數。 每個非私有成員函數的一個基本后置條件是它必須重新建立其類的不變量。

任何其他條件都不是錯誤,不應報告為錯誤。

在函數檢測到自身無法處理的錯誤並阻止其繼續以任何形式的正常或預期操作時,報告錯誤。

在有足夠知識來處理錯誤、翻譯錯誤或強制執行錯誤策略中定義的邊界的地方處理錯誤,例如在或線程主線上。

來源: C++ 編碼標准:101 條規則、指南和最佳實踐

一般來說,抓住你可以做一些有用的事情的水平。 例如,用戶嘗試連接到某個數據庫,但在方法 D 中失敗。

你想怎么處理? 也許通過放置一個對話框說“對不起,無法連接到服務器/數據庫”或其他什么。 是方法 A、B 或 C 創建了此 SERVER/DB 信息(例如,通過讀取設置文件或要求用戶輸入)並嘗試連接? 可能是應該處理異常的方法。 或者距離應該處理它的方法至少 1。

它確實因您的應用程序而異,因此這只能是非常一般的建議。 我的大部分經驗是使用 Swing/桌面應用程序,您通常可以根據哪些類在執行程序邏輯(例如“控制器”內容)以及誰在放置對話框(例如“查看”內容)來感受。 通常“控制器”應該捕獲異常並嘗試做一些事情。

在網絡應用程序中,這可能有所不同。

一些非常骨架的代碼,大多數類都不存在,我不確定數據庫的 URL 是否有意義,但你明白了。 飄忽不定...

/*  gets called by an actionListener when user clicks a menu etc... */
public URL openTheDB() {
  URL urlForTheDB = MyCoolDialogUtils.getMeAURL(URL somePreviousOneToFillInTheStart);
  try {
     verifyDBExists(urlForTheDB);
     // this may call a bunch of deep nested calls that all can throw exceptions
     // let them trickle up to here

     // if it succeeded, return the URL
     return urlForTheDB;
  }
  catch (NoDBExeption ndbe) {
    String message = "Sorry, the DB does not exist at " + URL;
    boolean tryAgain = MyCoolDialogUtils.error(message);
    if (tryAgain)
      return openTheDB();
    else
      return null;  // user said cancel...
  }
  catch (IOException joe) {
    // maybe the network is down, aliens have landed
    // create a reasonable message and show a dialog
  }

}

我將分享一種模式,它在一兩個生產環境中保存了我的培根。

動機

我的目標是確保在午夜試圖解決 sev1 支持票的可憐的家伙(也許是我)得到一個很好的“由”錯誤的層次結構,包括 ID 等數據,所有這些都不會過於混亂編碼。

方法

為了實現這一點,我捕獲所有已檢查的異常並將它們作為未檢查的異常重新拋出。 然后我在每個架構層的邊界使用全局捕獲(通常是抽象的或注入的,所以它只寫一次)。 正是在這些點上,我可以向錯誤堆棧添加額外的上下文,或者決定是否記錄和忽略,或者使用變量引發自定義檢查異常以保存任何額外的上下文。 順便說一句,我只在頂層記錄錯誤以阻止“雙重記錄”的發生(例如,cron 作業、ajax 的 spring 控制器)

throw new RuntimeException(checked,"Could not retrieve contact " + id);

使用這種方法,您的 GUI 或業務層的方法簽名不會因為必須為與數據庫相關的異常聲明“拋出”而變得混亂。

在現實生活中如何工作的一個例子:

假設我的代碼的工作是更新許多保險單的自動化過程。 該架構支持 GUI 手動觸發一項政策的續訂。 還可以說,對於這些策略之一,評級區域的郵政編碼在數據庫中已損壞。

我想要實現的錯誤日志類型的一個例子是。

日志消息:由於錯誤而標記策略 1234 以進行手動干預:

來自堆棧跟蹤:錯誤更新策略 1234。回滾事務......此捕獲還將涵蓋諸如保存錯誤或字母生成等錯誤。

來自堆棧跟蹤: 由:錯誤評級策略 1234 ......此捕獲將拾取檢索許多其他對象的錯誤,以及算法錯誤,例如 NPE 等......

從堆棧跟蹤:引起:錯誤檢索評級區域 73932 ...

從堆棧跟蹤: 引起: JPA:字段“郵政編碼”中的意外空值

您應該在盡可能低的級別處理異常。 如果方法不能正確處理異常,你應該拋出它。

  • 如果您有連接到資源的方法(例如打開文件/網絡)
  • 如果層次結構中較高的類需要有關錯誤的信息,則拋出

當您想將某些失敗的方法通知給調用者時,您通常會拋出異常。

例如無效的用戶輸入、數據庫問題、網絡中斷、文件缺失

正如其他人所說,作為一般規則,您應該在可以實際處理異常時捕獲異常,否則就拋出它。

例如,如果您正在編寫從保存文件中讀取有關連接播放器的信息的代碼,並且您的 I/O 方法之一拋出IOException ,那么您可能希望拋出該異常,並且調用load方法的代碼將希望捕獲該異常並相應地處理它(例如斷開播放器的連接,或向客戶端發送響應等)。 您不想在load方法中處理異常的原因是因為在該方法中,您無法有意義地處理異常,因此您將異常委托給調用者,希望他們可以處理它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM