异常处理策略 - 重用异常代码

时间:2012-08-06 09:43:14

标签: design-patterns java-ee exception-handling

我正在开发一个应用程序,这个过程就像这样进行

UI --> backend process --> result to UI.

在我的Java代码中,我使用try-catch处理了异常,但是在代码中我有很多重复的异常,可能会在不同的类中抛出相同的异常,这会降低可读性和代码重用。

所以,我打算做一个异常处理策略,这样我就不需要在不同的类中抛出相同的异常,而是需要组织异常并重用异常代码。

有人能建议我做最好的异常处理技术吗?

5 个答案:

答案 0 :(得分:3)

始终处理未经检查的异常尽可能靠近其来源,但避免创建它们,它们是最后的inherently unsafe编程习惯用语;当程序无法恢复时。

在实施应用程序时,代码会在编码时始终使用经过检查的异常来管理特殊工作流程;例如,当您的代码无法遵守其界面“Contract”时。

应用程序不应因未检查的异常而崩溃,因为它无法访问第三方信用评分系统。禁用该选项并继续允许创建现金客户。

概括异常处理,但专门创建它们。 e.g。

CustomerNotFound extends CustomerException
CreditCustomerNotFound  extends CustomerException 
CashCustomerNotFound  extends CutomerException

{
   aCustomer = customerList.find( ... )
} catch CustomerNotFound() { 
   logger.warn( ... )
   throw CustomerException(CustomerNotFound)
}

InvoiceAddressException extends InvoiceException

尽可能靠近源进行特定处理,记录详细信息并尽可能清理。仅传播

将通用处理和报告尽可能贴近用户界面。

答案 1 :(得分:2)

由于您有一个将由客户端使用的GUI,客户端需要知道是否发生了异常,因此处理异常的最佳位置将是您最上层的(与之通信的层) GUI)。

您还可以捕获抛出它的异常,记录它然后再次抛出它。

这意味着所有其他较低层将抛出异常。

您可以创建一些自定义异常并使用它们而不是常规异常(即捕获SqlException并抛出MyDBException以及异常代码,查询字符串等更多详细信息。)

修改

我还会考虑将例外分为两类:逻辑和功能。

  1. 逻辑 - 应用程序逻辑的问题,例如当用户尝试使用无效的用户名/密码登录时,这些错误通常会由您故意引发,并且是您创建的自定义错误。
  2. 功能 - 由于应用程序本身的问题而引发的所有常规异常(如数据库连接等)
  3. 然后您可以决定处理每种类型的策略。逻辑异常会将特定数据返回给用户(无效的用户名/密码),而Functinal异常会返回更通用的消息,如:出现问题,请联系支持部门。

答案 2 :(得分:1)

我想为此展示一种面向设计的方法,

  1. 首先,“Martin Spamer”是正确的,因为我们只需要捕获Checked例外....但是在非常精细的层面......例如。 InvalidCredentialException和AuthorizationException不应该作为LoginException抛出/捕获,因为,假设你把它作为Single类型异常抛出然后要求你以后以不同的方式处理这些类型,那么??

  2. 其次,有层级 UI ---->前层---->服务层-----> DAO等级

  3. 逻辑应该是,

    (i)前层将接收来自UI的请求并处理基于前端的验证/异常(例如登录时的MissingUserNameException)。如果一切正常,则将请求转发到服务层

    (ii)服务将验证业务逻辑,如果有效,则它将处理该请求。如果不是,它将向前端层

    发送带有正确消息的异常

    (iii)但是每个服务级别异常消息可能不适合用户显示。所以前级的责任应该是将业务异常转换为客户端可读的异常

    这种方法还有另外一个优势。假设有一个需求出现在你需要将一些业务方法暴露为Web服务的地方......所以你的服务无论如何都会暴露出来。现在,如果你把整个异常处理(即将异常转换为正确的消息)前端层和服务层中的逻辑完全没有意识到它们......那么你需要再次重构服务层代码(以及前线)。甚至有些时候,可能会出现另一种情况,即您的前端技术已经过时,您必须在新技术中重新编写整个异常处理逻辑。

    所以底线是,服务层应该知道+使用正确的消息处理所有业务验证异常。在使用正确的消息装饰异常之后,它将把它交给调用服务的客户端层。现在是客户层的责任,如果需要,显示消息并用其他消息再次装饰它。这样,您可以保持所有层独立。

