如何在响应式编程中划定界限

8
我已经在我的项目中使用RxJava一年左右了。 随着时间的推移,我越来越喜欢它 - 现在我开始认为可能是太喜欢了...
现在,我注意到有些方法需要大量工作来组合不同的可观察方法。 我感觉虽然我现在理解自己写的东西,但下一个程序员很难理解我的代码。
在我说到重点之前,让我直接给出一个来自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))
      }

简单来说,它的作用是:
- 从存储中检索一些带有时间戳的数据 - 如果数据不是最新的,则从网络中获取数据
- 同步网络数据与存储(以更新数据)
- 如果出现网络错误,则再次检索旧数据和错误信息
我的实际问题是:
响应式编程提供了一些非常强大的概念。但是我们知道“伟大的力量伴随着巨大的责任”。
我们该如何把握度?是否可以在整个程序中填充令人惊叹的响应式一行代码,或者只在真正平凡的操作中使用它?
显然这很主观,但我希望有更多经验的人分享他的知识和缺点。
让我更好地表达它:
如何设计反应性代码以使其易于阅读?
2个回答

15
当你使用Rx时,它会变成一个华丽闪亮的hammer,而所有事情都看起来像是等待你敲打的生锈钉子。
个人认为,最大的线索在于名称,即“反应式”框架。在满足需求时,您需要思考是否真正需要使用反应式解决方案。
在任何Rx提议中,您都要引入一个或多个事件流,并对事件做出一些响应。
我认为有两个关键问题需要问:
- 你是否控制事件流? - 在多大程度上必须按照事件流的速率完成响应?
如果您没有控制事件流并且必须以事件流的速率响应,则Rx是一个很好的选择。
在任何其他情况下,它可能是一个不良的选择。
我见过很多例子,人们为了证明Rx的合理性而费尽心思地创造了缺乏控制的幻象——这对我来说似乎很疯狂。为什么要放弃你已经拥有的控制权呢?
一些例子:
您需要从一系列固定的文件中提取数据并将其存储在数据库中。您决定将每个文件名推入一个主题,并创建一个反应式管道,打开每个文件并投影数据,然后以某种方式处理数据,最后将其写入数据库。这未通过控制测试和速率测试。更容易的方法是迭代文件并尽可能快地处理它们。这里“决定推”这个短语是暴露问题的关键。
您需要显示来自股票交易所的股票价格。显然,这是使用 Rx 的好选择。如果您不能跟上价格的普遍速度,那么您就完了。也许您会混淆价格(例如仅在每秒钟提供更新),但这仍然有资格保持同步。您唯一不能做的事情就是要求股票交易所放慢速度。
这些(现实世界的)示例几乎处于光谱的两端,并没有太多的灰色地带。但是在控制不清楚的地方还有很多灰色地带。
有时候在客户端/服务器系统中,你会戴上客户帽子,很容易陷入牺牲控制或将控制放错位置的陷阱——这可以通过正确的设计轻松解决。请考虑以下内容:
3. 客户端应用程序显示来自服务器的新闻更新。
* 新闻更新可以随时提交到服务器并且数量很大。 * 客户端应该按照客户端设置的时间间隔进行刷新。 * 刷新间隔可以随时更改,用户始终可以请求立即刷新。 * 客户端只显示由用户指定的特定关键字标记的更新。 * 新闻更新有时很长,客户端不应存储完整的新闻内容,而是显示标题和摘要。 * 在用户请求时,可以显示文章的完整内容。
在这里,新闻更新的频率不受客户端的控制。但期望的刷新率和感兴趣的标签是可控的。
客户端接收所有新闻更新并在客户端进行过滤是行不通的。但有很多选择:
  • 服务器是否应该发送考虑到客户端刷新速率的数据流更新?如果客户端离线会怎样?
  • 如果有成千上万个客户端怎么办?如果客户端希望立即刷新怎么办?

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

Rx社区对于反压存在很多争议。这是客户端应通知服务器何时过载,服务器通过某种方式减少事件流的思想。我认为这是一种误导人的方法,可能导致混淆的设计。

在我看来,只要客户需要提供这种反馈,就意味着它未通过响应率测试。此时,您不处于反应式情况,而是处于异步可枚举的情况下。也就是说,当客户准备好了并且等待服务器响应时,客户应该说“我已经准备好了”。
如果第一个场景被修改为到达一个文件夹中的文件,其长度和处理复杂度各不相同,那么这将是合适的。客户端应该对下一个文件进行非阻塞调用,处理它,并重复执行。(根据需要添加并行性)-而不是响应文件到达事件的流。
总结
我特意避免了其他有效的关注点,例如代码的可维护性、Rx本身的性能等。主要是因为它们在其他地方得到了解决,更重要的是我认为这里的想法比这些问题更具争议性。
因此,如果您反思场景中的控制和响应速率元素,您可能会保持正确的轨道。

响应率问题可能很微妙 - 程度方面很重要。到达率可能会波动,响应率也会有一定可接受的波动 - 显然,如果最终没有方法来“赶上”,那么客户端在某个时候将会爆炸。


真是个了不起的回答!非常感谢你,詹姆斯,这给了我很多思考的食粮。 - maxandron
2
很棒的回答,詹姆斯。这更符合IntroToRx中“Why Rx”部分的要求。http://introtorx.com/Content/v1.0.10621.0/01_WhyRx.html - Lee Campbell
这是一个很好的回答,不过我没有完全理解。在股票价格的例子中,Rx 如何帮助您跟上价格? - Emilios1995
请查看http://www.zerobugbuild.com/?p=192-这只是一种合并的方法之一。基本上,您创建一个运算符,在等待订阅者从当前的OnNext()调用返回时以某种方式聚合。 - James World
我有一个应用程序,它实际上将所有内容都包装在可观察对象中。这完全是不必要的。 - filthy_wizard

3

在撰写 Rx(或任何稍微复杂/新的技术)时,我发现有两件事需要记在脑中:

  1. 我能测试它吗?
  2. 我可以轻松地雇用某人来维护它。不是维护它很困难,而是可以独立地维护它。

为此,我还发现仅仅因为你能做到,并不总是意味着你应该这么做。作为指南,我尽量避免创建超过7行代码的查询。比这个更大的查询,我会尝试将其分解成子查询并进行组合。

如果你提供的代码处于核心代码库中,并且处于极端复杂性的端点上,则可能没问题。然而,如果你发现所有 Rx 代码都有这么多的复杂性,那么你可能正在创建一个难以使用的代码库。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接