.NET:如何应对边界异常?
背景
为什么语言引入了异常
一直没有思考过这个问题,但是异常确实让我的编程生活更快乐,今天早上似乎找到了这个问题的答案:exception之于call stack就像break和continue之于while或for、就像return之于method,总结为一句话:异常只是一种返回机制。
为什么异常让程序更简洁
代码里只有正常的处理逻辑。
1 /// <summary> 2 /// 创建。 3 /// </summary> 4 public ActionResult Create(TAggregateRoot item) 5 { 6 this.CurrentCommandService.Execute(new TCreateCommand 7 { 8 Aggregate = item 9 }); 10 11 return this.NewtonsoftJson(new 12 { 13 success = true, 14 items = this.GetById(item.Id) 15 }); 16 } 17 18 /// <summary> 19 /// 修改。 20 /// </summary> 21 public ActionResult Update(TAggregateRoot item) 22 { 23 this.CurrentCommandService.Execute(new TUpdateCommand 24 { 25 Aggregate = item 26 }); 27 28 return this.NewtonsoftJson(new 29 { 30 success = true, 31 items = this.GetById(item.Id) 32 }); 33 }
我的程序代码基本上也是CQRS的,凡是以写为目的的,都是用void进行声明。
异常的处理
异常有五种处理思路,如下图所示:
关于这五种处理思路的概要介绍可以参考这篇文章:http://www.cnblogs.com/happyframework/archive/2013/04/09/3010082.html。
今天的主要目的是介绍“边界异常处理”。
边界异常处理
当异常到达边界,毫无疑问我们必须进行处理。总体来说,到达边界的异常分为两大类:我们有意抛出的异常和未处理异常,针对这两种异常我们需要不同的处理思路,如:
- 有意抛出的异常:不希望写入日志,希望显示到UI。
- 未处理的异常:希望写入日志,不希望直接显示到UI,希望定制这种异常的显示信息。
一个简单的边界异常处理框架
结构
下图的友好异常就是我们有意抛出的异常或我们能意识到的异常。
几个核心类型
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Web.Mvc; 7 8 namespace Happy.Web.Mvc.ExceptionHanding 9 { 10 /// <summary> 11 /// 异常信息提供者接口,如果你希望为异常返回更多的信息,可以实现该接口。 12 /// </summary> 13 public interface IExceptionInformationProvider 14 { 15 /// <summary> 16 /// 创建信息。 17 /// </summary> 18 object CreateInformation(Exception exception); 19 } 20 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Web.Mvc; 7 8 using Common.Logging; 9 using Happy.Web.Mvc.Newtonsoft; 10 11 namespace Happy.Web.Mvc.ExceptionHanding 12 { 13 /// <summary> 14 /// 处理应用程序未捕获的异常。 15 /// </summary> 16 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 17 public class WriteExceptionResultAttribute : FilterAttribute, IExceptionFilter 18 { 19 /// <inheritdoc /> 20 public void OnException(ExceptionContext filterContext) 21 { 22 var exception = filterContext.Exception; 23 24 this.LogException(exception); 25 26 filterContext.Result = ExceptionInformationProviderRegistry.CreateErrorResult(exception); 27 28 filterContext.ExceptionHandled = true; 29 } 30 31 private void LogException(Exception exception) 32 { 33 if (FriendlyExceptionRegistry.Contains(exception.GetType())) 34 { 35 return; 36 } 37 38 LogManager.GetCurrentClassLogger().Error(exception); 39 } 40 } 41 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Web.Mvc; 7 using System.Data; 8 9 using Happy.ExtensionMethod; 10 using Happy.DesignByContract; 11 using Happy.Web.Mvc.Newtonsoft; 12 13 namespace Happy.Web.Mvc.ExceptionHanding 14 { 15 /// <summary> 16 /// 异常信息提供者注册处。 17 /// </summary> 18 public static class ExceptionInformationProviderRegistry 19 { 20 private static readonly Dictionary<Type, IExceptionInformationProvider> _providers 21 = new Dictionary<Type, IExceptionInformationProvider>(); 22 23 static ExceptionInformationProviderRegistry() 24 { 25 Register<OptimisticConcurrencyException>(new Internal.OptimisticConcurrencyExceptionInformationProvider()); 26 } 27 28 /// <summary> 29 /// 注册提供者。 30 /// </summary> 31 public static void Register<TException>(IExceptionInformationProvider provider) 32 where TException : Exception 33 { 34 Register(typeof(TException), provider); 35 } 36 37 /// <summary> 38 /// 注册提供者。 39 /// </summary> 40 public static void Register(Type exceptionType, IExceptionInformationProvider provider) 41 { 42 exceptionType.MustNotNull("exceptionType"); 43 provider.MustNotNull("provider"); 44 45 _providers[exceptionType] = provider; 46 } 47 48 internal static ActionResult CreateErrorResult(Exception exception) 49 { 50 exception.MustNotNull("exception"); 51 52 var exceptionType = exception.GetType(); 53 54 var information = CreateDefaultInformation(exception); 55 56 if (_providers.ContainsKey(exceptionType)) 57 { 58 var extInformation = _providers[exceptionType].CreateInformation(exception); 59 60 foreach (var item in extInformation.ToDictionary()) 61 { 62 information[item.Key] = item.Value; 63 } 64 } 65 66 return new NewtonsoftJsonResult 67 { 68 Data = information 69 }; 70 } 71 72 private static Dictionary<string, object> CreateDefaultInformation(Exception exception) 73 { 74 return new Dictionary<string, object> 75 { 76 { "success", false }, 77 { "exception", exception.GetType().Name }, 78 { "message",exception.Message } 79 }; 80 } 81 } 82 }
为乐观并发异常自定义返回消息
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Data; 7 using System.Web.Mvc; 8 9 using Happy.Web.Mvc.Newtonsoft; 10 11 namespace Happy.Web.Mvc.ExceptionHanding.Internal 12 { 13 internal sealed class OptimisticConcurrencyExceptionInformationProvider : ExceptionInformationProvider<OptimisticConcurrencyException> 14 { 15 protected override object CreateResult(OptimisticConcurrencyException exception) 16 { 17 return new 18 { 19 message = Messages.Error_OptimisticConcurrencyExceptionMessage 20 }; 21 } 22 } 23 }
备注
像微软的异常处理框架都是一个非常好的东西。
合理的利用和使用异常会让程序的结构更加简洁,这些概念只有真正使用了才能深刻的明白。