java 示例_功能Java示例 第2部分–讲故事

java 示例

这是称为“ Functional Java by Example”的系列文章的第2部分。

我在本系列的每个部分中开发的示例是某种“提要处理程序”,用于处理文档。 在上一部分中,我从一些原始代码开始,并应用了一些重构来描述“什么”而不是“如何”。

为了帮助代码向前发展,我们需要先讲一个故事 。 这就是这个部分的来历。

如果您是第一次来这里,最好从头开始阅读。 它有助于了解我们从何处开始以及如何在整个系列中继续前进。

这些都是这些部分:

我将在每篇文章发表时更新链接。 如果您通过内容联合组织来阅读本文,请查看我博客上的原始文章。

每次代码也被推送到这个GitHub项目

作为参考,我们现在以以下代码为起点:

class FeedHandler {

  Webservice webservice
  DocumentDb documentDb

  void handle(List<Doc> changes) {

    changes
      .findAll { doc -> doc.type == 'important' }
      .each { doc ->

      try {
        def resource = webservice.create(doc)
        doc.apiId = resource.id
        doc.status = 'processed'
      } catch (e) {
        doc.status = 'failed'
        doc.error = e.message
      }
      documentDb.update(doc)
    }
  }
}

大声读出

当我最初开始使用Spock作为测试框架时,由于它是多年前Grails默认提供的,因此它的众多功能和易用性给我留下了深刻的印象(至今仍是)。

你知道什么是假人,存根和间谍吧? Mockito拥有它们,Powermock拥有它们以及基本上所有其他严肃的(单元)测试框架。 Mock的概念并不难掌握(您可以在此处阅读全部内容),但是Spock具有一种特殊的方式来描述其(与预期的)模拟交互。

关于“基于交互的测试”的一章非常出色,它解释了如何使用代码示例来记录这些交互。

“模拟”子章开头为:

模拟是描述(强制)规范下的对象与其协作者之间的交互的行为。 这是一个例子:

def "should send messages to all subscribers"() {
  when:
  publisher.send("hello")

  then:
  1 * subscriber.receive("hello")
  1 * subscriber2.receive("hello")
}

如果您不熟悉Spock,Groovy或仅仅具有上述写作风格,请不要担心!

Spock上面的文档的作者也认识到,并不是每个人都会立即理解这里发生的事情。

他们会提供一些建议,并继续提供文档:

大声读出 :“当发布者发送“ hello”消息时,两个订户都应该只收到一次该消息。”

我对“大声朗读”的重视是我的,因为我认为这很重要。 这里不讨论Spock的更多细节,而是我自己在日常编码中要牢记的建议本身。

  • 当我编写一段代码时,我可以大声读出吗?
  • 当其他人读取我的代码时,他/她可以大声读出吗?

这里的“大声” 与音量无关,而是可以用一种简洁易懂的方式描述“这里正在发生什么”。 这使得对代码的推理变得容易。

高级别与低级别

请考虑以下情形:

在不知名的城市中开车游览了几个小时以找到剧院后,汽车导航系统出现故障后,您最终决定停下来询问方向。

您在行人附近停下。

您:

先生,您碰巧知道如何从这里到达剧院

行人:

  1. 当然,这很容易。 开始了:

  2. 检查窗户以确保您具有良好的可见性。 检查后视镜以确保它们正确对齐,从而为您提供正确的道路视野。

  3. 调整座椅,使双脚舒适地到达两个踏板。

  4. 关闭视窗。

  5. 重置转向信号灯。

  6. 开始驾驶前,请松开驻车制动器。

  7. 啊,我看你有自动档。 请把变速杆放在“驱动器”中。

  8. 慢慢踩刹车,并监控仪表盘仪表。

  9. 继续前进,提高速度,监控车速表,将RPM保持在2000附近

  10. 大约120码后,在开始转向左车道之前,请先与您的方向灯指示至少两秒钟。

  11. 缓慢将汽车移至另一车道。 稍微将您的手放在方向盘上,以改变车道。 车轮只需要很小的移动即可; 因为大多数现代汽车都装有动力转向系统。 更改车道大约需要一到三秒钟。 减少一点,您做得太快了; 再也没有,你做得太慢了。

  12. 再走X步…

  13. 祝好运!

或者,考虑对话将像这样的替代宇宙:

您:

先生,您是否会知道如何从这里到达剧院?

行人:

  1. 当然,这很容易。 开始了:

  2. 左转,过桥。 在你的右边。

  3. 祝好运!

最后一种情况是轻而易举:明确指示要做什么和去哪里!

但是,第一种情况是细节缠身 -有关驾驶汽车本身的低级细节 -即使我们不希望在现实生活中得到这样的指导,我们仍然会编写这样的软件。

告诉我一些正确的内容。 如果我需要具体信息,我会要求它。

(顺便说一句wikihow.com:如何驾驶汽车,请捐赠上述说明中的一些。如果您确实需要学习驾驶,它有很多资源!)

