zoukankan      html  css  js  c++  java
  • .NET:何时应该 “包装异常”?

    背景

    提到异常,我们会想到:抛出异常、异常恢复、资源清理、吞掉异常、重新抛出异常、替换异常、包装异常。本文想谈谈 “包装异常”,主要针对这个问题:何时应该 “包装异常”?

    “包装异常” 的技术形式

    包装异常是替换异常的特殊形式,具体的技术形式如下:

    1             try
    2             {
    3                 // do something
    4             }
    5             catch (SomeException ex)
    6             {
    7                 throw new WrapperException("New Message", ex);
    8             }

    注意:WrapperException 需要将 ex 作为 InnerException,这样才不至于丢失 StackTrace,WrapperException.StackTrace 和 ex.StackTrace 共同构成了完整的 StackTrace。

    让例子帮助我们得出答案

    有这样一种场景:我希望为各种 ORM 框架提供一种抽象,这可以让应用开发人员自由的在不同的 ORM 实现之间做出选择。

    第一个版本的实现

    实现伪代码

     1     interface IRepository<TEntity>
     2     {
     3         void Update(TEntity entity);
     4     }
     5 
     6     class EntityFrameworkRepository<TEntity> : IRepository<TEntity>
     7     {
     8         public void Update(TEntity entity)
     9         {
    10             throw new EntityFrameworkConcurrentException(); // 可能会抛出这样的异常,这里的代码不是十分准确。
    11         }
    12     }
    13 
    14     class NHibernateRepository<TEntity> : IRepository<TEntity>
    15     {
    16         public void Update(TEntity entity)
    17         {
    18             throw new NHibernateConcurrentException(); // 可能会抛出这样的异常,这里的代码不是十分准确。
    19         }
    20     }

    有什么问题?

    处理并发异常是应用层开发人员的一个非常重要的职责,他们或者选择自动重试、或者选择让用户重试、甚至允许并发带来的不一致性,如果使用了上面的接口问题就大了,应用中该拦截哪种并发异常呢?EntityFrameworkConcurrentException?NHibernateConcurrentException?这样的接口和实现无论如何都达不到:OCP 和 LSP。

    将异常作为契约的一部分

    实现伪代码

     1     interface IRepository<TEntity>
     2     {
     3         void Update(TEntity entity);
     4     }
     5 
     6     class ConcurrentException : Exception
     7     {
     8     }
     9 
    10     class EntityFrameworkRepository<TEntity> : IRepository<TEntity>
    11     {
    12         public void Update(TEntity entity)
    13         {
    14             try
    15             {
    16             }
    17             catch (EntityFrameworkConcurrentException ex)
    18             {
    19                 throw new ConcurrentException(ex);
    20             }
    21         }
    22     }
    23 
    24     class NHibernateRepository<TEntity> : IRepository<TEntity>
    25     {
    26         public void Update(TEntity entity)
    27         {
    28             try
    29             {
    30             }
    31             catch (NHibernateConcurrentException ex)
    32             {
    33                 throw new ConcurrentException(ex);
    34             }
    35         }
    36     }

    有什么问题?

    目前来说还觉得不错,如果 C# 编译器或 CLR 能支持异常契约就好了,Java 虽然支持,但是对于调用者来说又不太友好。

    这里给出答案

    当异常是契约的一部分时,才需要包装异常。

    可能还会有其它答案,等我再思考思考,朋友们也可以给出一些想法。

    微软的一个反例

     MethodBae.Invoke

    1         //   System.Reflection.TargetInvocationException:
    2         //     调用的方法或构造函数引发异常。
    3         //
    4         //   System.MethodAccessException:
    5         //     调用方没有调用此构造函数的权限。
    6         //
    7         //   System.InvalidOperationException:
    8         //     声明此方法的类型是开放式泛型类型。 即,System.Type.ContainsGenericParameters 属性为声明类型返回 true。
    9         public abstract object Invoke(object obj, BindingFlags invokeAttr, Binder binder, object[] parameters, CultureInfo culture);

    当方法内部抛出异常时,Invoke 会将内部异常给包装起来,这明显不是我们期望的行为,后来微软的 dynamic 调用 和 CreateDelegate 之后使用 Delegate 调用 都修复了这个问题。

    备注

    最近在读第四版的 clr via c#,确实是一部好书,关于异常作为契约部分的想法,和作者产生了很大的共鸣,书中对异常处理的讲解非常细致,推荐大家读一读这本书。

  • 相关阅读:
    数据结构-树与二叉树-思维导图
    The last packet successfully received from the server was 2,272 milliseconds ago. The last packet sent successfully to the server was 2,258 milliseconds ago.
    idea连接mysql报错Server returns invalid timezone. Go to 'Advanced' tab and set 'serverTimezone' property
    redis学习笔记
    AJAX校验注册用户名是否存在
    AJAX学习笔记
    JSON学习笔记
    JQuery基础知识学习笔记
    Filter、Listener学习笔记
    三层架构学习笔记
  • 原文地址:https://www.cnblogs.com/happyframework/p/3404158.html
Copyright © 2011-2022 走看看