2010-02-05 118 views
15

我想知道什麼是正確的方法是從一個方法傳遞到另一個異常。傳遞異常的正確方法是什麼? (C#)

我正在開發一個項目,分爲演示(網頁),業務和邏輯層以及錯誤(例如SqlExceptions)需要傳遞到鏈中,以便在出現問題時通知Web層。

我已經看到了3點基本的方法:

try 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw ex; 
} 

(簡單地重新拋出)

try 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw new MyCustomException(); 
} 

(引發自定義異常,這樣的數據提供商的依賴性不傳遞)
然後簡單地

//error code 

(不做任何事根本就是讓自己的錯誤冒起來)

當然,在catch塊中也會發生一些日誌記錄。

我更喜歡3號,而我的同事使用方法1,但我們都不能真正激發原因。

使用每種方法的優點/缺點是什麼?有沒有更好的方法我不知道?有沒有被接受的最佳方式?

+17

您的第一個示例是C#的反模式,使用'throw;'來代替,否則您將堆棧跟蹤重置爲當前的throw點。只要使用'throw;'就會保持原始的堆棧跟蹤。 – 2010-02-05 22:21:35

+1

雙重複制:http://stackoverflow.com/questions/178456/what-is-the-proper-way-to-re-throw-an-exception-in-c和http://stackoverflow.com/questions/ 22623 /淨投擲例外的最佳實踐。 – 2010-02-05 22:32:20

+1

請注意,即使您使用catch更改了異常的類型(Exception ex),也可以保留堆棧跟蹤{throw new MyCustomException(ex); }如果您在MyCustomException類中使用Exception類的提供構造函數(就像所有的.NET Exception類型一樣)。 – dbemerlin 2010-02-05 22:52:47

回答

13

如果你什麼都不做,你應該簡單地讓它走上一些人會處理它的地方。

您可以隨時處理其中的一部分(如日誌記錄)並重新拋出它。您只需發送throw;即可重新投擲,而不必顯式指定名稱。

try 
{ 

} 
catch (Exception e) 
{ 
    throw; 
} 

的優勢來處理它,你可以確保一些機構那裏通知您,您有一個錯誤,你有一個不疑。

但是,在某些情況下,比如說第三方,您希望讓用戶處理它,並且在這種情況下,您應該讓它繼續冒泡。

+1

請記住,在上面的例子中,您將捕獲像NullReferenceException這樣的錯誤,因此,在throw point和try/catch之間的任何finally塊都將被執行 - 即使已經明顯檢測到一個致命錯誤。那些最後的街區會做什麼,節目處於這個未知的狀態?誰知道? – 2010-02-05 22:40:22

+2

@Earwicker,你的觀點很好,但最後的塊不管。面對不好的情況,終極塊必須寫得很強大。我在這裏更關心的是安全影響,而不是健壯性影響。健壯已經在窗外;服務器崩潰了。讓我們不要幫助任何導致服務器崩潰的人。 – 2010-02-05 22:50:57

+0

@Eric - 爲我自己說話我不知道我知道如何寫一個finally塊,所以它對任何bug都是強大的! :)因此需要異常過濾和FailFast。但是我有時需要隱藏信息。您不希望將詳細信息返回給生產站點上的客戶端。但是你可能仍然希望儘可能私自登錄服務器。 – 2010-02-05 22:56:31

6

正確的方式重新扔在C#中的例外是象下面這樣:

try 
{ 
    .... 
} 
catch (Exception e) 
{ 
    throw; 
} 

的細節請參見本thread

+0

對,否則你會失去你的調用堆棧,這會使調試變得痛苦。 – 2010-02-05 22:24:09

6

只能圍繞您期望和可以處理的異常使用try/catch塊。如果你捕捉到了一些你無法處理的東西,它就會失敗處理預期錯誤的try/catch的目的。

捕捉大的異常很少是一個好主意。你第一次遇到OutOfMemoryException你真的能夠優雅地處理它嗎?大多數API的文檔都記錄了每種方法可能拋出的異常,並且這些應該是唯一可以處理的異常,只有當您可以優雅地處理它時纔是如此。

如果您想要處理鏈條上的錯誤,讓它自己冒泡,而不會捕捉並重新拋出它。唯一的例外是用於記錄目的,但在每一步記錄都會做大量的工作。最好只記錄你的公共方法可能允許冒泡的異常,並讓API的使用者決定如何處理它。

9

我想你應該有一個稍微不同的問題

如何指望其他組件來與我的模塊中拋出的異常互動開始?

如果消費者能夠很好地處理較低/數據層拋出的異常,那麼很簡單,什麼也不做。上層能夠處理例外情況,並且您只應執行維持您的狀態所需的最小數量,然後重新投擲。

如果消費者無法處理低級別異常,而是需要更高級別的異常,那麼可以創建一個他們可以處理的新異常類。但請確保將原始異常傳遞給內部異常。

throw new MyCustomException(msg, ex); 
0

通常你只捕獲你應該有異常,可以處理,讓正常方式進一步推廣應用工作。如果你想有一些額外的錯誤日誌記錄,你會發現一個異常,做記錄並使用「throw」重新拋出它。所以堆棧跟蹤不會被修改。自定義異常通常是爲了報告特定於應用程序的錯誤而創建的。

3

