跳到主要內容

Effective Java - Exceptions

本篇收集觀看Effective Java針對Exceptions章節後所整理的心得。

Use checked exceptions for recoverable conditions and runtime exceptions for programming errors

這個Item在強調例外的使用時機,包含

  • Checked Exceptions應用在recoverable conditions
  • Unchecked(Runtime) Exception應用在programming errors

下一個問題就是怎麼定義recoverable conditions與programming errors。我分享笑談軟體工程:例外處理設計的逆襲中的觀點:

  • Programming errors: design fault造成的,通常代表是bug;因為是bug,所以retry 1億次也還是錯的。
  • Recoverable conditions: component fault造成的,可能是軟體元件、硬體元件突然發生故障,或與其溝通的過程中發生故障而導致的暫態缺陷,透過retry是有機會修復的。如果是永久缺陷,可能就需要排除原因才有機會執行成功。

根據上面的定義,就可以知道什麼時候該拋checked exception,什麼時候該拋unchecked exception了。對應到REST API的回應結果,unchecked exception預設通常都會是5xx錯誤,而checked exception就需要交給client去識別,是屬於需要user介入處理的4xx錯誤,還是server暫時的5xx錯誤。

除此之外,這個Item還有提到幾個設計要注意的事情:

  • 按照慣例,Error應保留給JVM使用,用以表明程序無法繼續執行的情況。所以開發人員通常不該去Extend Error。
  • 例外本身是有著設計上的代表意義,可以在類別上定義能供給Client做進一步處理的介面。不建議Client使用例外本身的字串訊息去做例外處理,因為字串訊息說變就變,程式容易出問題。

Reference:

Favor the use of standard exceptions

與之前Favor the use of standard functional interfaces強調概念類似,盡量reuse現有東西。書中的範例主要都是針對unchecked exception,去說明適用情境:

  • NullPointerException: 不允許輸入參數為null時,就會使用它。可以直接使用Objects.requireNonNull去做檢查。
  • IndexOutOfBoundsException: 輸入參數超過可接受的範圍大小,就會使用它。
  • IllegalArgumentException: 輸入參數不合法,就會使用它,但請撇除上面2個情境。
  • llegalStateException: 當method在不適當的時候被呼叫,就會使用它。其實以物件命名想表達的是物件可能處於error state而無法正常工作,因而拋出llegalStateException。有的時候,這個例外我也有看過被用在絕對不可能發生的情況下去拋出llegalStateException;不過應該要盡量避免這種情境,因為單元測試可能寫不出來。StackOverflow有人對javadoc和effective java描述的使用情境認為有所不同,有興趣可以看這裡,我認為effective java描述的情境可應用的場合會比javadoc廣泛拉。
  • UnsupportedOperationException: 呼叫了不支援的method,就會使用它。我想這個例外,應該也適用於某一個method接收http method,但不支援PUT的時候,client卻傳了PUT的情境吧?
  • ConcurrentModificationException: 如果某個method偵測到multiple threads存取時,就有可能會拋這個例外。書中是說,這個是提示意味不一定準確,而我也沒拋過這個例外,因為通常會去避免發生這種情境,而不會做複雜的檢查。
最後就是這個item也有提到不要直接使用Exception、Throwable、RuntimeException或Error,我認為其實是要呼應Item 73: Throw exceptions appropriate to the abstraction。

Reference:

  1.  Effective Java, 3/e, Item 72。

Throw exceptions appropriate to the abstraction

這個Item講了幾個重要概念:

  1. 要將例外從某個物件拋出去的時候,應該拋出與這個物件相襯的例外,而不要直接將底層來的例外拋出去。這個是要避免client與實作細節直接相依而很容易造成改變。除非這個底層例外對於更上層來說,也是合適的例外。
  2. 將例外轉換且重新包裝(exception wrapping)的時候,要將底層例外設為新例外的cause,以利於client去做完整的log或其它處理。
  3. 勿濫用這個重新包裝機制,盡量消除會造成發生底層例外的原因。例如先做precheck,避免呼叫底層API;或者是在原先API發生問題的時候,進行例外處理,讓服務不會受到中斷,並且log以便之後備查。
第一點與第二點非常同意。以第一點來說,通常我們以不同模組、階層的角度去設計對應的例外,不同模組、階層以Clean Architecture來說,如Gateway、Domain、UseCase layer等,會設計不同的Checked Exception並搭配Homogeneous Exception(同質例外)降低介面演進的機會。關於Homogeneous Exception(同質例外)可以參考搞笑談軟工提及的內容。

第三點的話,我就會看情況而決定。假如我需要多做precheck或者是在API發生問題的時候去讓服務可以正常使用,這些勢必會額外增加開發與維護成本。

Reference:

  1.  Effective Java, 3/e, Item 73。

Strive for failure atomicity

