Brian的雜記
  • Introduction
  • Brian's 雜記
    • My Awesome API
    • FB 大頭貼
    • 硬體雜記
    • PHP
    • project
      • 模擬器
      • WAMP
    • WinMerge
    • 雜記
      • LINQ
      • 方法
      • Grid View
      • namespace
      • global
      • 物件導向
      • Excel
      • VS2017
      • single sign on
      • Master
      • Https
      • 憑證
      • 略過憑證不符
      • NLog
      • 團隊開發
      • .NET Core
      • 共用網路上的芳鄰
      • 爬蟲
      • NPOI
      • RSS
      • 多執行緒
      • 記憶體回收
      • 密碼學
        • BCrypt
        • AES
      • 主機環境建置
      • Session
      • Error
      • IIS 相關
      • 無障礙相關
      • 介面
        • 影像地圖
      • telnet
        • smtp
      • nslookup
      • 協助客戶解決問題
      • 驗證欄位
      • 網站管理
      • 工具整理
    • 正規表示法
    • 影像處理
    • IntelliJ Idea
    • 觀念
      • Clean Code
        • 命名
        • 函式
        • 註解
        • 編排
        • 物件及資料結構
        • 錯誤處理
        • 邊界
        • 單元測試
        • 類別
      • Java 程式風格
      • Design Pattern
        • 單例模式
      • 同步
      • 畫圖
        • ER-Model
        • 類別圖
        • Use Case
        • 有限狀態機
      • 資料 API 文件 分析
      • CORS & SSL
      • 利用DISC幫助溝通
      • OAS
    • 檔案上傳
      • FileStore
      • App Engine
      • Google Storage
    • OAuth vs Open ID
    • MIME
    • 虛擬桌面
    • 待看資料
    • Selenium
    • CDN
    • HTTP
    • 編碼
    • 2nd-ML100Days
      • jupyter
    • 微服務
      • 設計
        • 1 ~ 5
        • 6
        • 7
        • 8
        • 9
    • Gradle
    • Maven
    • Error
    • 批次檔 BAT
    • Kurento
    • WebSocket & WebRTC
  • 需求面能力
    • User Story
  • Google Cloud Platform
    • Compute Engine
  • Python
    • 基本語法
    • Pandas
    • 套件
    • Matplotlib
    • Encoder
    • jupyter
  • Java
    • Java
      • File
      • Exception
      • 物件導向觀念
      • 加密
      • HTTP
      • 集合
      • Stream()
      • Web
      • ResultSet
      • JDK6
      • JDK8
    • 讀取、複寫MP3 Tag
    • Log4j2
    • Servlet
      • 容器
    • JSP
    • JBOSS
    • JWT
    • PreparedStatement
    • Error
    • Spring
      • Spring Boot
        • @Value
        • Build
      • RequestParameter
      • Error
      • Autowired
      • JPA
      • FeignClient
      • WebSocket
      • thymeleaf
      • Security
      • Test
      • Scheduled
      • Redirect
    • IntelliJ
  • Linux
    • Linux
    • Shell Script
    • Cygwin(在Windows執行Linux指令)
  • 前端
    • HTML
      • Link
    • CSS
      • Position
      • padding color
      • display
    • JS
      • jQuery
        • Select2
      • fancybox
      • ES6
      • 效能
      • GoogleMap API
        • Marker
        • InfoWindow
      • 事件
      • CKEditor
      • TGOS
      • JSON
      • QRcode
      • 核心概念
        • 物件 變數 型別
          • number
          • String
          • boolean
          • null & undefined
          • Symbol
        • JS 物件概念
        • 深入理解JS 函式物件
        • 更多ES2015/ES6 全新語言特性
      • Promise
    • 效果應用
  • 資料庫
    • 注意事項
    • MariaDB
    • MySQL
      • inner join 和 join
      • 字串比對
      • 倒數資料
    • SQL
      • DDL 資料定義語言
      • DML 資料操縱語言
      • DCL 資料控制語言
      • TCL 交易控制語言
      • T-SQL
      • CTE
      • JOIN
    • Oracle
    • MSSQL 操作
      • 新增使用者
      • SQL 指令
      • Sequence
    • 差異比較
    • MyBatis
    • Workbench 操作
    • SQL Injection
  • 版本控制
    • Gitlab
      • sign up
      • sign in
      • add project
      • add members
    • SourceGit
      • install
    • SmartGit
      • install
      • operate
      • git 操作雜記
    • TortoiseGit
    • Git
    • TFS
    • SVN
  • Test
    • 軟體測試原則
    • 演算法
    • XMind
      • install
    • Jenkins
      • 建置
    • HTTPie
    • Postman
    • 測試驅動開發
    • 撰寫測試的觀念
    • 測試框架
    • IoC & DI
    • 隔絕相依性的方式
    • JUnit
    • NUnit
    • 習慣
    • 虛設常式
  • Angular
    • hello world
    • ng-options
    • ES6
    • Build & Conponect
    • HttpClient
    • 部署
  • ASP.NET Web Form
    • Chapter 2
      • 2-1
        • 小東西
    • 略過請求驗證
  • Go
