在哪里用反应式编程绘制线

时间:2016-03-13 18:28:20

标签: system.reactive rx-java rx-android

我在我的项目中使用RxJava大约一年了。 随着时间的推移,我变得非常喜欢它 - 现在我想的可能太多了......

我现在写的大多数方法都有某种形式的Rx,这很棒! (直到它不是)。 我现在注意到一些方法需要大量工作来组合不同的可观察生产方法。 我觉得虽然我理解我现在写的东西,但下一个程序员将​​很难理解我的代码。

在我到达底线之前,让我直接从我在Kotlin的代码中给出一个例子(不要深入研究它):

private fun <T : Entity> getCachedEntities(
      getManyFunc: () -> Observable<Timestamped<List<T>>>,
      getFromNetwork: () -> Observable<ListResult<T>>,
      getFunc: (String) -> Observable<Timestamped<T>>,
      insertFunc: (T) -> Unit,
      updateFunc: (T) -> Unit,
      deleteFunc: (String) -> Unit)
      = concat(
      getManyFunc().filter { isNew(it.timestampMillis) }
          .map { ListResult(it.value, "") },
      getFromNetwork().doOnNext {
        syncWithStorage(it.entities, getFunc, insertFunc, updateFunc, deleteFunc)
      }).first()
      .onErrorResumeNext { e ->  // If a network error occurred, return the cached data and the error
        concat(getManyFunc().map { ListResult(it.value, "") }, error(e))
      }

简要说明这是做什么的:

  • 从存储中检索一些带时间戳的数据
    • 如果数据不是新的,则从网络获取数据
      • 再次与存储同步网络数据(进行更新)
    • 如果发生网络错误,请再次检索旧数据和错误

这是我的实际问题: 反应式编程提供了一些非常强大的概念。但正如我们所知with great power comes great responsibility

我们在哪里划线?是否可以用令人敬畏的反应性oneliners来填充我们的整个程序,还是我们应该只为真正平凡的操作保存它?

显然这是非常主观的,但我希望有经验的人可以分享他的知识和陷阱。 让我更好地说出来

  

如何将我的代码设计为具有反应性且易于阅读?

2 个答案:

答案 0 :(得分:15)

当你拿起Rx时,它变成了这个令人敬畏的闪亮hammer,一切都开始看起来就像一块生锈的钉子,等着你砰砰直跳。

就个人而言,我认为最大的线索就是名称​​ reactive 框架。鉴于需求,您需要反思反应性解决方案是否真正有意义。

在任何Rx提议中,您都希望引入一个或多个事件流并执行一些操作以响应事件。

我认为有两个关键问题要问:

  • 您是否掌控了事件流?
  • 您必须以什么程度完成事件流的回复?

如果 无法控制事件流, 必须 以事件流然后Rx是一个很好的候选人。

在任何其他情况下,这可能是一个糟糕的选择。

我见过许多例子,人们为了证明Rx的合理性而跳出篮球来创造一种缺乏控制的错觉 - 这对我来说似乎很疯狂。为什么要放弃你拥有的控制权?

一些例子:

  1. 您必须从固定的文件列表中提取数据并将其存储在数据库中。您决定将每个文件名推送到主题中,并创建一个反应管道,打开每个文件并投影数据,然后以某种方式处理数据,最后将其写入数据库。

    这未通过控制测试和速率测试。迭代文件并它们并尽可能快地处理它们会容易得多。短语&#34;决定推动&#34;这是赠品。

  2. 您需要显示证券交易所的股票价格。

    显然,这是Rx的不错选择。如果你不能跟上一般的价格水平,那你就搞砸了。 可能是你将价格混为一谈的情况(也许只是为了每秒提供一次更新) - 但这仍然符合保持价格的要求。你不能做的一件事就是要求证券交易所放慢速度。

  3. 这些(现实世界)的例子几乎落在光谱的两端,并没有太多的灰色区域。但是那里有很多灰色区域,控制并不清晰。

    有时候你在客户端/服务器系统中戴着客户端帽子,很容易陷入牺牲控制或将控制放在错误位置的陷阱 - 这可以通过正确的设计轻松修复。考虑一下:

    1. 客户端应用程序显示来自服务器的新闻更新。

      • 新闻更新随时会提交给服务器,并以大批量创建。
      • 客户端应按客户端设置的时间间隔刷新。
      • 可以随时更改刷新间隔,用户可以随时请求立即刷新。
      • 客户端仅显示标记有特定关键字的更新,具体由用户指定。
      • 新闻更新有时很长,客户不应存储新闻更新的全部内容,而应显示标题和摘要。
      • 根据用户请求,可以显示文章的完整内容。
    2. 此处,新闻更新的频率不受客户控制。但是所需的刷新率和感兴趣的标签是。

      客户端收到所有新闻更新并过滤后,客户端无法正常工作。但是有很多选择:

      • 服务器是否应该考虑客户端刷新率发送更新数据流?如果客户端下线怎么办?
      • 如果有数以千计的客户怎么办?如果客户希望立即刷新怎么办?

      有许多有效的方法可以解决这个问题,包括或多或少的反应元素。但任何好的解决方案都应该考虑客户对标签的控制和所需的刷新率,以及缺乏对新闻更新频率的控制(通过客户端或服务器)。您可能希望服务器通过更新它推送到客户端的事件来响应以更改客户端感兴趣的更改 - 只要客户端正在侦听(通过心跳检测到),它就会推送它。当用户想要一篇完整的文章时,客户端会文章。

      Rx界有很多关于背压的争论。这是客户端在超载时通知服务器并且服务器以某种方式响应减少事件流的想法。我认为这是一种误导的方法,可能导致设计混乱。

      在我看来,一旦客户需要提供此反馈,它就无法通过响应率测试。此时,您处于被动状态,您处于 async enumerable 状态。即客户应该说&#34;我准备好&#34;当它准备好更多,然后以非阻塞的方式等待服务器响应。

      如果第一个场景被修改为到达drop-folder的文件,这将是合适的,具有不同的长度和复杂性来处理。客户端应对下一个文件进行非阻塞调用,处理它并重复。 (根据需要添加并行性) - 而不是响应文件到达事件流。

      总结

      我故意避免其他有效的问题,例如代码的可维护性,Rx本身的性能等。大多数是因为它们在其他地方得到解决,更重要的是因为我认为这里的想法比那些问题更具分裂性。

      因此,如果您在场景中反映控制响应率的元素,您可能会保持正确的轨道。

      回复率问题可能很微妙 - 方面很重要。到达率可能会波动,并且回复率会出现一些可接受的波动程度 - 显然,如果你最终没有办法赶上&#34;赶上&#34;然后在某些时候客户会爆炸。

答案 1 :(得分:3)

我发现在编写Rx(或任何稍微复杂/新技术)时我会记住两件事

  1. 我可以测试一下吗?
  2. 我可以轻松雇用可以维护它的人。没有努力维持它,但保持它会好好保留吗?
  3. 为此,我也发现,只因为你可以,并不总是意味着你应该。作为指南,我尝试避免创建超过7行代码的查询。查询大于此,我尝试分离我编写的子查询。

    如果您提供的代码是代码库的核心,并且处于复杂性的极端,那么它可能没问题。但是,如果您发现所有Rx代码都具有很高的复杂性,那么您可能会难以使用代码库。