软件设计的哲学
Outlinks (1)
1 · 读书笔记:软件设计的哲学
A Philosophy of Software Design
1.1 · 核心观点:
复杂度通常不会凭空消失,而是在不同模块、不同角色之间重新分配;优秀设计会先明确系统职责与接口能力,再尽量把复杂度留在实现内部,而不是转嫁给接口使用者。
软件设计的首要目标不是尽快把功能写出来,而是持续降低系统复杂度。 好的设计应当把复杂度消灭在模块内部,用简单接口隐藏复杂实现,让未来的修改更小、更稳、更容易理解。 复杂度一旦以依赖和模糊性的形式扩散出去,后续所有开发都会持续为今天的捷径付利息。
1.2 · 启发点(关键洞察):
- 复杂度不是”代码变多”本身,而是小改动要改很多地方、读代码必须记住很多隐含前提、以及你甚至不知道风险藏在哪。
- 深模块比浅模块更有价值。接口应尽量简单,但模块内部可以承担足够多的实现复杂度。
- 设计接口之前,先定义模块职责:这个系统边界内到底该负责什么、不该负责什么;接口能力应服务于职责,而不是把未想清楚的决策暴露给外部。
- 信息隐藏的重点不是”藏得越多越好”,而是隐藏不重要的实现细节,同时把真正重要的约束和语义说清楚。
- 相邻层必须提供不同层级的抽象;如果只是原样透传参数和语义,这一层大概率没有设计价值。
- 很多配置项、布尔开关和异常分支,本质上是在把设计问题转嫁给调用方。能内部消化的复杂度,尽量不要上抛。
- 好的注释不是复述代码,而是补充代码本身无法清楚表达的设计意图、边界条件和接口契约。
- 重要设计至少想两个方案。第一反应通常只是”先想到的方案”,未必是复杂度最低的方案。
- 修改旧代码时,不应只打补丁,而要顺手把结构往”如果一开始就知道这个需求,会怎么设计”那个方向推近一点。
1.3 · 行动:
- 以后做设计评审时,先问一句:这次改动是在消除复杂度,还是把复杂度转移给别人。
- 写接口前先写一句注释,逼自己先说明职责、输入输出、约束和失败语义。
- 遇到 pass-through wrapper、层层透传参数、满天飞布尔参数时,优先考虑是否该重构抽象边界。
- 新增配置项和错误分支前先判断:它是否真的需要交给调用方决策,还是应该在模块内部给出默认策略。
- 拆分类或拆函数时,不再机械追求短小,而是以”是否形成更深的抽象、是否减少认知负担”为判断标准。
- 对关键模块尝试至少两个设计方案,再比较接口复杂度、信息隐藏程度和未来演进成本。
1.4 · 金句:
- “The most fundamental problem in computer science is problem decomposition: how to take a complex problem and divide it up into pieces that can be solved independently.” — John Ousterhout
- “Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.” — John Ousterhout
- “The best modules are those that provide powerful functionality yet have simple interfaces.” — John Ousterhout
- “Working code isn’t enough.” — John Ousterhout
- “Modules should be deep.” — John Ousterhout
1.5 · 与软件设计底层原则的对应:
这本书的核心主线是”持续降低复杂度”。如果把这个目标进一步拆开看,它基本都可以落回软件设计底层原则里的 6 个维度:
- 深模块、简单接口、把实现复杂度压进模块内部,对应
封装。 - 明确模块边界、避免无价值的 pass-through wrapper 和原样透传,对应
职责。 - 强调相邻层必须提供不同层级的抽象,而不是机械叠层,对应
组合。 - 反对把配置、布尔开关、异常分支随意上抛给调用方,本质上是在控制复杂度沿调用链传播,对应
依赖。 - 注释应补充设计意图、边界条件、接口契约,本质上是在显式表达系统的规则与不变量,对应
约束。 - 重要设计至少比较两个方案,修改旧代码时顺手改善结构,关注的都是未来可修改性,对应
演化。
所以 软件设计底层原则 可以看作这本书里诸多设计判断的一个压缩版索引:它把”降低复杂度”这件事,拆成了几个更稳定、可迁移的观察维度。