繁体   English   中英

如何从未经检查的异常中恢复?

[英]How do I recover from an unchecked exception?

如果您想以相同的方式处理每个故障,例如通过记录它并跳到下一个请求,向用户显示消息并处理下一个事件等,如果这是我的用例,那么所有我都可以使用未经检查的异常。要做的是在我的系统中捕获一些高级别的常规异常类型,并以相同的方式处理所有内容。

但我希望从特定问题中恢复,而且我不确定使用未经检查的异常来处理它的最佳方法。 这是一个具体的例子。

假设我有一个使用Struts2和Hibernate构建的Web应用程序。 如果异常冒泡到我的“动作”,我会记录它,并向用户显示一个非常道歉。 但我的Web应用程序的一个功能是创建需要唯一用户名的新用户帐户。 如果用户选择一个已经存在的名称,Hibernate会在我的系统内部抛出一个org.hibernate.exception.ConstraintViolationException (一个未经检查的异常)。 我真的想通过要求用户选择另一个用户名来恢复这个特定问题,而不是给他们相同的“我们记录了你的问题,但现在你已经被软化了”的消息。

以下是需要考虑的几点:

  1. 有很多人同时创建帐户。 我不想在“SELECT”之间锁定整个用户表以查看名称是否存在,如果不存在则锁定“INSERT”。 在关系数据库的情况下,可能有一些技巧可以解决这个问题,但我真正感兴趣的是一般情况下,由于基本的竞争条件,预先检查异常将不起作用。 同样的事情可能适用于在文件系统上查找文件等。
  2. 考虑到我的首席技术官倾向于通过读取“Inc.”中的技术专栏引起的偷渡管理,我需要在持久性机制周围留下一层间接,这样我就可以抛弃Hibernate并使用Kodo,或者其他任何东西,除了最低限度之外不会改变任何东西持久性代码层。 事实上,我的系统中有几个这样的抽象层。 尽管有未经检查的异常,我怎样才能防止它们泄漏?
  3. 已检查异常的一个声明的弱点是必须在堆栈的每个调用中“处理”它们 - 或者通过声明调用方法抛出它们,或者通过捕获它们并处理它们。 处理它们通常意味着将它们包装在另一个适合抽象级别的类型的检查异常中。 因此,例如,在checked-exception UserNotFoundException ,我的UserRegistry的基于文件系统的实现可能捕获IOException ,而数据库实现将捕获SQLException ,但两者都会抛出隐藏底层实现的UserNotFoundException 如何利用未经检查的异常,免除每层的包装负担,而不泄漏实现细节?

IMO,包装异常(检查或其他)有几个值得花费的好处:

1)它鼓励您考虑您编写的代码的失败模式。 基本上,您必须考虑您调用的代码可能抛出的异常,反过来您将考虑您为调用您的代码抛出的异常。

2)它使您有机会将额外的调试信息添加到异常链中。 例如,如果您有一个方法在重复的用户名上抛出异常,您可以用一个包含有关失败情况的附加信息(例如,提供欺骗用户名的请求的IP)的方法来包装该异常。低级代码无法使用。 异常的cookie跟踪可以帮助您调试复杂的问题(它肯定对我而言)。

3)它允许您从低级代码变为独立于实现。 如果你要包装异常并需要将Hibernate替换为其他ORM,你只需要更改你的Hibernate处理代码。 所有其他代码层仍将成功使用包装的异常,并将以相同的方式解释它们,即使基础环境已发生变化。 请注意,即使Hibernate以某种方式发生更改,这也适用(例如:它们在新版本中切换异常); 它不仅仅是批发技术的替代品。

4)它鼓励您使用不同类别的异常来表示不同的情况。 例如,当用户尝试重用用户名时可能会出现DuplicateUsernameException,而当由于数据库连接断开而无法检查重写用户名时,可能会出现DatabaseFailureException。 反过来,这可以让您以灵活而有力的方式回答您的问题(“我该如何恢复?”)。 如果您收到DuplicateUsernameException,则可能决定向用户建议不同的用户名。 如果您收到DatabaseFailureException,您可能会让它冒泡到用户向用户显示“向下维护”页面的位置,并向您发送通知电子邮件。 一旦你有自定义异常,你就有了可定制的响应 - 这是一件好事。

我喜欢重新打包我的应用程序的“层”之间的异常,所以例如一个特定于DB的异常在另一个异常中重新打包,这个异常在我的应用程序的上下文中是有意义的(当然,我把原来的异常留给了成员所以我不破坏堆栈跟踪)。

也就是说,我认为一个非唯一的用户名不是一个“特殊”的足够的情况来保证投掷。 我会使用布尔返回参数。 在不了解您的架构的情况下,我很难说出更具体或更适用的内容。

请参阅错误的生成,处理和管理模式

来自拆分域和技术错误模式

技术错误永远不会导致生成域错误(从来没有twain应该满足)。 当技术错误必须导致业务处理失败时,它应该被包装为SystemError。

域错误应始终从域问题开始,并由域代码处理。

