c#异常处理,实例。你会怎么做?

时间:2012-03-14 19:35:50

标签: c# exception-handling

我正在努力改善处理异常,但是当我尽力抓住它们时,我觉得我的代码变得非常丑陋,难以理解和混乱。我很想看看其他人如何通过提供实际例子和比较解决方案来解决这个问题。

我的示例方法从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在intellisense上没有显示异常。这是否意味着构造函数永远不会失败?我可以肯定吗?

其他问题是某些方法抛出相同的异常,这使得错误更难描述(这或者这个或者这个错误),因此对用户/调试器没用。

第三种选择是忽略除允许我采取重试连接之类的操作之外的所有异常。如果url为null,则url为null,捕获的唯一好处是更详细的错误消息。

我很想看到你的想法和/或实施!

7 个答案:

答案 0 :(得分:20)

异常处理的规则之一 - 不捕获您不知道如何处理的异常。

仅仅为了提供好的错误消息而捕获异常是值得怀疑的。异常类型和消息已经包含开发人员的足够信息 - 您提供的消息不会添加任何值。

  

DataContractJsonSerializer在intellisense上没有显示异常。这是否意味着构造函数永远不会失败?我可以肯定吗?

不,你不能确定。一般而言,C#和.NET与您 的Java不同,以声明可能抛出的异常。

  

第三种选择是忽略除允许我采取重试连接之类的操作之外的所有异常。

这确实是最好的选择。

您还可以在应用程序顶部添加一个通用异常处理程序,它将捕获所有未处理的异常并记录它们。

答案 1 :(得分:13)

首先,阅读我关于异常处理的文章:

http://ericlippert.com/2008/09/10/vexing-exceptions/

我的建议是:您必须处理代码可能抛出的“烦恼异常”和“外部异常”。 Vexing例外是“非例外”例外,所以你必须处理它们。由于您无法控制的考虑因素,可能会发生外部异常,因此您必须处理它们。

一定不能处理致命和愚蠢的例外。您不需要处理的骨头异常,因为您永远不会做任何导致它们被抛出的事情。如果它们被抛出,那么你有一个错误,解决方案是修复错误。不要处理异常;那隐藏了这个bug。并且你无法有意义地处理致命异常,因为他们是致命的。这个过程即将结束。您可以考虑记录致命异常,但请记住,日志记录子系统可能是首先触发致命异常的事情。

简而言之:只处理可能发生的 如果您不知道如何处理它,请将它留给您的来电者;来电者可能比你更了解。

在您的特定情况下:不要处理此方法中的任何异常。让调用者处理异常。如果来电者通过了无法解决的网址,将其置为。如果坏网址是一个错误,那么调用者就有一个错误需要修复,而你正在通过引起他们的注意来帮助它们。如果坏网址不是错误 - 例如,因为用户的互联网连接搞砸了 - 那么调用者需要通过询问真实找出获取失败的原因 >例外。来电者可能知道如何恢复,所以请帮助他们。

答案 2 :(得分:1)

首先,出于所有实际目的,您应该永远不要抛出类型Exception。总是扔一些更具体的东西。即使是ApplicationException也会更好。其次,当且仅当调用者有理由关注哪个操作失败时,对不同的操作使用单独的catch语句。如果在程序中的某一点发生的InvalidOperationException将暗示对象的状态与在其他时间发生的状态不同,并且如果您的调用者将关心区别,那么您应该包装第一部分你的程序在'try / catch'块中,它将InvalidOperationException包装在一些其他(可能是自定义的)异常类中。

“只捕获你知道如何处理的异常”的概念在理论上是很好的,但遗憾的是大多数异常类型对于底层对象的状态都是如此模糊,以至于几乎不可能知道是否可以“处理”异常或不。例如,可能有一个TryLoadDocument例程,如果无法加载文档的某些部分,则必须在内部使用可能抛出异常的方法。在发生此类异常的99%的情况下,“处理”此类异常的正确方法是简单地放弃部分加载的文档并返回而不将其暴露给调用者。不幸的是,很难确定1%的情况是不够的。你应该努力在你的例行程序失败而不做任何事情的情况下抛出不同的例外情况,而不是那些可能产生其他不可预测的副作用的情况;不幸的是,你可能会被猜测从你调用的例程中解释大多数异常。

答案 3 :(得分:0)

异常e.message应该有足够的错误消息数据供您正确调试。当我进行异常处理时,我通常只使用一些关于它发生位置的简短信息和实际异常来记录它。

我不会那样拆分它,这只会弄得一团糟。例外主要是针对你的。理想情况下,如果您的用户导致异常,您可能会更早地捕获它们。

我不建议抛出不同的命名异常,除非它们不是真正的异常(例如,有时在某些API调用中响应变为null。我通常会检查它并为我抛出一个有用的异常。)

答案 4 :(得分:0)

看看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...
        }             
    }
}

当然还有其他拦截框架。

答案 5 :(得分:0)

考虑将方法拆分为较小的方法,以便对相关错误进行错误处理。

你在同一个方法中发生了多个半无关的事情,结果每个代码行的错误处理都要多或少。

即。对于您的情况,您可以将方法拆分为:CreateRequest(此处处理无效参数错误),GetResponse(处理网络错误),ParseRespone(处理内容错误)。

答案 6 :(得分:-1)

我不同意@oded说:

“异常处理的规则之一 - 不捕获您不知道如何处理的异常。”

对于学术目的而言可能没问题,但在现实生活中,您的客户不希望在他们的脸上出现非信息性错误。

我认为您可以而且应该捕获异常,并为用户生成一些信息性异常。当向用户显示一个很好的错误信息时,它可以获得有关他/她应该如何解决问题的更多信息。

此外,当您决定记录错误时,捕获所有异常会很有用,或者甚至更好地将它们自动发送给您。

我的所有项目都有一个Error类,我总是使用它来捕获每个异常。虽然我在这门课上做的不多,但它可以用于很多事情。