简体   繁体   English

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

[英]c# exception handling, practical example. How would you do it?

I'm trying to get better at handling exceptions but I feel like my code gets very ugly, unreadable and cluttered when I try my best to catch them. 我试图在处理异常方面做得更好,但是当我尽最大努力捕获异常时,我觉得我的代码变得非常丑陋,不可读且混乱。 I would love to see how other people approach this by giving a practical example and compare solutions. 我很想看看其他人如何通过举一个实际的例子并比较解决方案来解决这个问题。

My example method downloads data from an URL and tries to serialize it into a given type, then return an instance populated with the data. 我的示例方法从URL下载数据,并尝试将其序列化为给定类型,然后返回一个填充有数据的实例。

First, without any exception-handling at all: 首先,完全没有任何异常处理:

    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);            
    }

I feel like the method is fairly readable like this. 我觉得这种方法是相当可读的。 I know there are a few unnecessary steps in the method (like WebRequest.Create() can take a string, and I could chain methods without giving them variables) there but I will leave it like this to better compare against the version with exception-handling. 我知道方法中有一些不必要的步骤(例如WebRequest.Create()可以采用字符串,并且我可以在不给方法变量的情况下链接方法)在那里,但我会像这样将其更好地与带有异常的版本进行比较-处理。

This is an first attempt to handle everything that could go wrong: 这是尝试处理可能会出错的所有问题的第一次尝试:

    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;
    }

The problem here is that while everything that could possibly go wrong will be caught and given a somewhat meaningful exception, it is a clutter-fest of significant proportions. 这里的问题是,尽管所有可能出错的东西都会被捕获,并给出某种有意义的例外,但这是一个相当大的混乱。

So, what if I chain the catching instead. 因此,如果我改为将捕获链链接起来,该怎么办。 My next attempt is this: 我的下一个尝试是:

    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);
        }
    }

While it certainly is easier on the eyes, I am leaning heavily the intellisense here to provide all exceptions that can possibly be thrown from a method or class. 尽管从外观上看这当然更容易,但我在这里倾向于智能化地提供所有可能从方法或类中抛出的异常。 I don't feel confident that this documentation is 100% accurate, and would be even more skeptical if some of the methods came from an assembly outside the .net framework. 我不确定此文档是否100%准确,如果其中某些方法来自.net框架之外的程序集,我将更加怀疑。 As an example the DataContractJsonSerializer show no exceptions on the intellisense. 例如,DataContractJsonSerializer在智能感知上不显示任何异常。 Does this mean the constructor will never fail? 这是否意味着构造函数永远不会失败? Can I be sure? 我能确定吗

Other issues with this is that some of the methods throw the same exception, which makes the error harder to describe (this or this or this went wrong) and so is less useful to the user / debugger. 与此相关的其他问题是,某些方法会引发相同的异常,这使得错误更难以描述(此错误或此错误),因此对用户/调试器的用处不大。

A third option would be to ignore all exceptions apart from the ones that would allow me to take an action like retrying the connection. 第三种选择是忽略所有例外,除了那些例外之外,那些例外会让我采取类似重试连接的操作。 If the url is null then the url is null, the only benefit from catching that is a little bit more verbose error message. 如果url为null,则url为null,捕获的唯一好处是更详细的错误消息。

I would love to see your thoughts and/or implementations! 我很乐意看到您的想法和/或实施!

Rule one of exception handling - do not catch exceptions you don't know how to handle. 异常处理的规则之一-不要捕获您不知道如何处理的异常。

Catching exceptions just in order to provide nice error messages is questionable. 仅为了提供良好的错误消息而捕获异常是可疑的。 The exception type and message already contain enough information for a developer - the messages you have provided do not add any value. 异常类型和消息已经为开发人员提供了足够的信息-您提供的消息不会添加任何值。

the DataContractJsonSerializer show no exceptions on the intellisense. DataContractJsonSerializer在智能感知上不显示任何异常。 Does this mean the constructor will never fail? 这是否意味着构造函数永远不会失败? Can I be sure? 我能确定吗

No, you can't be sure. 不,您不确定。 C# and .NET in general are not like Java where you have to declare what exceptions may be thrown. 通常,C#和.NET与Java不同,您必须声明可能会引发哪些异常。

A third option would be to ignore all exceptions apart from the ones that would allow me to take an action like retrying the connection. 第三种选择是忽略所有例外,除了那些例外之外,那些例外会让我采取类似重试连接的操作。

