2021-04-07

[.net] 关于Exception的几点思考和在项目中的使用(一)

关于exception的基本语法和作用,这里不再赘述,下面记录一下我在项目中关于Exception的一些思考。

一,使用Exception,而不是Error Code

在初始设计项目的时候,有时候我们为了明确错误类型,定义如下结构:

enum ErrCode{ Ok, ArgumentErr, OtherErr1, OtherErr2}class SomeClass{ public ErrCode DealSomething() {  //.....  return ErrCode.Ok; }}class CallerClass{ public void CallSome(SomeClass some) {		ErrCode err = some.DealSomething();  		if(err == ErrCode.Ok)  {   //....  }  else  {   //.....  } }}

使用ErrCode并不是说完全不可以,比如在Web Api调用中返回ErrCode就是不错的选择。

1,吃掉异常

但是在其他情况,使用ErrCode会让使用者痛苦不堪,因为每次调用都要小心谨慎的处理和判断ErrCode,否则就特别容易吃掉异常

对比如下程序片段:

使用ErrCode使用Exception
class SomeClass{ public ErrCode DealSomething() {  //.....  return ErrCode.SomeErr; }}class CallerClass{ public void CallSome(SomeClass some) {		some.DealSomething();		giveMoney();	}}
class SomeClass{ public void DealSomething() {  //.....  if(somthingWrong)  {  	throw new SomeException(...);  } }}class CallerClass{ public void CallSome(SomeClass some) {		some.DealSomething();		giveMoney();	}}

 

左侧使用ErrCode因为不小心没有接受处理ErrCode,导致后续代码继续执行;

而使用Exception,即使没有处理,也不会执行后续代码,并且抛出等待上层使用者处理。

此外,Exception还有其他众多好处,比如Stack Trace信息,跨process等等。

所以Exception的本质就是现成的错误处理(error-handling)机制,就不要再去使用自定义的ErrCode了。

二,Exception在项目中的使用

既然明确使用Exception作为我们的错误处理方案。那么具体怎么使用呢?

1,使用细化的Exception

首先要避免以下写法:

class Dal{ public void Add(Entity entity) {  //....  if(somthingWrong)  {   throw new Exception("add wrong");  }  //... }}

这种写法的问题在于,调用者无法明确Exception的原因,类型,仅仅依靠文档或者message去理解,当项目越积越多的时候,就是漫天相同的Exception乱飞,日志里充满了各种神奇的message的时候。

我们可以采取多维度细化Exception:类型、错误种类,具体原因。

使用具体类型化的Exception,比如ConnectionException, TimeoutException, 即为每种错误定义一个Exception类型,

这种方法想必大家都很清楚,但是如果一个模块里有十几种错误,而你又有十几个模块呢?

对于这种情况有两种处理方法。

  • 按内部外部划分

    如果一个异常需要模块外部调用者接收和处理的,那么就定义一个具体的Exception类型,比如ConnectionException,TimeoutException;

    如果一个异常是模块内存自处理的,那么只用同一个InnerException(或者其他名字)加上其他信息来区分。(是不是又闻到了ErrCode的味道,没错)。

    这种方法经常在很多开源类库中看到。

  • 一个模块定义一种Exception类型,Exception与ErrCode相结合

    刚说过ErrCode不能用,这里又提到,大家别误会,仔细往下看。

    比如如下代码:

    class ErrCode{ public int Code { get; set; } public string? Name { get; set; } public string? Message { get; set; }}class DalException : Exception{ public ErrCode Code { get; }  public DalException(ErrCode errCode, Exception? innerException = null):base(errCode.Message, innerException) { 	Code = errCode;  }}

    ErrCode已经不简简单单是一个enum了,而是有更大的用处。

    这样每一个模块都拥有自己Exception类型,每一种错误类型又能得到有效的分类。