這個Item主要在宣導「在工作失敗時,要努力讓物件的狀態完好如呼叫前」。如果method滿足這個條件,就可以稱為具有failure-atomic的method。(Note.要考慮的不只有待操作的物件,應還有做為輸入參數的物件)

第一個方法是操作Immutable object。因為它不會被改變,自然而然就滿足所謂的failure-atomic。依照過往經驗,如果要回傳一個Collection,盡量讓它是Immutable object只用於計算,並避免直接的修改造成side effect。

第二個方法就是在造成物件狀態改變之前,做precheck。假如不做檢查讓物件進入了錯誤的狀態,反而可能會衍生更多問題,且不好debug。

第三個方法就是先把物件複製一份在操作。就算發生問題,也不會影響到原本的物件狀態。

最後一個方法,就是要實做一段recovery code,好讓發生問題時,可以rollback回原本的狀態。像是使用資料庫的Transaction,或操作檔案前先複製一份,有問題在還原檔案等等。

使用這些方法,都要注意一下“代價”與“多執行緒存取”的問題。像要複製物件或準備環原點,都會存在著性能開銷,且程式碼往往會複雜一些些;比較低成本的Immutable object不一定適用於各種情況;precheck則大部分情況都有可能要注意多執行緒的存取,假如是依賴目標物件的狀態做precheck的話。

另外,書中有建議,如果無法滿足failure-atomic的method,“建議”要寫註解讓用的人知道。

Reference:

  1.  Effective Java, 3/e, Item 76。

Don't ignore exceptions

這個是大家都知道的“不該忽略例外”,但卻都會忽略例外的處理,或者是亂處理。如果有裝像SonarLint檢查工具,在你沒處理例外時,它一定會通知你。沒去處理例外最大的問題,就是當它發生問題的時候,你完全不會知道;針對這種Bad Smell可以參考link內容去做對應的重構。除了沒處理的情況外,我這裡也列一些可能亂處理的情況供大家參考:

  • Dummy handler: link
  • Exceptions should be either logged or rethrown but not both: link

留言

這個網誌中的熱門文章

PostgreSQL - Unattended installation on windows

Introduction 要將別人軟體包裝到自己軟體中,不可或缺的東西就是Unattended installation。以Unattended installation來說,我們可以選擇透過Installer的silent mode安裝,也可以透過把目標軟體做成portable的版本。本篇文章分享這兩種方法,教導大家如何將PostgreSQL透過Unattended installation方式安裝到目標系統成為service。 Note. 本篇以PostgreSQL 10.7為例。 Install with installer Tips 安裝程式或反安裝程式的參數,除了可以直接上官網搜尋Installation User Guide以外,也可以直接使用help參數查詢: postgresql- 10.7 - 2 -windows-x64.exe --help Windows安裝程式主要有EnterpriseDB與BigSQL兩種。BigSQL版本安裝元件是透過網路下載且支援參數不如EnterpriseDB版本多,以我們需求來說,我們傾向於使用EnterpriseDB版本。接下來分享給大家安裝與反安裝方法。 Installation @ echo off set INSTALL_DIR =C:\postgres10 set INSTALLER =postgresql- 10.7 - 2 -windows-x64.exe   rem options for installation set SSMDB_SERVICE =postgresql- 10 set MODE =--unattendedmodeui none --mode unattended   set DB_PASSWD =--superpassword postgres set DB_PORT =--serverport 5432   set SERVICE_NAME =--servicename % SSMDB_SERVICE %   set PREFIX =--prefix "%INSTALL_DIR%" set DATA_DIR =--datadir "%INSTALL_DIR%\data"   set OPTIONS =

How to install RIDE on Windows?

Introduction 多年沒在Windows上開發RobotFramework,趁著這次整理一下RIDE安裝方法。 目前RIDE最新版本與Python對應版本如下: (3.6 < python <= 3.11) Install current released version (2.0.8.1) with: pip install -U robotframework-ride 安裝Python 直接到Python官網找尋最新的3.11版本,我使用3.11.9: link 。安裝就是一直下一步而已。 安裝wxPython 每次安裝RIDE最困難的都是wxPython。看了一下 官網 描述,我就姑且相信一下: 接著進入下 載頁面 就有安裝教學。基本上就是到Python目錄下的Scripts直接執行以下command: pip install -U wxPython 安裝RIDE 接著就如RIDE官網所說,執行以下command: pip install -U robotframework-ride 啟動RIDE 直接在相同目錄下執行ride就可以啟動了,你也可以直接在桌面建ride連結,加快下次啟動時間。 沒想到這次這麼順利就安裝完成了。因為我是使用java去啟動robot framework,就不特別講要怎麼使用pip安裝robot framework了。

Hello World!

即將要搬家,因此舊網頁內容將慢慢轉移至Blogger。 如果要存取舊網頁,可以使用以下連結: https://wiki.tonylin.idv.tw/dokuwiki/doku.php