c异常处理,实例.你会怎么做

本文关键字:你会怎么做 实例 异常处理 | 更新日期: 2023-09-27 18:27:44

我试图更好地处理异常,但当我尽力捕捉它们时,我觉得我的代码变得非常丑陋、不可读和混乱。我很想看看其他人是如何通过举一个实际的例子来处理这个问题并比较解决方案的。

我的示例方法从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);
        }
    }

虽然这看起来确实更容易,但我在这里非常倾向于intellisense,以提供所有可能从方法或类抛出的异常。我不相信这个文档是100%准确的,如果其中一些方法来自.net框架之外的程序集,我会更加怀疑。例如,DataContractJsonSerializer在intellisense上没有显示异常。这是否意味着构造函数永远不会失败?我能确定吗?

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

第三种选择是忽略所有异常,除了那些允许我采取类似重试连接的操作的异常。如果url是null,那么url就是null,捕获它的唯一好处就是更详细一点的错误消息。

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

c异常处理,实例.你会怎么做

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

仅仅为了提供漂亮的错误消息而捕获异常是有问题的。异常类型和消息已经为开发人员包含了足够的信息-您提供的消息没有添加任何值。

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

不,你不能确定。C#和.NET通常与Java不同,在Java中,来声明可能引发的异常。

第三种选择是忽略所有异常,除了那些允许我采取类似重试连接的操作的异常。

这确实是最好的选择。

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

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

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

我的建议是:您必须处理代码可能引发的"令人烦恼的异常"answers"外部异常"。Vexing异常是"非异常"异常,因此您必须处理它们。外来的异常可能是由于你无法控制的考虑而发生的,所以你必须处理它们。

您不能处理致命和愚蠢的异常。你不需要处理愚蠢的异常,因为你永远不会做任何导致它们被抛出的事情。如果它们被抛出,则您有一个错误,解决方案是修复该错误。不要处理异常;这就是隐藏漏洞。你不能有意义地处理致命的异常,因为它们是致命的。这个过程即将结束。您可能会考虑记录致命异常,但请记住,日志记录子系统可能是最初触发致命异常的原因

简言之:只处理可能发生的异常,知道如何处理这些异常。如果你不知道如何处理,就把它留给你的来电者;打电话的人可能比你更清楚。

在您的特殊情况下:不要在这个方法中处理任何异常。让调用者处理异常。如果调用者向您传递了一个无法解析的url,则会使其崩溃。如果坏的url是一个bug,那么调用者有一个bug需要修复,而你通过引起他们的注意来帮他们一个忙。如果坏的url不是bug——比如说,因为用户的互联网连接一团糟——那么调用者需要通过询问real异常来找出获取失败的原因。打电话的人可能知道如何恢复,所以请帮助他们。

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

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

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

我不会那样分开的,那样只会弄得一团糟。例外情况主要针对你。理想情况下,如果您的用户正在引发异常,您会更早地发现它们。

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

查看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类,我总是用它来捕捉每个异常。尽管我在这个类上做得不多,但它就在那里,它可以用于很多事情。