如何遵循 Effective Java 建议?

How to follow Effective Java advice?

我读完了 Effective Java 这本书,读完之后我感到很困惑。在这本书中,Bloch 坚持减少可变性,使字段最终化,拒绝 public 构造函数并支持工厂或构建器模式,使用最终 类 和方法等禁用继承

但现在我正在开发一个基于 Spring 框架的项目,并且:

  1. 所有实体必须有默认构造函数(Spring Data JPA)
  2. 所有服务都应该是非最终的以模拟它们
  3. 所有 类 必须是非最终的才有机会被告知(Spring AOP,AspectJ)

乍一看,这本书的大部分内容似乎都违反了 AOP,嘲笑和 Spring Data/Hibernate。

我该如何处理?书在实践中没用吗?

有效 Java 描述了在可能的情况下最好遵循的一般最佳实践。但它考虑的是纯粹的 java,而不是任何框架特性。

框架定义了项目的架构,您应该遵循这些规则。该框架有自己的最佳实践。

不可变对象很好,因为它们本质上是线程安全的。它们的不变量由构造函数建立,如果它们的状态不能改变,这些不变量总是成立的。但是没有严格的规定每个对象都应该是不可变的,有时在给定任务的范围内是不可能的。

构建器和工厂模式仍然很好,可以在 Spring project 的范围内使用。我在实际项目中同时使用了 Spring 依赖项和 Factory 模式,因为 Factory 仍然允许您 @Autowire 对象。

作为一个整体示例,我在 Spring 项目中使用了 Spark 函数。其中一些功能是 @Autowire Spring 服务。但功能本身并不是 Spring 服务。您不能通过 new Function() 创建它们,因为那样 Spring 将无法 autowire 服务。但是有了 Factory,您可以帮助 Spring 做到这一点。

有许多好的设计原则,例如 SOLID, DRY, KISS、设计模式,它们通常很有帮助,可以让您更好地组织代码。但有时,在现实生活中,你只是无法将所有这些应用到你的特定案例中。这里的主要规则是您不应该将任何最佳实践绝对化,而是在实现最终目标应用最佳实践之间找到一个中间地带。

这里有几个维度需要考虑:

  • 有时来自不同来源的最佳实践不能很好地结合在一起。但这始终是一个很好的最佳实践......遵循最佳实践(不要让你的读者感到惊讶)。意思是:当 Spring 与所有这些 "guidelines" 一起出现时,您就跟随它们。即使这意味着违反 "more generic" 最佳实践。
  • 另请记住,技术 限制通常会影响您作为开发人员的能力。如:Java 原样。所以,当一个框架想要使用默认构造函数创建 + 填充对象,然后反射时……好吧,这就像你在 Java 中唯一的选择。其他语言可能使您能够以更简洁的方式执行此类操作,但在 Java 中,如前所述:您的选择非常有限。

因此,最好的做法是:把"best practices"看成围绕同一个中心点绘制的多个圆圈。您首先关注那些与您正在使用的技术直接相关的内容(比如 Spring)。然后你可以检查"what else is there",你试着遵循这些想法。但是最内圈强调的东西总是吹嘘来自"further outside".

的东西

这是一个很好的问题,但根据我的说法,并不是所有的 "best practices" 都可以在同一个项目中被跟踪,实际上你需要做的是尽可能地跟踪它们,但是当有没有选择我认为你应该首先考虑实现你的项目想要实现的目标。

事实上,我一直在遵循 google 团队的 android 最佳实践,但自 iO2018 活动以来,大多数先前的最佳实践被称为 "bad practice"但这并不意味着演讲者没有用,它只是时间和条件发生了变化。

所以我的建议是您遵循适用于您当前项目的最佳实践并忽略那些这会限制您实现预期目标

设计是达到目的的手段,设计模式是常见问题的标准解决方案。

设计书籍不应该读成"always to do this",而是读成"this is a standard solution for this problem"。如果你没有问题,你就不需要解决它。有时您的特定上下文可能允许更简单或更好的解决方案。

让我们看看为什么 Joshua Bloch 推荐这些项目,好吗? (抱歉偶尔的不准确,我是凭记忆转述的)

decreasing mutability, making fields final

不可变状态是引用透明的,因此更容易推理。它本身也是线程安全的。

...但是数据库保存可变数据。因此,数据库应用程序必须处理可变数据,表达这一点的最清晰方法是使用可变对象。

为了帮助处理并发变化,数据库通过事务保护应用程序免受并发更改。

也就是说,不可变对象和事务范围对象是针对并发突变推理问题的不同解决方案。

refuse default constructors

在处理对象时,我们通常希望它们被完全初始化。

我们可以通过编写初始化对象的构造函数来强制执行此操作。

...但是数据库中保存的状态已经初始化。通过提供默认构造函数,框架可以在绕过初始化的同时重新创建对象。

也就是说,我们可以通过在构造函数中初始化对象或让框架重新创建已初始化的对象来确保对象被初始化。

(顺便说一句,复杂的 JPA 实体通常使用这两种方法:它们有一个 public 构造函数用于初始化,还有一个包可见的构造函数用于重新创建)

disabling inheritance

在编写库时,您需要能够在不破坏客户端的情况下更改代码,甚至可能需要防止恶意客户端破坏您的代码。除非仔细管理,否则继承会干扰两者。

当您编写应用程序时,您的代码通常不会被其他团队转class,因此在更改超级 class 时很容易更改子 classes。

也就是说,您可以通过阻止 subclassing 来防止 super class 更改破坏 sub classes,方法是将您自己限制在 superclass 更改可以不破坏 subclasses,或者在它们破坏时改变 subclasses。

How should I deal with it? Is the book useless in practice?

通过考虑这些常见问题的标准解决方案,当您遇到这些问题时。