异常设计 - 每个层的自定义异常

时间:2016-07-05 13:39:37

标签: java c# exception exception-handling

在我们的系统设计中,我们有以下几层:

Web API -> BusinessLayer -> HelperLayer -> DataLayer - Call hierarchy

Web API是Rest服务层,Business在业务实体上进行业务操作,Helper将Data实体转换为Business Entity,Data从数据库中获取POCO

在我们讨论系统的异常管理策略时,以下是两种观点:

我更喜欢所有错误都会传播到Web API,我们使用错误过滤器来拦截,记录错误并更改Context.Response以向最终用户提供友好的消息,同样的好处是:

  1. 错误来源保持不变

  2. 我们处理其所需的例外情况

  3. 简单直接的错误处理机制

  4. 另一组队友喜欢的是我们为每个层创建一个自定义异常,比如DALException,HelperException,BusinessException,其中给定层抛出异常,调用层处理它,填充内部异常并因此继续,按照他们的好处是:

    1. 每个层都可以提供问题/异常的自定义信息,这有助于错误/异常抽象
    2. 对我来说,这个设计的问题是:

      1. 更改异常来源,这是一个很好的做法
      2. 捕获异常而不进行任何处理
      3. 通过在任何地方添加try catch来获取大量额外代码,这可能会影响我理解的性能
      4. 我看到的唯一好处是我们可以向客户提供特定的消息,但如果我们了解基础核心异常并根据某些代码进行区分,从而提供ABC失败而不是通用消息等自定义消息,那么这甚至是可能的。

        如果需要澄清,请分享您的观点并告诉我

3 个答案:

答案 0 :(得分:1)

我建议通过使用拦截器(某种程度上)来避免这个问题,而不是将任何逻辑直接放在你的类和方法中。

如果一个类的职责是接收某些数据的请求并从SQL返回,那么它就不应该关注哪些层期望的异常类型。异常处理成为该类责任之外的额外逻辑。

有许多不同的方法可以实施拦截。它可能取决于您的应用程序中已包含哪些工具。我使用Windsor进行依赖注入,因此使用拦截器很方便。如果我没有使用温莎,那么我会看PostSharp

但是效果是你要么在你的类上有一个属性,要么在你声明你的依赖项时有一个声明,然后是"抛出这种类型的异常的所有逻辑,抓住它,包装它和那个,并重新投掷"所有人都生活在一个拦截器类中。您可以来回更改它而不会污染您的其他课程。

99%的时间这使我在代码中根本没有try/catch块。记录和重新抛出被降级为拦截器。我有任何异常处理的唯一一次是如果我需要优雅地处理一些事情,所以我需要捕获一个异常,记录它,并返回一个非异常结果。

与拦截无关:

在实践中,我发现大部分时间都有一种类型的异常与另一种类型或在其他类型上包装异常是无用的。 99%的战斗只是拥有异常细节而没有任何东西。只要没有人throw ex(删除堆栈跟踪),您就会在异常详细信息中获得所需的内容。如果我们巧妙地将异常包装在更多例外中,那么我们只需创建更多我们将要忽略的信息。当我们寻找我们真正关心的一件事时,我们会有更多细节要筛选 - 什么是例外,它在哪里抛出?

唯一的例外(我真的希望我有一个同义词)是业务层抛出包含面向用户信息的异常。例如,用户尝试更新某些内容,他们无法更新,并且您希望包装该异常以便解释他们需要更正的内容。特定类型的异常可能表明该异常是面向用户的。

但是,如果异常消息是业务逻辑的结果("您不能放置此订单,因为该项目缺货")那么这真的是一个例外吗?也许该调用应返回失败消息而不是抛出异常。我们不应该使用例外来传达信息。

答案 1 :(得分:1)

让堆栈trac初始抛出错误是很有价值的。 如果捕获并重新引发异常,则将异常添加到内部异常非常重要,这样信息就不会丢失。 根据我的经验,捕获和重新抛出异常的论点来自于不熟悉使用堆栈跟踪进行调试的开发人员。

如果要捕获并重新抛出异常,它会创建额外的代码以通过内部异常进行迭代。必须记录或传输此数据,这会增加存储要求或网络开销。在调试与抛出的异常相关的问题时,还有更多的数据需要筛选,这可能会增加所需的开发人员时间。

另一方面,如果开发人员可以预测异常并在编写代码时使用此洞察,那么提供额外的顶级信息会很有帮助,因此在这种情况下捕获和重新抛出异常是有效的。特别是其他(可能经验不足)开发人员将使用相同的代码。如果异常是可预测的,但在许多情况下,最好处理错误并相应地表现,而不是推迟异常。

在application / dll boundaraies中捕获异常可能很有价值。这样做可以使这些模块的开发在其他开发人员使用或最终用户使用时不那么脆弱。

答案 2 :(得分:0)

如何通过异常链接重新抛出异常并在Filter中解析此链(在chain(cause)中仅选择第一个异常或解析所有层的所有上下文)?

class App {
    public static void main(String[] args) {
        View view = new View();
        try {
            System.out.println(view.doRender());
        } catch (ViewException e) {
            System.out.println("ERROR: " + unrollChain(e));
        }
    }

    static String unrollChain(Exception e) {
        Throwable current = e;
        while (current.getCause() != null) {
            current = current.getCause();
        }
        return current.getMessage();
    }
}

class View {
    private Business business = new Business();

    String doRender() throws ViewException {
        try {
            return "<html>" + business.doBusiness() + "</html>";
        } catch (BusinessException e) {
            if (System.nanoTime() % 2 == 0)
                throw new ViewException("Some context if have one", e);
            else
                throw new ViewException(e); // no local context, pure rethrow
        }
    }
}

class Business {
    private Dao dao = new Dao();

    int doBusiness() throws BusinessException {
        try {
            return dao.select() + 42;
        } catch (DaoException e) {
            if (System.nanoTime() % 2 == 0)
                throw new BusinessException("Some context if have one", e);
            else
                throw new BusinessException(e); // no local context, pure rethrow
        }
    }
}

class Dao {
    int select() throws DaoException {
        if (System.nanoTime() % 2 == 0)
            return 42;
        else
            throw new DaoException();
    }
}

class DaoException extends Exception {
}

class BusinessException extends Exception {
    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }

    public BusinessException(Throwable cause) {
        super(cause);
    }
}

class ViewException extends Exception {
    public ViewException(String message, Throwable cause) {
        super(message, cause);
    }    
    public ViewException(Throwable cause) {
        super(cause);
    }
}