软件设计哲学 Agent Skill:从《A Philosophy of Software Design》到 AI 代码审查
1. 项目概述
GitHub: luoling8192/software-design-philosophy-skill
Stars: 254 | Forks: 11 | License: MIT
作者: luoling8192 (RainbowBird)
定位: 基于 John Ousterhout 名著《A Philosophy of Software Design》的 Claude Code Agent Skill,在代码审查、架构讨论、API 设计等场景提供软件设计哲学指导。
安装命令:
# Claude Code
claude install-skill luoling8192/software-design-philosophy-skill
# Skills.sh (Vercel)
npx skills add luoling8192/software-design-philosophy-skill2. 为什么需要软件设计哲学
软件开发的核心挑战是管理复杂性。复杂性不是由单一灾难性错误导致,而是通过成千上万个小决策逐渐累积。
复杂性的三种症状
| 症状 | 描述 | 危险程度 |
|---|---|---|
| Change Amplification(变更放大) | 看似简单的变更需要在多个不同地方修改代码 | ⭐⭐ |
| Cognitive Load(认知负荷) | 开发者需要吸收大量信息才能安全完成任务 | ⭐⭐⭐ |
| Unknown Unknowns(未知未知) | 不清楚需要修改哪些代码或需要什么信息才能完成任务 | ⭐⭐⭐⭐⭐ |
复杂性的两个根本原因
- 依赖(Dependencies): 代码不能孤立地理解或修改,它与其他必须同时考虑的代码相关联
- 隐蔽(Obscurity): 重要信息不明显——模糊的命名、缺失的文档、隐含的约定、隐藏的约束
核心洞察: 复杂性是递增的。必须采取零容忍心态——每一丝"微小"的复杂性都值得重视。
3. 战略编程 vs 战术编程
战术编程(反模式)
- 目标: 尽快让功能工作
- 心态: “先让它跑起来”、“以后再重构”
- 结果: 复杂性快速累积,技术债务失控
战略编程(推荐)
- 目标: 产出优秀设计;工作代码只是副产品
- 心态: 将约 10-20% 的开发时间投入到设计改进
- 实践:
- 每次变更时寻找改进设计的机会
- 工作代码不够——设计质量同样重要
- 软件开发的增量应该是抽象,而不是功能
投资原则: 在每个功能开发中花 10-20% 时间改进设计,长期回报远大于短期成本。
4. 深度模块:最重要的设计概念
核心比喻
将模块想象成一个矩形:
- 宽度(Width) = 接口的复杂性
- 高度/深度(Height/Depth) = 内部隐藏的功能量
| 类型 | 接口 | 实现 | 评价 |
|---|---|---|---|
| 深度模块(Deep) | 简单 | 丰富 | ✅ 好设计 |
| 浅层模块(Shallow) | 复杂 | 很少 | ❌ 坏设计 |
经典案例
深度模块:
- Unix 文件 I/O —— 仅 5 个系统调用(open, read, write, lseek, close)就暴露了强大的文件系统
浅层模块:
- Java I/O —— 读取文件需要组合 FileInputStream, BufferedInputStream, ObjectInputStream 等
实用原则
- 设计接口时,最常见的用法应尽可能简单
- 简单的接口比简单的实现更重要
- 罕见用例可以接受更复杂的调用模式,但常用路径不应为之付出代价
5. 信息隐藏与信息泄漏
信息隐藏
每个模块应封装设计决策(知识),只暴露简化的接口。
隐藏的信息包括:
- 数据结构
- 算法
- 底层机制
- 策略决策
信息泄漏 🚩红旗
当相同的设计决策反映在多个模块中,信息就泄漏了。
常见来源——时间分解: 按执行顺序(而非信息隐藏)分割模块会导致步骤间共享过多知识。
修复方法:
- 将共享知识合并到单个模块
- 如果无法合并,将共享信息统一到一个深度模块背后
6. 通用模块 vs 专用模块
核心原则: 通用模块通常更深。
一个通用接口比专用接口更简单,因为它用更少的方法覆盖了更多用例场景。
判断标准:
- 接口应足够通用,无需修改就能支持多种用例
- 但实现可以只做当前需要的事(不要过度构建)
- 通用代码和专用代码应干净分离
7. 不同层、不同抽象
每层应提供与其相邻层不同的抽象。
🚩红旗——穿透方法: 一个方法几乎什么都不做,只是将其参数转发到另一个签名相似的方法。这表明层没有提供不同的抽象——职责分割有缺陷。
🚩红旗——穿透变量: 一个变量从顶层传到到底层,但中间层不使用它。
解决方案: 上下文对象、依赖注入、或重新思考模块边界。
8. 复杂性下沉
核心原则: 当复杂性不可避免时,模块应内部吸收它,而不是推给调用者。
大多数模块的用户比开发者多——开发者受苦比每个用户受苦要好。
反模式:
- 把艰难决策变成配置参数推给系统管理员
- 对不确定的情况抛出异常,让调用者处理
9. 将错误定义为不存在
核心洞察: 异常处理是复杂性的最大来源之一。减少必须处理的异常数量是降低复杂性的最佳技术之一。
策略
重新定义语义使错误情况不可能出现
unset(key)即使 key 不存在也成功——它只是保证"调用后 key 不存在"substring(start, end)自动裁剪越界参数而不是抛出异常
异常屏蔽: 在低层检测和处理异常,这样异常永远不会到达调用者
异常聚合: 在一个集中地方处理多种异常类型,而不是在每个调用点分散处理
10. 设计两次
对于任何重要的设计决策,构思至少两种不同的方案后再选择。
即使第一个想法看起来很棒,也要强迫自己想出一个替代方案。
比较维度: 接口简洁性、通用性、性能、实现难度。
11. 注释哲学
为什么写注释
注释捕捉代码无法表达的设计决策和意图。
好注释 = 抽象的一部分——好的接口文档意味着用户不需要阅读实现。
先写注释原则: 在写实现之前写接口注释——用注释作为设计工具。如果写不出清晰的注释,设计本身可能就有问题。
注释应该描述什么
- 非显而易见的: 代码不能直接表达的 为什么、约束、边界条件、副作用
- 不应该: 重复代码已经说明的内容
注释层次
| 类型 | 描述什么 |
|---|---|
| 接口注释 | 描述 什么 和 为什么——无实现细节 |
| 实现注释 | 解释 如何 以及 为什么这样做 |
| 跨模块注释 | 记录跨越模块边界的设计决策和依赖 |
12. 命名艺术
好命名的标准
| 标准 | 含义 |
|---|---|
| 精确(Precise) | 向读者传达含义,无歧义 |
| 一致(Consistent) | 整个代码库中相同名字始终意味相同事物 |
| 信息丰富(Informative) | 好名字本身就是轻量级文档 |
命名红旗 🚩
- 模糊名字: 太通用(
data,result,tmp,info)——不携带有用信息 - 难以命名: 如果找不到精确、直观的名字——设计本身可能有缺陷
13. 代码应明显
目标: 读者应能快速理解代码的行为和意图,无需 significant mental effort。
明显代码技术:
- 好的命名和一致性
- 谨慎使用空格和格式来揭示结构
- 解释非显而易见的注释
- 避免事件驱动代码中的隐式控制流(除非必要)
14. 红旗快速参考
代码审查中使用的 14 个危险信号:
| 红旗信号 | 含义 |
|---|---|
| Shallow Module | 接口几乎和实现一样复杂 |
| Information Leakage | 相同设计决策反映在多个模块中 |
| Temporal Decomposition | 模块按执行顺序分割,而非信息隐藏 |
| Overexposure | 常见 API 强迫调用者了解罕见功能 |
| Pass-Through Method | 方法只是将参数转发到另一个签名相似的方法 |
| Repetition | 非平凡代码在多个位置重复 |
| Special-General Mixture | 通用和专用代码未干净分离 |
| Conjoined Methods | 理解一个方法需要理解另一个 |
| Comment Repeats Code | 注释只是代码的英文翻译 |
| Impl Contaminates Interface | 接口文档暴露了用户不需要的实现细节 |
| Vague Name | 名字太通用,无法传达有用信息 |
| Hard to Pick Name | 找不到精确直观的名字——设计可能有缺陷 |
| Hard to Describe | 文档必须很长才能完整——模块可能太复杂 |
| Non-Obvious Code | 代码的行为或含义不容易理解 |
15. Skill 安装与使用
安装方式
# Claude Code
claude install-skill luoling8192/software-design-philosophy-skill
# Skills.sh (Vercel)
npx skills add luoling8192/software-design-philosophy-skill触发场景
当用户提到以下话题时自动触发:
| 话题类别 | 触发词 |
|---|---|
| 复杂性 | “code is too complex”, “reduce complexity” |
| 模块分割 | “how to split modules”, “module decomposition” |
| 接口设计 | “interface design”, “API design” |
| 耦合 | “reduce coupling”, “tight coupling” |
| 深度/浅层 | “deep modules”, “shallow modules” |
| 信息泄漏 | “information leakage”, “design decision leak” |
| 错误处理 | “error handling”, “exceptions” |
| 代码可读性 | “code readability”, “obvious code” |
| 设计哲学 | “design philosophy”, “software design” |
| 复杂性下沉 | “pull complexity down” |
| 消除错误 | “define errors out of existence” |
使用示例
# 在代码审查中,Agent 会自动识别以下问题:
# 🚩 红旗:浅层模块
class UserValidator:
def validate(self, user_data):
# 检查所有字段...
return self.validate_email(user_data) and \
self.validate_password(user_data) and \
self.validate_username(user_data)
def validate_email(self, email): ...
def validate_password(self, password): ...
def validate_username(self, username): ...
# Agent 建议:合并为一个深度接口16. 与其他 Skill 的对比
| Skill | 定位 | 触发场景 |
|---|---|---|
| software-design-philosophy | 设计哲学理论 | 代码审查、架构讨论 |
| tdd | 测试驱动开发 | 写测试、先红后绿 |
| improve-codebase-architecture | 代码库架构改进 | 重构、大型变更 |
17. 总结
软件设计哲学 Skill 的核心价值:
- 复杂性管理: 复杂性是递增的,采取零容忍心态
- 深度模块: 好设计 = 简单接口 + 丰富实现
- 信息隐藏: 模块应封装设计决策,不泄漏
- 通用优于专用: 通用接口更简洁、更深
- 复杂性下沉: 让模块承受复杂性,而不是用户
- 错误消除: 通过更好的语义设计减少异常
- 注释投资: 注释是设计工具,不是事后补救
- 命名精确: 好名字是轻量级文档
关键时刻投资: 每次开发中花 10-20% 时间改进设计。长期回报远大于短期成本。
参考书籍: John Ousterhout《A Philosophy of Software Design》是软件工程领域的经典,本 Skill 将其核心原则凝练为可操作的 AI Agent 指导。