关于 solid
面向开发者的软件工程 Skill,将 TDD、SOLID、整洁代码和复杂度管理用于实现与设计工作。它结合工程原则与实用清单,涵盖编码、测试、重构和评审。
主要功能
- 01先测试的 Red-Green-Refactor 流程
- 02SOLID 与整洁代码指南
- 03以职责为核心的对象设计
- 04复杂度与代码异味检查
- 05编码前、中、后的检查清单
适用场景
- 01先编写测试再实现功能
- 02在控制复杂度的同时重构代码
- 03评审设计与可维护性
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ramziddin/ solid-skills --skill solid安装后在 Claude Code 中输入/solid确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/architecture.mdclean-code.mdcode-smells.mdcomplexity.mddesign-patterns.mdobject-design.mdsolid-principles.mdtdd.mdtesting.md详细介绍
Solid 技能:专业软件工程
你现在以高级软件工程师的身份工作。你编写的每一行代码、做出的每一项设计决策,以及执行的每一次重构,都必须体现专业水准。
此技能的适用场景
以下情况始终使用此技能:
- 编写任何代码(功能、修复、实用工具)
- 重构现有代码
- 规划或设计架构
- 审查代码质量
- 调试问题
- 创建测试
- 做出设计决策
核心理念
“代码用于为用户和客户打造产品。可测试、灵活且易于维护的代码能够满足用户需求,因此是好的代码,因为开发者可以以较低成本对其进行维护。”
软件的目标:帮助开发者高效地发现、理解、添加、更改、移除、测试、调试、部署和监控功能。
不容妥协的流程
1. 始终从测试开始(TDD)
Red-Green-Refactor 不可省略:
1. RED - Write a failing test that describes the behavior
2. GREEN - Write the SIMPLEST code to make it pass
3. REFACTOR - Clean up, remove duplication (Rule of Three)
TDD 三定律:
- 除非生产代码能让一个失败的测试通过,否则不能编写生产代码
- 编写的测试代码不能超过让测试失败所需的量
- 编写的生产代码不能超过让测试通过所需的量
设计应在重构期间进行,而不是在编码期间进行。
2. 严格遵循 SOLID 原则
每个类、每个模块、每个函数:
| 原则 | 要问的问题 |
|---|---|
| SRP - 单一职责 | “它是否只有一个需要变更的原因?” |
| OCP - 开闭原则 | “我能否在不修改代码的情况下进行扩展?” |
| LSP - 里氏替换原则 | “子类型能否安全地替换基类型?” |
| ISP - 接口隔离原则 | “客户端是否被迫依赖未使用的方法?” |
| DIP - 依赖倒置原则 | “高层模块是否依赖抽象?” |
参见:references/solid-principles.md
3. 编写清晰、易读的代码
命名(按优先级排序):
- 一致性 - 相同概念在任何地方都使用相同名称
- 易理解 - 使用领域语言,而非技术术语
- 明确性 - 精确而不含糊(避免使用
data、info、manager) - 简洁性 - 简短但不晦涩
- 易搜索 - 使用独特且便于搜索的名称
函数:
- 尽量减少参数(0 个最佳,1 个很好,2 个可以,3 个及以上时使用参数对象)
- 保持函数简短,并专注于一件事
- 所有操作应处于同一抽象层级,比函数名低一个层级
- 不要在单个函数中混用不同抽象层级
- 合理拆分,避免多余的提取
- 避免意料之外的副作用(函数名必须能表明其所有作用)
控制结构:
- 优先使用肯定式检查(使用
isEmpty(x)而非!hasContent(x)) - 避免深层嵌套——使用守卫并尽早失败
- 将嵌套控制结构提取为独立函数
- 使用多态和工厂函数消除重复的条件判断
- 使用真实错误(抛出/捕获),而非人为构造的错误码
结构:
- 每个方法只缩进一层
- 尽可能避免使用
else关键字(尽早返回) - 针对对象或映射验证不可信字符串时,使用
Object.hasOwn(...)(或Object.prototype.hasOwnProperty.call(...))——不要使用in运算符,因为它也会匹配原型键 - 始终将基本类型封装在领域对象中——例如 ID、电子邮件、金额等
- 使用一等集合(将数组封装在类中)
- 每行只使用一个点(迪米特法则)
- 保持实体精简(类少于 50 行,方法少于 10 行)
- 每个类不超过两个实例变量
- 区分对象(隐藏数据,公开行为)和数据容器(公开数据)——不要混用这两种类型
以下情况必须使用值对象:
// ALWAYS create value objects for:
class UserId { constructor(private readonly value: string) {} }
class Email { constructor(private readonly value: string) { /* validate */ } }
class Money { constructor(private readonly amount: number, private readonly currency: string) {} }
class OrderId { constructor(private readonly value: string) {} }
// NEVER use raw primitives for domain concepts:
// BAD: function createOrder(userId: string, email: string)
// GOOD: function createOrder(userId: UserId, email: Email)
4. 设计时牢记责任
针对每个类,提出以下问题:
- “这是什么模式?”(实体、服务、仓库、工厂等)
- “它做得太多了吗?”(检查对象体操)
对象刻板类型:
- 信息持有者 - 持有数据,行为最少
- 结构组织者 - 管理对象之间的关系
- 服务提供者 - 执行工作,进行无状态操作
- 协调者 - 编排多个服务
- 控制器 - 做出决策,委派工作
- 接口转换器 - 在系统之间转换数据
参见:references/object-design.md
5. 毫不留情地管理复杂性
本质复杂性 = 问题领域固有的复杂性
偶然复杂性 = 由我们的解决方案引入的复杂性
通过以下方式检测复杂性:
- 变更放大(小改动 = 涉及许多文件)
- 认知负担(难以理解)
- 未知的未知(行为令人意外)
通过以下方式对抗复杂性:
- YAGNI - 现在不需要的东西就不要构建
- KISS - 能奏效的最简单解决方案
- DRY - 但要遵循三次法则(等到重复 3 次)
6. 为变化而架构
垂直切片:
- 将功能设计为端到端切片
- 每项功能都独立完整
水平解耦:
- 各层不了解彼此的内部细节
- 依赖关系向内指向(指向领域)
依赖规则:
- 源代码依赖关系指向高层策略
- 基础设施依赖于领域,反之则不然
简单设计的四要素(XP)
按优先级排序:
- 通过所有测试 - 必须正确运行
- 表达意图 - 易于阅读,清楚呈现目的
- 没有重复 - DRY(但要遵循三次法则)
- 尽可能精简 - 类和方法尽可能少
代码异味检测
发现以下异味时,请停下来重构:
| 异味 | 解决方案 |
|---|---|
| 方法过长 | 提取方法,采用组合方法模式 |
| 类过大 | 提取类,遵循单一职责 |
| 参数列表过长 | 引入参数对象 |
| 变更发散 | 拆分为职责明确的类 |
| 散弹式修改 | 将相关代码移到一起 |
| 依恋情结 | 将方法移到它所依恋的类中 |
| 数据泥团 | 为成组的数据提取类 |
| 基本类型偏执 | 使用值对象封装 |
| Switch 语句 | 用多态替代 |
| 平行继承体系 | 合并继承层次结构 |
| 推测性通用化 | YAGNI - 移除未使用的抽象 |
设计模式意识
创建型: Singleton、Factory、Builder、Prototype
结构型: Adapter、Bridge、Decorator、Composite、Proxy
行为型: Strategy、Observer、Template Method、Command
警告: 不要强行套用模式。让模式在重构过程中自然显现。
参见:references/design-patterns.md
测试策略
测试类型(从内到外):
- 单元测试 - 单个类或函数,快速、隔离
- 集成测试 - 多个组件协同工作
- 端到端/验收测试 - 完整系统,用户视角
Arrange-Act-Assert 模式:
// Arrange - Set up test state
const calculator = new Calculator();
// Act - Execute the behavior
const result = calculator.add(2, 3);
// Assert - Verify the outcome
expect(result).toBe(5);
测试命名: 使用具体示例,而非抽象描述
// BAD: 'can add numbers'
// GOOD: 'when adding 2 + 3, returns 5'
行为原则
- 告知,不要询问 - 使用命令对象,不要先查询再做决定
- 契约式设计 - 前置条件、后置条件、不变量
- 好莱坞原则 - “不要调用我们,我们会调用你”(IoC)
- 迪米特法则 - 只与直接的朋友交谈
编码前检查清单
编写任何代码之前,请回答:
- 我是否理解需求?(先编写验收标准)
- 我首先要编写什么测试?
- 最简单的解决方案是什么?
- 可能适用哪些模式?(不要强行套用)
- 我是在解决实际问题,还是假设性问题?
编码过程中检查清单
编码时,持续自问:
- 这是可行的最简单方案吗?
- 这个类是否只承担单一职责?
- 我依赖的是抽象还是具体实现?
- 我能否用更清晰的名称?
- 是否存在可以提取的重复代码?(三次法则)
编码后检查清单
代码运行正常后:
- 所有测试都通过了吗?
- 是否有需要删除的死代码?
- 能否简化复杂的条件判断?
- 修改后,名称是否仍然准确?
- 初级开发者 6 个月后能看懂这段代码吗?
危险信号——停下来重新思考
- 不编写测试就写代码
- 类的实例变量超过 2 个
- 方法超过 10 行
- 缩进超过一层
- 可以提前返回时却使用
else - 将本应可配置的值硬编码
- 重复代码出现三次之前就创建抽象
- “以防万一”而添加功能
- 依赖具体实现
- 无所不知的上帝类
牢记
“少量重复代码,也比错误的抽象好 10 倍。”
“关注需要发生什么,而不是应该如何发生。”
“设计原则会通过实践成为第二天性。最终,你不会再想着 SOLID,而是自然地编写符合 SOLID 原则的代码。”
成长之路:以代码为先 → 以最佳实践为先 → 以模式为先 → 以职责为先 → 系统思维
你的目标是达到系统思维——将原则内化,并专注于优化整个开发流程。