mcpField
S
software-design-philosophy

software-design-philosophy

github.com/wondelai开发2.5k6.1k 次安装7 周前更新
代码仓库

关于 software-design-philosophy

面向软件工程师的设计框架,帮助在模块设计、API 评审、重构和架构决策中管理复杂性。它以深度模块、信息隐藏和战略性编程等原则,评估设计如何影响系统的理解与修改。

主要功能

  1. 01复杂性诊断与评分
  2. 02深度模块与简化接口
  3. 03识别并减少信息泄漏
  4. 04平衡通用性与特定需求
  5. 05以注释记录设计意图
  6. 06将重构融入日常开发

适用场景

  1. 01评审模块或 API 设计
  2. 02重构难以理解的代码
  3. 03定位跨模块的信息泄漏
  4. 04评估架构决策对复杂性的影响

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/wondelai/skills --skill software-design-philosophy

安装后在 Claude Code 中输入/software-design-philosophy确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
comments-as-design.md
complexity-symptoms.md
deep-modules.md
general-vs-special.md
information-hiding.md
strategic-programming.md

详细介绍

软件设计哲学框架

管理软件工程根本挑战——复杂性——的实用框架。在设计模块、评审 API、重构代码或提供架构决策建议时,运用这些原则。

核心原则

**编写软件时最大的限制,是我们理解自己正在构建的系统的能力。**复杂性是敌人:它会让系统难以理解、难以修改,并成为 Bug 的来源。评估每项设计决策时,都要问:“这会增加还是降低系统的整体复杂性?”——目标不是消除所有复杂性,而是尽量减少不必要的复杂性,并将必要的复杂性集中到可管理之处。

评分

**目标:10/10。**评审或创建设计时,根据满足的快速诊断表中的 8 行来评分(每项约 1.25 分),然后对照以下区间进行合理性检查:

  • 9-10 — 深模块的接口远比实现简单;没有信息泄漏(实现可以变更而无需修改调用方);接口注释阐明设计意图;设计改进是常规工作。8 项诊断全部通过。
  • 6-8 — 整体上模块较深,但存在一两处泄漏、浅类或缺少文档的抽象。5-6 项诊断通过。
  • 3-5 — 类膨胀或按时间顺序拆分,反复出现泄漏,注释只是重述代码。2-4 项诊断通过。
  • ≤2 — 战术龙卷风式代码:模块浅薄、信息泄漏普遍、没有记录设计意图。0-1 项诊断通过。

始终说明当前评分、未通过的诊断项,以及每项需要进行的具体改动,才能达到 10/10。

软件设计框架

管理复杂性并构建易于理解和修改的系统的六项原则:

1. 复杂性及其成因

**核心概念:**复杂性是指系统结构中使其难以理解和修改的任何因素。复杂性有三种表现——修改放大、认知负担和未知的未知——以及两个成因:依赖关系和晦涩难懂。

关键要点:

  • 修改放大:简单的改动需要在许多地方修改
  • 认知负担:开发者必须记住太多内容才能进行修改
  • 未知的未知:不清楚需要修改什么,或哪些信息相关——这是最糟糕的表现
  • 复杂性是逐渐累积的——数百个小决策不断叠加(“千刀万剐”),因此每个决策都很重要

代码应用:

场景模式示例
修改放大集中管理共享知识提取颜色常量,而不是在 20 个文件中硬编码 #ff0000
认知负担减少开发者必须了解的内容使用 open(path),而不是要求指定缓冲区大小、编码和锁定模式
未知的未知明确依赖关系类型系统和接口会揭示改动会影响哪些内容
晦涩难懂精确命名使用 numBytesReceived 而不是 n;使用 retryDelayMs 而不是 delay

当你需要在修复前判断代码库存在哪种复杂性表现时,请参阅 references/complexity-symptoms.md——其中包含针对每种表现的识别测试、依赖关系分类法(语法/语义/时间/隐式)、成本公式 C = Σ(cp·tp),以及一张包含 10 行的危险信号表。

2. 深层模块与浅层模块

核心概念: 最好的模块是深层模块:通过简单的接口提供强大的功能。浅层模块的接口相对于其提供的功能而言过于复杂——它们增加了复杂性,而非将其隐藏起来。

为什么有效: 接口是模块施加给系统其余部分的成本;实现则是收益。因此,学习一个方法比自己重新实现它还困难时,这个方法带来的净收益就是负的——决定模块是否值得保留的是深度,而非代码行数。

关键见解:

  • 深度 = 提供的功能 / 施加的接口复杂度(Unix 文件 I/O 很深;Java 中薄薄的 I/O 封装则很浅)
  • “类泛滥症”:创建过多小型浅层类的弊病——每个接口都会增加认知负担
  • 小方法本身并不一定好;深度比大小更重要
  • 最好的抽象能用少数几个简单概念隐藏大量复杂性

