关于 solid
solid 是一项开发技能,介绍五项 SOLID 设计原则,并提供实践建议和代码审查问题,面向构建模块化、可测试系统的开发者。它也说明了何时不必增加抽象。
主要功能
- 01讲解全部五项 SOLID 原则
- 02列举常见设计违例
- 03提供实践应用建议
- 04提供代码审查清单
- 05提示避免过度抽象
适用场景
- 01审查模块和组件的职责划分
- 02重构臃肿接口或分支逻辑
- 03评估依赖边界是否便于测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jordancoin/ codingskills --skill solid安装后在 Claude Code 中输入/solid确认已加载。
详细介绍
SOLID 原则
应用之前
如果 .agents/stack-context.md 存在,请先阅读。根据检测到的技术栈,使用符合其惯例的模式来应用此原则。涉及框架的细节,请使用 context7 MCP 或网络搜索,不要猜测。
原则
五项设计原则,可构建模块化、易于测试且可靠的系统。这些原则最初是针对面向对象设计提出的,但其核心思想适用于任何编程范式——函数、模块、服务或组件。
为什么这对生产环境很重要
遵循 SOLID 原则的代码更易于修改。生产系统会持续变化——添加新功能、修复 Bug、集成服务、扩展规模。按照 SOLID 原则设计的系统能够局部吸收变化,而不必引发连锁修改。这意味着回归问题更少、功能交付更快,出错时影响范围也更小。
S — 单一职责原则
一个模块应该只有一个,且只有一个,变更原因。
每个模块、函数或组件都应只负责一项功能。收到变更请求时,你应该能够直接判断需要编辑哪个文件,而不必四处查找。
违反原则的表现:
- 一个函数同时验证输入、查询数据库、应用业务逻辑并格式化响应
- 一个“utils”文件不断膨胀,因为其他功能都找不到明确的归属
- 一个类会随着系统中任何功能的变化而修改
应用方法:
- 如果一个函数做了两件事,就将其拆分为两个函数
- 如果一个模块既负责“做什么”,又负责“怎么做”,就将策略与机制分开
- 根据职责为模块命名——如果名称含糊(“Manager”“Helper”“Utils”),说明职责不明确
O — 开闭原则
软件应当对扩展开放,对修改关闭。
你应该能够添加新行为,而无需修改现有的可用代码。这并不意味着你永远不动现有代码,而是说你应当通过设计,让常见扩展点无需修改现有代码。
违反原则的表现:
- 每添加一项功能,
switch语句就要增加一个分支 - 一个函数对不断扩大的类型列表逐一检查
if type == "x" - 每次添加新集成都必须修改一个中央分发函数
应用方法:
- 使用分发表、注册表或多态,而不是根据类型进行分支
- 围绕契约(接口、协议、函数签名)进行设计,让新的实现能够满足这些契约
- 使用插件架构——但前提是确实有多个插件(参见 YAGNI)
L — 里氏替换原则
子类型必须能在任何需要父类型的地方使用,且不会带来意外。
如果代码接受某个基类型,那么该类型的任何实现都应能正常工作。不要特殊处理,也不要添加“除非类型是 X”的判断。
违反原则的表现:
- 子类对继承的方法抛出“未实现”错误
- 接受通用类型后,再通过类型检查(
instanceof、typeof)来决定行为 - 使用方必须知道当前使用的是哪个具体实现
应用方法:
- 如果子类型无法满足父类型的完整契约,就不应将其作为子类型——改用组合
- 根据使用方的需求设计接口,而不是根据实现能够提供什么来设计
- 通过替换进行测试:更换实现,并验证系统仍能正确运行
I — 接口隔离原则
不应强迫任何使用方依赖自己不会用到的方法。
让接口保持小巧且专注。只读取数据的使用方,不应依赖一个同时包含写入、删除和管理操作的接口。
违规情况包括:
- 包含 20 多个方法的“上帝接口”,大多数实现只能部分满足其要求
- 使用方导入了大型模块,却只用到其中一个函数
- 测试中的模拟对象需要桩接 15 个方法,而测试实际上只会调用其中 2 个
应用方式:
- 将大型接口拆分为按角色划分的接口:用
Reader、Writer、Admin代替Repository - 在没有接口的语言中,将这一原则应用于模块边界——只导出使用方需要的内容
- 如果测试模拟对象很难编写,说明你的接口范围太广
D — 依赖倒置原则
高层策略不应依赖底层细节。两者都应依赖抽象。
业务逻辑不应直接导入数据库驱动、HTTP 客户端或文件系统操作。它应依赖由这些细节实现的抽象。
违规情况包括:
- 业务逻辑直接实例化数据库连接、HTTP 客户端或外部 SDK
- 不启动真实基础设施就无法测试业务规则
- 更换数据库或 API 提供商需要重写业务逻辑
应用方式:
- 通过依赖注入传入依赖,而不是在内部构造它们
- 在业务逻辑的边界定义接口——让基础设施适配这些接口
- 在函数式代码中:接受函数作为参数,而不是将对特定实现的调用硬编码
边界
- SOLID 是一组启发式原则,而非法律。 小型脚本、原型和胶水代码不需要完整遵循 SOLID。在这些原则能降低变更成本的地方应用它们。
- 不要过度抽象。 将 SOLID 推向极端会产生大量细小的类和接口,比原来的单体更难理解。抽象必须能服务多个使用方或实现有意义的可测试性,才能体现其价值。
- 与 KISS 的张力: 每个接口、注入点和抽象都会增加一层间接性。在系统确实会沿这些方面变化的地方应用 SOLID,而不是为了预防而应用。
- 函数式代码在某些方面天然满足 SOLID。 纯函数天然符合 SRP。高阶函数天然符合 DIP。应将 SOLID 的重点放在最重要的架构边界上。
代码审查清单
- 每个模块是否都有明确且单一的职责?
- 能否在不修改现有可正常工作的代码的情况下添加新行为?
- 接口是否足够小,使使用方不必依赖自己不会用到的内容?
- 依赖是否通过注入传入,而不是硬编码?
- 是否可以替换某个实现(例如数据库、API 客户端),而不触及业务逻辑?
相关技能
- separation-of-concerns:用于分层结构(SoC 说明“是什么”,SOLID 说明“怎么做”)
- law-of-demeter:用于减少组件之间的耦合
- yagni:用于避免通过过早抽象而过度应用 SOLID