在过去的几年里,java正在以新的方式描述了一种新的、更实用的 Java 代码编写方式。为什么我们应该使用这种新的编码风格?
Java 会随着时间的推移而发展。编写 Java 代码的风格也是如此。与 2004-2006 年 Java5 和 Java6 发布之后编写的代码有很大不同。泛型和注释现在如此普遍,以至于很难想象没有它们的 Java 代码。
然后是带有 lambdas 的 Java8Stream
我认为有两个主要原因。
第一个原因是,即使是 Java 作者也对新的功能元素如何融入现有的 Java 生态系统感到不确定。要看到这种不确定性,阅读Optional
API 注释:Optional 主要用作方法返回类型,其中明确需要表示“无结果”,并且使用 null 可能会导致错误。
API 也显示了相同的内容:get()方法(可能会抛出 NPE)以及一些orElseThrow()方法的存在是对传统命令式 Java 编码风格的明确尊重。
第二个原因是现有的 Java 代码,尤其是库和框架,与函数式方法不兼容——null业务异常是惯用的 Java 代码。
快进到现在:Java 17 发布后,Java 11 正在迅速得到广泛采用,取代了几年前无处不在的 Java 8。然而,我们的代码看起来与 7 年前 Java 8 发布时几乎相同。
也许值得退一步回答另一个重要问题:我们是否需要改变编写 Java 代码的方式?它在很长一段时间内为我们提供了很好的服务,我们拥有技能、指南、最佳实践和大量教我们如何以这种方式编写代码的书籍。我们真的需要改变吗?
我相信这个问题的答案可以从另一个问题的答案中得出:我们是否需要提高开发性能?
我认为我们会。业务推动开发人员更快地交付应用程序。理想情况下,应该在业务甚至意识到实际需要实施之前编写、测试和部署我们正在处理的项目。当然,开个玩笑,但交货日期“昨天”是许多商人的梦想。
所以,我们肯定需要提高开发性能。每一个框架、IDE、方法论、设计方法等等,都专注于提高软件(当然,有必要的质量标准)的实施和部署速度。尽管如此,尽管如此,仍没有明显的开发性能突破。
当然,有许多元素定义了软件交付的速度。本文仅关注开发性能。
从我的角度来看,大多数提高开发性能的尝试都假设编写更少的代码(通常是更少的代码)自动意味着更好的性能。流行的库和框架,如 Spring、Lombok、Feign——都试图减少代码量。甚至 Kotlin 也是出于对简洁而不是 Java“冗长”的痴迷而创建的。历史确实多次证明这个假设是错误的(Perl 和 APL,也许是最显着的例子),然而,它仍然存在并推动了大部分努力。
任何开发人员都知道编写代码只是开发活动的一小部分。大多数时间我们都在阅读代码。阅读更少的代码更有效率吗?第一个意图是说yes,但实际上,代码量和它的可读性几乎没有关系。阅读和编写相同的代码往往会以心理开销的形式出现不同的“阻抗”。
这种“阻抗”差异的最好例子可能是正则表达式。正则表达式非常紧凑,并且在大多数情况下很容易编写,尤其是使用无数专用工具时。但是阅读正则表达式通常很痛苦,而且会消耗更多时间。为什么?原因是丢失的上下文. 当我们编写正则表达式时,我们知道上下文:我们想要匹配什么,应该考虑哪些情况,输入可能是什么样子,等等。表达式本身是此上下文的压缩表示。但是当我们阅读它们时,上下文会丢失,或者准确地说,是使用非常紧凑的语法进行压缩和打包。尝试从正则表达式中“解压缩”它是一项非常耗时的任务。在某些情况下,从头开始重写比尝试理解现有代码花费的时间要少得多。
上面的例子给出了一个重要的提示:减少代码量只有在上下文保持保留的情况下才有意义。一旦减少代码导致上下文丢失,它就会开始适得其反并损害开发性能。
那么,如果代码大小不是那么重要,那么我们如何才能真正提高生产力?
显然,通过保留和/或恢复丢失的上下文。但是上下文何时以及为什么会丢失?
上下文上下文是导致上下文丢失的编码实践或方法。惯用的 Java 代码有几个这样的上下文者。流行的框架通常会添加它们的上下文者。让我们来看看两个最普遍的上下文者。
可空变量是的,你没有看错。可空变量隐藏了部分上下文 - 变量值可能丢失的情况。看看这个 代码示例:
String value = service.method(parameter);
仅通过查看此代码,您无法判断是否value可以为 null。换句话说,部分上下文丢失了。要恢复它,需要查看代码service.method()并对其进行分析。导航到那个方法,阅读它的代码,返回——所有这些都是对当前任务的干扰。并且不断需要记住变量可能是null,导致心理开销。有经验的开发人员很擅长记住这些事情,但这并不意味着这种心理开销不会影响他们的开发性能。
我们总结一下:
例外可空变量是上下文者、开发性能杀手和运行时错误的来源。
惯用的 Java 使用业务异常进行错误传播和处理。有两种类型的异常 - 已检查和未检查。顺便说一下,尽管引入受检异常的最初意图是保留上下文。编译器甚至有助于保存它。尽管如此,随着时间的推移,我们已经切换到未经检查的异常。未经检查的异常是为技术错误而设计的——访问空变量、尝试访问数组边界之外的值等。
想一想:我们正在使用技术性未经检查的异常进行业务错误处理和传播。
在其设计区域之外使用语言功能会导致上下文丢失和类似于为可空变量描述的问题。甚至原因都是一样的——未经检查的异常需要导航和阅读代码(通常在调用链的深处)。它们还需要在当前任务和错误处理之间来回切换。就像可空变量一样,如果处理不当,异常可能成为运行时错误的来源。
概括:
作为上下文吞噬者的框架业务例外是上下文、开发性能杀手和错误来源。
由于框架通常特定于特定项目,因此由它们引起的问题也是特定于项目的。然而,如果你有上下文丢失/保留的想法,你可能会注意到像 Spring 和其他流行的框架,它们使用类路径扫描、“约定优于配置”习语和其他“魔法”,有意删除了大部分上下文并将其替换为默认设置的隐式知识(即心理开销)。通过这种方法,应用程序被分解为一组松散相关的类。如果没有 IDE 支持,甚至很难在组件之间导航,因此它们之间是断开的。除了丢失大部分上下文之外,还有另一个对生产力产生负面影响的重大问题:大量错误从编译时转移到运行时。
- 更多的测试是必要的。著名contextLoads()的测试就是这个问题的明显标志。
- 软件支持和维护需要更多的时间和精力。
因此,通过减少几行代码的输入,我们会遇到很多麻烦并降低开发性能。这才是“魔法”的真正代价
实用的函数式 Java 方式在务实Java功能是解决问题上面提到的一些问题的一种尝试。虽然最初的意图是通过将编码为变量类型来保留上下文,但实际使用确实显示了所采用方法的许多其他好处:
- 显着减少导航。
- 许多错误从运行时转移到编译时,这反过来又提高了可靠性并减少了必要的测试数量。
- 删除了大部分样板甚至类型声明 - 更少的输入,更少的代码阅读,业务逻辑不再因技术细节而混乱。
- 明显减少脑力开销,需要记住与当前任务无关的技术问题。



