一、參考為什么不建議用 try catch
try catch機制非常好。那些覺得try catch不行的人,是他們自己的水平有問題,無法理解這種機制。并且這群人寫代碼不遵守規(guī)則,喜歡偷懶,這才造成try catch不好的錯覺。
詳細解釋:
1.程序要健壯,必須要設(shè)計報錯機制。
最古老,也是最常見的,比如:
bool CreateFile( );
//如果創(chuàng)建文件失敗就返回false,否則返回true。
這種報錯方式,顯然不好。因為它沒有給出產(chǎn)生錯誤的具體原因。
2.改進:一個函數(shù)或過程,會因為不同的原因產(chǎn)生錯誤,報錯機制必須要把這些錯誤原因進行區(qū)分后,再匯報。
比如:
int CreateFile():
//如果創(chuàng)建成功就返回1.
//如果是因為沒有權(quán)限,導(dǎo)致失敗,返回-1。
//如果是因為文件已經(jīng)存在,導(dǎo)致失敗,返回-2。
//如果是因為創(chuàng)建文件發(fā)生超時,導(dǎo)致失敗,返回-3。
這樣看上去,比【1】要好些,至少指出了比較具體的失敗原因,但是,還不夠。
3.很多情況下,函數(shù)需要把詳細的原因,用字符串的方式,返回:
<pre>
class Result
{
....int State;//同【2】
....string ErrorMessage;//如果失敗,這里將給出詳細的信息,如果有可能,應(yīng)該把建議也寫上去。
}
Result CreateFile();
//如果創(chuàng)建成功,返回的Result,State為1,ErrorMessage為null。
//如果是因為沒有權(quán)限,導(dǎo)致失敗,返回的Result,State為-1,ErrorMessage為"用戶【guest】沒有權(quán)限在【C:\】這個目錄下創(chuàng)建該文件。建議您向管理員申請權(quán)限,或者更換具有權(quán)限的用戶。"。
//如果是因為文件已經(jīng)存在,導(dǎo)致失敗,返回的Result,State為-2,ErrorMessage為"文件【C:\abc.txt】已經(jīng)存在。如果需要覆蓋,請?zhí)砑訁?shù):arg_overwrite = true"。
//如果是因為創(chuàng)建文件發(fā)生超時,導(dǎo)致失敗,返回的Result,State為-3,ErrorMessage為"在創(chuàng)建文件時超時,請使用chkdsk檢查文件系統(tǒng)是否存在問題。"。
</pre>
4.我個人推崇上面這種方式,完整,美觀。但是這種流程,容易與正常的代碼混在一起,不好區(qū)分開。因此,Java、C#等設(shè)計了try catch這一種特殊的方式:
<pre>
void CreateFile()
//如果創(chuàng)建成功就不會拋出異常。
//如果是因為沒有權(quán)限,導(dǎo)致失敗,會拋出AccessException,這個Exception的Msg屬性為"用戶【guest】沒有權(quán)限在【C:\】這個目錄下創(chuàng)建該文件。建議您向管理員申請權(quán)限,或者更換具有權(quán)限的用戶。"。
//如果是因為文件已經(jīng)存在,導(dǎo)致失敗,會拋出FileExistedException,這個Exception的Msg屬性為"文件【C:\abc.txt】已經(jīng)存在。如果需要覆蓋,請?zhí)砑訁?shù):arg_overwrite = true"。
//如果是因為創(chuàng)建文件發(fā)生超時,導(dǎo)致失敗,會拋出TimeoutException,這個Exception的Msg屬性為"在創(chuàng)建文件時超時,請使用chkdsk檢查文件系統(tǒng)是否存在問題。"。
</pre>
可見,上述機制,實際上是用不同的Exception代替了【3】的State。
這種機制,在外層使用時:
<pre>
try
{
....CreateFile( "C:\abc.txt" );
}
catch( AccessException e )
{
....//代碼進入這里說明發(fā)生【沒有權(quán)限錯誤】
}
catch( FileExistedException e )
{
....//代碼進入這里說明發(fā)生【文件已經(jīng)存在錯誤】
}
catch( TimeoutException e )
{
....//代碼進入這里說明發(fā)生【超時錯誤】
}
</pre>
對比一下【3】,其實這與【3】本質(zhì)相同,只是寫法不同而已。
5.綜上,我個人喜歡【3】這類面向過程的寫法。但很多喜歡面向?qū)ο蟮呐笥眩烙嫺矚g【4】的寫法。然而【3】與【4】都一樣。這兩種機制都是優(yōu)秀的錯誤處理機制。
6.理論說完了,回到正題,題注問:為什么不用try catch?
答:這是因為,很多菜鳥,以及新手,他們是這樣寫代碼的:
void CreateFile( )
//無論遇到什么錯誤,就拋一個 Exception,并且也不給出Msg信息。
這樣的話,在外層只能使用:
<pre>
try
{
....CreateFile( "C:\abc.txt" );
}
catch( Exception e )
{
....//代碼進入這里說明發(fā)生錯誤
}
</pre>
當(dāng)出錯后,只知道它出錯了,并不知道是什么原因?qū)е洛e誤。這同【1】。
以及,即使CreateFile是按【4】的規(guī)則設(shè)計的,但菜鳥在外層是這樣使用的:
<pre>
try
{
....CreateFile( "C:\abc.txt" );
}
catch( Exception e )
{
....//代碼進入這里說明發(fā)生錯誤
....throw Exception( "發(fā)生錯誤" )
}
</pre>
這種情況下,如果這位菜鳥的同事,調(diào)用了這段代碼,或者用戶看到這個錯誤信息,也只能知道發(fā)生了錯誤,但并不清楚錯誤的原因。這與【1】是相同的。
出于這些原因,菜鳥的同事,以及用戶,并沒有想到,造成這個問題是原因菜鳥的水平太差,寫代碼圖簡單省事。他們卻以為是try catch機制不行。
因此,這就導(dǎo)致了二逼同事,以及傻比用戶,不建議用try catch。
二、參考項目中到處都是try-catch是一種常態(tài)嗎
通常try/catch適用于以下場景:
- 在代碼中對可預(yù)見而又無法掌控的情況進行處理。比如在SOCKET BIND時發(fā)現(xiàn)端口已經(jīng)被占用了、或者IO在打開文件時發(fā)現(xiàn)文件不存在,就需要在catch中做適當(dāng)?shù)奶幚肀苊獬绦騝rash掉;
- 將問題向更上一層面?zhèn)鬟f,將處理權(quán)讓渡給caller。假如你寫了個ORM FRAMEWORK,在delete的時候發(fā)現(xiàn)外鍵關(guān)聯(lián)刪除失敗,F(xiàn)RAMEWORK不能擅自替上層的代碼決定該怎么辦,于是只好把DB的報的錯誤原樣(或者加層外衣)throw出來,調(diào)用者根據(jù)業(yè)務(wù)需要選擇處理方式;
除此之外,所有問題應(yīng)該由程序員主動判斷,就地解決。在規(guī)模比較大的軟件中,定義自己的Exception體系并正確、克制地使用try/catch,可以讓代碼變得易讀易維護還美觀。
傳遞給上層來解決例子如下:
<pre>
void handlearray(int a[]) throws Npe
{
if(a==null)
throw new Npe();
a[0]……//處理部分
}
上層:
try{
handlearray(a);
}catch(E… e)
{
//對a進行處理。
}
</pre>
這時候傳入數(shù)組為空,這個錯誤不是你當(dāng)前這個函數(shù)所能處理的,只能是拋給上層,也就是生成這個數(shù)組,或者能對這個數(shù)組負責(zé)的那部分代碼,讓上層去處理,上層去try cacth,并在catch中對異常處理,類庫中類似的像文件io的時候很多讀寫類都會拋出FileNotFoundException,也是一個道理,當(dāng)上層給我一個找不到的文件,那在我的io類中肯定無法處理你這個異常,只能拋到給我這個文件的那一層,讓那一層的代碼對這個問題進行反應(yīng)。
當(dāng)然有些時候不需要,比如:
<pre>
void makearray(int a[])
{
a=new int[];
……//生成部分
if(a==null)
……//處理部分,此處一般不用拋異常,直接可以在這一層處理掉。
}
</pre>
像這個就不一樣了,因為這次發(fā)生的問題是在我這一層代碼所能控制之內(nèi)的,所以我直接把問題處理掉就好了,沒必要給上層了。
為什么講“正確”并“克制”地使用?因為有些又蠢又懶的程序員喜歡這么干:
- 將函數(shù)所有代碼都放到try{}之中,哪怕 int i = 1這種賦值的都不放過。然后在catch里輸出一個錯誤信息就萬事大吉。這樣看起來是很省心哇,不用動腦子去分析哪里可能發(fā)生什么錯誤,反正所有錯誤都在catch的掌控之中;
- 用try/catch來控制流程。舉個簡單的例子,假設(shè)有這么個要求:
將字符串轉(zhuǎn)換成數(shù)字,并返回該數(shù)字的絕對值,如果出錯了就返回-1. 于是乎,就能見到類似下面代碼的奇葩做法:
<pre>
int parse_number(const char* s){
try{
return abs(atoi(s));
}catch(Exception){
return -1;
}
}
</pre>
這多省事兒,不用考慮s是不是NULL、不用考慮s是不是包含非數(shù)字的字符、不用考慮s是不是超出int的取值范圍...我是個優(yōu)秀的程序員耶~~,我的代碼好簡潔。
try/catch和errno可以結(jié)合起來使用,二者不是非此即彼的關(guān)系,比如在某些場景下,可以將不確定的錯誤簡化歸納為固定的errno輸出,調(diào)用者直接檢查返回的errno即可,簡化了代碼,也減輕了負擔(dān)。比如某函數(shù),成功返回0,失敗返回-1:
<pre>
int foo(double d){
try{
do_something(d);
return 0;
}catch(Exception){
return -1;
}
}
void bar(double d){
int result = foo(d);
if(result == -1) return;
do_next_steps();
}
</pre>
三、細節(jié)
Java中try,catch,finally的用法
Java異常處理的組合方式:
1.try+catch
運行流程:運行到try塊中,如果有異常拋出,則轉(zhuǎn)到catch塊去處理。然后執(zhí)行catch塊后面的語句
2.try+catch+finally
運行流程:運行到try塊中,如果有異常拋出,則轉(zhuǎn)到catch塊,catch塊執(zhí)行完畢后,執(zhí)行finally塊的代碼,再執(zhí)行finally塊后面的代碼。
如果沒有異常拋出,執(zhí)行完try塊,也要去執(zhí)行finally塊的代碼。然后執(zhí)行finally塊后面的語句
Java finally語句到底是在return之前還是之后執(zhí)行?
網(wǎng)上有很多人探討Java中異常捕獲機制try...catch...finally塊中的finally語句是不是一定會被執(zhí)行?很多人都說不是,當(dāng)然他們的回答是正確的,經(jīng)過我試驗,至少有兩種情況下finally語句是不會被執(zhí)行的:
(1)try語句沒有被執(zhí)行到,如在try語句之前就返回了,這樣finally語句就不會執(zhí)行,這也說明了finally語句被執(zhí)行的必要而非充分條件是:相應(yīng)的try語句一定被執(zhí)行到。
(2)在try塊中有System.exit(0);這樣的語句,System.exit(0);是終止Java虛擬機JVM的,連JVM都停止了,所有都結(jié)束了,當(dāng)然finally語句也不會被執(zhí)行到。
四、檢查型異常和非檢查型異常
上圖摘自Java 進階 之 檢查型異常與非檢查型異常
- 直接繼承 Exception 的異常,屬于檢查型異常,必須用try語句塊進行處理或者把異常交給上級方法處理總之就是必須寫代碼處理它。如IOException,SQLException
- 繼承自Runtime Exception或 Error 的是非檢查型異常,可以不用捕獲
五、throw 和 throws區(qū)別
1、throws出現(xiàn)在方法函數(shù)頭;而throw出現(xiàn)在函數(shù)體。
2、throws表示出現(xiàn)異常的一種可能性,并不一定會發(fā)生這些異常;throw則是拋出了異常,執(zhí)行throw則一定拋出了某種異常對象。
3、兩者都是消極處理異常的方式(這里的消極并不是說這種方式不好),只是拋出或者可能拋出異常,但是不會由函數(shù)去處理異常,真正的處理異常由函數(shù)的上層調(diào)用處理。