【问题标题】:In java streams using .peek() is regarded as to be used for debugging purposes only, would logging be considered as debugging? [duplicate]在使用 .peek() 的 java 流中被认为仅用于调试目的,日志记录是否被视为调试? [复制]
【发布时间】:2019-06-21 02:48:33
【问题描述】:

所以我有一个对象列表,我希望处理部分或全部对象,并且我想记录那些已处理的对象。

考虑一个虚构的例子:

List<ClassInSchool> classes;
classes
.stream()
.filter(verifyClassInSixthGrade())
.filter(classHasNoClassRoom())
.peek(classInSchool -> log.debug("Processing classroom {} in sixth grade without classroom.", classInSchool)
.forEach(findMatchingClassRoomIfAvailable());

在这种情况下使用 .peek() 是否会被视为对 API 的意外使用?

为了进一步解释,this question 中的关键要点是:“不要以非预期的方式使用 API,即使它实现了您的近期目标。”我的问题是是否每次使用 peek,从调试您的流直到您验证整个链按设计工作并再次删除 .peek(),都是意外使用。因此,如果将其用作记录流实际处理的每个对象的一种方式,则被视为非预期用途。

【问题讨论】:

  • 不,根据 API 文档,选择的答案不清楚什么是无意使用 API,这是我的确切问题。在这种情况下,日志记录是否考虑调试,或者在构建实际流链时,peek 的使用实际上仅限于临时代码。

标签: java java-8 java-stream


【解决方案1】:

documentation of peek 将意图描述为

此方法的存在主要是为了支持调试,您希望在元素流过管道中的某个点时查看它们。

.peek(classInSchool -&gt; log.debug("Processing classroom {} in sixth grade without classroom.", classInSchool) 形式的表达式实现了这一意图,因为它是关于报告元素的处理。无论您是使用日志框架还是只打印语句都没有关系,如文档的示例.peek(e -&gt; System.out.println("Filtered value: " + e))。在任何一种情况下,意图都很重要,而不是技术方法。如果有人使用 peek打印所有元素,那将是错误的,即使它使用与文档示例相同的技术方法 (System.out.println)。

该文档不要求您必须区分生产环境或调试环境,以删除前者的 peek 用法。实际上,您的使用甚至可以实现这一点,因为日志框架允许您通过可配置的日志级别来静音该操作。

我仍然建议记住,对于某些管道,插入peek 操作可能会插入比实际操作更多的开销(或在一定程度上阻碍 JVM 的循环优化)。但是,如果您没有遇到性能问题,您可以按照旧的建议不要尝试优化,除非您有真正的理由……

【讨论】:

  • 因为我只希望记录整个链处理的对象 - 答案之一下的 OP 评论;在这种情况下依赖peek 是错误的
  • @Eugene 我有感觉,你过度解释了那个评论。对我来说,看起来 OP 试图说他可以只记录那些实际处理的元素。
  • 可能是。我的意思是 整个链 对我来说是终端操作的全部,使用 peek 可以记录更多,对吧?
  • @Eugene 不能保证最终消费者的代码运行完成,但日志消息的语法表明它打算在启动时记录。由于通过这一点的所有元素都旨在传递到下一个处理状态,即最终消费者,这可能是正确/预期的语义,即使在意外完成的情况下,尤其是在 forEach 的情况下,其中只有例外才能引起这种情况。
  • 啊!好点,您从问题中获取的代码比我更接近。我只是认为这是一些例子,而不是 OP 正在处理的真实情况。
【解决方案2】:

应避免 Peek,因为某些终端操作可能不会被调用,请参阅 this answer。在该示例中,在forEach 的操作中进行日志记录可能比使用peek 更好。在这种情况下调试意味着用于修复错误或诊断问题的临时代码。

【讨论】:

  • @tterag 如果我正确理解终端操作的工作原理,它不会调用 .peek() 以防流中的该对象不会调用 .forEach() ,这实际上会产生更多感觉。在那种情况下,我不会记录未处理的对象。
  • 问题在于终端操作在后台是如何工作的。由于未定义此行为,因此可以随时更改。通过终端操作员记录对象是执行您的示例尝试执行的操作的最可靠方法。
【解决方案3】:

在使用.peek()的java流中被认为仅用于调试目的,日志记录是否被视为调试?

这取决于您的日志记录代码是否会永久固定在您的代码中。

只有你才能真正知道你的日志记录的真正目的......

还要注意javadoc 说:

如果流实现能够优化部分或全部元素的生成(例如使用 findFirst 之类的短路操作,或在 count() 中描述的示例中),则该操作不会为这些元素调用。

因此,您可能会发现在某些情况下,peek 不是记录(或调试)管道的可靠方式。

一般来说,添加 peek 可能会改变管道的行为和/或 JVM 优化它的能力......在当前或未来一代的 JVM 中。

【讨论】:

  • 在我看来,短路操作不会将我的登录设置为短,因为我只希望记录整个链处理的对象。我对 400 条实际上掩盖发生的事情的日志不感兴趣,因为终止操作可能只处理了 40 条。如果这就是不使用 .peek() 的原因,那么按照这种逻辑,您也不应该使用 .map(),因为您可能不会使用终止操作,这样 .map() 就不会处理任何对象...跨度>
  • @Jonas 如果你想要整个链 - 在终端操作中进行。如果不能,请在终端操作的结果上执行。 peek 只是为了帮助您了解您的流管道并正确设置它,仅此而已。
  • @Jonas - 你可以随意忽略你从不同人那里得到的建议。 (但如果你打算这样做......为什么要征求意见?)
【解决方案4】:

嗯,这在某种程度上可以解释。意图并不总是容易确定的。

我认为添加 API 注释主要是为了阻止过度使用 peek,因为几乎所有理想的行为都可以在没有它的情况下完成。完全排除它对开发人员来说太有用了,但他们想明确的是,它的包含不应被视为无条件的认可;他们看到了滥用的可能性,并试图解决它。

我怀疑——尽管我只是推测——对于是否完全包含它存在不同的意见,并且在 JavaDoc 中包含一个带有警告的版本是一种妥协。

考虑到这一点,我认为我决定何时使用peek 的建议很简单:除非你有充分的理由,否则不要使用它。

在您的情况下,您绝对没有充分的理由这样做。您正在遍历所有内容并将结果传递给方法findMatchingClassRoomIfAvailable(嗯,大概-您的示例不是很好)。如果您想为流中的每个项目记录一些内容,那么只需将其记录在该方法的顶部即可。

是误用吗?我不这么认为。我会这样写吗?没有。

【讨论】:

    猜你喜欢
    • 2016-02-11
    • 2014-07-02
    • 1970-01-01
    • 2014-02-28
    • 1970-01-01
    • 2022-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多