[英]c# exception handling, practical example. How would you do it?
我試圖在處理異常方面做得更好,但是當我盡最大努力捕獲異常時,我覺得我的代碼變得非常丑陋,不可讀且混亂。 我很想看看其他人如何通過舉一個實際的例子並比較解決方案來解決這個問題。
我的示例方法從URL下載數據,並嘗試將其序列化為給定類型,然后返回一個填充有數據的實例。
首先,完全沒有任何異常處理:
private static T LoadAndSerialize<T>(string url)
{
var uri = new Uri(url);
var request = WebRequest.Create(uri);
var response = request.GetResponse();
var stream = response.GetResponseStream();
var result = Activator.CreateInstance<T>();
var serializer = new DataContractJsonSerializer(result.GetType());
return (T)serializer.ReadObject(stream);
}
我覺得這種方法是相當可讀的。 我知道方法中有一些不必要的步驟(例如WebRequest.Create()可以采用字符串,並且我可以在不給方法變量的情況下鏈接方法)在那里,但我會像這樣將其更好地與帶有異常的版本進行比較-處理。
這是嘗試處理可能會出錯的所有問題的第一次嘗試:
private static T LoadAndSerialize<T>(string url)
{
Uri uri;
WebRequest request;
WebResponse response;
Stream stream;
T instance;
DataContractJsonSerializer serializer;
try
{
uri = new Uri(url);
}
catch (Exception e)
{
throw new Exception("LoadAndSerialize : Parameter 'url' is malformed or missing.", e);
}
try
{
request = WebRequest.Create(uri);
}
catch (Exception e)
{
throw new Exception("LoadAndSerialize : Unable to create WebRequest.", e);
}
try
{
response = request.GetResponse();
}
catch (Exception e)
{
throw new Exception(string.Format("LoadAndSerialize : Error while getting response from host '{0}'.", uri.Host), e);
}
if (response == null) throw new Exception(string.Format("LoadAndSerialize : No response from host '{0}'.", uri.Host));
try
{
stream = response.GetResponseStream();
}
catch (Exception e)
{
throw new Exception("LoadAndSerialize : Unable to get stream from response.", e);
}
if (stream == null) throw new Exception("LoadAndSerialize : Unable to get a stream from response.");
try
{
instance = Activator.CreateInstance<T>();
}
catch (Exception e)
{
throw new Exception(string.Format("LoadAndSerialize : Unable to create and instance of '{0}' (no parameterless constructor?).", typeof(T).Name), e);
}
try
{
serializer = new DataContractJsonSerializer(instance.GetType());
}
catch (Exception e)
{
throw new Exception(string.Format("LoadAndSerialize : Unable to create serializer for '{0}' (databinding issues?).", typeof(T).Name), e);
}
try
{
instance = (T)serializer.ReadObject(stream);
}
catch (Exception e)
{
throw new Exception(string.Format("LoadAndSerialize : Unable to serialize stream into '{0}'.", typeof(T).Name), e);
}
return instance;
}
這里的問題是,盡管所有可能出錯的東西都會被捕獲,並給出某種有意義的例外,但這是一個相當大的混亂。
因此,如果我改為將捕獲鏈鏈接起來,該怎么辦。 我的下一個嘗試是:
private static T LoadAndSerialize<T>(string url)
{
try
{
var uri = new Uri(url);
var request = WebRequest.Create(uri);
var response = request.GetResponse();
var stream = response.GetResponseStream();
var serializer = new DataContractJsonSerializer(typeof(T));
return (T)serializer.ReadObject(stream);
}
catch (ArgumentNullException e)
{
throw new Exception("LoadAndSerialize : Parameter 'url' cannot be null.", e);
}
catch (UriFormatException e)
{
throw new Exception("LoadAndSerialize : Parameter 'url' is malformed.", e);
}
catch (NotSupportedException e)
{
throw new Exception("LoadAndSerialize : Unable to create WebRequest or get response stream, operation not supported.", e);
}
catch (System.Security.SecurityException e)
{
throw new Exception("LoadAndSerialize : Unable to create WebRequest, operation was prohibited.", e);
}
catch (NotImplementedException e)
{
throw new Exception("LoadAndSerialize : Unable to get response from WebRequest, method not implemented?!.", e);
}
catch(NullReferenceException e)
{
throw new Exception("LoadAndSerialize : Response or stream was empty.", e);
}
}
盡管從外觀上看這當然更容易,但我在這里傾向於智能化地提供所有可能從方法或類中拋出的異常。 我不確定此文檔是否100%准確,如果其中某些方法來自.net框架之外的程序集,我將更加懷疑。 例如,DataContractJsonSerializer在智能感知上不顯示任何異常。 這是否意味着構造函數永遠不會失敗? 我能確定嗎
與此相關的其他問題是,某些方法會引發相同的異常,這使得錯誤更難以描述(此錯誤或此錯誤),因此對用戶/調試器的用處不大。
第三種選擇是忽略所有例外,除了那些例外之外,那些例外會讓我采取類似重試連接的操作。 如果url為null,則url為null,捕獲的唯一好處是更詳細的錯誤消息。
我很樂意看到您的想法和/或實施!
異常處理的規則之一-不要捕獲您不知道如何處理的異常。
僅為了提供良好的錯誤消息而捕獲異常是可疑的。 異常類型和消息已經為開發人員提供了足夠的信息-您提供的消息不會添加任何值。
DataContractJsonSerializer在智能感知上不顯示任何異常。 這是否意味着構造函數永遠不會失敗? 我能確定嗎
不,您不確定。 通常,C#和.NET與Java不同,您必須聲明可能會引發哪些異常。
第三種選擇是忽略所有例外,除了那些例外之外,那些例外會讓我采取類似重試連接的操作。
那確實是最好的選擇。
您還可以在應用程序的頂部添加常規異常處理程序,該異常處理程序將捕獲所有未處理的異常並將其記錄下來。
首先,請閱讀我有關異常處理的文章:
http://ericlippert.com/2008/09/10/vexing-exceptions/
我的建議是:您必須處理代碼可能拋出的“煩人異常”和“外源異常”。 令人討厭的異常是“非例外”異常,因此您必須處理它們。 外部異常可能是由於您無法控制的考慮而發生的,因此您必須處理它們。
您一定不能處理致命的異常事件。 不需要處理的異常異常,因為您永遠不會做任何導致異常拋出的事情 。 如果拋出它們,則說明您有一個錯誤 ,解決方案是修復該錯誤 。 不要處理異常; 隱藏了錯誤。 而且您無法有意義地處理致命異常,因為它們是致命的 。 這個過程即將結束。 您可能考慮記錄致命異常,但請記住, 日志子系統可能首先是觸發致命異常的事物。
簡而言之:僅處理您知道如何處理的那些可能發生的異常。 如果您不知道如何處理,請留給來電者; 來電者可能比您更了解。
在您的特定情況下:不要在此方法中處理任何異常。 讓調用者處理異常。 如果呼叫者向您傳遞了無法解析的網址,請使其崩潰 。 如果錯誤的網址是一個錯誤,則調用者可以修復一個錯誤,您可以通過吸引他們的注意來幫助他們。 如果錯誤的網址不是錯誤(例如,由於用戶的互聯網連接混亂),則調用者需要通過詢問真正的異常來找出提取失敗的原因 。 呼叫者可能知道如何恢復,因此請幫助他們。
首先,出於所有實際目的,請勿拋出Exception
類型的Exception
。 總是扔一些更具體的東西。 甚至ApplicationException
也會略勝一籌。 其次,只有在調用者有理由關心哪個操作失敗時,才對不同的操作使用單獨的catch語句。 如果在程序中某一時刻發生的InvalidOperationException表示對象狀態與在其他時刻發生的狀態有所不同,並且如果調用者要關心區別,則應包裝第一部分。您的程序位於“ try / catch”塊中,該塊會將InvalidOperationException
包裝在其他(可能是自定義的)異常類中。
從理論上講,“僅捕獲您知道如何處理的異常”這一概念在理論上是不錯的,但是不幸的是,大多數異常類型對於底層對象的狀態都含糊不清,幾乎無法知道一個人是否可以“處理”異常。 例如,可能有一個TryLoadDocument
例程,該例程必須在內部使用如果文檔的某些部分無法加載可能會引發異常的方法。 在發生此類異常的99%的情況下,“處理”此類異常的正確方法將是簡單地放棄部分加載的文檔並返回而不將其暴露給調用者。 不幸的是,很難確定不足的1%的情況。 在例行程序失敗而沒有采取任何措施的情況下,以及在例行程序可能具有其他無法預料的副作用的情況下,您應努力拋出不同的異常。 不幸的是,您可能會困惑於所調用例程對大多數異常的解釋。
異常e.message應該具有足夠多的錯誤消息數據,以供您正確調試。 當我進行異常處理時,我通常只將其日志記錄在日志中,並提供一些簡短的信息,說明發生的位置以及實際的異常。
我不會像那樣分裂它,只會弄得一團糟。 例外情況主要針對您。 理想情況下,如果您的用戶引起異常,則應盡早捕獲它們。
我不建議拋出不同的命名異常,除非它們不是真正的異常(例如,有時在某些API調用中,響應變為null。我通常會檢查並為我拋出有用的異常)。
看一下Unity Interception 。 在該框架內,您可以使用稱為ICallHandler
東西,它使您可以截獲呼叫,並可以執行需要/想要執行的所有操作。
例如:
public IMethodReturn Invoke(IMethodInvocation input,
GetNextHandlerDelegate getNext)
{
var methodReturn = getNext().Invoke(input, getNext);
if (methodReturn.Exception != null)
{
// exception was encountered...
var interceptedException = methodReturn.Exception
// ... do whatever you need to do, for instance:
if (interceptedException is ArgumentNullException)
{
// ... and so on...
}
}
}
當然,還有其他攔截框架。
考慮將方法拆分為較小的方法,以便可以對相關錯誤進行錯誤處理。
同一方法中發生了多個半無關的事情,因為每行代碼必須要或多或少地處理結果錯誤。
就您的情況而言,您可以將方法拆分為:CreateRequest(在此處處理無效參數錯誤),GetResponse(處理網絡錯誤),ParseRespone(處理內容錯誤)。
我不同意@oded在他說:
“異常處理規則之一-不要捕獲您不知道如何處理的異常。”
出於學術目的,這可能還可以,但是在現實生活中,您的客戶不希望在自己的臉上冒出無用的錯誤。
我認為您可以並且應該捕獲異常,並且它們會為用戶生成一些有用的異常。 當向用戶顯示一個很好的錯誤(信息量大)時,它可以具有有關他/她應如何解決問題的更多信息。
此外,當您決定記錄錯誤或什至更好地自動將錯誤發送給您時,捕獲所有異常可能很有用。
我所有的項目都有一個Error類,我總是使用它來捕獲每個異常。 即使我在這堂課上沒有做太多事情,它也在那里而且可以用於很多事情。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.