我見過(並持有)有關這方面的各種強烈意見。答案是我認爲目前在C#中沒有理想的方法。

有一點我覺得(以Java思想的方式)異常是方法二進制接口的一部分,與返回類型和參數類型一樣多。但在C#中,它根本不是。這從事實上是清楚的,沒有拋出規範系統。

換句話說,如果您希望採取只有您的異常類型應該從您的庫方法中跳出來的態度,那麼您的客戶不會依賴於您的庫的內部細節。但很少有圖書館會這麼做。

官方C#團隊的建議是捕獲可能由方法拋出的每種特定類型,如果您認爲可以處理它們。不要抓住任何你無法處理的東西。這意味着不需要在庫邊界處封裝內部異常。

但是反過來說,這意味着您需要對給定方法可能拋出的內容進行完美的記錄。現代應用程序依賴於第三方庫的迅速發展。如果他們都試圖捕獲特定的異常類型,這些異常類型在將來的庫版本組合中可能是不正確的,並且沒有編譯時檢查,那麼它就會有一個靜態類型系統的嘲弄。

所以有人這樣做:

try 
{ 
} 
catch (Exception x) 
{ 
    // log the message, the stack trace, whatever 
} 

的問題是,這種捕獲所有的異常,包括那些從根本上表明存在嚴重的問題,比如一個空引用異常。這意味着該程序處於未知狀態。檢測到的時刻,它應該在它對用戶的持久數據造成一些損壞(開始垃圾文件,數據庫記錄等)之前關閉。

這裏隱藏的問題是try/finally。這是一個非常棒的語言功能 - 事實上它非常重要 - 但是如果一個足夠嚴重的例外情況出現,那麼它是否真的會導致最終的塊運行?如果有錯誤發生,您是否真的希望證據被破壞?如果程序處於未知狀態,那麼任何重要的東西都可能被那些最終塊破壞。

所以,你真正想要的是什麼(更新爲C#6!):

try 
{ 
    // attempt some operation 
} 
catch (Exception x) when (x.IsTolerable()) 
{ 
    // log and skip this operation, keep running 
} 

在這個例子中,你會寫IsTolerable作爲Exception擴展方法,如果最裏面的例外是NullReferenceException返回falseIndexOutOfRangeExceptionInvalidCastException您任何其他異常類型已經決定必須表明,必須停止執行,並要求調查一個低級錯誤。這些是「無法忍受」的情況。

這可能被稱爲「樂觀」的異常處理:假設所有的例外是,除了一組已知黑名單類型的容忍。另一種方法(由C#5及更早版本支持)是「悲觀」方法,只有已知白名單的異常被認爲是可容忍的,其他任何東西都是未處理的。

年前悲觀的做法是官方推薦的立場。但是現在CLR本身會捕獲Task.Run中的所有異常,因此它可以在線程之間移動錯誤。這最終導致塊執行。所以CRL是非常樂觀的默認情況下

您還可以爭取與AppDomain.UnhandledException事件,儘可能多的信息保存爲可以用於支持目的(至少堆棧跟蹤),然後調用Environment.FailFast到關閉過程之前的任何finally塊可以執行(這可能會破壞寶貴的調查錯誤所需的信息,或拋出隱藏原始錯誤的其他例外情況)。

+1

@Luaan已更新答案! – 2016-12-07 10:29:32

2

我不知道,真的有一個公認的最佳實踐,但IMO

try // form 1: useful only for the logging, and only in debug builds. 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw;// ex; 
} 

是沒有真正意義上除了記錄方面,所以我只會在調試版本做到這一點。捕捉重新投擲是昂貴的,所以你應該有理由支付這些成本,而不僅僅是你喜歡看代碼。

try // form 2: not useful at all 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw new MyCustomException(); 
} 

這一個沒有任何意義。這是丟棄真正的異常,並與一個包含關於實際真正的問題較少的信息替換它。我可以看到,如果我想用一些關於正在發生的事情的信息來增強例外,可能會這樣做。

try // form 3: about as useful as form 1 
{ 
    //error code 
} 
catch (Exception ex) 
{ 
    throw new MyCustomException(ex, MyContextInformation); 
} 

但我可以說,在幾乎所有情況下,你是不是處理異常的最好形式是簡單地讓更高級別的處理程序處理。

// form 4: the best form unless you need to log the exceptions. 
// error code. no try - let it percolate up to a handler that does something productive. 
4

還沒有人指出,你應該考慮的第一件事:什麼是威脅

當後端層拋出一個異常,什麼可怕的和意想不到的事情發生了。 意想不到的可怕情況可能發生,因爲該層受到攻擊由一個敵對用戶last在這種情況下,您想要做的事情是向攻擊者提供出錯的所有詳細列表以及爲什麼。當業務邏輯層出現問題時,要做的正確事情是仔細記錄有關異常的所有信息,並用通用的「我們很抱歉,出現問題,管理已收到警告,請重試」頁面。

需要跟蹤的事情之一就是關於用戶的所有信息以及發生異常時他們正在做什麼。這樣,如果您發現同一個用戶似乎總是出現問題,那麼您可以評估他們是否可能在探測您的弱點,或僅僅使用未經充分測試的應用程序的特殊角落。

獲得安全設計權第一個,只有後來擔心診斷和調試。

相關問題