Powered by GitBook
On this page
  • 錯誤處理
  • 在開頭寫下Try-Catch-Finally
  • 使用檢查型例外的代價
  • 提供發生例外的相關資訊
  • 從呼叫者的角度定義例外類別
  • 定義正常的程式流程
  • 不要回傳 null
  • 不要傳遞 null
  • 總結

Was this helpful?

  1. Brian's 雜記
  2. 觀念
  3. Clean Code

錯誤處理

錯誤處理

古早時代,當時還沒有例外事件的概念,當時在處理錯誤以設定錯誤旗幟(flag)或回傳錯誤碼,讓呼叫者循線去做處理。

但過去的作法會造成程式碼的雜亂,畢竟 "原先要做的事情" 和 "處理錯誤" 是兩件事情。而處理錯誤的程式碼,通常比較長(各種不同錯誤),導致更加不易閱讀。

而利用例外處理把這兩件事情分開,可以讓我們的程式碼乾淨許多。

在開頭寫下Try-Catch-Finally

Try-Catch-Finally 會在程式裡定義一個視野(Scope),代表Try內的程式隨時會中斷,跑去執行Catch的區塊。

最好養成只要程式碼有可能拋出例外,就在開頭加上Try-Catch-Finally的習慣。

使用檢查型例外的代價

使用檢查型例外的代價是違反了開放閉合準則(Open/Closed Principle)

如果從方法中拋出一個檢查型例外,而catch卻寫在三層外的方法,那麼就必須將這之間的所有方法,在方法署名處都加上該例外的宣告。(假如對低層次的進行修改,可能會被迫將一連串中~高層次的函式署名進行修改,即使那些函式內部的程式碼都沒有被改動)

最終的結果,就是一連串的修改!函式的密封性被破壞了...

建議

如果正在寫一個關鍵的函式庫,那麼檢查型例外有時候滿好用的,因為本來就必須捕捉例外事件。

但對於一般的程式,檢查型例外的相依性會耗費太多成本,不太划算。

提供發生例外的相關資訊

每一個拋出的例外都應該提供足夠的資訊,讓使用者判斷、追查錯誤發生的原因、位置。

在Java裡,可以從任何一個例外事件去獲得堆疊追蹤的訊息,但無法知道原先的意圖。

產生一個有益的錯誤訊息,讓它隨著傳遞。訊息中應包含哪個操作發生錯誤、錯誤的型態是什麼,如果有紀錄執行過程的相關資訊,那就請傳遞足夠的資訊給catch區塊,使那些資訊可以被記錄下來。

從呼叫者的角度定義例外類別

在大多數的例外處理中,不論真正造成例外的原因是什麼,讓我們做的事都是一些標準化流程的處理事項。我們必須將錯誤記錄下來,並確保能繼續執行程式。

在這種情況下,我們只要包裹(wrap)呼叫的API,並確保它只會回傳共用的例外型態,就可以大幅簡化程式。

(利用高層次的例外型態(EX:Exception)去抓取例外,並將例外物件e,丟到一個專門處理例外訊息的方法,達到簡化程式的目的)

定義這種包裹(Wrapper)是非常有用的。 事實上,包裹第三方API是非常有用的實作技巧