That indeed is the best option. 那确实是最好的选择。

You can also add a general exception handler at the top of the application that will capture all unhandled exceptions and log them. 您还可以在应用程序的顶部添加常规异常处理程序,该异常处理程序将捕获所有未处理的异常并将其记录下来。

First, read my article on exception handling: 首先,请阅读我有关异常处理的文章:

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

My advice is: you must handle the "vexing exceptions" and "exogenous exceptions" that can be thrown by your code. 我的建议是:您必须处理代码可能抛出的“烦人异常”和“外源异常”。 Vexing exceptions are "non exceptional" exceptions and so you have to handle them. 令人讨厌的异常是“非例外”异常,因此您必须处理它们。 Exogenous exceptions can happen due to considerations beyond your control, and so you have to handle them. 外部异常可能是由于您无法控制的考虑而发生的,因此您必须处理它们。

You must not handle the fatal and boneheaded exceptions. 一定不能处理致命的异常事件。 The boneheaded exceptions you don't need to handle because you are never going to do anything that causes them to be thrown . 不需要处理的异常异常,因为您永远不会做任何导致异常抛出的事情 If they are thrown then you have a bug and the solution is fix the bug . 如果抛出它们,则说明您有一个错误 ,解决方案是修复该错误 Don't handle the exception; 不要处理异常; that's hiding the bug. 隐藏了错误。 And you can't meaningfully handle fatal exceptions because they're fatal . 而且您无法有意义地处理致命异常,因为它们是致命的 The process is about to go down. 这个过程即将结束。 You might consider logging the fatal exception, but keep in mind that the logging subsystem might be the thing that triggered the fatal exception in the first place. 您可能考虑记录致命异常,但请记住, 日志子系统可能首先是触发致命异常的事物。

In short: handle only those exceptions that can possibly happen that you know how to handle. 简而言之:仅处理您知道如何处理的那些可能发生的异常。 If you don't know how to handle it, leave it to your caller; 如果您不知道如何处理,请留给来电者; the caller might know better than you do. 来电者可能比您更了解。

In your particular case: don't handle any exceptions in this method. 在您的特定情况下:不要在此方法中处理任何异常。 Let the caller deal with the exceptions. 让调用者处理异常。 If the caller passes you an url that cannot be resolved, crash them . 如果呼叫者向您传递了无法解析的网址,请使其崩溃 If the bad url is a bug then the caller has a bug to fix and you are doing them a favour by bringing it to their attention. 如果错误的网址是一个错误,则调用者可以修复一个错误,您可以通过吸引他们的注意来帮助他们。 If the bad url is not a bug -- say, because the user's internet connection is messed up -- then the caller needs to find out why the fetch failed by interrogating the real exception. 如果错误的网址不是错误(例如,由于用户的互联网连接混乱),则调用者需要通过询问真正的异常来找出提取失败原因 The caller might know how to recover, so help them. 呼叫者可能知道如何恢复,因此请帮助他们。

First of all, you should, for all practical purposes, never throw type Exception . 首先,出于所有实际目的,请勿抛出Exception类型的Exception Always throw something more specific. 总是扔一些更具体的东西。 Even ApplicationException would be better, marginally. 甚至ApplicationException也会略胜一筹。 Secondly, use separate catch statements for different operations when, and only when, the caller will have reason to care which operation failed. 其次,只有在调用者有理由关心哪个操作失败时,才对不同的操作使用单独的catch语句。 If an InvalidOperationException that occurs at one point in your program will imply something different about the state of your object than one which occurs at some other time, and if your caller is going to care about the distinction, then you should wrap the first part of your program in a 'try/catch' block which will wrap the InvalidOperationException in some other (possibly custom) exception class. 如果在程序中某一时刻发生的InvalidOperationException表示对象状态与在其他时刻发生的状态有所不同,并且如果调用者要关心区别,则应包装第一部分。您的程序位于“ try / catch”块中,该块会将InvalidOperationException包装在其他(可能是自定义的)异常类中。

