Java POJO 是否应该在 setter 方法中进行字段验证并抛出异常?

Should Java POJO have field validation and throw exceptions in setter methods?

假设我们有数十个 java POJO,它们代表我的领域,也就是说,我在系统中的数据作为对象在我系统的不同层之间流动。该系统可以是一个网络应用程序,也可以是一个简单的桌面应用程序。域由什么组成并不重要。

在设计我的系统时,我很困惑应该在哪里放置任何验证逻辑。我的 POJO(域对象)代表我的数据,这些对象中的一些字段必须遵守特定的标准,但是如果我在我的 setter 方法中放置大量验证逻辑,那么唯一的方法就是告诉调用客户端将抛出异常。如果我不希望系统崩溃,则异常必须是必须捕获和处理的已检查异常。这样做的结果是,每次我使用 setter 方法(甚至构造函数)创建一个新对象时,我都会重新抛出该异常或使用 try-catch 块。被迫在许多 setter 方法上使用 try-catch 感觉不对。

所以问题是我应该在哪里放置我的验证逻辑,这样我就不会用大量样板 try-catch 块和重新抛出使我的代码混乱。欢迎最优秀的JAVA字节吃货加入讨论

我进行了研究和谷歌搜索,但没有找到关于这个主题的任何具体讨论,所以我怀着极大的热情等待着更深入地了解事情到底应该如何完成。

你说的时候可能已经回答了你自己的问题

some of the fields inside of those objects must adhere to certain criteria

考虑系统中的不变性总是有帮助的,即您想要不惜一切代价维护的东西或必须始终遵守的规则。
您的 POJO 是数据对象上此类不变量的 "last line of defence",因此是放置验证逻辑的适当(甚至必要)位置。如果没有这样的验证,一个对象可能不再代表在您的域中有意义的东西。

这些系统不变量在您的对象(或方法)与其 "clients" 之间形成契约。如果有人试图违反(希望有详细记录的)合同使用它们,抛出异常是正确的做法,因为正确使用系统的各个部分是客户的责任。

随着时间的推移,对于任何违反合同的情况,我开始偏爱未经检查的异常而不是已检查的异常,部分原因是您提到的原因,即避免 try-catch无处不在.
Java 的标准未检查异常包括:

  • 空指针异常
  • IllegalArgumentException
  • IllegalStateException
  • UnsupportedOperationException

最佳实践指南是在错误被认为是可恢复未检查异常[=时使用已检查异常 45=] 否则。

Joshua Bloch 的“Effective Java, 2nd ed.”第 9 章提供了关于此主题的更多智慧:

  • 第 57 条:仅在特殊情况下使用例外
  • 项目 58:对可恢复条件使用检查异常,对编程错误使用运行时异常
  • 第 59 条:避免不必要地使用已检查的异常
  • 第 60 条:赞成使用标准例外

None 以上应该阻止您在更高级别使用任何适当的验证逻辑,尤其是强制执行任何特定于上下文的业务规则或约束。

总而言之,我也不认为有适合所有需求的独特解决方案,这取决于您的场景和偏好。

从封装的角度来看,我认为 setter 验证是正确的方法,因为它是确定所提供信息是否正确并提供详细解释的合乎逻辑的地方。错误的。但是我不确定你的意思:

And If I don't want the system to crash, the exception must be a checked exception...

为什么系统会崩溃?未经检查的异常可以像检查的异常一样被很好地捕获。您需要弄清楚当此类事件发生时您的程序应该如何表现,以便您可以决定在哪里捕捉它们以及做什么。

Checked 与 Unchecked 一直以来,并且仍然以各种方式和信念进行辩论,但我看不出没有理由不抛出 unchecked 异常。只需创建一个通用的 ConfigurationException(或使用已经存在的 IllegalArgumentException)或任何使您的船漂浮的东西,相应地标记您的方法签名并添加适当的 java-docs,以便调用它们的人知道会发生什么, 并在需要时抛出它。

根据您的对象关系和层次结构,另一种解决方案可能是一些 builders 您在创建实例时自定义验证的 运行。但就像我说的,这真的取决于场景,你可能无法阻止其他人手动实例化和错误填充某些对象。

在某些情况下,解决方案是将所有 setter 设为私有并提供用于初始化对象的单一入口点。然后所有的验证和异常处理都在那个初始化方法中。
这也确保没有对象可以被部分初始化。
对象状态改变也可以通过这种方式处理,通过使对象不可变除了通过构造函数/初始化方法,如果滥用,这可能会变得浪费。

虽然您可以将验证逻辑放在 bean setter 方法上,但我发现在实践中,更清晰的关注点分离是将任何复杂的验证移动到另一个 class 特定的验证。听我说完。

使用 bean 验证很好,但在许多情况下(我相信您现在已经遇到过),一个简单的注释将无法进行足够彻底的验证。像 Spring 这样的框架有 Validator 可以实现它。

分离验证逻辑有很多好处:

  • 代码重用。在某些情况下,一个 bean 的 setter 验证可能 与另一个 bean 相同。这也促进了更简洁的单元测试。
  • 在很多情况下,您根本不会处理异常,但是 而是向用户显示错误消息
  • 您可以适当地记录日志,而无需使用日志语句填充 POJO 或强制它们实现记录器
  • 代码可读性更强,意图更容易理解

希望对您有所帮助。