當包裹一個第三方的API,就可以減少依賴。將來在更換另一個不同的函式庫時,可以節省許多力氣。而在測試時,也會比較方便。

包裹的另外一個優點,是可以設計自己習慣的API,而不會綁死在原先函式庫的API設計。

?通常,對於程式碼的某個區域而言,單一的例外事件類別是比較有幫助的。伴隨例外事件而發出的資訊,可用來分辨錯誤種類。如果你想捕捉某一個例外事件,並允許其它例外事件通過時,那就使用不同的例外類別。? (看不懂)

定義正常的程式流程

如果遵循上述的建議,最終應能成功分離程式裡的商業法則及錯誤處理,大量的程式看起來像簡潔簡單的演算法。

在大部分的狀況是,這會是很好的安排,不過有時候,你可能會不想要終止運算。

下面這段程式碼取自某個記帳程式的加總消費功能。

try{
    MealExpenses expenses = expenseReportDAO.getMeals(employee.getID());
    m_total += expenses.getTotal();
}catch(MealExpensesNotFound e){
    m_total += getMealPerDiem();
}

在這個商業法則裡,如果肉類被消費,那肉類消費會加總到總消費裡。如果該日未消費的話,員工可以得到一日總量的伙食補貼。 這個例外搞亂了程式的邏輯,如果我們不需要處理特殊情況的話,不是更好嗎?

我們可以修改 expenseReportDAO,讓它總是回傳一個MealExpenses 物件。如果未消費肉類,就直接回傳一個伙食補貼的MealExpense 物件。

public class PerDiemMealExpenses implements MealExpenses{
    public int getTotal() {
        //return the per diem default
    }
}

這樣的寫法稱作 SPECIAL CASE PATTERN[Fowler] (特殊情況模式)。 你建立一個類別或設定一個物件,去特殊處理某些情況,利用這種方法讓程式碼不必處理例外事件,因為例外的行為被包裹在特殊情況物件裡。

不要回傳 null

可以回傳null的方法,就代表使用方法之後,要檢查是否為null,不然很容易造成 NullPointerException。 與其這樣,不如試著讓其拋出一個例外事件,或回傳一個SPECIAL CASE物件(特殊情況物件)來取代回傳null。

如果正要呼叫一個會回傳null的第三方API方法,試著將這個方法用另一個新方法包裹起來,新方法會幫忙拋出例外事件或回傳SPECIAL CASE物件。

在大部分情況下,特殊情況物件能簡單地進行補救措施。狀況如下:

List<Employee> employees = getEmployees();
if(employees != null) {
    for(Employee e : employees) {
        totalPay += e.getPay();
    }
}

現在,getEmployees 可以回傳null,所以必須檢查它。 倘若我們修改getEmployees 函式,使得它回傳一個空白串列(empty list),我們就可以將程式碼整理成:

List<Employee> employees = getEmployees();
for(Employee e : employees) {
    totalPay += e.getPay();
}

以Java來說,當沒有東西的時候,就回傳Collections.emptyList()。 倘若用這種方式來寫程式,就可以將發生NullPointerException的機率降至最低。

不要傳遞 null

除非使用的API有預期會接收到null,否則應該避免傳遞null。

雖然自己在寫API時,可以去檢查參數是否為null,但其實還是需要耗費力氣去寫去判斷,最好的做法還是避免去傳遞null。

大部分的程式語言並沒有一個很好的作法,來處理意外將null傳遞給函式的情況。

因此最好預設禁止傳遞null給函式,大家共同遵守這個規範,因為大家知道,null出現在參數,代表潛在的風險。 而以這樣的經驗來撰寫程式,也可以大幅降低出錯的可能。

總結

Clean Code是易讀的,但它也必須是耐用的,這兩者並無衝突。

當我們將錯誤處理看成一件重要的事情,並將之處理成獨立於主邏輯的可讀程式,代表我們寫出了整潔耐用的程式碼。

當作到這個地步,我們就能獨立的處理它,在程式的可維護性又前進了一大步。

Previous物件及資料結構Next邊界

Last updated 5 years ago

Was this helpful?