代码应用:

场景模式示例
深层模块通过简单 API 隐藏复杂性file.read(path) 隐藏了磁盘块、缓存、缓冲和编码
治疗类泛滥症合并相关的浅层类RequestParser + RequestValidator + RequestProcessor → 一个 RequestHandler
简化接口减少参数和方法使用带合理默认值的 config.get(key),而非 15 个构造函数参数

判断某个抽象是否值得保留时,请参阅 references/deep-modules.md——其中包含深度比率的前后代码对比、类泛滥症的解决示例,以及案例研究(Unix I/O、GC、TCP/IP)。

3. 信息隐藏与泄漏

核心概念: 每个模块都应该封装其他模块不需要了解的知识。信息泄漏——同一个设计决策体现在多个模块中——是软件设计中最重要的警示信号之一。

为什么有效: 只存在于一个模块中的决策可以在该模块内修改,无需改动其他地方;同一个决策泄漏到 N 个模块中,就会让一次修改变成 N 次,而编译器不会提醒你还有哪些地方需要修改。隐藏信息可以将变更扩散转化为局部修改。

关键见解:

  • 按时间拆分会导致泄漏:按何时发生来拆分代码,会迫使不同阶段共享知识——应改为按知识组织
  • 通过数据格式、协议或共享假设产生的后门泄漏是最隐蔽的形式
  • 装饰器经常造成泄漏——它们会暴露被装饰对象的接口
  • 如果两个模块共享知识,就合并它们,或创建一个封装这些知识的新模块

代码应用:

场景模式示例
格式泄漏集中处理序列化由一个模块负责 JSON 编码和解码,而不是到处调用 json.dumps
按时间拆分按知识而非时间组织将“读取配置”和“应用配置”合并到一个配置模块中
协议泄漏抽象传输细节MessageBus.send(event) 隐藏了 HTTP、gRPC 与队列之间的差异

当一项变更迫使你同步修改两个模块时,请参阅 references/information-hiding.md——其中包含四种泄漏形式的代码示例(接口、后门、时间拆分、装饰器)、五种减少泄漏的策略、HTTP 处理案例研究,以及一个检测表。

4. 通用型模块与专用型模块

核心概念: 设计「一定程度上通用」的模块:接口足够通用,可支持多种用途;实现则满足当前需求。可以问自己:「能满足当前所有需求的最简单接口是什么?」

为何有效: 反直觉的是,通用接口通常更简单——随着需求增长,特例方法会不断增加,而一个通用方法就能涵盖这些需求。反过来的陷阱是:当前需求并不需要的通用性,只会带来推测性的复杂度;你现在就要为一种可能永远不会出现的用例付出代价。

关键要点:

  • 「一定程度上通用」是介于过于具体和过于泛化之间的最佳选择
  • 将复杂度向下推:让较低层的模块处理棘手情况,使较高层保持简单
  • 配置参数往往意味着尚未做出决定——每个参数都是推给调用方的复杂度
  • 拿不准时,先实现更简单、更通用的方案

代码应用:

场景模式示例
API 通用性围绕概念设计,而非单一用例使用 text.insert(position, string),而不是 text.addBulletPoint()
减少配置自动确定行为自动检测文件编码,而不是设置 encoding 参数
避免过度专用化一个通用方法胜过多个专用方法使用 store(key, value, options),而不是 storeUser()、storeProduct()、storeOrder()

选择接口应具备多大通用性时,请参阅 references/general-vs-special.md:其中介绍了「满足当前所有需求的最简单接口」测试、配置参数这一反模式,以及将复杂度向下推的实例。

5. 将注释作为设计文档

核心概念: 注释应描述代码中不明显的内容:设计意图、抽象的理由、不变量和假设。对于低层实现细节以外的内容,「好代码会自我说明」是一种迷思。

为何有效: 代码只能记录它做什么,却永远无法记录为什么选择这种方案而不是其他方案,也无法记录它默默依赖的假设。这些理由是系统中最容易消失的信息:它们只存在于作者脑海中,作者一离开就会消失,因此注释是记录这些信息的唯一机会。

关键要点:

  • 注释分为四类:接口注释(最重要——它们定义抽象)、数据结构成员注释、实现注释、跨模块注释
  • 先写注释(注释驱动设计),在编写代码之前理清思路
  • 不要重复代码中已明确表达的内容;注释放在其描述的代码旁边,并与代码一同更新
  • 如果注释很难写,设计可能过于复杂

代码应用:

