点关注,不迷路↑↑↑
相关阅读
一、错误处理:一个反直觉的策略
Ousterhout 抛出了一个让我停下来的统计数字:分布式数据密集系统中,90% 以上的灾难性故障都是由不正确的错误处理造成的。 也就是说,我们为了避免错误而写的那些错误处理代码,很多时候恰恰在制造错误。
他的解决方案是——用什么也不做,代替抛出异常。 他称之为"定义错误不存在"。
这听起来像是在教人"吞异常"。但仔细想,这是一种设计思维:在较低层级(接近出错点的位置)屏蔽错误,在较顶层聚合错误,而不是让每个中间层都承担传递和处理异常的认知负担。屏蔽让下层自己消化无关紧要的失败,聚合让上层在知道全局上下文的情况下做出正确决策。中间层最好什么异常都不知道。
这句话让我想起自己做后端时的经历——一个数据库连接抖动,错误信息一层层往上抛,每层都加一行日志,最后排查问题时看的不是错误本身,而是日志大海捞针。"什么都不做"在很多时候反而是更优解。
二、注释与命名:两个被低估的设计工具
全书关于注释的部分非常务实,有几条直击要害的原则:
注释解释"做什么",不是"如何做"。 "如何做"本质上就是把代码翻译成英文/中文,属于信息冗余。而"做什么"描述的是意图——意图是代码里读不出来的。
先写注释,后写代码。 拖延意味着永远不会有,或者写出来的时候已经和代码信息重复了。先写注释还有一个意外的好处:它能倒逼你更清晰地思考抽象。如果你写不出一个干净的方法注释,说明你还没想清楚这个方法到底应该承担什么职责。
注释放在距离代码最近的地方。 离得越远,更新概率越低;一旦和代码脱节,注释不仅没用,而且有害。
两个容易被忽略的注释需求:事件驱动函数的调用逻辑极不明显,特别需要注释说明它什么情况下会被触发;容器数据类型(map、tuple 等)的含义也要注释——或者干脆别用通用容器,包装成一个有明确名称的类,名字本身就是文档。
命名也是一样。变量命名的好坏,由阅读者决定,而不是编写者决定。 避免歧义,避免冗余信息。你在写的时候觉得"够清楚了"不算,别人读的时候"够清楚了"才算。
三、AI 时代的复杂性新问题
这是笔记中最有个人思考的部分。Ousterhout 在书中描述了一种典型角色——"战术龙卷风":永远用战术思维编程,效率极高,但留下的全是破坏,需要其他工程师在后面收拾。
我的直觉是:以后的"战术龙卷风",非 AI coder 莫属。 AI 可以极快地生成大量代码,但它天然倾向战术——解决眼前的这一个问题,不考虑它对系统复杂性的长期影响。一小时内用 AI 生成 5000 行代码听起来高效,但如果后续理解这些代码需要两周,这个"高效"就打了折扣。
当然,全 AI 编程的项目除外——如果整个项目都由 AI 构建和维护,人类不需要理解其中任何一部分,那战术思维就无所谓了。但至少在现阶段,更合理的分工是:让 AI 帮助人类理解系统、降低复杂性,而设计和编码的核心决策还是由人来做出。
这又回到前面的核心命题:模块化的本质是封装复杂性,让人不需要一次性面对全部——如果未来 AI 真的强大到可以同时面对任意复杂性,模块化设计是否还需要?我的直觉是:人类还是会需要。因为"不需要理解"不等于"不值得理解",正如即使有导航,你还是想看看地图。
四、软件设计是一种职业素养
Ousterhout 把设计与职业口碑挂钩——架构上的投资,会影响公司在工程师界的口碑,从而影响招聘。 优秀工程师不愿意"屎上雕花",他们选择公司时会看代码质量。前一阵传的腾讯 AI Infra 比较弱,就是一个活生生的例子——技术口碑一旦形成,招人就变难了。
敏捷开发的影响也值得讨论。增量式迭代——增量的核心应该是抽象,而不是特性。只增量特性不增量抽象,系统就变成一堆胡乱拼贴的功能块——这正是很多团队"快速迭代"的终局。
还有一个关于测试的提醒:单元测试鼓励重构,这是好事。但测试驱动开发容易导致战术性编程——你只写刚好通过测试的代码,而不是为降低复杂性而设计。如果 TDD 是你的默认模式,至少时不时退后一步,问问自己:这段代码通过测试了吗?好的。那它的设计好吗?
五、意外收获:从软件设计到人生哲学
书的最后几章,Ousterhout 把讨论拉到了软件之外:专注于重要的东西。
确定几件真正重要的事情,把注意力花在上面。不要把精力浪费在你认为不重要的事情上。这不只是工程建议,它是一种存在方式。
行动清单
下次设计新模块时,先问:这个接口对 90% 的使用场景是否足够简单? 遇到异常时,先想:能不能"定义它不存在"?屏蔽在低层,聚合在高层。 写代码之前先写注释——连注释都写不出来的方法,说明还没想透。 CR 中看到"多文件小改动"的信号,停下来想:这说明哪里的抽象出了问题? 每次改代码,顺手改进一点设计——用投资心态,不用重构心态。
点关注,不迷路↓↓↓
夜雨聆风