隔离复杂性是成功抽象的本质
BALI @ 2025.01.24 , 07:02 上午复杂性无法消除,只能被隔离。成功的抽象让复杂性显现于明确之处,为开发者带来掌控感和效率提升。
复杂性无处不在,但它不必无所不在。
在软件开发中,与复杂性的斗争是一个永恒的话题。开发者反复讨论:函数和方法应该加多少注释?抽象的最佳程度是什么?框架的“魔法”何时会过多?一个组织中语言种类会不会太多?
我们努力消除复杂性,试图控制它,追求简单。然而,这种思路可能并不准确。复杂性无法彻底消除,它总得存在于某个地方。
以微服务为例。我们试图让每个服务独立且简单。但如果这种简单性不足以让整个应用被迫变得简单,那么复杂性就会转移到其他地方。如果它不在单个微服务中,那它又在哪里呢?
幸运的话,复杂性会存在于明确的位置,比如代码中你决定容纳复杂性的部分,支持代码的文档,或者培训工程师的课程里。你给它一个安放之处,而不是试图隐藏它。你通过管理和定位它来与之共存。如果你试图完全回避复杂性,它就会漫无目的地散布在系统中,包括代码和开发者的认知中。而当团队成员流动时,对复杂性的理解会逐渐流失。
这种逻辑也适用于类型系统。类型始终存在,它们所代表的复杂性也始终存在。关键问题在于,我们是否清晰地记录这些类型,并建立可执行的契约。类型系统的表达能力决定了我们能将多少复杂性通过类型加以明确。
某些开发者抗拒类型,觉得它是束缚;而那些喜欢强类型的人则认为,类型是工具,就像测试一样。类型为程序增加约束,但这些约束是我们选择的,它们是我们在程序中编码的知识。测试也类似,它们表达的是与类型不同的一组知识。通过测试驱动开发,我们将知识编码到程序中,以此应对复杂性。
这是我喜欢Rust的原因之一。Rust通过其类型系统,尤其是著名的借用检查器,将空间和时间安全的复杂性集中在一个地方。剩余的复杂性被隔离在unsafe块中。尽管unsafe无法告诉你错误的复杂性会以何种形式表现,但它允许你明确复杂性的位置,从而实现管理和控制。
相比之下,垃圾回收器虽然让你不必显式考虑内存分配和释放,但复杂性并未消失。它被转移到实现层面,比如性能瓶颈或难以调试的内存泄漏。然而,这种权衡通常是值得的。
无论是Rust还是垃圾回收器,隔离复杂性意味着开发者不需要时时保持对这些问题的关注,而能专注于其他任务。这也是为什么越来越多的JavaScript和Python开发工具使用Rust编写。C或C++需要开发者承担所有安全问题,而Rust通过借用检查器降低了这种认知负担,为更多开发者打开了大门。
同样的逻辑让我喜欢TypeScript。无论是以前为Ember和LinkedIn编写的复杂类型,还是现在的应用实践,TypeScript并未增加代码的复杂性,而是揭示了已有的复杂性。它让我们看清复杂性,并提供工具与之抗争。
如同Hebert所说:“幸运的话,复杂性会存在于明确的位置。”虽然我们并非总能隔离复杂性,但当这种可能性存在时,抽象的成功正是因此而显现。
本文译自 Chris Krycho,由 BALI 编辑发布。
PREV : 从养老承诺到自主管理:401(k)如何颠覆美国退休体系
NEXT : 呼吸练习,赶走压力