繁体   English   中英

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

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM