4

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

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

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

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

谁能建议我最好的异常处理技术来做到这一点?

4

5 回答 5

3

始终在尽可能接近其源的位置处理未经检查的异常,但避免创建它们,它们本质上是最后的不安全编程习惯;当程序不可能恢复时。

在实现应用程序时,代码防御性地购买总是在编码时使用检查异常来管理异常工作流;例如当您的代码未能遵守其接口“合同”时。

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

泛化异常处理,但专门化它们的创建。例如

CustomerNotFound extends CustomerException
CreditCustomerNotFound  extends CustomerException 
CashCustomerNotFound  extends CutomerException

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

InvoiceAddressException extends InvoiceException

尽量靠近源头做具体处理,记录细节,尽量清理。仅传播

尽可能靠近用户界面进行一般处理和报告。

于 2012-08-06T11:38:51.243 回答
2

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

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

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

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

编辑

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

  1. 逻辑 - 应用程序逻辑问题,例如当用户尝试使用无效的用户名/密码登录时,这些错误通常是您故意抛出的,并且是您创建的自定义错误。
  2. 功能性 - 由于应用程序本身的问题(如数据库连接等)而引发的所有常规异常

然后,您可以决定处理每种类型的策略。逻辑异常会向用户返回特定数据(无效的用户名/密码),功能异常会返回更通用的消息,例如:出现问题,请联系支持。

于 2012-08-06T09:53:35.907 回答
1

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

  1. 首先,“Martin Spamer”是正确的,因为我们只需要捕获 Checked 异常......但在非常精细的层面......例如,不应将 InvalidCredentialException 和 AuthorizationException 作为 LoginException 抛出/捕获,因为假设您将其作为单一类型异常抛出然后要求您将来以不同的方式处理这些类型,然后呢?

  2. 其次,有层是 UI ----> front tier ----> service tier -----> DAO tier

逻辑应该是,

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

(ii) 服务将验证业务逻辑,如果有效则处理请求。如果不是,它将向前端层抛出带有正确消息的异常

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

这种方法将有另一个额外的优势。假设,在您需要将一些业务方法公开为 Web 服务的地方出现了一个需求……所以无论如何您的服务都会被公开。现在,如果您将整个异常处理(即将异常转换为正确的消息)逻辑放在前端层并且服务层完全不知道它们......那么您需要再次重构服务层代码(以及前排)。甚至在一段时间之后,可能会出现另一种情况,即您的前端技术已经过时,您必须用新技术重新编码整个异常处理逻辑。

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

于 2012-08-08T07:03:57.543 回答
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)而不是指导用户到“error.xhtml”页面(在 web.xml 中引入)。

// 对于上述场景,我想出了这个解决方案。
它甚至似乎工作得很好。

  1. 创建拦截器和拦截器绑定注解。
  2. 注释您希望清理冒泡的 EJBExceptions 的类或方法。
    • 不要去拦截一切,否则你会得到不必要的处理/开销。只需在 BackingBean/ManagedBean 级别上实际需要它的地方使用它。

拦截器绑定注解

@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;
    }
}

豆类.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的 Exception 类。根据需要构建类,以传输有用的信息以供以后记录并在屏幕上显示干净的错误消息,而不是在屏幕上呕吐异常,无论是否在web.xml中定义了错误页面(您也应该将其作为后备) )。不要忘记在类中包含原始异常!

根据需要创建更多您自己的扩展 EJBException 的异常,并根据需要抛出它们(作为未选中的 RuntimeExceptions)。然后只需在拦截器中添加 catch 块并执行针对该案例的日志记录和/或错误消息显示。

一般来说:

  • 在您可以对错误做出反应的地方使用已检查的异常。

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

      尝试 {...} 捕获 (SomeException | OtherException e) {...}

  • 如果您有很多已检查的异常,您可以/应该将它们分组(扩展您的一些更通用的异常)。然后,您可以基于该分组异常进行捕获(除了/而不是具有分组捕获子句)。

  • 将这些异常转换为污染您的代码的 EJB/RuntimeExceptions(如果您在附近确实无能为力)。然后这些应该由通用EjbExceptionInterceptor捕获和处理。拦截器将捕获异常 => 显示干净的错误消息/弹出窗口,但用户将保持在同一页面上而不会丢失他/她正在处理的数据。
  • 避免抛出普通的RuntimeException。这些表示代码中的错误(我的观点),上面的拦截器会让它们在error-page上失败。这也将使用户远离他/她当前所在的页面。
    • 这类错误应该始终是要修复的高优先级错误(例如 NullPointerException 会这样做)。
    • 另外:不要几乎所有事情都抛出异常,那将是糟糕的编程。如果您的代码可以事先处理错误(例如,使用重载方法或默认值来防止使用空参数),那么就这样做,不要将问题泄露给客户端(调用方法)。那只是懒惰的编程+使用 RuntimeExceptions 隐藏这些错误是不负责任的!进行单元测试!


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

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

于 2016-09-09T08:12:09.690 回答
-2

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

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

避免异常代码

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

第 39 条:仅对异常情况使用异常(Jochua Bloch 的 Effective Java 章节)

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

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

Java 语言没有太大帮助,但理想情况下,您可以返回该业务错误,作为函数的输出(例如,EnumAddBasketError 的实例),而不是创建 AddProductToBasketWithoutQuantityException。在实践中,人们往往会引发异常,破坏正常的控制流程,并减慢应用程序的速度(异常是有代价的)。

避免检查异常

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


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

于 2013-04-21T00:31:21.640 回答