我想anders的另一個觀點在于:exception spec復雜化了接口簽名,復雜化了語言。很明顯的一點,引入了generics的話,exception spec就讓情況復雜很多。
總而言之,在有得有失,利弊難說的時候,保持簡單也許是一個從長遠角度考慮的正確決定。加一個東西永遠比去掉一個東西容易。(當然,眼前確實可能會讓軟件不那么健壯了)
-----------------------------------------------------------------------------------------
ajoo>>
更正一下,沒有看清楚robbin的上下文就胡亂評價了. 該死.
我完全同意robbin的關于loginUser的設計方法.
如果只有用戶存在不存在的簡單情況,自然用返回值多快好省.
但是,當可能的錯誤很多的時候,比如:
用戶不存在,
用戶存在但是被封
密碼錯誤
等等等等, 自然是用exception更好.
用exception的好處是錯誤信息是結構化的,并且錯誤是可以任意擴展的.
比如,用戶被刪除了, 我甚至可以報告用戶被刪除了幾天.
用戶被封了,我甚至可以報告用戶被哪個該死的斑竹封的,什么時候封的,封了幾天等等.
這些,你拿返回值來玩?
這個exception是否是runtime的倒是值得商討.
runtime的缺點是無法強制客戶代碼處理.
checked的缺點是如果加入了新的exception種類,客戶代碼必須馬上改動, 如果中間有很多層,就要層層上報,即使這個exception暫時可能不會出現.
(比如系統封人制度還沒有啟動)
各有利弊. 一般來說我會讓UserNotExistException之類的作為checked exception, 一些不那么重要的exception也許就可以作為runtime.
---------------------------------------------------------------------------------------
robbin
使用Checked Exception還是UnChecked Exception的原則,我的看法是根據需求而定。
如果你希望強制你的類調用者來處理異常,那么就用Checked Exception;
如果你不希望強制你的類調用者來處理異常,就用UnChecked。
那么究竟強制還是不強制,權衡的依據在于從業務系統的邏輯規則來考慮,如果業務規則定義了調用者應該處理,那么就必須Checked,如果業務規則沒有定義,就應該用UnChecked。
還是拿那個用戶登陸的例子來說,可能產生的異常有:
IOException (例如讀取配置文件找不到)
SQLException (例如連接數據庫錯誤)
ClassNotFoundException(找不到數據庫驅動類)
NoSuchUserException
PasswordNotMatchException
以上3個異常是和業務邏輯無關的系統容錯異常,所以應該轉換為RuntimeException,不強制類調用者來處理;而下面兩個異常是和業務邏輯相關的流程,從業務實現的角度來說,類調用者必須處理,所以要Checked,強迫調用者去處理。
在這里將用戶驗證和密碼驗證轉化為方法返回值是一個非常糟糕的設計,不但不能夠有效的標示業務邏輯的各種流程,而且失去了強制類調用者去處理的安全保障。
至于類調用者catch到NoSuchUserException和PasswordNotMatchException怎么處理,也要根據他自己具體的業務邏輯了。或者他有能力也應該處理,就自己處理掉了;或者他不關心這個異常,也不希望上面的類調用者關心,就轉化為RuntimeException;或者他希望上面的類調用者處理,而不是自己處理,就轉化為本層的異常繼續往上拋出來。
-----------------------------------------------------------------------------------
ajoo>>
今天和workmate也有了關于異常的爭論.
問題是這樣:
我們需要實現一個根據一些規則驗證url輸入的正確性.
我的workmate設計成這樣:
bool validateUrl(HttpServletRequest);
我說: 你只能判斷輸入是否正確, 卻不能說哪里出錯啊. 為什么不用exception呢? 不管有什么錯誤, 用exception表示出來, 什么信息都不會丟失.
他說: exception不好. 因為exception不應該參與正常control flow.
我說: 這是你自己的意見. 看你怎么定義control flow了.
他說, 90%的人都這樣認為.
我說, 返回bool失去了對錯誤的fine control.我們只知道是否錯了, 不知道錯在哪里.
他說, 我們可能不一定需要fine control.
然后爭論不休.
最后, 我問他, 那么你怎么告訴別人到底出了什么錯呢?
他說: 我可以提供一個getError()函數嘛.
我說: 那么, 你的getError()是否要重新validate一遍輸入呢?
他說:那... 我可以返回String
String validateUrl(...);
我說, 光string怎么夠呢? 萬一我需要在html上面顯式錯誤信息, 我需要格式化的, 如果你的error message有回車, 你還要換成<br>呢.
他說, 不用擔心, 我會處理br的. (估計是自己把回車替換成<br>吧)
唉, 我還沒敢說, 萬一我們需要對同一個錯誤生成不同的東西呢? 比如, 在數據庫里log東西啦; 在html上用各種不同顏色,字體顯式不同級別的錯誤; 給mainframe發送xml信息之類的.
他肯定會說: 沒那么復雜的.
我說: 如果你throw exception, adapt成bool是舉手之勞. 而如果你返回bool,則不可能轉換成throw exception.
反正,這老哥本著一個:不能用exception來處理control flow的教條, 就是不同意用exception.
沒辦法.
----------------------------------------------------------------------------------------
ajoo>>
無數人說異常不應該控制程序流程。
誰仔細想過為什么嗎?只怕都是人云亦云罷了。
知道,異常效率低嘛。可是程序流程不見得就是都要高效率的呀。
要真是效率決定一切,java程序員都去要飯得了,連c++扇子們都知道什么2/8原則,你們這里拿效率這么個雞毛還當令箭耍什么勁?
沒人什么地方都用異常。但是有些場合用異常就是方便,錯誤種類可擴展,錯誤信息詳細,錯誤處理結構化,可以集中處理錯誤,不復雜化函數接口等等等等,這些都是用異常表達部分程序邏輯的好處。
你說不應該控制程序流程,好辦。給個替代品啊。
Java代碼
UserInfo login(String username, String pwd);
throws AuthencationException;
UserInfo login(String username, String pwd);
throws AuthencationException;
我這里如果login失敗,用異常清晰簡單。您給個其它的好用的解決方案先?
也不知道哪個弱人腦子都不過就給出這么個教條來。scheme還有continuation呢,那可是超級異常,不是一樣用來處理程序流程?
具體情況具體分析,哪個方案最好用就用哪個,哪來那么多教條?
----------------------------------------------------------------------------------------
checked Exception只不過實現了一個自動文檔化的功能,再就是提醒調用者去處理這個錯誤。
但是,為了這點好處把方法簽名和exception綁在一起,其實是個得不償失的錯誤。這會使接口無謂復雜化,還增加了代碼的耦合程度。
舉個例子來說,比如有一個接口I 的某個方法F throws ExceptionA,它的一個實現類里面,F方法里實現里調用了一個類的方法,這個類的方法throws Exception B,這個時候,如果你有三種選擇
1、把Exception B用catch 吃掉
2、改變接口的聲明,這個顯然行不通
3、catch之后拋出一個unchecked Exception,Exception B就這樣被強暴了,變成了一個unchecked Exception。
可見,checked Exception 如果沒有unchecked Exception的幫忙,連最基本的任務都做不好,這個小小的例子,已經充分證明了checked Exception自身就不是一個完備的異常體系,只不過是當時設計者頭腦一發熱,做出了一個自以為漂亮的愚蠢設計。
C#取消掉checked Exception,完全是合理的。
------------------------------------------------------------------------------------------
robbin>>
異常類層次設計的不好帶來的結果就是非常糟糕,例如JTA的異常類層次,例如EJB的異常類層次,但是也有設計的很好的,例如Spring DataAccessException類層次結構。
用設計糟糕的異常類層次來否定異常這個事物,是極度缺乏說服力的,就好像有人用菜刀砍了人,你不能否定這把菜刀一樣。
這個帖子這么長了,該討論的問題都討論清楚了,總結也總結過n遍了,所以我早就沒有興趣再跟帖了。
實際上,這個討論中隱含兩個不斷糾纏的話題:
1、checked ,還是unchecked異常?
2、用自定義的方法調用返回code,還是用異常來表達不期望各種的事件流
經過這么長的討論,我認為結論已經非常清楚:
1、應該適用unchecked異常,也就是runtimeexception,這樣可以讓無法處理異常的應用代碼簡單忽略它,讓更上層次的代碼來處理
2、應該適用異常來表達不期望的各種事件流
事實上,你們去看一下現在Spring,Hibernate3都已經采用這種方式,特別是Spring的DataAccessException異常層次設計,是一個很好的例子。即使用RuntimeException來表達不期望的各種事件流。
-----------------------------------------------------------------------------------------
第一部分 選擇checked or unchecked
這里需要對異常的理解。什么算異常?java的異常處理機制是用來干什么的?異常和錯誤有什么區別?
異常機制就是java的錯誤處理機制!java中的異常意味著2點:第一,讓錯誤處理代碼更有條理。這使得
正常代碼和錯誤處理代碼分離。第二,引入了context的概念,認為有些錯誤是可以被處理的。問題就出在這兒了。
java的checked異常指的就是在當前context不能被處理的錯誤!
這句話其實是對上面2點的總結。首先checked異常是一種錯誤,其次這種錯誤可以被處理(或修復)。
checked異常就是可以被處理(修復)的錯誤,unchecked異常其實就是無法處理(修復)的錯誤。
說到這兒,應該清楚了。別的語言沒有checked異常,就是說它們認為錯誤都無法被修復,至少在語言級
不提供錯誤修復的支持。java的catch clause干的就是錯誤修復的事。
我的理解是,用好java的異常,其實就是搞清楚什么時候該用checked異常。應該把unchecked異常當作
缺省行為。unchecked異常的意思是:當我做這件事時,不可思議的情況發生了,我沒辦法正常工作下去!
然后拋出一個unchecked異常,程序掛起。而checked異常的意思是:當我做這件事時,有意外情況發生,
可以肯定的是,活是沒法干了,但是要不要掛起程序,我這個函數沒法做主,我只能匯報上級!
其實,從上面的分析可以看出,java引入checked異常只是讓程序員多了一個選擇,它并不強迫你使用checked異常。
如果你對什么時候應該使用checked異常感到迷惑,那么最簡單的辦法就是,不要使用checked異常!這里包括2個
方面:
第一,你自己不必創建新的異常類,也不必在你的代碼中拋出checked異常,錯誤發生后只管拋出unchecked異常;
第二,對已有API的checked異常統統catch后轉為unchecked異常!
使用unchecked異常是最省事的辦法。用這種方法也可以享受“正常代碼和錯誤處理代碼分離”的好處。因為我們在調用方法時,
不用根據其返回值判斷是否有錯誤出現,只管調用,只管做正事就ok了。如果出現錯誤,程序自然會知道并掛起。這樣的效果是怎樣
的呢?
第一,我們的業務代碼很清晰,基本都是在處理業務問題,而沒有一大堆判斷是否有錯的冗余代碼。(想想看,如果沒有
throw異常的機制,你只能通過函數的返回值來判斷錯誤,那么你在每個調用函數的地方都會有判斷代碼!)
第二,我們的代碼假設一切正常,如果確實如此,那么它工作良好。但是一旦出現任何錯誤,程序就會掛起停止運行。當然,你可以查看
日志找到錯誤信息。
那么使用checked異常又是怎樣的呢?
第一,你需要考慮更多的問題。首先在設計上就會更加復雜,其次就是代碼更加冗長。設計上復雜
體現在以下方面:
1 對異常(錯誤)的抽象和理解。你得知道什么情況才能算checked異常,使得上級確實能夠處理(修復)這種異常,并且讓整個程序
從這種設計中確實得到好處。
2 對整個自定義checked異常繼承體系的設計。正如那篇文章所說,總不能在一個方法后面拋出20個異常吧!設計自定義checked異常,
就要考慮方法簽名問題,在合適的時候拋出合適的異常(不能一味的拋出最具體的異常,也不能一味拋出最抽象的異常)
第二,業務代碼相比較使用unchecked的情況而言,不夠直接了當了。引入了throws簽名和catch clause,代碼里有很多catch,方法
簽名也和異常綁定了。
第三,有了更強的錯誤處理能力。如果發生了checked異常,我們有能力處理(修復)它。表現在不是任何錯誤都會導致程序掛起,出現了
checked異常,程序可能照樣運行。整個程序更加健壯,而代價就是前面2條。
第二部分 使用checked異常的最佳實踐
現在假設有些錯誤我們確定為checked異常,那么我們針對這些checked異常要怎樣編碼才合理呢?
1 不要用checked異常做流程控制。無論如何,checked異常也是一種錯誤。只是我們可以處理(修復)它而已。這種錯誤和普通業務
流程還是有區別的,而且從效率上來說,用異常控制業務流程是不劃算的。其實這個問題有時候很難界定,因為checked異常“可以修復”,
那么就是說修復后程序照常運行,這樣一來真的容易跟普通業務流程混淆不清。比如注冊用戶時用戶名已經存在的問題。這個時候我們要考慮,
為什么要用checked異常?這和使用業務流程相比,給我帶來了什么好處?(注意checked異常可以修復,這是和unchecked異常本質的區別)
照我的理解,checked異常應該是介于正常業務流程和unchecked異常(嚴重錯誤)之間的一種比較嚴重的錯誤。出現了這種錯誤,程序無法
完成正常的功能是肯定的了,但我們可以通過其他方式彌補(甚至修復),總之不會讓程序掛起就是。其實這一點也是設計checked異常時要考慮
的問題,也是代價之一吧。
2 對checked異常的封裝。這里面包括2個問題:
第一,如果要創建新的checked異常,盡量包含多一點信息,如果只是一條message,那么用Exception好了。當然,用Exception會
失去異常的型別信息,讓客戶端無法判斷具體型別,從而無法針對特定異常進行處理。
第二,不要讓你要拋出的checked exception升級到較高的層次。例如,不要讓SQLException延伸到業務層。這樣可以避免方法
簽名后有太多的throws。在業務層將持久層的所有異常統統歸為業務層自定義的一種異常。
3 客戶端調用含有throws的方法要注意:
第一,不要忽略異常。既然是checked異常,catch clause里理應做些有用的事情——修復它!catch里為空白或者僅僅打印出錯信息都是
不妥的!為空白就是假裝不知道甚至瞞天過海,但是,出來混遲早要還的,遲早會報unchecked異常并程序掛起!非典就是個例子。
打印出錯信息也好不到哪里去,和空白相比除了多幾行信息沒啥區別。如果checked異常都被這么用,那真的不如當初都改成unchecked好了,
大家都省事!
第二,不要捕獲頂層的Exception。你這么干,就是在犯罪!因為unchecked異常也是一種Exception!你把所有異常都捕獲了——不是我
不相信你的能力,你根本就不知道該如何處理!這樣做的直接的后果就是,你的程序一般來說是不會掛起了,但是出現錯誤的時候功能廢了,
表面上卻看不出什么!當然,深究起來,這也不是什么罪大惡極,如果你在catch里打印了信息,這和上面那條的情況是差不多的。而這2條
的共同點就是,沒有正確使用checked異常!費了那么大勁設計的checked異常就是給你們上級(客戶端)用的,結果你們不會用!真的
不如用unchecked干脆利落了!
上面的最佳實踐是引用前面回帖中那篇翻譯的文章,再加上自己的一些理解寫成。ajoo對“不要用異常處理業務流程”提出異議,我是無法辯駁的,畢竟水平不夠。但我想,對于很多如我這樣尚在初級階段的程序員們,把前輩的話奉為教條也無可厚非吧? 至少這樣會少犯錯誤吧,呵呵。
以上觀點均是自己的理解,水平所限,請不吝指正。