異常能清楚地將“普通程式”和“錯誤處理”分開了,這使得程式更容易理解。 代碼的可理解性應該是我們虔誠追求的目標。 ...
本小節目錄
13Replace Error Code with Exception(以異常取代錯誤碼)
概要
某個函數返回一個特定的代碼,用以表示某種特定的情況。改用異常。
動機
異常能清楚地將“普通程式”和“錯誤處理”分開了,這使得程式更容易理解。
代碼的可理解性應該是我們虔誠追求的目標。
範例
class Account { /// <summary> /// 餘額 /// </summary> private int _balance; /// <summary> /// 取款 /// </summary> /// <param name="amount">取款金額</param> /// <returns></returns> public int Withdraw(int amount) { if (amount > _balance) { return -1; } _balance -= amount; return 0; } public bool CanWithdraw(int amount) { return amount <= _balance; } public void HandOverdran() { } public void DoTheUsualThing() { } }
為了讓這段代碼使用異常,首先決定使用受控異常還是非受控異常。關鍵在於:調用者是否有責任在取款之前檢查存款餘額,還是應該由Withdraw()函數負責檢查。如果“檢查餘額”是調用者的責任,那麼“取款金額大於存款金額”就是一個編程錯誤,應該使用非受控異常。如果“檢查餘額”是Withdraw()函數的責任,就必須在函數中拋出這個異常。
範例:非受控異常
使用非受控異常就表示應該由調用者負責檢查。首先需要檢查調用端的代碼,它不應該使用Withdraw()函數的返回值,因為返回值只是用來指出程式員的錯誤。如果看到這樣的代碼:
Account account = new Account(); if (account.Withdraw(100) == -1) { account.HandOverdran(); } else { account.DoTheUsualThing(); }
應該將它替換成這樣的代碼:
Account account = new Account(); if (!account.CanWithdraw(100)) { account.HandOverdran(); } else { account.Withdraw(100); account.DoTheUsualThing(); }
現在移除錯誤碼,併在程式出錯時拋出異常。由於這種行為是異常的、罕見的,所以使用衛語句檢查這種情況:
public void Withdraw(int amount) { if (amount > _balance) { throw new ArgumentException("Amount too large."); } _balance -= amount; }
範例:使用受控異常
受控異常的處理方式略有不同。首先可以新建一個合適的異常:
class BalanceException:Exception { }
當然了,這裡不新建也是可以的。
然後調整調用端如下:
Account account = new Account(); try { account.Withdraw(100); account.DoTheUsualThing(); } catch (BalanceException ex) { account.HandOverdran(); }
接下來修改Withdraw()函數,讓它以異常表示錯誤狀況:
public void Withdraw(int amount) { if (amount > _balance) { throw new BalanceException(); } _balance -= amount; }
小結
將錯誤碼替換成異常之後,使得代碼更容易理解。
15Replace Exception with Test(以測試取代異常)
概要
面對一個調用者可以預先檢查的條件,你拋出了一個異常。
修改調用者,使它在調用函數之前先做檢查。
動機
異常可以協助我們避免很多複雜的錯誤處理邏輯。但是,異常也會被濫用。“異常”只應該被用於異常的、罕見的行為,也就是那些產生意料之外的錯誤的行為,而不應該成為條件檢查的替代者。
範例
下麵的例子中,以一個ResourcePool對象管理一些創建代價高昂而又可以重覆使用的資源。這個對象帶有兩個“池”:一個用以保存可用資源,一個用以保存已分配資源。當用戶請求一份資源時,ResourcePool對象從“可用資源池”中取出一份資源交出,並將這份資源轉移到“已分配資源池”。當用戶釋放一份資源時,ResourcePool對象就該將資源從“已分配資源池”放回“可用資源池”。如果“可用資源池”不能滿足用戶的請求,ResourcePool對象就創建一份新資源。
class ResourcePool { private Stack<Resource> _available; private Stack<Resource> _allocated; public Resource GetResource() { Resource result; try { result = _available.Pop(); _allocated.Push(result); return result; } catch (Exception ex) { result = new Resource(); _allocated.Push(result); return result; } } } class Resource { }
在這裡,“可用資源用盡”並不是一件意料外的事件,因此不該使用異常表示這種情況。
為了去掉這裡的異常,首先添加一個適當的提前測試,併在其中處理“可用資源為空”的情況:
class ResourcePool { private Stack<Resource> _available; private Stack<Resource> _allocated; public Resource GetResource() { Resource result; if (_available.Count == 0) { result = new Resource(); _allocated.Push(result); return result; } result = _available.Pop(); _allocated.Push(result); return result; } } class Resource { }
在這裡,可以對條件代碼加以整理,使用Consolidate Duplicate Conditional Fragments:
class ResourcePool { private Stack<Resource> _available; private Stack<Resource> _allocated; public Resource GetResource() { Resource result; if (_available.Count == 0) { result = new Resource(); } else { result = _available.Pop(); } _allocated.Push(result); return result; } } class Resource { }
小結
階段性小結
在對象技術中,最紅要的概念莫過於“介面”。容易被理解和被使用的介面,是開發良好面向對象軟體的關鍵。
最簡單也最重要的一件事就是修改函數名稱。名稱是程式寫作者與閱讀者交流的關鍵工具。只要能理解一段程式的功能,就應該大膽地使用Rename Method將所知道的東西傳達給他人。
函數參數在介面中扮演十分重要的角色。Add Parameter和Remove Parameter都是很常見的重構手法。剛接觸面向對象技術的程式員往往使用很長的參數列。但是,使用對象技術,可以保持參數列的簡短。如果來自同一個對象的多個值被當做參數傳遞,可以運用Preserve Whole Object將它們替換為單一對象,從而縮減參數列。如果此前並不存在這樣一個對象,可以運用Introduce Parameter Object將它創建出來。如果函數參數來自該函數可獲取的一個對象,則可以使用Replace Parameter with Methods避免傳遞參數。如果某些參數被用來在條件表達式中做選擇依據,可以實施Replace Parameter with Explicit Method。另外,還可以使用Parameterize Method為數個相似函數添加參數,將它們合併到一起。
明確地將“修改對象狀態”的函數和“查詢對象狀態”的函數分開設計是一個很好的習慣。如果看到這兩種函數混在一起,可以使用Separate Query from Modifier將它們分開。
良好的介面只向用戶展現必須展現的東西。如果一個介面暴露了過多細節,可以將不必要暴露的東西隱藏起來,從而改進介面的質量。進行重構時,往往需要暫時暴露某些東西,最後再以Hide Method和Remove Setting Method將它們隱藏起來。
構造函數往往比較“麻煩”,因為它強迫你必須知道要創建的對象屬於哪個類,而你往往並不需要知道這一點。可以使用Replace Constructor with Factory Method避免了這不必要的信息。
和許多現代編程語言一樣,C#也有異常處理機制,這使得錯誤處理相對容易一些。不習慣使用異常的程式員,往往會以錯誤碼表示程式遇到麻煩。可以使用Replace Error Code with Exception來運用新的異常特性。但有時候異常也並不是最合適的選擇,應該實施Replace Exception with Test先測試一番。
To Be Continued……