The notion of "only catch exceptions you know how to handle" is nice in theory, but unfortunately most exception types are so vague about the state of underlying objects that it's almost impossible to know whether one can "handle" an exception or not. 从理论上讲,“仅捕获您知道如何处理的异常”这一概念在理论上是不错的,但是不幸的是,大多数异常类型对于底层对象的状态都含糊不清,几乎无法知道一个人是否可以“处理”异常。 For example, one might have a TryLoadDocument routine which must internally use methods that might throw exceptions if parts of the document cannot be loaded. 例如,可能有一个TryLoadDocument例程,该例程必须在内部使用如果文档的某些部分无法加载可能会引发异常的方法。 In 99% of cases where such an exception occurs, the proper way to "handle" such an exception will be to simply abandon the partially-loaded document and return without exposing it to the caller. 在发生此类异常的99%的情况下,“处理”此类异常的正确方法将是简单地放弃部分加载的文档并返回而不将其暴露给调用者。 Unfortunately, it's very difficult to identify the 1% of cases where that is insufficient. 不幸的是,很难确定不足的1%的情况。 You should endeavor to throw different exceptions in the cases where your routine has failed without doing anything, versus those where it may have had other unpredictable side-effects; 在例行程序失败而没有采取任何措施的情况下,以及在例行程序可能具有其他无法预料的副作用的情况下,您应努力抛出不同的异常。 unfortunately, you'll probably be stuck guessing at the interpretation of most exceptions from routines you call. 不幸的是,您可能会困惑于所调用例程对大多数异常的解释。

Exception e.message should have more than enough error message data for you to debug it properly. 异常e.message应该具有足够多的错误消息数据,以供您正确调试。 When I do exception handling I generally just logfile it with some short information about where it happened, and the actual exception. 当我进行异常处理时,我通常只将其日志记录在日志中,并提供一些简短的信息,说明发生的位置以及实际的异常。

I wouldn't split it like that, that just makes a mess. 我不会像那样分裂它,只会弄得一团糟。 Exceptions are mostly for YOU. 例外情况主要针对您。 Ideally if your user is causing exceptions you would have caught them earlier. 理想情况下,如果您的用户引起异常,则应尽早捕获它们。

I wouldn't recommend throwing different named exceptions unless they're not really exceptions (for example sometimes in certain API calls the response becomes null. I'd usually check for that and throw a helpful exception for me). 我不建议抛出不同的命名异常,除非它们不是真正的异常(例如,有时在某些API调用中,响应变为null。我通常会检查并为我抛出有用的异常)。

Look at Unity Interception . 看一下Unity Interception Within that framework, you can use something called an ICallHandler , which allows you to intercept calls and do whatever you need/want to do with the intercepted call. 在该框架内,您可以使用称为ICallHandler东西,它使您可以截获呼叫,并可以执行需要/想要执行的所有操作。

For example: 例如:

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

There are other interception frameworks, of course. 当然,还有其他拦截框架。

Consider splitting the method into smaller ones so error handling can be done for related errors. 考虑将方法拆分为较小的方法,以便可以对相关错误进行错误处理。

You have multiple semi-unrelated things happening in the same method, as result error handling have to be more or less per line of code. 同一方法中发生了多个半无关的事情,因为每行代码必须要或多或少地处理结果错误。

Ie for your case you can split method into: CreateRequest (handle invalid arguments errors here), GetResponse (handle network errors), ParseRespone (handle content errors). 就您的情况而言,您可以将方法拆分为:CreateRequest(在此处处理无效参数错误),GetResponse(处理网络错误),ParseRespone(处理内容错误)。

I do not agree with @oded when he says: 我不同意@oded在他说:

"Rule one of exception handling - do not catch exceptions you don't know how to handle." “异常处理规则之一-不要捕获您不知道如何处理的异常。”

It may be OK for academic purposes, but in real life your customers do not want non informative errors popping up on their faces. 出于学术目的,这可能还可以,但是在现实生活中,您的客户不希望在自己的脸上冒出无用的错误。

I think you can and should catch exceptions and them generate some informative exception to the user. 我认为您可以并且应该捕获异常,并且它们会为用户生成一些有用的异常。 When an nice error, well informative, is shown to the user it can have more information about what he/she should do to solve the problem. 当向用户显示一个很好的错误(信息量大)时,它可以具有有关他/她应如何解决问题的更多信息。

Also, catching all exception can be useful when you decide to log errors or, even better, send them to you automatically. 此外,当您决定记录错误或什至更好地自动将错误发送给您时,捕获所有异常可能很有用。

All my projects have a Error class and I always catch every exception using it. 我所有的项目都有一个Error类,我总是使用它来捕获每个异常。 Even though i dont do much on this class, it is there and it can be used to a lot of things. 即使我在这堂课上没有做太多事情,它也在那里而且可以用于很多事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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