21CTO导读:
命令式编程:一种软件开发范例,其中解决问题所需的每个步骤中的函数都隐式编码。
声明式编程:一种抽象软件执行操作所需逻辑的控制流的方法。相反,它涉及说明任务或期望结果是什么。
声明式编程是一种特殊的魔法:你只需描述自己想要的逻辑,它就会发生。你无需担心执行的细节,编译器会解决它。
你的代码现在会变得简单而优雅。它可以轻松被其他程序员阅读、审查或贡献。只要确保你正确地指定了最小逻辑,剩下的就很简单了。这绝对是切片面包以来最好的东西,对不对?
嗯……也不完全是,或者至少——不总是。当你只解释要做什么时,“如何”仍然是一个谜,程序员应该害怕有其它谜团。
stream.mapToLong(Long::longValue).sum()
stream.reduce(0L, Long::sum)0L, Long::sum)
stream.collect(Collectors.summingLong(Long::longValue))
final LongAdder longAdder = new LongAdder();
stream.forEach(longAdder::add);
很重要的是,需要对代码进行基准测试。但是,没有样板代码的问题在于你无法对其进行优化。
如果性能不够好,你可以尝试不同的流操作或从另一个角度解决问题——但你无法改变迭代的执行方式。
来玩一个小游戏。我将对以下情况进行基准测试,看看你是否能猜出哪一个更有效率。结果在页面底部。
private static final long MIN_VALUE = 0L;
private static final long MAX_VALUE = 10_000L;
...
// With or without "parallel" (simple calculation)?
final long streamNoParallelResult = LongStream.range(MIN_VALUE, MAX_VALUE).sum();
final long streamParallelResult = LongStream.range(MIN_VALUE, MAX_VALUE).parallel().sum();
// Assume both list have the exact same elements as the range above
// ArrayList or LinkedList? map or reduce?
final long arrayListResult = arrayList.stream().mapToLong(Long::longValue).sum();
final long linkedListResult = linkedList.stream().mapToLong(Long::longValue).sum();
final long linkedListReduceResult = linkedList.stream().reduce(0L, Long::sum);
// With or without "parallel" (complicated calculation)?
final long arrayListParallelHeavyResult = arrayList
.parallelStream()
.map(this::heavyOperation)
.mapToLong(Long::longValue)
.sum();
final long linkedListParallelHeavyResult = linkedList=
.parallelStream()
.map(this::heavyOperation)
.mapToLong(Long::longValue)
.sum();
final long arrayListHeavyResult = arrayList
.stream()
.map(this::heavyOperation)
.mapToLong(Long::longValue)
.sum();
剧透:使用stream.range是非常高效的;即使没有并行性,ArrayList 也比 LinkedList 更好(这并不奇怪,因为 ArrayList 使用连续内存,这对于读取和缓存非常有用);除非你的任务繁重,否则不应使用 parallelStream,并且 Stream
同样值得一提的是,此基准测试是按顺序执行的,因此在实际应用中,其他线程可能不太可用,从而使并行性的好处不那么明显。
在基准测试中,我们发现即使是非常小的变化,比如单个单词并行,性能也会发生巨大变化。程序员和审阅者都很容易忽略它(它的存在或缺失),这使得流有点性能风险。
但是,代码的某些部分对单元测试等性能问题不太敏感。没有人关心它们的执行方式,只要它们最终是绿色的(并且不会将 CI/CD 延迟太久)。认真地问一下我的审阅者,他们可以毫无理由地流式传输 TreeList ,或者使用可能在将来使用Bogosort实现的排序方法的神秘调用。只要它清晰并正确涵盖所有情况,那就没有问题。
我并不指望你们中的任何人会因为低级语言的必要性而迁移你们基于 Java 的现代微服务或基于 Python 的机器学习库,但在很多情况下,你们至少应该尝试了解底层工作原理。
一层又一层的过甜的语法糖会让你的味蕾和编程技巧变得迟钝。不考虑后果就轻率地使用比康定斯基更抽象的概念是危险的。
声明式编程非常适合清晰地传达想法,或者对于你不关心如何执行的代码部分,无论是因为性能不是问题,还是执行逻辑由外部库等另一方管理,都是一个不错的选择。
对于你希望完全控制的代码部分,命令式编程是更好的选择。如果您希望能够修改或优化完成事情的方式 - 你应该自己动手。即使声明式方式的当前行为看起来不错,你也永远不知道下一次代码更改会导致什么,更不用说 - 一旦负责“如何”的工具更新,即使是未更改的代码的性能也可能会发生变化。
声明式编程并不完美。还有其他方法可以表达相同的想法,这可能会影响性能。如果你最终努力优化声明,例如许多人对 SQL 查询所做的那样,我们的技术可能并不像您想象的那样具有声明性。也许下一代声明式编程将包含一个 NLP 模型,该模型可以理解我们的最终目标并根据其对内部实现的熟悉程度为其选择最佳解决方案。那真的会很神奇。
作者:场长
本文为 @ 场长 创作并授权 21CTO 发布,未经许可,请勿转载。
内容授权事宜请您联系 webmaster@21cto.com或关注 21CTO 公众号。
该文观点仅代表作者本人,21CTO 平台仅提供信息存储空间服务。