场景模式示例
接口注释描述抽象,而非实现「返回最接近指定位置的 widget;如果没有任何 widget 在阈值范围内,则返回 null」
数据结构注释说明不变量「列表按优先级降序排列;优先级相同时按插入顺序排列」
实现注释解释原因,而非内容// 二分查找:列表始终有序,可容纳 100k+ 个项目
跨模块注释关联相关决策// 此超时值必须与 RetryPolicy.java 中的重试间隔一致

编写或审阅注释时,如果不确定该写什么,请参阅 references/comments-as-design.md:其中包含四类注释及示例、注释驱动设计流程,以及对「代码自我说明」迷思的反驳。

6. 战略编程与战术编程

核心概念: 战术编程能快速实现功能,但每一次走捷径都会累积复杂性。战略编程会多投入 10-20% 的精力做好设计,把每次改动都视为改善结构的机会。

为何有效: 战术编程的速度是借来的:每一次走捷径都会让后续改动更困难,而战略投入则会产生复利效应——经过战略设计的系统,几个月后会更容易修改。

关键见解:

  • 战术龙卷风:一位交付迅速却留下满地狼藉的开发者——短期内备受赞誉,长期来看却具有破坏性
  • 你的首要工作是做好设计,并让它恰好能正常运行,而不是写出恰好有设计的可运行代码
  • 初创公司最需要战略编程——早期走的捷径会不断累积,随着团队壮大,最终变成沉重的技术债
  • 每次改动都是投资机会:让代码变得稍微更好一点;重构是每项功能的一部分,而非特殊事件

代码应用:

场景模式示例
战术陷阱抵制快速粗糙的修复不要为了“就这一个特殊情况”添加布尔参数
战略投资在开发功能时改善结构添加功能时,重构笨拙的模块接口
设计评审评估结构,而不仅是正确性询问“这样能让系统更简单吗?”,而不只是“这样能运行吗?”

在决定一项改动值得投入多少设计精力,或为投入设计精力陈述理由时,请参阅 references/strategic-programming.md——其中介绍了 10-20% 投入的计算、战术龙卷风模式,以及初创公司为何最需要战略编程。

常见错误

错误失败原因修正方法
创建太多小类类膨胀会增加接口,却不增加深度;每个边界都会带来认知负担将相关的浅层类合并为更深入的模块
按时间顺序拆分模块“先读取,再处理,最后写入”会迫使不同模块共享知识将共享知识的代码归入同一模块
在接口中暴露实现细节调用方会依赖内部细节;改动会层层传递围绕抽象设计接口;隐藏格式和协议
把注释视为可有可无设计意图和假设会丢失;新成员只能猜测先写接口注释;并与代码一同维护
事事都用配置参数将参数交给调用方,就意味着你放弃了做决定(参见 §4)自动确定行为;提供合理的默认值
快速粗糙的战术修复走捷径会不断累积,直到系统无法使用额外投入 10-20% 的精力;将每次改动都视为设计机会
透传方法只把参数转发给另一个方法的方法增加了接口,却没有增加功能将透传逻辑合并到调用方或被调用方中
针对特定用例进行设计特殊用途的接口会不断累积特殊情况换个角度问:能满足所有当前需求的最简单接口是什么?

快速诊断

问题如果否行动
你能用一句话描述每个模块吗?模块承担的工作过多,或缺乏明确用途拆分为职责连贯且易于描述的部分
接口是否比实现更简单?模块过于浅薄——复杂性泄漏到了外部隐藏更多细节;将浅层类合并为更深的类
你能在不影响调用方的情况下更改实现吗?信息正在跨越边界泄漏将泄漏的信息封装到一个模块中
接口注释是否描述了抽象?设计意图丢失;模块可能被误用记录模块的承诺,而不是它的工作方式
代码评审是否包含设计讨论?评审能发现错误,却无法阻止复杂性增长将“这是否降低了复杂性?”纳入评审标准
每个模块是否都隐藏着一个重要的设计决策?模块是围绕代码而非信息组织的重新组织模块,让每个模块负责特定知识
新人能否在不阅读实现的情况下理解模块边界?抽象缺少文档说明或存在泄漏改进接口注释;简化接口
你是否将 10-20% 的时间用于改进设计?每增加一个功能,技术债务都会累积在每个 PR 中纳入设计改进

延伸阅读

如需查看包含详细示例的完整方法:

关于作者

John Ousterhout 是斯坦福大学计算机科学系 Bosack Lerner 教授,也是 Tcl 脚本语言和 Tk 工具包的创作者。他基于在斯坦福大学 CS 190 课程中的教学,撰写了 A Philosophy of Software Design,将数十年的系统构建经验提炼为适用于不同语言和规模的原则。

评论

支持 Markdown