何謂異常
很多人在討論異常的時候很模糊,仿佛所謂異常就是try{}catch{},異常就是Exception,非常的片面,所以導致異常影響性能,XXXX……等很多奇怪的言論,所以在此我意在對異常正名。以下,我將異常這個很寬泛,容易被曲解的詞進行嚴格的划分。
異常機制
所謂異常機制也就是指的語言平台支持異常這種錯誤處理模式的機制,比如c#里的Exception對象,try{}catch{}finally{}結構,throw拋出異常的語句,等等,均為c#語言里對異常機制的實現。
異常機制是隨着語言而存在的,一種語言既然支持異常機制,那么異常就是不可回避的,哪怕你自己不throw異常,你所使用的系統類,也會拋出異常給你,所以說討論在系統里用不用異常是非常可笑的事情。異常機制就像系統的后門,當一個過程執行中系統出錯的時候,或者你認為系統不正常的時候,就把當時的情況拍個快照生成異常對象,通過特殊的通道通知調用這個過程的方法。
異常對象
異常對象是異常機制中用來描述異常,記錄錯誤信息,可以說是錯誤發生是的快照,就跟你開快車闖紅燈被天眼拍到的照片差不多。
拋出異常
所謂拋出異常就是異常機制中通知調用的方法本方法出錯了的手段。而拋出異常也是很多人詬病異常對性能影響的地方,因為系統性能的開銷都是由拋出異常所引起的。但是拋出異常也無法避免,因為這是系統本身的特點,你不拋出異常,系統自己也會拋出,一旦系統拋出了異常對象,你怎么來避免這個性能的開銷?沒轍
比如方法A調用了方法B,方法B訪問數據庫,出錯了,那么B就會拋出異常,而A就需要catch這個異常來處理。
拋出異常有兩種形式,一種是系統拋出的異常,一種是我們自己認為在處理一段邏輯的時候當邏輯錯誤,就可以通過拋出一個異常對象,來通知調用這個方法的方法,這里出錯了。
系統拋出異常
系統拋出的異常對編程人員來說是透明的,也就是我們不需要關心系統是如何得知出錯了的,系統類庫一旦出錯就會將異常對象拋給我們調用它的方法,因為系統本身並不知道要如何處理這個錯誤。
用戶拋出異常
用戶拋出異常通常在自己寫類庫,定義一套API給別人使用的時候用到,這個時候我們並不知道要如何處理這些邏輯錯誤,所以就需要交給知道如何處理邏輯錯誤的方法去處理。
在C#里通過定義了throw關鍵字來拋出異常對象,這里除了拋出新創建的異常對象,也可以通過將已經catch到的異常重新拋出。如果不知道如何處理這個異常,重新拋出異常是很好的習慣,因為既然已經拋出了異常,那么性能已經損失了,所以也不在乎多這么一丁點來更好的挽回錯誤的結果。
處理異常
所有的異常必須得到妥善的處理,也就是說你必須處理所有的異常。因為系統出錯就拋異常,所以這個是c#骨子里的東西,無法避免,所以,系統里到處都會充斥着try{}catch{}。
但是try{}catch{}本身並不會影響系統的性能,所以在沒有異常發生的時候try{}catch{}是不會讓你系統變慢的,而一旦發生系統異常,你不處理系統就崩潰掉了,你到底是願意系統慢一點點然后處理掉這個錯誤呢還是願意系統崩潰掉呢?
對於處理異常這一點其實我覺得java那種比較嚴格的,要求嚴格聲明並處理所有異常的方式比較好,能夠強制讓你重視起異常這回事來,免得系統崩潰掉才想起自己沒處理這個異常。
如何處理
如何正確的處理異常?這是個很多c#程序員都沒最終搞明白的話題,很多人所了解的也無非就是不要用異常處理當業務邏輯,但是何謂用異常來處理邏輯就不得而知了。這里我來詳細說明一下,什么地方要用異常,什么地方不要用。
首先有一個前提,異常機制是一個由底向上的冒泡的過程,所以正常的邏輯是,異常由底層拋出,由高層來處理。
處理異常正確的例子
要編寫健壯的引用程序,首先要保證必須處理所有的系統異常,也就是調用.NET類庫的方法的時候,這個方法可能拋出的異常
例:
我們可以看到,GetResponse方法會拋出兩個異常 InvalidOperationException和WebException。那么我們就需要將調用的代碼所在的catch單獨處理這兩個異常
如果你在這個方法中不知道應該如何處理這個異常,比如這個方法不上不下,和表現層離了好幾層,而又需要在表現層通知用戶或者由上層業務來決定是通知用戶還是悄悄的進村,又或者是悄悄的重新嘗試一次,那么自己不能決定的事情就要拋給上層去處理。
有人可能會說.NET可以統一處理異常的,不過我不推薦那種大而全的處理方式,不夠細致,很多時候會被奇怪的錯誤搞得你很追查錯誤的本源。且比如說遇到網絡問題重試就沒法處理。
異常必須就近處理,這樣才能方便追查,而且注意那個“xxx方法出錯了”那個地方,很重要,這個描述可以讓你在系統排錯的時候少走很多彎路,盡量寫詳細點。
如果你的代碼是給別的程序員使用,而不是和最終用戶直接交互,那么除了處理所有系統拋出的異常以外,還需要用異常來驗證過濾入口參數。比如,假設你要給其他程序員提供一個將用戶對象插入數據庫的方法:
public void InsertUser(User user) { if(user==null) { throw new ArgumentNullException("參數user為null"); } //調用Orm }
這里我們驗證了入口參數,並盡早的拋出了異常,因為這里拋出的異常和不處理等db操作拋出異常,肯定是這里手動拋出的開銷更小。這里有一個原則就是,如果這個參數會造成底層代碼直接出錯,那么就就近處理它,而不要放任其在底層造成系統異常的拋出。當然還有一個原則就是不要在這里判斷業務邏輯,比如上面的例子就不要在這里驗證User的屬性的數值是不是合法之類的。
處理異常錯誤的例子
1:用異常驗證用戶輸入
用戶輸入的合法性驗證是屬於業務邏輯的一部分,絕對不要用異常去處理,注意,是用戶輸入,所以這個經驗僅限於表現層邏輯
典型的錯誤1:
try { int i=int.Parse(textBox1.Text); } catch(Exception ex) { alert(“不要輸入非數字”); }
典型錯誤2:
void ValidateInput(int i) { if(i<0&&i>100) { throw new Exception("輸入數據范圍錯誤"); } }
以上兩種錯誤都是錯誤使用異常的典型,
2:將異常延遲到底層
這一點我們在正確的例子里提到過
典型錯誤3:
try { string name=Request.QueryString["xx"]; List<User> userls=User.QueryUserByName(name); } catch(SqlException ex) { }
這個錯誤在於完全不驗證用戶輸入而直接把數據的驗證拋向數據庫,等待數據庫報錯來判斷用戶輸入的正確性,這個是非常致命的錯誤,很多注入漏洞都是由此產生的。
3:完全不用異常機制
產生這個錯誤肯定是一個非常腦殘的決定造成的。不過很多時候某些不了解異常機制的人,由於對異常的性能開銷的恐懼感,經常會做出這么腦殘的決定。
典型錯誤4:
public bool InsertUser(User user,ref int errcode) { if(user==null) { errcode=110;//參數為空錯誤的代碼 return false; } //調用Orm }
感覺就是一夜回到了解放前,性能倒是高了,但是系統異常怎么辦呢?一旦數據庫出錯就只等着系統崩潰了。某些有經驗的說我會把下面的try{}catch{}起來,不過那不是脫了褲子放屁么,異常都拋出來了,開銷已經產生了,結果換來的是犧牲了異常對象的豐富信息而換來了畸形的系統邏輯。性能也沒得到提高。
異常對性能的影響
異常機制是C#的特征,因此決定了你不可能逃避,所以討論異常給你帶來了多大開銷都是扯淡,沒有必要的研究,從根本上無法解決問題。我們應該弄清楚的是,異常的拋出給系統帶來什么樣的影響,如何在保證系統健壯性的基礎上減小不必要的性能消耗。
1.異常的性能開銷隨着調用棧的深度增加而增大。
對比測試:
測試代碼1
Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 10; i++) { try { throw new Exception("test"); } catch { } } sw.Stop(); MessageBox.Show(sw.ElapsedMilliseconds + "ms");
測試結果:
調用次數 | 第一次 | 第二次 | 第三次 | 第四次 |
時間 | ![]() |
![]() |
![]() |
![]() |
測試代碼2
Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 10; i++) { try { System.IO.File.OpenRead("c:\\不存在的txt.txt"); } catch { } } sw.Stop(); MessageBox.Show(sw.ElapsedMilliseconds + "ms");
測試結果
調用次數 | 第一次 | 第二次 | 第三次 | 第四次 |
時間 | ![]() |
![]() |
![]() |
![]() |
小結:由此我們可以發現隨着調用棧的深入,性能開銷也越大,所以異常應該盡早拋出。
2.對輸入的數據應該在業務邏輯中嚴格檢查。
3.try{}catch{}不會造成任何的系統開銷,造成系統開銷的是throw 拋出異常,這是再三強調的了
諸如:
這種言論就純屬腦殘了。
結論
異常機制是C#內置的錯誤處理機制,你無法避免它,唯一正確的道路是學會如何正確使用
轉載:http://www.mamicode.com/info-detail-99312.html