答案 3 :(得分:0)

我没有写更多评论(删除多余的评论),而是将我的一些想法放在这里+我提出的一种通用解决方案。

http://docs.oracle.com/javaee/6/tutorial/doc/bnbpj.html

这将这个问题放在简单的背景下。当人们不习惯异常时(比如脚本/功能程序员),他们只会使用RuntimeExceptions ...这会让你在UI上使用未经管理的错误来污染前端。 (如果你长期考虑程序代码,我觉得这很方便......可维护性)

http://www.onjava.com/pub/a/onjava/2003/11/19/exceptions.html

  

如果客户端可以采取一些备用操作从异常中恢复,请将其作为已检查的异常。如果客户端无法执行任何有用的操作,则取消选中该异常。有用的,我的意思是采取措施从异常中恢复,而不仅仅是记录异常。

(有关于使用例外记录模式的帖子,但是我不会在这里发现,因为它已经落后了)

很难检测到未经检查的异常,并且很容易通过您的"接近来源"醒目。 (当它们未被选中时,它们将对客户端代码不可见,并且会漏掉)。

我遇到了这个问题的挑战,一些遗留代码抛出了大量未经检查的异常,现在会导致UI崩溃。 (因为服务不可靠)

可能"每个人"他们有自己的看法如何正确地做事,但我试图有一个通用的模式来捕捉接近UI的错误(未经检查的异常)并将它们呈现在漂亮的错误弹出窗口(顺便说一下JSF),而不是将用户引导到& #34; error.xhtml" page(在web.xml中引入)。

