当前位置:首页 > 技术 > 后端 > 正文内容

.NET Core学习笔记(7)——Exception最佳实践

anan3个月前 (07-27)后端109

1.为什么不要给每个方法都写try catch

为每个方法都编写try catch是错误的做法,理由如下:

a.重复嵌套的try catch是无用的,多余的。

这一点非常容易理解,下面的示例代码中,OutsideMethodA中的try catch是不起作用的。

            class NestedTryCatch
            {internal void OutsideMethodA()
                {try{this.InsideMethodB();
                    }catch (Exception ex)
                    {
                        Console.WriteLine(ex.ToString());
                    }
                }        private void InsideMethodB()
                {try{this.ExceptionMethod();
                    }catch (Exception ex)
                    {
                        Console.WriteLine(ex.ToString());
                    }
                }        private void ExceptionMethod() 
                {throw new NotImplementedException("You did't implement this method!");
                }
            }

 

b.多余的try catch会掩盖严重的bug,将bug珍藏在log里并不会增值。

下面的代码中,一旦参数uri为null,意味着程序逻辑必然有bug,存在错误的调用。与其将这个bug和HttpRequestException混在一起写log,然后相忘于江湖。不如大大方方在开发阶段就每次crash,强迫必须修复隐藏的逻辑错误。

同时我们可以看到,catch里再次返回了null,这又是一种不负责任给上层代码挖坑的行为。上层代码两眼一黑,就得一个null,啥也不知道,估计也不敢问。
注释的部分给出了两种解决方案,Assert或者主动throw。

        internal async Task<string> DownloadContent(string uri)
                {//Debug.Assert(!string.IsNullOrEmpty(uri));        //if (string.IsNullOrEmpty(uri))//{//    throw new ArgumentNullException("uri is null");//}try{using (var httpClient = new HttpClient())
                        {return await httpClient.GetStringAsync(uri);
                        }
                    }catch (Exception ex)
                    {
                        Console.WriteLine(ex.ToString());return null;
                    }
        }

 

c.当程序因Exception进入不可继续的状态时,通过try catch避免程序crash,除了可以稍微体面地退出,并没有更大意义。

例如网络游戏在运行过程中,发生了错误。本地数据与服务器不再同步,是不会允许继续运行,也不会承认期间产生的本地数据。
硬用代码举例的话,就比如在构造函数里搞个try catch吞掉Exception,这个new出来的实例谁还敢用的,请站出来……

d.都知道空的try catch是错误的。

try{
    ……
}catch{ }

 

 难道加个日志就会产生质变了嘛?

try{
    ……
}catch (Exception ex)
{
    Log.Error(“xxxx方法失败了!”);
}

 

  2.何时使用try catch?只提出问题不给出解决方案,会被骂耍流氓。下面我们来分析几个适于添加try catch的场景。

a.仅在你真的打算,并且知道如何处理当前的Exception时,加try catch。比较明显的场景是网络请求中的retry。

        public async Task<string> HandleHttpRequestExceptionAsync()
        {
            HttpClient client = new HttpClient();try{return await client.GetStringAsync("http://www.ajshdgasjhdgajdhgasjhdgasjdhgasjdhgas.tk/");
                
            }catch (HttpRequestException ex)
            {//Simulate to try again.//log here then retryreturn await client.GetStringAsync("http://www.dell.com/");
            }finally{
                client?.Dispose();
            }
        }

 

b.当常规流程控制无法避免异常时,加try catch。

通常可以用if来避免的问题,就不应通过try catch处理。反例如IO处理,无法确认用户会不会拔U盘,该情况下需catch IOException。

c.功能性的类库中的API缺乏业务逻辑,不知道如何处理Exception时,不应加try catch。应将错误抛给上层,由存在业务逻辑的调用方处理。

比较典型的,在使用Microsoft UI Automation的API时,找元素的API可能会抛出COMException。API本身认为调用方传参错误,传入了不存在元素的ID。但上层的调用代码会知道,是因为当前页面未加载完全。如果我们希望在这里retry或者忽略这个错误,try catch是合理的。

d.为了体面的退出。

