從指令式函數返回錯誤碼輕微違反了指令與詢問分隔的規則。它鼓勵了在if語句判斷中把指令當作表達式使用。
- if (deletePage(page) == E_OK)
這不會引起動詞/形容詞混淆,但卻導致更深層次的嵌套結構。當返回錯誤碼時,就是在要求調用者立刻處理錯誤。
- if (deletePage(page) == E_OK) {
- if (registry.deleteReference(page.name) == E_OK) {
- if (configKeys.deleteKey(page.name.makeKey()) == E_OK){
- logger.log("page deleted");
- } else {
- logger.log("configKey not deleted");
- }
- } else {
- logger.log("deleteReference from registry failed");
- }
- } else {
- logger.log("delete failed");
- return E_ERROR;
- }
另一方面,如果使用異常替代返回錯誤碼,錯誤處理代碼就能從主路徑代碼中分離出來,得到簡化:
- try {
- deletePage(page);
- registry.deleteReference(page.name);
- configKeys.deleteKey(page.name.makeKey());
- }
- catch (Exception e) {
- logger.log(e.getMessage());
- }
Try/catch代碼塊醜陋不堪。它們搞亂了代碼結構,把錯誤處理與正常流程混爲一談。最好把try和catch代碼塊的主體部分抽離出來,另外形成函數。
- public void delete(Page page) {
- try {
- deletePageAndAllReferences(page);
- }
- catch (Exception e) {
- logError(e);
- }
- }
- private void deletePageAndAllReferences(Page page) throws Exception {
- deletePage(page);
- registry.deleteReference(page.name);
- configKeys.deleteKey(page.name.makeKey());
- }
- private void logError(Exception e) {
- logger.log(e.getMessage());
- }
在上例中,delete函數只與錯誤處理有關。很容易理解然後就忽略掉。deletePageAndAllReference函數只與完全刪除一個page有關。錯誤處理可以忽略掉。有了這樣美妙的區隔,代碼就更易於理解和修改了。
錯誤處理就是一件事
函數應該只做一件事。錯誤處理就是一件事。因此,處理錯誤的函數不該做其他事。這意味着(如上例所示)如果關鍵字try在某個函數中存在,它就該是這個函數的第一個單詞,而且在catch/finally代碼塊後面也不該有其他內容。
錯誤依賴磁鐵
返回錯誤碼通常暗示某處有個類或是枚舉,定義了所有錯誤碼。
- public enum Error {
- OK,
- INVALID,
- NO_SUCH,
- LOCKED,
- OUT_OF_RESOURCES,
- WAITING_FOR_EVENT;
- }
這樣的類就是一塊依賴磁鐵(dependency magnet);其他許多類都得導入和使用它。當Error枚舉修改時,所有這些其他的類都需要重新編譯和部署。 這對Error類造成了負面壓力。程序員不願增加新的錯誤代碼,因爲這樣他們就得重新構建和部署所有東西。於是他們就複用舊的錯誤碼,而不添加新的。
使用異常替代錯誤碼,新異常就可以從異常類派生出來,無需重新編譯或重新部署。