    这种方法经常在公司项目的业务模块、基础框架模块里用到。

2,使用Exception.Data

上面的代码段,大家是否有点惊讶,哥们儿,你把Message直接干到ErrCode里面固定起来,是不是有点激进啊。

其实这也是在实际开发中,总结出来的经验。尽量不要放任程序员们零散的去写各种Exception的Message。

但是我们可以用Exception.Data来实现更多场景。

比如下面代码段:

static class ExceptionFactory{ public static DalException OnMigrateError(int oldVersion, int newVersion, string sql, string cause) {  DalException ex = new DalException(ErrCodes.MigrationErr);    ex.Data["OldVersion"] = oldVersion;  ex.Data["NewVersion"] = newVersion;  ex.Data["Cause"] = cause; }}class SomeCls{ void Migration() {  //.....  throw ExceptionFactory.OnMigrationErr(oldVersion,) }}

这样为每种错误固定了具体需要记录的信息,其中cause就是让程序员记录当下的原因。当然了,你可以重写OnMigrateError来记录另外一种场景。

把异常Exception具体需要的信息,记录在Data这个Dictionary中, 而不是写到Message中的好处,是有助于今后进一步的处理,比如结构化日志。

当然这带来了一个需要关注的问题,就是对Exception.Data的显式处理,因为Exception.ToString()方法,并不打印Data。

在记录日志方面,如果你使用 Serilog, 那么推荐你使用 Serilog.Exceptions, 他对Exception.Data十分友好。

 

至此,我们在细化Exception上,以此递进的使用了 类型---> ErrorCode ---> 具体cause

 

3,集中化管理

漫天飞的Exception,不明所以的处理方案,都需要用规则来解决,但是规则需要时刻牢记,所以最靠谱的还是用代码结构来解决。

首先建立ErrCodes静态类,然后建立ExceptionFactory类,这样业务代码里抛出异常都具有throw ExceptionFactory.XXException()这样的形式。

//所有的错误代码internal static class ErrCodes{ public static ErrCode MigrationErr { get; } = new ErrCode(1, nameof(MigrationErr), "Error happens in Migration."); //........ other errors}//所有的Exception都由此生成internal static class ExceptionFactory{ public static DalException OnMigrateError(int oldVersion, int newVersion, string sql, string cause) {  DalException ex = new DalException(ErrCodes.MigrationErr);    ex.Data["OldVersion"] = oldVersion;  ex.Data["NewVersion"] = newVersion;  ex.Data["Cause"] = cause; } //.....其他场景}

这样,code review时,只要看到有程序员在代码中自己 new XXException,那么就督促他在ExceptionFactory里寻找合适的异常场景或者自己添加。

随着项目积累,即使异常、错误种类众多,大家也只需在ErrCodesExcepionFactory两个类中,总结归纳,重构。

4,参考代码

具体的代码,上传在 Github, 欢迎探讨和指正。

三,预告

在下一篇,我将要探讨下关于捕捉异常的话题(这么简单?)。

 









原文转载:http://www.shaoqun.com/a/669740.html

跨境电商:https://www.ikjzd.com/

海鹰数据:https://www.ikjzd.com/w/2539

sonar:https://www.ikjzd.com/w/215


关于exception的基本语法和作用,这里不再赘述,下面记录一下我在项目中关于Exception的一些思考。一,使用Exception,而不是ErrorCode在初始设计项目的时候,有时候我们为了明确错误类型,定义如下结构:enumErrCode{Ok,ArgumentErr,OtherErr1,OtherErr2}classSomeClass{publicErrCodeDealSomethin
环球华网:https://www.ikjzd.com/w/1063
let go:https://www.ikjzd.com/w/825
isbn:https://www.ikjzd.com/w/174
亚马逊发布商品标题新规 / Facebook推出加密货币Libra计划:https://www.ikjzd.com/home/98558
如何利用Youtube视频卡片让流量飞起来:https://www.ikjzd.com/home/96846
嗯啊好紧要夹断了父女 出差那晚将旅店老板娘摁在床上疯狂穿插:http://lady.shaoqun.com/a/274480.html

No comments:

Post a Comment