software-design-philosophy by wondelai/skills
npx skills add https://github.com/wondelai/skills --skill software-design-philosophy一个用于管理软件工程根本挑战——复杂性——的实用框架。在设计模块、评审 API、重构代码或提供架构决策建议时应用这些原则。核心论点是:复杂性是大多数软件问题的根源,管理它需要在设计的每个层面进行深思熟虑的战略性思考。
编写软件最大的限制是我们理解所创建系统的能力。 复杂性是敌人。它使系统难以理解、难以修改,并成为错误的来源。每个设计决策都应通过提问来评估:“这会增加还是减少系统的整体复杂性?” 目标不是零复杂性——这在有用的软件中是不可能的——而是最小化不必要的复杂性,并将必要的复杂性集中在可以管理的地方。
目标:10/10。 在评审或创建软件设计时,根据对以下原则的遵循程度,按 0-10 分进行评分。10/10 意味着具有清晰抽象的深层模块、出色的信息隐藏、关于复杂性的战略性思考,以及捕捉设计意图的注释。较低的分数表明模块浅薄、信息泄露、战术性捷径或缺少设计文档。始终提供当前分数以及达到 10/10 所需的具体改进措施。
管理复杂性并产出易于理解和修改的系统的六项原则:
核心概念: 复杂性是任何与软件系统结构相关、使其难以理解和修改的事物。它通过三种症状表现出来:变更放大、认知负荷和未知的未知。
为什么有效: 通过识别复杂性的具体症状,开发者可以精确诊断问题,而不是依赖“代码混乱”这种模糊概念。两个根本原因——依赖性和模糊性——为设计改进提供了明确的目标。
关键见解:
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 集中共享知识 |
提取颜色常量,而不是在 20 个文件中硬编码 #ff0000 |
| 认知负荷 | 减少开发者必须知道的内容 | 使用简单的 open(path) API,而不是要求缓冲区大小、编码和锁定模式 |
| 未知的未知 | 使依赖关系显式化 | 使用类型系统和接口来展示变更影响的范围 |
| 依赖管理 | 最小化跨模块耦合 | 通过明确定义的接口传递数据,而不是共享的全局状态 |
| 减少模糊性 | 精确命名事物 | 使用 numBytesReceived 而非 n;使用 retryDelayMs 而非 delay |
核心概念: 最好的模块是深层的:它们在简单的接口背后提供强大的功能。浅层模块相对于其提供的功能具有复杂的接口,增加了复杂性而非减少复杂性。
为什么有效: 一个模块的接口代表了它强加给系统其余部分的复杂性。其实现代表了它提供的功能。深层模块提供了功能与接口复杂性的高比率。接口是成本;实现是收益。
关键见解:
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 深层模块 | 将复杂性隐藏在简单 API 背后 | file.read(path) 隐藏了磁盘块、缓存、缓冲、编码 |
| 浅层模块 | 避免仅做传递的薄包装器 | 一个 FileInputStream 包装在 BufferedInputStream 中,再包装在 ObjectInputStream 中 |
| 类炎疗法 | 合并相关的浅层类 | 将 RequestParser、RequestValidator、RequestProcessor 合并为一个 RequestHandler |
| 方法深度 | 方法应该做实质性的事情 | 一个处理锁定、日志记录、缓存失效和重新平衡的 delete(key) 方法 |
| 接口简洁性 | 更少的参数,更少的方法 | 具有合理默认值的 config.get(key),而不是 15 个构造函数参数 |
核心概念: 每个模块都应封装其他模块不需要的知识。信息泄露——当一个设计决策反映在多个模块中时——是软件设计中最重要的危险信号之一。
为什么有效: 当信息隐藏在模块内部时,对该知识的更改只需要修改该模块。当信息泄露跨越模块边界时,更改会在系统中传播。信息隐藏减少了依赖性和模糊性这两个复杂性的根本原因。
关键见解:
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 信息隐藏 | 封装格式细节 | 一个模块拥有 HTTP 解析逻辑;调用者获得结构化对象 |
| 时间分解 | 按知识而非时间组织 | 将“读取配置”和“应用配置”合并到一个配置模块中 |
| 格式泄露 | 集中序列化 | 一个模块处理 JSON 编码/解码,而不是到处散布 json.dumps |
| 协议泄露 | 抽象协议细节 | MessageBus.send(event) 隐藏了传输是 HTTP、gRPC 还是队列 |
| 装饰器泄露 | 谨慎使用深层包装器 | 优先在文件类内部添加缓冲,而不是在外部包装它 |
核心概念: 设计“某种程度上通用”的模块:接口应足够通用以支持多种用途,而不受限于当前的具体需求,同时实现处理当前需求。提问:“覆盖我所有当前需求的最简单接口是什么?”
为什么有效: 通用接口往往更简单,因为它们消除了特殊情况。它们也使设计具有未来适应性,因为新的用例通常符合现有的抽象。然而,过度泛化会浪费精力,并可能通过不必要的抽象本身引入复杂性。
关键见解:
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| API 通用性 | 为概念而非单一用例设计 | 使用 text.insert(position, string) API 而不是 text.addBulletPoint() |
| 将复杂性下推 | 在模块中处理默认值 | 一个选择合理缓冲区大小的 Web 服务器,而不是要求调用者配置它们 |
| 减少配置 | 自动确定行为 | 自动检测文件编码,而不是要求 encoding 参数 |
| 避免过度特化 | 移除特定于用例的方法 | 使用一个 store(key, value, options) 而不是 storeUser()、storeProduct()、storeOrder() |
| 某种程度上通用 | 通用接口,具体实现 | 一个当前基于 PostgreSQL 但不暴露 SQL 概念的 Datastore 接口 |
核心概念: 注释应描述代码中不明显的事物。它们捕捉设计意图、抽象原理以及无法在代码中表达的信息。“好代码是自文档化的”这种说法对于低级实现细节之外的任何事情来说都是神话。
为什么有效: 代码告诉你程序做什么,但没有告诉你为什么这样做、有哪些设计备选方案,或者代码做了哪些假设。注释捕捉了设计者的心智模型——抽象——这是系统中最有价值也最易逝的信息。
关键见解:
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 接口注释 | 描述抽象,而非实现 | “返回最接近给定位置的小部件,如果阈值距离内没有小部件则返回 null” |
| 数据结构注释 | 解释不变量和约束 | “列表按优先级降序排序;优先级相同时按插入顺序排序” |
| 实现注释 | 解释为什么,而非是什么 | “// 这里使用二分查找,因为列表总是排序的,并且可能包含 10 万+ 项” |
| 跨模块注释 | 链接相关的设计决策 | “// 此超时必须与 RetryPolicy.java 中的重试间隔匹配” |
| 注释驱动设计 | 在编写代码前先写接口注释 | 首先草拟函数的契约和行为,然后实现 |
核心概念: 战术性编程专注于快速实现功能,每个捷径都会积累复杂性。战略性编程额外投入 10-20% 的精力进行良好设计,将每次变更视为改进系统结构的机会。
为什么有效: 战术性编程在短期内看起来更快,但会逐渐降低代码库质量,使未来的每次变更都更加困难。战略性编程产生的代码库随着时间的推移保持易于修改。前期的小额投资会复利增长——战略性设计的系统在几个月后工作起来更快。
关键见解:
代码应用:
| 上下文 | 模式 | 示例 |
|---|---|---|
| 战术陷阱 | 抵制快速而肮脏的修复 | 不要添加布尔参数来处理“仅此一个特殊情况” |
| 战略性投资 | 在功能工作中改进结构 | 添加功能时,如果模块接口变得笨拙,就重构它 |
| 战术龙卷风 | 识别并干预 | 一个编写 2 倍代码但造成 3 倍维护负担的开发者 |
| 初创公司纪律 | 从第一天起就投资于设计 | 即使在时间压力下也要保持清晰的模块边界和良好的抽象 |
| 渐进式改进 | 每个 PR 修复一个设计问题 | 每个拉取请求至少改进一个抽象或消除一个复杂性片段 |
| 设计评审 | 评估结构,而不仅仅是正确性 | 代码评审应问“这会使系统更简单吗?”而不仅仅是“它能工作吗?” |
| 错误 | 为何失败 | 修复方法 |
|---|---|---|
| 创建过多小类 | 类炎增加了接口而没有增加深度;每个类边界都是认知开销 | 将相关的浅层类合并为具有更简单接口的深层模块 |
| 按时间顺序拆分模块 | “读取,然后处理,然后写入”迫使知识在三个模块间共享 | 围绕信息组织:将共享知识的代码分组到一个模块中 |
| 在接口中暴露实现 | 调用者依赖内部细节;变更到处传播 | 围绕抽象而非实现设计接口;隐藏格式和协议细节 |
| 将注释视为可选 | 设计意图、假设和抽象丢失;新开发者会猜错 | 首先编写接口注释;随着代码演进维护它们 |
| 为所有事物配置参数 | 每个参数都将一个决策推给调用者,增加了认知负荷 | 自动确定行为;提供合理的默认值;最小化所需配置 |
| 快速而肮脏的战术修复 | 每个捷径都会增加少量复杂性;随着时间的推移,系统变得无法工作 | 额外投入 10-20% 的精力进行良好设计;将每次变更视为设计机会 |
| 传递方法 | 仅委托给另一个方法的方法增加了接口而没有增加深度 | 将传递方法合并到调用者或被调用者中 |
| 为特定用例设计 | 专用接口会积累特殊情况并变得臃肿 | 提问“覆盖所有当前需求的最简单接口是什么?” |
| 问题 | 如果答案为“否” | 行动 |
|---|---|---|
| 你能用一句话描述每个模块做什么吗? | 模块做太多事情或目的不明确 | 拆分为具有连贯、可描述职责的模块 |
| 接口是否比实现更简单? | 模块是浅层的——它们将复杂性泄露出去 | 重新设计以隐藏更多内容;将浅层类合并为深层类 |
| 你能更改模块的实现而不影响调用者吗? | 信息正在跨越模块边界泄露 | 识别泄露的知识并将其封装在一个模块内 |
| 接口注释是否描述抽象而非代码? | 设计意图丢失;开发者会误用模块 | 编写解释模块承诺什么而非如何工作的注释 |
| 设计讨论是代码评审的一部分吗? | 评审只捕捉错误,而非复杂性增长 | 将“这会减少还是增加系统复杂性?”添加到评审标准中 |
| 每个模块是否至少隐藏一个重要设计决策? | 模块围绕代码而非信息组织 | 重新组织,使每个模块拥有特定的知识片段 |
| 新团队成员能否在不阅读实现的情况下理解模块边界? | 抽象未记录或泄露过多 | 改进接口注释并简化接口,直到它们不言自明 |
| 你是否将 10-20% 的时间用于设计改进? | 技术债务随着每个功能在积累 | 采用战略性思维;在每个 PR 中包含设计改进 |
此技能基于 John Ousterhout 的软件设计实用指南。如需包含详细示例的完整方法论:
John Ousterhout 是斯坦福大学 Bosack Lerner 计算机科学教授。他是 Tcl 脚本语言和 Tk 工具包的创造者,并共同创立了包括 Electric Cloud 和 Clustrix 在内的多家公司。Ousterhout 获得了众多奖项,包括 ACM 软件系统奖、加州大学伯克利分校杰出教学奖和 USENIX 终身成就奖。他从在斯坦福教授的 CS 190 课程中发展出了《软件设计哲学》,在该课程中,学生从事多阶段软件设计项目,并学习识别和减少复杂性。这本书将数十年构建系统软件和教授软件设计的经验提炼为一套简洁的原则,适用于各种语言、范式和系统规模。目前该书已出第二版,已成为寻求将设计技能从正确性提升到清晰度的软件工程师广泛推荐的资源。
每周安装量
249
代码库
GitHub 星标数
255
首次出现
2026年2月23日
安全审计
安装于
codex240
opencode238
amp237
gemini-cli237
kimi-cli237
cursor237
A practical framework for managing the fundamental challenge of software engineering: complexity. Apply these principles when designing modules, reviewing APIs, refactoring code, or advising on architecture decisions. The central thesis is that complexity is the root cause of most software problems, and managing it requires deliberate, strategic thinking at every level of design.
The greatest limitation in writing software is our ability to understand the systems we are creating. Complexity is the enemy. It makes systems hard to understand, hard to modify, and a source of bugs. Every design decision should be evaluated by asking: "Does this increase or decrease the overall complexity of the system?" The goal is not zero complexity -- that is impossible in useful software -- but to minimize unnecessary complexity and concentrate necessary complexity where it can be managed.
Goal: 10/10. When reviewing or creating software designs, rate them 0-10 based on adherence to the principles below. A 10/10 means deep modules with clean abstractions, excellent information hiding, strategic thinking about complexity, and comments that capture design intent. Lower scores indicate shallow modules, information leakage, tactical shortcuts, or missing design documentation. Always provide the current score and specific improvements needed to reach 10/10.
Six principles for managing complexity and producing systems that are easy to understand and modify:
Core concept: Complexity is anything related to the structure of a software system that makes it hard to understand and modify. It manifests through three symptoms: change amplification, cognitive load, and unknown unknowns.
Why it works: By identifying the specific symptoms of complexity, developers can diagnose problems precisely rather than relying on vague notions of "messy code." The two fundamental causes -- dependencies and obscurity -- provide clear targets for design improvement.
Key insights:
Code applications:
| Context | Pattern | Example |
|---|---|---|
| Change amplification | Centralize shared knowledge | Extract color constants instead of hardcoding #ff0000 in 20 files |
| Cognitive load | Reduce what developers must know | Use a simple open(path) API instead of requiring buffer size, encoding, and lock mode |
| Unknown unknowns | Make dependencies explicit | Use type systems and interfaces to surface what a change affects |
| Dependency management | Minimize cross-module coupling | Pass data through well-defined interfaces, not shared global state |
| Obscurity reduction | Name things precisely | numBytesReceived not ; not |
See: references/complexity-symptoms.md
Core concept: The best modules are deep: they provide powerful functionality behind a simple interface. Shallow modules have complex interfaces relative to the functionality they provide, adding complexity rather than reducing it.
Why it works: A module's interface represents the complexity it imposes on the rest of the system. Its implementation represents the functionality it provides. Deep modules give you a high ratio of functionality to interface complexity. The interface is the cost; the implementation is the benefit.
Key insights:
Code applications:
| Context | Pattern | Example |
|---|---|---|
| Deep module | Hide complexity behind simple API | file.read(path) hides disk blocks, caching, buffering, encoding |
| Shallow module | Avoid thin wrappers that just pass through | A FileInputStream wrapped in BufferedInputStream wrapped in ObjectInputStream |
| Classitis cure | Merge related shallow classes | Combine RequestParser, RequestValidator, into one |
See: references/deep-modules.md
Core concept: Each module should encapsulate knowledge that is not needed by other modules. Information leakage -- when a design decision is reflected in multiple modules -- is one of the most important red flags in software design.
Why it works: When information is hidden inside a module, changes to that knowledge require modifying only that module. When information leaks across module boundaries, changes propagate through the system. Information hiding reduces both dependencies and obscurity, the two fundamental causes of complexity.
Key insights:
Code applications:
| Context | Pattern | Example |
|---|---|---|
| Information hiding | Encapsulate format details | One module owns the HTTP parsing logic; callers get structured objects |
| Temporal decomposition | Organize by knowledge, not time | Combine "read config" and "apply config" into a single config module |
| Format leakage | Centralize serialization | One module handles JSON encoding/decoding rather than spreading json.dumps everywhere |
| Protocol leakage | Abstract protocol details | A MessageBus.send(event) hides whether transport is HTTP, gRPC, or queue |
| Decorator leakage | Use deep wrappers sparingly | Prefer adding buffering inside the file class over wrapping it externally |
See: references/information-hiding.md
Core concept: Design modules that are "somewhat general-purpose": the interface should be general enough to support multiple uses without being tied to today's specific requirements, while the implementation handles current needs. Ask: "What is the simplest interface that will cover all my current needs?"
Why it works: General-purpose interfaces tend to be simpler because they eliminate special cases. They also future-proof the design since new use cases often fit the existing abstraction. However, over-generalization wastes effort and can itself introduce complexity through unnecessary abstractions.
Key insights:
Code applications:
| Context | Pattern | Example |
|---|---|---|
| API generality | Design for the concept, not one use case | A text.insert(position, string) API instead of text.addBulletPoint() |
| Push complexity down | Handle defaults in the module | A web server that picks reasonable buffer sizes instead of requiring callers to configure them |
| Reduce configuration | Determine behavior automatically | Auto-detect file encoding instead of requiring an encoding parameter |
| Avoid over-specialization | Remove use-case-specific methods | One store(key, value, options) instead of , , |
See: references/general-vs-special.md
Core concept: Comments should describe things that are not obvious from the code. They capture design intent, abstraction rationale, and information that cannot be expressed in code. The claim that "good code is self-documenting" is a myth for anything beyond low-level implementation details.
Why it works: Code tells you what the program does, but not why it does it that way, what the design alternatives were, or what assumptions the code makes. Comments capture the designer's mental model -- the abstraction -- which is the most valuable and most perishable information in a system.
Key insights:
Code applications:
| Context | Pattern | Example |
|---|---|---|
| Interface comment | Describe the abstraction, not the implementation | "Returns the widget closest to the given position, or null if no widgets exist within the threshold distance" |
| Data structure comment | Explain invariants and constraints | "List is sorted by priority descending; ties are broken by insertion order" |
| Implementation comment | Explain why, not what | "// Use binary search here because the list is always sorted and can contain 100k+ items" |
| Cross-module comment | Link related design decisions | "// This timeout must match the retry interval in RetryPolicy.java" |
| Comment-driven design | Write the interface comment before the code | Draft the function's contract and behavior first, then implement |
See: references/comments-as-design.md
Core concept: Tactical programming focuses on getting features working quickly, accumulating complexity with each shortcut. Strategic programming invests 10-20% extra effort in good design, treating every change as an opportunity to improve the system's structure.
Why it works: Tactical programming appears faster in the short term but steadily degrades the codebase, making every future change harder. Strategic programming produces a codebase that stays easy to modify over time. The small upfront investment compounds -- systems designed strategically are faster to work with after a few months.
Key insights:
Code applications:
| Context | Pattern | Example |
|---|---|---|
| Tactical trap | Resist quick-and-dirty fixes | Don't add a boolean parameter to handle "just this one special case" |
| Strategic investment | Improve structure during feature work | When adding a feature, refactor the module interface if it has become awkward |
| Tactical tornado | Recognize and intervene | A developer who writes 2x the code but creates 3x the maintenance burden |
| Startup discipline | Invest in design from day one | Clean module boundaries and good abstractions even under time pressure |
| Incremental improvement | Fix one design issue per PR | Each pull request improves at least one abstraction or eliminates one piece of complexity |
| Design reviews | Evaluate structure, not just correctness | Code reviews should ask "does this make the system simpler?" not just "does it work?" |
See: references/strategic-programming.md
| Mistake | Why It Fails | Fix |
|---|---|---|
| Creating too many small classes | Classitis adds interfaces without adding depth; each class boundary is cognitive overhead | Merge related shallow classes into deeper modules with simpler interfaces |
| Splitting modules by temporal order | "Read, then process, then write" forces shared knowledge across three modules | Organize around information: group code that shares knowledge into one module |
| Exposing implementation in interfaces | Callers depend on internal details; changes propagate everywhere | Design interfaces around abstractions, not implementations; hide format and protocol details |
| Treating comments as optional | Design intent, assumptions, and abstractions are lost; new developers guess wrong | Write interface comments first; maintain them as the code evolves |
| Configuration parameters for everything | Each parameter pushes a decision to the caller, increasing cognitive load | Determine behavior automatically; provide sensible defaults; minimize required configuration |
| Quick-and-dirty tactical fixes |
| Question | If No | Action |
|---|---|---|
| Can you describe what each module does in one sentence? | Modules are doing too much or have unclear purpose | Split into modules with coherent, describable responsibilities |
| Are interfaces simpler than implementations? | Modules are shallow -- they leak complexity outward | Redesign to hide more; merge shallow classes into deeper ones |
| Can you change a module's implementation without affecting callers? | Information is leaking across module boundaries | Identify leaked knowledge and encapsulate it inside one module |
| Do interface comments describe the abstraction, not the code? | Design intent is lost; developers will misuse the module | Write comments that explain what the module promises, not how it works |
| Is design discussion part of code reviews? | Reviews only catch bugs, not complexity growth | Add "does this reduce or increase system complexity?" to review criteria |
| Does each module hide at least one important design decision? | Modules are organized around code, not around information | Reorganize so each module owns a specific piece of knowledge |
| Can a new team member understand module boundaries without reading implementations? |
This skill is based on John Ousterhout's practical guide to software design. For the complete methodology with detailed examples:
John Ousterhout is the Bosack Lerner Professor of Computer Science at Stanford University. He is the creator of the Tcl scripting language and the Tk toolkit, and co-founded several companies including Electric Cloud and Clustrix. Ousterhout has received numerous awards, including the ACM Software System Award, the UC Berkeley Distinguished Teaching Award, and the USENIX Lifetime Achievement Award. He developed A Philosophy of Software Design from his CS 190 course at Stanford, where students work on multi-phase software design projects and learn to recognize and reduce complexity. The book distills decades of experience in building systems software and teaching software design into a concise set of principles that apply across languages, paradigms, and system scales. Now in its second edition, the book has become a widely recommended resource for software engineers seeking to improve their design skills beyond correctness and into clarity.
Weekly Installs
249
Repository
GitHub Stars
255
First Seen
Feb 23, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex240
opencode238
amp237
gemini-cli237
kimi-cli237
cursor237
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
106,200 周安装
Schema结构化数据完整指南:实现富媒体结果与AI搜索优化(2025)
244 周安装
实用程序员框架:DRY、正交性等七大元原则提升软件质量与架构设计
244 周安装
Python PDF处理指南:合并拆分、提取文本表格、创建PDF文件教程
244 周安装
Ruby on Rails 应用开发指南:构建功能全面的Rails应用,包含模型、控制器、身份验证与最佳实践
245 周安装
代码规范库技能 - 多语言编码标准库,支持Python/Go/Rust/TypeScript等自动加载
245 周安装
阿里云Model Studio模型爬取与技能自动生成工具 - 自动化AI技能开发
245 周安装
nretryDelayMsdelayRequestProcessorRequestHandler| Method depth | Methods should do something substantial | A delete(key) that handles locking, logging, cache invalidation, and rebalancing |
| Interface simplicity | Fewer parameters, fewer methods | config.get(key) with sensible defaults, not 15 constructor parameters |
storeUser()storeProduct()storeOrder()| Somewhat general | General interface, specific implementation | A Datastore interface that currently backs onto PostgreSQL but does not expose SQL concepts |
| Each shortcut adds a small amount of complexity; over time the system becomes unworkable |
| Invest 10-20% extra in good design; treat every change as a design opportunity |
| Pass-through methods | Methods that just delegate to another method add interface without adding depth | Merge the pass-through into the caller or the callee |
| Designing for specific use cases | Special-purpose interfaces accumulate special cases and become bloated | Ask "what is the simplest interface that covers all current needs?" |
| Abstractions are not documented or are too leaky |
| Improve interface comments and simplify interfaces until they are self-evident |
| Are you spending 10-20% of time on design improvement? | Technical debt is accumulating with every feature | Adopt a strategic mindset; include design improvement in every PR |