翻译
和复杂性的斗争是软件开发领域永恒的话题, 各种场合都能见到相关的讨论: 应该写多少注释? 什么情况下会觉得一个框架过于复杂? 一个组织里面应该允许多少种开发语言?
我们总是追求简洁, 尝试避免或者控制复杂性. 这是不正确的思路, 因为复杂性是无法被消灭的.
控制论的弹性工程(Resilience Engineering)的一个概念就是”必要多样性原则”: 只有通过提高复杂性才能对抗复杂性.
构建工具开发过程中遇到的一些问题:
- 简单的工具不能处理实际的各种场景
- 想支持各类场景, 则不能建立一些固有规则
- 想通过合理的默认配置项简化默认场景规则, 则这个默认场景规则必须被广泛接受和实施
- 允许配置, 等于提供用户制造规则的能力以匹配既有系统, 固有规则无法建立
- 工具做简单, 必须限制可配置的选项
- 最终, 如果你的工具不能满足需求, 用户会再包一层”胶水层”以达到目的
这些问题都是无法避免的, 因为复杂性是人们尝试解决问题的本质不可分割的一部分.
如果我们总是允许胶水层, 胶水层内部的逻辑复杂性就是我们要维护的的东西. 至此, 复杂性不再”冬眠”, 而是游走在对接的边界, 每个人都得花费精力学习并适应它.
人们看到两个不融洽的概念, 总是想办法绕过. 这个必要的复杂性可以通过匹配工具迁移掉, 或者通过重整彻底消灭. 重整需要大量的投入, 引入很多调整调整点. 为了确保成功, 在实施重整过程中, 需要让人们认识理解并处理各调整点的复杂性. 有时候, 重整因为产生了新的和人们固有预期不一致, 故不得不妥协又包一层, 最终并不能让事情变得简单. 本质复杂性(essential complexity)不过是历史悠久的偶发复杂性(accidental complexity)的累积. 只要一直在做事, 偶发复杂性就无法避免, 且不断变化.
“脑海中的知识”和”世界上的知识”: 你知道这个东西是可以按的, 因为它看上去长得像一个按钮. 对现实的解释是基于文化以及具体场景的, 并且依赖脑海中的先验知识 (你脑海里得现有按钮这个概念). 专业能力就是利用脑海中的知识帮你更好的解读世界.
软件开发一个常见陷阱就是由于过于追求代码的简单易读, 在简化过程中, 将代码里的复杂性”迁徙”到了其它地方.
作者设计rebar3 (Erlang项目构建工具) 时, 觉得这个工具可以做的很简单. 其前提是大家对于Erlang/OTP项目规范有基本的了解, 只要你熟悉这些规范, 工作可以很顺利. 这里, 复杂性外化到Erlang这个技术生态里. 这些规范rebar3的潜在用户一定要学的, 否则无法理解这个工具. 这样做虽然方便了既有成熟用户的使用, 但是牺牲了新用户的学习成本. 其它技术生态下, 为不同目的开发的工具, 设计上也会做类似的折衷取舍.
这个问题是在软件架构中的慢性病. 当我们采用了如微服务之类的技术, 我们尝试将每个独立服务变得简单. 但是除非这个每个独立服务解决的问题本质上都很简单独立, 否则复杂性还是在那里. 如果复杂性不在每个独立服务中, 那它去那儿了?
复杂性总是存在的. 幸运的时候, 复杂性存在于明确定义的位置: 在代码中, 在解释代码的文档中, 或是在新人手把手培训的过程中. 给复杂性一个位置, 不要尝试隐藏它, 想办法管理好它, 从而需要的时候知道从哪里找出来.
如果很不幸, 你假装认为复杂性是可以整体被避免的, 想去去碾碎它, 没地儿去的复杂性, 只能碎裂四散开来, 一些藏在在代码里, 一些在相关维护人员的口口相传里, 并随着人员的变动, 逐渐消逝.
复杂性不可被避免. 接受它, 给它一个应得的位置, 设计系统以及人员结构的时候, 承认复杂性的存在, 专注并适应之, 你的复杂性可能会变成你的优势.
个人想法
没有银弹, 软件工程就是和复杂性做斗争的工作.
识别不必要的复杂性
- 一类事情不同做法带来的复杂性, 好听点叫多样性
- 从大生态来说, 多样性有助于竞争演化, 以及规避团灭风险; 但从小团体角度, 则会导致低效无序
- 不同开发语言之争, 不同工具/框架之争, 不同云平台服务之争, 有助于技术进步; 但是在一个小业务内过多的选型 (勾搭过多的妹子)…嗯…维护的梦魇
- 因此越小团体范围内, 需要越细化的规则约束, 一类问题一种做法, 不要百花齐放
- 低质量”编码”模式 (文档/注释/代码/语言表达/…), 导致的理解复杂性
- 学习新东西的时候, 由于资料行文组织的原因, 看了半天不得要领; 换一个更适合的学习资料后, 会有种茅塞顿开之感
- 和啰嗦的人沟通, 总觉话多不得要点, 云里雾里; 换个人讲, 一句话就能够点破要领
- 把事情写清楚, 讲明白, 是最重要的能力
- 同样的, 技术代码也要能够写的简练
- 构建尽可能多的”预信息”, 降低解码/编码成本
应对本质复杂性
- 消灭之
- 价值判断, 砍鸡肋
- 很多时候, 混乱的根源来自于上游, 抽丝剥线的下游工作, 不如上游快刀斩乱麻直接了当
- 围栏之
- 农场里, 鸡鸭猪分开圈养在不同的围栏里; 温顺的牛羊圈养在一起问题则不大; 农作物分别种在不同的开放地里; 便于分类管理
- 构建充分的边界, 专门的管控, 足够的测试, 完善的变更管理
- 技术上, 不要将不同层面的逻辑/复杂性放在一起理解, 例如: 算法识别的复杂性和数据库读写变更的分离
- 固化之
- 越是简单的东西变更越快, 越是精密复杂的技术, 换代就越慢, 比如飞机, 还是几十年积累的技术在用
- 如果复杂又频繁变更, 那一定是有什么问题
- “隐藏”之: 国之重器, 不轻易示人
- 从变更率来分层
- 简单 / 经常会变: 用户选项, 配置
- 中等 / 偶尔变化: 业务流程代码
- 复杂 / 不常变化: 底层工具, 框架, 三方算法库, 编译运行时依赖, 等等
惯性是强大的, 变革是困难的, 不要尝试修改用户习惯. 不要图新鲜追逐潮流的浪花尖, 深入学习理解以及迁移的成本, 或者换言之过程复杂性, 是巨大的.
要学会说不, 要认识到所有工具都是有其使用场景和局限的. 不要为了多上车, 尽可能广泛的使用落地, 而把工具做的很复杂. 要有接受世界是多元的, 不能用一种工具解决所有的问题. 同样的, 不要试图讨好所有人.
从既有工具/流程思考问题, 会思维僵化, 拒绝新事物, 导致严重的不匹配. 手里只有个锤子, 于是什么问题都是钉子, 哪怕它其实是个螺丝, 应该用螺丝刀 (工具错配), 或者是个可以直接摁下去的大头钉 (不必要的工具引入).
从零开始的项目, 就像个新生儿, 一开始可以策马奔腾, 快速发展; 但受限于自身重量 (不断累积的复杂性), 越长越慢, 除非注重日常”锻炼”(维护改善, 局部重构), 否则最终都会停留在一个苟延残喘的阶段, 或者慢慢消亡.