读书笔记:软件设计的哲学

A Philosophy of Software Design

https://book.douban.com/subject/37119755

  • 是什么(What):
    • 降低复杂度为核心的软件设计方法论,讨论如何通过模块划分、接口设计、信息隐藏和注释构建易理解、易修改的系统。
  • 为什么(Why):
    • 能工作的代码并不等于设计良好的代码;复杂度会在持续迭代中以依赖和模糊性的形式累积,使每次修改都更慢、更危险。
  • 怎么做(How):
    • 设计深模块和简单接口,把复杂度与决策封装在实现内部;明确职责与抽象层次,减少透传、配置和特殊情况,并通过比较多个方案持续改善设计。

核心观点:

复杂度通常不会凭空消失,而是在不同模块、不同角色之间重新分配;优秀设计会先明确系统职责与接口能力,再尽量把复杂度留在实现内部,而不是转嫁给接口使用者。

  • 软件设计的首要目标不是尽快把功能写出来,而是持续降低系统复杂度。
  • 好的设计应当把复杂度消灭在模块内部,用简单接口隐藏复杂实现,让未来的修改更小、更稳、更容易理解。
  • 复杂度一旦以依赖和模糊性的形式扩散出去,后续所有开发都会持续为今天的捷径付利息。

启发点(关键洞察):

  1. 复杂度不是"代码变多"本身,而是小改动要改很多地方、读代码必须记住很多隐含前提、以及你甚至不知道风险藏在哪。
  2. 深模块比浅模块更有价值。接口应尽量简单,但模块内部可以承担足够多的实现复杂度。
  3. 设计接口之前,先定义模块职责:这个系统边界内到底该负责什么、不该负责什么;接口能力应服务于职责,而不是把未想清楚的决策暴露给外部。
  4. 信息隐藏的重点不是"藏得越多越好",而是隐藏不重要的实现细节,同时把真正重要的约束和语义说清楚。
  5. 相邻层必须提供不同层级的抽象;如果只是原样透传参数和语义,这一层大概率没有设计价值。
  6. 很多配置项、布尔开关和异常分支,本质上是在把设计问题转嫁给调用方。能内部消化的复杂度,尽量不要上抛。
  7. 好的注释不是复述代码,而是补充代码本身无法清楚表达的设计意图、边界条件和接口契约。
  8. 重要设计至少想两个方案。第一反应通常只是"先想到的方案",未必是复杂度最低的方案。
  9. 修改旧代码时,不应只打补丁,而要顺手把结构往"如果一开始就知道这个需求,会怎么设计"那个方向推近一点。

行动:

  1. 以后做设计评审时,先问一句:这次改动是在消除复杂度,还是把复杂度转移给别人。
  2. 写接口前先写一句注释,逼自己先说明职责、输入输出、约束和失败语义。
  3. 遇到 pass-through wrapper、层层透传参数、满天飞布尔参数时,优先考虑是否该重构抽象边界。
  4. 新增配置项和错误分支前先判断:它是否真的需要交给调用方决策,还是应该在模块内部给出默认策略。
  5. 拆分类或拆函数时,不再机械追求短小,而是以"是否形成更深的抽象、是否减少认知负担"为判断标准。
  6. 对关键模块尝试至少两个设计方案,再比较接口复杂度、信息隐藏程度和未来演进成本。

金句:

  1. 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.
  2. Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.
  3. The best modules are those that provide powerful functionality yet have simple interfaces.
  4. Working code isn't enough.
  5. Modules should be deep.
  1. https://blog.pragmaticengineer.com/a-philosophy-of-software-design-review/

评论