About
最近 一个月,在用 Redux-for-Android 写真实的项目。
有些问题,吹 Functianal Programming 的人不会告诉你,只有掉坑了才知道。
函数式编程的坑
immutable data,当 业务很复杂,状态层级很深的时候,immutable 的弊端就出来了。
不仅更改深层的状态非常繁琐,另外即使更改一个很小的状态,也会导致整个状态发生变化。
一连串的连锁反映,最终的问题就是,不仅内存,性能也会受影响。
真的是牵一发而动全身,业务逻辑很复杂的时候,很难维护。
举个例子:
1 | // 用户资料页的数据模型 |
实际会用到的几个逻辑, 写起来是这样的:
class UserProfileModel
1 | fun addPost(post: Post) { |
解决方案
每个数据都带上 ‘id’, 通过 id 缩减 嵌套的 数据类型
1 | // 用户资料页的数据模型 |
数据结构的定义 跟之前差别不大。只是用 Id 替换了 目标的数据类型。这样就可以把逻辑 分开,按各自的 类型处理, 例如 可以拆成
ProfileModel, UserModel, PostModel, CommentModel 基本上每个 data 的逻辑 只有一个 Model 负责,
其他的 Model 负责 处理 数据的 转换。
例如 ProfileModel 负责 处理 用户资料页的数据,UserModel 负责 处理 用户的基本信息,PostModel 负责 处理 帖子的信息,CommentModel 负责 处理 评论的信息。不需要像 UserProfileModel 处理全部的 state 逻辑。现在的 UserProfileModel 只需要组合 各个 model
这样的好处是,我们的 数据 state 模型 还是对应 业务的功能的,从 data state 很容易看出来这个业务的核心数据,拆分的只是逻辑。
而 核心 流程 还是在 UserProfileModel, 只是拆分了各自的子数据处理逻辑
推荐阅读 Redux Normalization State Shape, 虽然 Android 开发用不到 Redux,但是 Redux 的很多思想,在 kotlin state flow 中都有应用, 特别是 复杂的状态管理方面,Redux 的很多思想是可以直接借鉴的。
例如 上面的使用 id flatten 数据层级,我在一开始只想到 mongo db 的 projection,直到写这个的时候查资料,才发现 Redux 已经实现了一整套方案。
关于 ArrowKt 的解决方案
Arrow 也有完整的解决方案。我没使用过 Arrow,不能深入评价。如果只从 入门使用者的角度看,我认为 Arrow 为了更符合函数式编程的 概念,例如 lens,引入了不必要的复杂度。而这些复杂度,在 OOP 里本来是不存在的。lens 更应该是到了特定的,明确需要的场景再使用。很多时候 lens 引入的复杂度 还不如 直接用 data class 的嵌套 copy.
Funtional Program 在我看来,经常犯的错就是,过于注重形式,而不是解决问题。如果要用函数式的思想,就不能用面向对象的思想。使用 OOP 的是个异类,放火烧死他。《人月神话》里很早就说过了,不存在银弹。但是很多 FP 的使用者 却认为 只有 FP 是正确的,只能用 FP 去解决问题。
但是我是实用派,我不关心是 FP,还是 OOP,我更关心的是,如何用最简洁高效的方式,解决问题。
例如官方给的读写 Data:
1 | val me = Person( |
读取 name 从 me.name 变成了 Person.name.get(me), 为了更符合FP的定义,FP 经常干这种脱裤子放屁的事。
Kotlin DSL
AI 提到 可以用 DSL, 这个还没用过:
1 | user update { |
2025-06-30: 尝试使用 DSL 解决这个问题,并不行。DSL 写起来也很复杂。复杂的问题,不一定存在简单的解决方案
推荐&参考
- mongo db 的 懒加载优化:类似MongoDB的投影(projection)机制,减少初始数据量
- Redux Normalization State Shape
- Arrow.kt Immutable Data