目录

软件设计哲学 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-skill

2. 为什么需要软件设计哲学

软件开发的核心挑战是管理复杂性。复杂性不是由单一灾难性错误导致,而是通过成千上万个小决策逐渐累积。

复杂性的三种症状

症状描述危险程度
Change Amplification(变更放大)看似简单的变更需要在多个不同地方修改代码⭐⭐
Cognitive Load(认知负荷)开发者需要吸收大量信息才能安全完成任务⭐⭐⭐
Unknown Unknowns(未知未知)不清楚需要修改哪些代码或需要什么信息才能完成任务⭐⭐⭐⭐⭐

复杂性的两个根本原因

  1. 依赖(Dependencies): 代码不能孤立地理解或修改,它与其他必须同时考虑的代码相关联
  2. 隐蔽(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 等

实用原则

  1. 设计接口时,最常见的用法应尽可能简单
  2. 简单的接口比简单的实现更重要
  3. 罕见用例可以接受更复杂的调用模式,但常用路径不应为之付出代价

5. 信息隐藏与信息泄漏

信息隐藏

每个模块应封装设计决策(知识),只暴露简化的接口。

隐藏的信息包括:

  • 数据结构
  • 算法
  • 底层机制
  • 策略决策

信息泄漏 🚩红旗

相同的设计决策反映在多个模块中,信息就泄漏了。

常见来源——时间分解: 按执行顺序(而非信息隐藏)分割模块会导致步骤间共享过多知识。

修复方法:

  1. 将共享知识合并到单个模块
  2. 如果无法合并,将共享信息统一到一个深度模块背后

6. 通用模块 vs 专用模块

核心原则: 通用模块通常更深。

一个通用接口比专用接口更简单,因为它用更少的方法覆盖了更多用例场景。

判断标准:

  • 接口应足够通用,无需修改就能支持多种用例
  • 但实现可以只做当前需要的事(不要过度构建)
  • 通用代码和专用代码应干净分离

7. 不同层、不同抽象

每层应提供与其相邻层不同的抽象。

🚩红旗——穿透方法: 一个方法几乎什么都不做,只是将其参数转发到另一个签名相似的方法。这表明层没有提供不同的抽象——职责分割有缺陷。

🚩红旗——穿透变量: 一个变量从顶层传到到底层,但中间层不使用它。

解决方案: 上下文对象、依赖注入、或重新思考模块边界。


8. 复杂性下沉

核心原则: 当复杂性不可避免时,模块应内部吸收它,而不是推给调用者。

大多数模块的用户比开发者多——开发者受苦比每个用户受苦要好。

反模式:

  • 把艰难决策变成配置参数推给系统管理员
  • 对不确定的情况抛出异常,让调用者处理

9. 将错误定义为不存在

核心洞察: 异常处理是复杂性的最大来源之一。减少必须处理的异常数量是降低复杂性的最佳技术之一。

策略

  1. 重新定义语义使错误情况不可能出现

    • unset(key) 即使 key 不存在也成功——它只是保证"调用后 key 不存在"
    • substring(start, end) 自动裁剪越界参数而不是抛出异常
  2. 异常屏蔽: 在低层检测和处理异常,这样异常永远不会到达调用者

  3. 异常聚合: 在一个集中地方处理多种异常类型,而不是在每个调用点分散处理


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 的核心价值:

  1. 复杂性管理: 复杂性是递增的,采取零容忍心态
  2. 深度模块: 好设计 = 简单接口 + 丰富实现
  3. 信息隐藏: 模块应封装设计决策,不泄漏
  4. 通用优于专用: 通用接口更简洁、更深
  5. 复杂性下沉: 让模块承受复杂性,而不是用户
  6. 错误消除: 通过更好的语义设计减少异常
  7. 注释投资: 注释是设计工具,不是事后补救
  8. 命名精确: 好名字是轻量级文档

关键时刻投资: 每次开发中花 10-20% 时间改进设计。长期回报远大于短期成本。

参考书籍: John Ousterhout《A Philosophy of Software Design》是软件工程领域的经典,本 Skill 将其核心原则凝练为可操作的 AI Agent 指导。