在正确的级别上讲内容,不仅意味着使用正确命名的类和方法,而且还意味着在其中使用正确的抽象类型

让我们再次看一下我们的代码:

class FeedHandler {

  Webservice webservice
  DocumentDb documentDb

  void handle(List<Doc> changes) {

    changes
      .findAll { doc -> doc.type == 'important' }
      .each { doc ->

      try {
        def resource = webservice.create(doc)
        doc.apiId = resource.id
        doc.status = 'processed'
      } catch (e) {
        doc.status = 'failed'
        doc.error = e.message
      }
      documentDb.update(doc)
    }
  }
}

故事

我们如何在代码中结合“大声读出”和“高级还是低级”?

我们的单handle方法当前显示为什么样?

  1. 查找type -property等于字符串"important"所有文档。

  2. 呼叫createwebservice与文档,返回的资源。

  3. 如果我们有资源,请将资源的id分配给文档的apiId属性。

  4. 将文档的status属性设置为字符串"processed"

  5. 如果发生异常,请将文档的status属性设置为字符串"failed" 将文档的status属性设置为来自异常的message

  6. 最后,使用documentDb在documentDb上调用update

基本上,这只是重复代码语句!

什么故事,我想告诉取而代之的,是以下情况:

  1. 通过Web服务“创建资源”来处理“重要”文档。

  2. 每次成功时,将两者关联在一起并“将文档标记为已处理”,否则将其标记为“失败”。

读得很好,你不觉得吗?

实际上,我们可以通过在IDE中使用几种“提取方法”重构并为提取的方法选择一些好的名称来实现这一目标。

上面故事中用双引号引起的短语是我想在高层看到的重要部分。

“重要”

我为什么要关心文档使用什么属性来确定其重要性? 现在是字符串"important" ,它表示“嘿,我很重要!” 但是如果条件变得更加复杂怎么办?

doc.type == 'important'提取到其自身的方法isImportant

changes
    .findAll { doc -> isImportant(doc) }
    // ...

  private boolean isImportant(doc) {
    doc.type == 'important'
  }

“创造资源”

为什么我在这里关心如何在Web服务中调用什么方法? 我只想创建一个资源。

将与Web服务的所有处理都提取到它自己的方法(称为createResource

def resource = createResource(doc)
  // ...

  private Resource createResource(doc) {
    webservice.create(doc)
  }

“更新为已处理”

提取将资源/文档/将状态设置为其自己的方法(称为updateToProcessed

updateToProcessed(doc, resource)
  // ...

  private void updateToProcessed(doc, resource) {
    doc.apiId = resource.id
    doc.status = 'processed'
  }

“更新失败”

不在乎细节。 提取到updateToFailed

updateToFailed(doc, e)
  // ...

  private void updateToFailed(doc, e) {
    doc.status = 'failed'
    doc.error = e.message
  }

似乎我们最后只剩下了documentDb.update(doc)

这是在数据库中存储已处理/失败文档的一部分,我已经在最高级别上进行了描述。

我将其放在每个刚刚创建的updateTo*方法中-一个较低的级别。

private void updateToProcessed(doc, resource) {
    doc.apiId = resource.id
    doc.status = 'processed'
    documentDb.update(doc)
  }

  private void updateToFailed(doc, e) {
    doc.status = 'failed'
    doc.error = e.message
    documentDb.update(doc)
  }

那么,提取出细节之后,有什么变化?

void handle(List<Doc> changes) {

    changes
      .findAll { doc -> isImportant(doc) }
      .each { doc ->

      try {
        def resource = createResource(doc)
        updateToProcessed(doc, resource)
      } catch (e) {
        updateToFailed(doc, e)
      }
    }
  }

任何人(例如,同事,您未来的自我)都会“一口气”地读一读,将了解30,000英尺的行程。

如果您需要任何这些步骤的详细信息,只需深入了解该方法即可。

能够写声明式的东西(本系列的前一部分)并在正确的水平上讲故事(本部分)还将有助于在第3部分及以后的部分中更轻松地进行将来的更改。

现在就这样

作为参考,这是重构代码的完整版本。

class FeedHandler {

  Webservice webservice
  DocumentDb documentDb

  void handle(List<Doc> changes) {

    changes
      .findAll { doc -> isImportant(doc) }
      .each { doc ->

      try {
        def resource = createResource(doc)
        updateToProcessed(doc, resource)
      } catch (e) {
        updateToFailed(doc, e)
      }
    }
  }

  private Resource createResource(doc) {
    webservice.create(doc)
  }

  private boolean isImportant(doc) {
    doc.type == 'important'
  }

  private void updateToProcessed(doc, resource) {
    doc.apiId = resource.id
    doc.status = 'processed'
    documentDb.update(doc)
  }

  private void updateToFailed(doc, e) {
    doc.status = 'failed'
    doc.error = e.message
    documentDb.update(doc)
  }

}

翻译自: https://www.javacodegeeks.com/2017/11/functional-java-example-part-2-tell-story.html

java 示例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值