抓住我,如果你......不能否则

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

我不知道这是一个反模式还是一个常见且非常流行的错误,但我到处都看到它并且必须写下来。我说的是 不重新抛出异常捕获 。我说的是像这样的 Java 代码:


 try {
  stream.write(data);
} catch (ioexception ex) {
  ex.printstacktrace();
}


©catch me if you can (2002) 史蒂文斯皮尔伯格

注意:我对这段代码没有任何反对意见:


 try {
  stream.write(data);
} catch (ioexception ex) {
  ex.printstacktrace();
}

这称为 异常链接 ,是一个完全有效的结构。

那么捕获异常并记录它有什么问题呢?让我们先看看大局。我们谈论的是面向对象编程——这意味着我们正在处理对象。这是一个对象(准确地说是它的类)的样子:


 try {
  stream.write(data);
} catch (ioexception ex) {
  ex.printstacktrace();
}


这是我如何使用此类:


 try {
  stream.write(data);
} catch (ioexception ex) {
  ex.printstacktrace();
}


看起来不错,对吧?当我调用 send(1) 时,我不需要担心那个 ioexception 。它将在内部处理,如果发生,堆栈跟踪将被记录。但这是一种完全错误的思维方式,它无一例外地继承自语言,例如c。

异常的发明是为了通过将整个错误处理代码从主要逻辑中移开来简化我们的设计。此外,我们不仅将其移开,而且将其集中在一个地方 — 在 main() 方法中,即整个应用程序的入口点。

异常的主要目的是尽可能多地收集有关错误的信息,并将其提升到最高级别,用户可以在该级别上执行某些操作。异常链允许我们在其向上扩展该信息,从而进一步提供帮助。每次我们抓住它并重新抛出时,我们基本上都会将我们的泡沫(例外)放入更大的泡沫中。当它碰到表面时,会出现许多气泡,每个气泡都像俄罗斯套娃一样留在另一个气泡中。最初的例外是最小的气泡。

当您捕获异常而不重新抛出它时,您基本上会弹出气泡。其中的所有内容,包括原始异常和包含其中信息的所有其他气泡,都在您的手中。你不让我看到他们。你以某种方式使用它们,但我不知道如何使用它们。你在幕后做某事,隐藏潜在的重要信息。

如果你对我隐瞒,我不能向我的用户保证我会诚实地对待他并在问题发生时公开报告。我再也不能相信您的 send() 方法,我的用户也不会相信我。

通过捕获异常而不重新抛出它们,您基本上打破了对象之间的信任链。

我的建议是尽可能少地捕获异常,并且每次捕获它们时都重新抛出。

不幸的是,java的设计在很多地方都违背了这个原则。例如,java 有已检查和未检查的异常,而在我看来应该只有已检查的异常(您必须捕获或声明为可抛出的异常)。此外,java 允许在一个方法中将多个异常类型声明为可抛出的——这又是一个错误;坚持只声明一种类型。另外,在层次结构的顶部有一个通用的 exception 类,我认为这也是错误的。除此之外,一些内置类不允许抛出任何已检查的异常,例如 runnable.run() 。 java中的异常还有很多其他问题。

但请牢记这一原则,您的代码会更简洁:只有在您别无选择时才 catch

ps 这是课程的外观:


 try {
  stream.write(data);
} catch (ioexception ex) {
  ex.printstacktrace();
}