//对于上述情况,我提出了这个解决方案。
它甚至似乎运作得很好。

  1. 创建拦截器和拦截器绑定注释。
  2. 注释要对其冒泡的EJBExceptions进行清理的类或方法。
    • 不要截取所有内容,否则会得到不必要的处理/开销。只需在BackingBean / ManagedBean级别上实际需要它的地方使用它。
  3. 拦截器绑定注释

    @Inherited
    @InterceptorBinding
    @Target({ ElementType.TYPE, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface InterceptEJBExceptions {}
    

    拦截器

    @Interceptor
    @InterceptEJBExceptions
    public class EjbExceptionInterceptor {
        @AroundInvoke
        public Object interceptBubblingExceptions(final InvocationContext context) throws Exception {
            try {
                return context.proceed();
            } catch (MySpecificEJBException msejbe) {
                Object[] args = { msejbe.getErrorCode(), msejbe.getMethodSignature(), msejbe.getParameters()};
                // This would create a FacesMessage in JSF
                addErrorMessageToFacesContext(null, summary_template, details_template, args);
            } catch (EJBException ejbe) {
                super.addErrorMessageToFacesContext(null, "Unspecified EJBException", ejbe.getMessage());
            }
            // "Pure" RuntimeExceptions will fall through and will be shown on 'error-page' specified in web.xml
            return null;
        }
    }
    

    的beans.xml

    <beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
    
        <interceptors>
            <class>xxx.xxx.xxx.exception.interceptor.EjbExceptionInterceptor</class>
        </interceptors>
    </beans>
    

    java类中的用法

    @RequestScoped // Class must be in EE context
    // @InterceptEJBExceptions // Have the annotation at class level to intercept all method calls in this class
    public class MyBB {
        @InterceptEJBExceptions // Only intercept when this method gets called
        public String myEjbExceptionThrowingActionListener(Object obj) {
            // Some code that may throw an EJBException
        }
    }
    

    MySpecificEJBException 将是一个扩展 EJBException 的异常类。根据需要构造类,以便为以后的日志记录传输有用的信息,并在屏幕上显示干净的错误消息,而不是在 web中定义错误页的情况下在屏幕上呕吐异常。 xml (您还应该将其作为后备) 不要忘记在课堂中包含原始的Exception!

    根据需要创建更多自己的Exceptions扩展EJBException并根据需要抛出它们(因为RuntimeExceptions未被选中)。然后只需在拦截器中添加catch块,并显示特定于该情况的日志记录和/或错误消息。

    一般情况下:

    • 使用已检查的例外情况,您可以对错误做出反应。

      • 在Java 7中,您可以使用以下语法对可以类似处理的异常进行分组。

        尝试{...} catch(SomeException | OtherException e){...}

    • 如果您有大量已检查的例外,您可以/应该将它们按类别分组(扩展一些更通用的例外)。然后,您可以根据该分组异常进行捕获(除了/而不是具有分组catch子句)。

    • 将这些异常转换为污染代码的EJB / RuntimeExceptions(如果您无法在附近执行任何操作)。然后应该通过通用 EjbExceptionInterceptor 捕获和处理这些。拦截器将捕获异常=&gt;显示干净的错误消息/弹出窗口,但用户将保持在同一页面上而不会丢失他/她正在处理的数据。
    • 避免抛出普通的 RuntimeException 。这些信号表示代码中的错误(我的意见),上面的拦截器会让它们落在错误页面上。这也将使用户远离他/她当前所在的页面。
      • 这类错误应该始终是要修复的高优先级错误(例如NullPointerException会执行此操作)。
      • 另外:不要仅仅针对所有内容抛出异常,这将是糟糕的编程。如果您的代码可以事先对错误做些什么(例如使用重载方法或默认值来防止使用null参数),然后这样做,不要将问题泄漏给客户端(调用方法)。这只是懒惰的编程+使用RuntimeExceptions隐藏这些错误是不负责任的! 进行单元测试!


    我找不到任何错误处理的实际解决方案(作为模式),但是很多争论什么是对的,什么是错的。我们有两种类型的Throwables,那么为什么不建设性地使用它们呢? 我希望这不是继续这场辩论,而是希望这将是一个真正的解决方案,并且可以做到这一点。

    我感谢所有建设性意见和改进建议。

答案 4 :(得分:-2)

修改:原始答案在您的其他问题中移到此处: https://stackoverflow.com/a/16172074/82609

为了在问题范围内保留我的答案,以下是有趣的部分:

避免例外代码

异常类型应足以用于流控制决策。解析异常或流控制只会创建无用的代码。添加更多异常类型,就像你有异常代码一样。

第39项:仅在特殊情况下使用例外(Jochua Bloch的有效Java章节)

第39项:仅在例外条件下使用例外。也就是说,不要对控制流使用异常,例如在调用Iterator.next()时捕获NoSuchElementException而不是首先检查Iterator.hasNext()。

在函数式语言中,我们倾向于仅使用异常来处理异常条件。 例外情况是“无法连接到数据库”,而不是“用户没有在输入文本中提供他想要的文章数量”。这不是例外,这是一个商业错误。

Java语言没有那么多帮助,但理想情况下,您可能会返回该业务错误,作为函数的输出(例如,EnumAddBasketError的实例),而不是创建AddProductToBasketWithoutQuantityException。在实践中,人们倾向于提出异常,打破正常的控制流程,并减慢应用程序的速度(例外有成本)。

避免检查异常

请在此处阅读我的其他答案:https://stackoverflow.com/a/16172074/82609 已检查的异常是针对可恢复的故障(Sun建议)。对于不可恢复的故障,未经检查的异常更容易处理。


最后我的意思是你可能不需要所有这些例外,因为它们中的大多数可能是不可恢复的,或者可能不是例外。