在顶层加入try catch记录log是可行的。调用堆栈的信息会完整的保存下来。(针对Task的异常堆栈丢失问题,请看《.NET Core学习笔记(3)——async/await中的Exception处理》

3.在顶层代码应用try catch的一些可行做法

a.如果我们真的害怕且不能接受crash。

可以试着在Main方法里加个try catch,然后记录log。
b.不是主线程的UnHandle Exception。

通过AppDomain.UnhandledException来处理。

        public static void Main()
        {
            AppDomain currentDomain = AppDomain.CurrentDomain;
            currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler);try{throw new Exception("1");
            }catch (Exception e)
            {
                Console.WriteLine("Catch clause caught : {0} \n", e.Message);
            }throw new Exception("2");
        }static void MyHandler(object sender, UnhandledExceptionEventArgs args)
        {
            Exception e = (Exception)args.ExceptionObject;
            Console.WriteLine("MyHandler caught : " + e.Message);
            Console.WriteLine("Runtime terminating: {0}", args.IsTerminating);
        }

 

默认情况下.NET 程序将会退出,因为此时的程序因为这个unhandle exception,被认为进入了未知,且不可继续的状态。
此时即使通过某些特殊手段保持程序不退出,也没有任何意义。unhandle exception的意思就是有crash bug没处理。开发阶段干嘛去了。
https://docs.microsoft.com/en-us/dotnet/standard/threading/exceptions-in-managed-threads#application-compatibility-flag
上述链接提供了程序不退出的可能选项,但我认为实不可取。

4.判断Exception类型的一些技巧,

仍然以HttpClient.GetStringAsync举例,我们可以通过查看MSDN得知该方法可能抛出如下几个Exceptions:

a.AugumentNullException

我们上文提过了,上层调用代码可以通过null check来避免,或者主动抛出exception。
b.HttpRequestException

网络错误都会抛这个异常,通常我们需要捕获该异常,并通过异常中返回的Status或是其他信息来针对性处理。
c.TaskCanceledException

在以下两种情况会被抛出:


    • 指定了HttpClient.Timeout同时本次网络请求超出指定时间

    • 在使用Task异步编程时,在Task Completed之前调用CancellationTokenSource对象的Cancel()方法

那么在写代码的时候,就要判断是否是.NET Core,同时符合以上两点。否则就无需对该异常添加处理。
举该例子更重要的目的是想说,除了顶层代码的最后一道用于记录log的try catch。没有任何理由用到基类Exception。

文中提到的示例代码可以在这里找到:
https://github.com/manupstairs/PracticeOfException
本篇提到了处理Exception时的一些实践经验,且为一家之言,如有错误的地方还请指出。

打赏
版权声明:所有来源为第三方内容,若本站收录的文章无意侵犯了贵司版权,请给下面邮箱地址来信,我们会及时处理和回复,谢谢。

管理员邮箱:42004990@qq.com

微信公众号

分享给朋友:

相关文章

OAuth 2.0详解

OAuth 2.0详解

OAuth 2.0详解概念:OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如基本消息,照片,联系人列表),而无需将 用户名 和 密码 提供给第三方应...

设计模式之解释器模式

设计模式之解释器模式

解释器模式 InterpreterIntro解释器模式,给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。这和解释型编程语言的解释器有点类似,要根据一段输...

redis入门指南(六)—— 集群

redis入门指南(六)—— 集群

写在前面  学习《redis入门指南》笔记,结合实践,只记录重要,明确,属于新知的相关内容。   配置集群  1、配置集群,集群解决了单点故障以及单台机器内存上限的问题,使用集群时,只需要将...

扯扯Java中的锁

扯扯Java中的锁

前言    又过去了一个周末,最近陆陆续续的看了《并发编程的艺术》一书,对锁有不少感悟,这次就聊聊Java中的锁事。本文纯粹是漫谈,想到哪说到哪,但准确性肯定会保证,倘若有不正确之...

Java面试必问:ThreadLocal终极篇 淦!

Java面试必问:ThreadLocal终极篇 淦!

点赞再看,养成习惯,微信搜一搜【敖丙】关注这个互联网苟且偷生的程序员。本文 GitHub https://github.com/JavaFamily 已收录,有一线大厂面试完整考点、资料以及我的系列文...

OAuth2.0分布式系统环境搭建

OAuth2.0分布式系统环境搭建

好好学习,天天向上本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航介绍OAuth(开放授权)是一个开放...