域错误应该“无缝地”通过技术边界。 可能必须将这些错误序列化并重新构建以实现此目的。 代理人和外墙应该对此负责。

应在应用程序的特定点处理技术错误,例如边界(请参阅分布边界的日志)。

与错误一起传回的上下文信息量将取决于它对后续诊断和处理的有用程度(找出替代策略)。 您需要询问来自远程计算机的堆栈跟踪是否对域错误的处理完全有用(尽管此时错误和变量值的代码位置可能很有用)

因此,使用未经检查的域异常(例如“UniqueUsernameException”)将hibernate异常包装在边界处,然后让它一直冒泡到它的处理程序。 确保抛出异常的javadoc,即使它不是一个经过检查的异常!

由于您当前正在使用hibernate,因此最简单的方法就是检查该异常并将其包装在自定义异常或您可能已在框架中设置的自定义结果对象中。 如果你想稍后抛弃hibernate只是确保你只在一个地方包装这个异常,首先你从hibernate中捕获异常,那就是你在切换时可能需要改变的代码,所以如果抓住的话在一个地方,然后额外的开销几乎是zilch。

救命?

我同意尼克。 您描述的异常并非真正的“意外异常”,因此您应该相应地设计代码,并考虑可能的异常。

另外,我建议您查看Microsoft Enterprise Library 异常处理块的文档,它有一个很好的错误处理模式大纲。

您可以捕获未经检查的异常而无需包装它们。 例如,以下是有效的Java。

try {
    throw new IllegalArgumentException();
} catch (Exception e) {
    System.out.println("boom");
}

因此,在您的操作/控制器中,您可以在进行Hibernate调用的逻辑周围使用try-catch块。 根据异常,您可以呈现特定的错误消息。

但我猜你今天可能是Hibernate,明天就是SleepLo​​ngerDuringWinter框架。 在这种情况下,您需要假装拥有自己的包含第三方框架的小型ORM框架。 这将允许您将任何特定于框架的异常包装到更有意义和/或已检查的异常中,您知道如何更好地理解这些异常。

  1. 这个问题与检查与未检查的辩论无关,这同样适用于两种异常类型。

  2. 在抛出ConstraintViolationException的点和我们想要通过显示一个好的错误消息来处理违规的点之间是堆栈上的大量方法调用应该立即中止并且不应该关心问题。 这使得异常机制成为正确的选择,而不是重新设计从异常到返回值的代码。

  3. 实际上,使用未经检查的异常而不是检查异常是很自然的,因为我们真的希望调用堆栈上的所有中间方法都忽略异常而不处理异常。

  4. 如果我们只想通过向用户显示一个很好的错误消息(错误页面)来处理“唯一名称违例”,那么实际上并不需要特定的DuplicateUsernameException。 这将使异常类的数量保持较低。 相反,我们可以创建一个可以在许多类似场景中重用的MessageException。

    我们尽快捕获ConstraintViolationException并将其转换为带有好消息的MessageException。 重要的是很快转换它,当我们可以确定时,它确实是违反的“唯一用户名约束”而不是其他约束。

    在靠近顶级处理程序的地方,只需以不同的方式处理MessageException。 而不是“我们记录你的问题,但现在你已经被软化”只是显示MessageException中包含的消息,没有堆栈跟踪。

    MessageException可以使用一些额外的构造函数参数,例如问题的详细说明,可用的下一个操作(取消,转到另一个页面),图标(错误,警告)......

代码可能如下所示

// insert the user
try {
   hibernateSession.save(user);
} catch (ConstraintViolationException e) {
   throw new MessageException("Username " + user.getName() + " already exists. Please choose a different name.");
}

在一个完全不同的地方有一个顶级异常处理程序

try {
   ... render the page
} catch (MessageException e) {
   ... render a nice page with the message
} catch (Exception e) {
   ... render "we logged your problem but for now you're hosed" message
}

@Jan Checked与unchecked是一个核心问题。 我质疑你的假设(#3)在中间帧中应该忽略异常。 如果我这样做,我将在我的高级代码中得到特定于实现的依赖。 如果我替换Hibernate,则必须修改我的应用程序中的catch块。 然而,与此同时,如果我在较低级别捕获异常,我使用未经检查的异常并没有获得太多好处。

此外,这里的场景是我想捕获特定的逻辑错误并通过重新提示用户输入不同的ID来更改应用程序的流程。 简单地更改显示的消息是不够的,并且已经基于异常类型映射到不同消息的能力已经内置到Servlet中。

@erikson

只是为你的想法添加食物:

Checked与unchecked也在这里进行辩论

未经检查的异常的使用符合它们被IMO用于由函数的调用者引起的异常的事实(并且调用者可以是该函数之上的几个层,因此其他帧必须忽略该异常)

关于你的具体问题,你应该在高级别捕获未经检查的异常,并将其封装,如@Kanook在你自己的例外中所说,而不显示callstack(由@Jan Soltis提及)

话虽这么说,如果底层技术发生变化,那确实会对你代码中已经存在的那些catch()产生影响,并且不能回答你的最新情况。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM