【问题标题】:Where to draw the line with reactive programming [closed]反应式编程的界限在哪里[关闭]
【发布时间】:2016-03-13 18:28:20
【问题描述】:

我已经在我的项目中使用 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

我们在哪里画线?是否可以用很棒的响应式单行器填充我们的整个程序,还是应该只将其保存用于真正平凡的操作?

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

如何将我的代码设计为既反应灵敏又易于阅读的代码?

【问题讨论】:

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


    【解决方案1】:

    当你拿起 Rx 时,它会变成这个闪亮的 hammer,一切都开始看起来像一根生锈的钉子,正等着你敲进去。

    就个人而言,我认为最大的线索在于名称,reactive 框架。给定一个需求,您需要考虑反应式解决方案是否真正有意义。

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

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

    • 您能控制事件流吗?
    • 您必须在多大程度上以事件流的速率完成响应?

    如果您控制事件流并且您必须以事件流,那么 Rx 是一个不错的选择。

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

    我见过很多例子,人们为了证明 Rx 的合理性而跳入圈套制造缺乏控制的错觉——这对我来说似乎很疯狂。为什么要放弃你的控制权?

    一些例子:

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

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

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

      显然,这对于 Rx 来说是一个不错的选择。如果你不能跟上一般的价格速度,你就完蛋了。 可能您将价格混为一谈(也许每秒只提供一次更新) - 但这仍然符合跟上的条件。你不能做的一件事是要求证券交易所放慢速度。

    这些(现实世界)示例几乎处于光谱的两端,没有太多的灰色区域。但是那里有很多灰色区域,控制不明确。

    有时您在客户端/服务器系统中戴着客户端帽子,很容易陷入牺牲控制的陷阱,或者将控制放在错误的位置 - 通过正确的设计很容易解决这些问题。考虑一下:

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

      • 新闻更新随时提交到服务器并大量创建。
      • 应按客户端设置的时间间隔刷新客户端。
      • 可以随时更改刷新间隔,用户可以随时请求立即刷新。
      • 客户端仅显示用用户指定的特定关键字标记的更新。
      • 新闻更新有时很长,客户端不应存储新闻更新的全部内容,而应显示标题和摘要。
      • 可根据用户要求显示文章的全部内容。

    在这里,新闻更新的频率不受客户端的控制。但所需的刷新率和感兴趣的标签是。

    让客户端在所有新闻更新到达时接收它们并在客户端过滤它们是行不通的。但是有很多选择:

    • 服务器是否应该在考虑客户端刷新率的情况下发送更新数据流?客户端下线了怎么办?
    • 如果有成千上万的客户怎么办?如果客户想要立即刷新怎么办?

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

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

    在我看来,只要客户需要提供此反馈,它就无法通过响应率测试。此时,您不是处于 reactive 情况,而是处于 async enumerable 情况。即客户端应该在准备好更多时说“我准备好了”,然后以非阻塞方式等待服务器响应。

    如果将第一个方案修改为到达放置文件夹中的文件,这些文件的长度和处理复杂性各不相同,这将是合适的。客户端应该对下一个文件进行非阻塞调用,处理它,然后重复。 (根据需要添加并行性)- 并且不响应文件到达事件流。

    总结

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

    因此,如果您在您的场景中反思 控制响应率 的元素,您很可能会走在正确的轨道上。

    响应率问题可能很微妙 - degree 方面很重要。到达率可能会波动,并且响应率会有一定程度的波动 - 显然,如果您最终没有办法“赶上”,那么在某些时候客户会崩溃。

    【讨论】:

    • 惊人的答案!非常感谢詹姆斯,这让我深思……
    • 詹姆斯的好答案。这更符合 IntroToRx introtorx.com/Content/v1.0.10621.0/01_WhyRx.html 中的 Why Rx 部分
    • 这是一个很好的答案,但是,我没有完全明白。在股票价格示例中,Rx 如何帮助您跟上价格?
    • 看看zerobugbuild.com/?p=192 - 这只是一种合并方法。本质上,您创建了一个在等待订阅者从当前 OnNext() 调用返回时以某种方式聚合的运算符。
    • 有一个应用程序,它将所有内容都包装在一个 observable 中。完全没有必要。
    【解决方案2】:

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

    1. 我可以测试一下吗?
    2. 我可以轻松雇用可以维护它的人吗?不费力地维护它,但可以独自维护它吗?

    为此,我还发现仅仅因为你可以,并不总是意味着你应该。作为指导,我尽量避免创建超过 7 行代码的查询。比这更大的查询,我尝试将其分成我编写的子查询。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-20
      • 2015-04-11
      • 1970-01-01
      • 1970-01-01
      • 2011-03-30
      • 2010-11-06
      • 1970-01-01
      • 2011-02-22
      相关资源
      最近更新 更多