关于 maintainable-code
maintainable-code 是供编码 Agent 处理源代码任务的 Skill,涵盖可维护性、生产环境韧性与可测试性。它提供按任务情境选择的设计指导,强调小幅修改、明确边界和适当验证。
主要功能
- 01指导清晰的代码结构与命名
- 02为后台任务和分布式流程提供韧性指导
- 03建议可替换依赖边界与测试替身
- 04按实现、重构、评审等任务提供决策路径
- 05强调适当的验证与可恢复性
适用场景
- 01实现功能或修复缺陷时遵循本地架构
- 02重构时分步保留现有行为
- 03为任务队列或外部 API 设计故障处理
- 04评审代码中的可测试性与维护风险
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jpcaparas/ skills --skill maintainable-code安装后在 Claude Code 中输入/maintainable-code确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
AGENTS.mdmetadata.jsonREADME.mdskill-card.prompt.mdagents/.gitkeepassets/.gitkeepevals/evals.jsonevals/files/runtime-defaults.tsreferences/commenting.mddecomposition.mddependency-boundaries.mddistributed-systems.mdgotchas.mdguardrails-and-quality-gates.mdimplementation-plans.mdjobs-and-queues.mdobservability.mdprinciples.mdresilience.mdreview-rubric.mdsource-notes.mdtest-doubles.mdscripts/analyze_app_resilience.pyanalyze_maintainability.pyanalyze_mockability.pytest_skill.pyvalidate.pytemplates/maintainability-review.mdmockability-review.mdresilience-review.md详细介绍
maintainable-code
编写、编辑和审查代码,让人类维护者无需破解晦涩技巧,就能理解、测试并在日后修改代码;无需连接调试器,就能排查生产故障;并能在测试中替换依赖,而不扭曲生产环境的设计。
这项技能整合了过去彼此独立的三项关注点:通用可维护性、生产应用韧性(作业、队列、重试、可观测性)以及可模拟性(依赖边界和测试替身)。
被动触发
只要任务涉及源代码,就在后台加载此技能,即使用户没有提及可维护性也一样。这包括面向生产环境的运行时行为,例如后台工作器、定时任务、Webhook、支付流程、导入/导出、通知、API 集成、数据库状态转换、缓存、文件上传、队列、cron、身份验证/会话流程、日志记录、跟踪、告警或可靠性行为。也包括任何依赖协作者、I/O、时间、随机性、持久化、配置、网络调用、SDK、进程状态或框架上下文的行为。
小型编辑要保持轻量:默默应用核心规则,只提及会影响最终实现的决策。如果要添加或修改测试,且 {{ skill:maintainable-tests }} 可用,也加载它。
决策树
当核心规则或本地证据不足以指导不明显的设计决策时,使用以下路径。仅仅主题匹配,并不意味着必须阅读一组参考资料。
-
实现新功能或修复错误: 优先匹配本地架构。让改动保持小巧、类型明确、命名直白,并通过范围最窄且有意义的验证。面向具备扎实基础知识、但对本系统背景了解不完整的未来维护者编写代码。
-
重构现有代码: 借助充分的现有覆盖测试,分小步保留行为。边界不清时参考
references/decomposition.md;纠缠的副作用妨碍安全验证时,参考references/dependency-boundaries.md。仅在必要时添加特征化测试或替换点。 -
审查代码: 审查发现必须引用具体代码,并说明对维护者的影响。严重程度或范围难以判断时,参考
references/review-rubric.md;需要解释不明显的运维背景时,参考references/commenting.md。 -
为其他 Agent 或团队成员编写计划: 清楚说明目标、范围和验证方式,不依赖会话上下文。交接复杂度较高时参考
references/implementation-plans.md;需要保留不明显的理由时参考references/commenting.md。说明会影响工作的失败处理和依赖决策,但不必固定章节数量。 -
编写运维代码、CI 工作流、迁移、生成的胶水代码或密集命令流水线: 对不明显的意图、不变量和外部约束添加注释。注释放置位置或说明方式不明确时,参考
references/commenting.md;不要赘述显而易见的代码。 -
添加后台作业、队列、cron、Webhook、导入/导出、电子邮件、支付捕获或通知: 针对适用的故障模式,确保安全处理重复请求、限制工作量并具备恢复能力。复用已验证的框架机制或现有保障。仍有未解决风险时,参考
references/resilience.md或references/jobs-and-queues.md,不要默认添加额外机制。 -
涉及多个服务、外部 API、事件发布、分布式数据变更或异步工作流: 限制等待和重试,防止有害的重复操作,并考虑部分完成的情况。所有权或保障机制不明确时,参考
references/distributed-systems.md;outbox/inbox、对账和补偿都是可选方案,并非通用必备方案。 -
添加日志记录、指标、追踪、告警、仪表板或事件诊断: 使用能够说明用户影响和恢复情况的现有信号。当诊断缺口确实需要埋点时,查阅
references/observability.md,不要为每次编辑都添加遥测。 -
选择严格的运行时默认值、按环境划分的安全护栏、兼容性策略或仓库质量门禁: 区分已配置、适用、已选定和成功应用的状态;将危险操作设为选择加入;分阶段引入会改变行为的默认值;并验证所声明的兼容范围。需要进一步设计策略时,查阅
references/guardrails-and-quality-gates.md。 -
添加调用依赖项的代码,或使难以测试的代码变得可替换: 将外部集成置于精简的端口/适配器或等效的本地边界之后;将领域策略与构造、配置和传输分开。在边界处控制时间、随机性和环境读取。现有接缝不够用时,查阅
references/dependency-boundaries.md。 -
选择如何在测试中替换依赖项: 优先选择能够证明行为且能力最弱的替身:固定答案用桩,逼真的内存行为用伪实现,交互契约用模拟对象或间谍对象。替换方案或适配器漂移存在不明显的风险时,查阅
references/test-doubles.md。 -
不确定某种设计是否易于维护: 选择能减少后续读者负担、又不会隐藏重要领域行为的方案。查阅
references/principles.md,或针对具体未解决风险的参考文档。 -
用户要求巧妙、精简或广泛抽象: 按照用户要求的取舍处理,无需让他们重新说明。保持正确性、类型和安全性;在可行时限制巧妙实现的范围,并且只说明影响重大的维护成本。
快速参考
| 情况 | 默认操作 |
|---|---|
| 新代码 | 遵循现有的本地模式,使用精确名称、强类型和直接控制流 |
| 重复逻辑 | 只有当重复内容有相同的变更原因时才提取 |
| 复杂分支 | 尽早规范化输入,防范无效情况,然后让正常路径一目了然 |
| 运维脚本或 CI YAML | 为每个阶段命名,并注释不明显的命令组、外部 API 特性、制品契约和故障处理 |
| 大型函数 | 按稳定职责拆分,而不是按任意行数拆分 |
| 新抽象 | 至少要求两个真实调用点,或一个需要保护的明确边界 |
| 隐式框架行为 | 如果兼容性证据支持,优先采用失败时明确报错或不可变的默认值;将开关视为行为迁移 |
| 危险或会覆盖数据的操作 | 默认关闭;交互式工具使用确认机制,程序化契约则明确意图、范围、前置条件、幂等性和恢复方式 |
| 质量策略 | 提供一个检查模式命令,组合已配置的各层:格式化/代码检查、分析、行为测试;只有存在相应工具时才运行安全的重构试运行 |
| 版本兼容性 | 使用技术栈支持的接缝——运行时能力检查、版本化适配器、构建标志或条件编译——并测试最低支持组合和当前版本 |
| 注释 | 在最小的有效范围内解释不明显的理由或约束;仅在有助于说明流程时使用 ASCII 图示 |
| 计划 | 提供足够的范围、本地上下文和验证证据,以达成所请求的结果 |
弹性:
仅在存在相应风险时应用这些做法。只要现有数据库、提供商、框架和平台的保证覆盖了该操作的范围和配置,就应将其视为有效保障;仅针对缺口添加机制。
| 情况 | 默认操作 |
|---|---|
| 浏览器重复提交、Webhook 重放、重试或工作进程重启 | 通过天然幂等性、唯一性约束或持久化意图键,防止有害的重复影响 |
| 十位用户触发同一个高开销任务 | 原子地合并相同且已授权的意图,并限制并发执行;复用现有任务状态 |
任务在 pending 或 running 状态停留过久 | 确保能够检测到过期工作,并安全恢复或将其置为终态;现有队列恢复机制足够时优先使用 |
| 远程 API 调用 | 确保连接等待和请求等待时间受限;如需重试,应对错误分类,并采用有界且安全的退避策略 |
| 队列负载激增 | 使用现有容量控制或反压机制限制资源使用并保护共享依赖项 |
| 数据库写入后的副作用 | 确保不会丢失必要的交接;使用事务机制、发件箱或其他经过验证的恢复路径 |
| 多步骤分布式工作流 | 明确状态和部分完成结果;必要时进行协调或补偿 |
| 部分服务中断 | 优先降低低价值功能的服务能力,并保障用户的核心任务 |
| 记录请求/任务进度 | 包含识别工作、结果和恢复决策所需的安全上下文 |
| 指标 | 衡量相关的用户健康状况和容量风险;添加计数器前先复用现有指标 |
| 跟踪 | 对无法解释的跨边界延迟或故障添加跨度,而非对微小的本地辅助函数添加跨度 |
| 告警 | 仅在影响用户或自动化处理已耗尽时触发页面通知;只有在有助于诊断或采取行动时才使用非页面通知信号 |
可测试性:
| 情况 | 默认操作 |
|---|---|
| 直接访问网络、数据库、文件、时钟、随机数、环境变量或队列 | 将其保留在外层边界,或通过小型契约传入 |
| 业务规则与 SDK 调用混杂 | 将规则提取为确定性函数,并注入面向 SDK 的适配器 |
| 构造函数在内部创建客户端 | 除非本地惯例另有规定,否则从调用方接收客户端、工厂或配置 |
| 静态/全局单例依赖项 | 优先在边界使用显式协作者、上下文对象或精简封装 |
| 框架处理程序 | 在处理程序中保留解析和响应格式化;将策略移至可调用服务/函数 |
| 测试需要真实的外部服务 | 在连接共享基础设施前,使用伪对象、存根、契约测试或本地测试容器 |
| 接口看起来有用 | 仅在确有替换需求、需要保护边界或语言惯例要求时添加 |
| 评审中发现不可模拟的代码 | 指出隐藏依赖,并展示最小修复路径,不要一概要求重写 |
| 测试 | 复用足够的覆盖;在进行高风险变更前,补足行为或回归测试缺口 |
| 评审 | 优先关注缺陷、令人困惑的边界、缺失的测试以及未来变更风险 |
核心规则
清晰性与结构:
- 优先考虑下一位胜任的维护者,而不是展示高深技巧。假设他们具备扎实的基础,但不了解你脑海中的系统历史。
- 在命名、提取或引入模式之前,先阅读周边代码。
- 让行为贴近能够解释它的数据和策略。
- 优先使用朴素、类型明确的数据结构,而不是字符串、杂乱的选项集合或隐藏约定。
- 让函数保持在单一且稳定的抽象层级:编排、策略、转换或 I/O。
- 在语言和代码库支持的情况下,让无效状态难以表示。
- 只对名称和结构无法表达的非显而易见的理由、不变量或约束添加注释。将说明放在相关的类、方法、属性、分支或代码块旁;不要添加需要用户删除的评论。
- 对不确定、与版本相关或影响重大的语言/框架声明,依据项目实际技术栈的官方资料进行核实。在有助于核实声明或用户提出要求时,附上来源链接;默认不要研究已知基础知识,也不要夸大文档所述行为。
- 当紧凑的 ASCII 图示有助于说明非显而易见的数据流、状态转换、队列、重试、所有权或边界跨越时,将其放在注释或文档块中。确保图示与说明一致;如果代码变更导致其中任一内容过时,立即更新两者。
- 对非平凡行为进行重构时,做好充分验证;复用现有测试,仅针对覆盖缺口添加特征测试。
- 如果你有意保留复杂性,请在最终答复中说明其中影响重大的权衡。
- 在本地兼容性允许的范围内,从类型、分析、框架行为和边界验证等层面逐步提高严格性。启用会改变行为的严格模式之前,先添加迁移覆盖。
- 在入口处分开处理取消、可选能力缺失、操作失败和意外缺陷。保留可采取行动的原因,不要把所有结果都折叠成
false、null或一个通用错误。 - 默认关闭危险能力。强制标志可以跳过交互,但不得绕过授权、验证、不变量或恢复检查。
生产行为与恢复:
- 将重试、重复投递、并发、延迟、部分故障、部署重启和过期状态视为正常输入,而非罕见意外。
- 当重复操作可能损害正确性或浪费资源时,应区分新的意图与重放。天然幂等性或现有身份标识可能已经足够;没有必要时,不要持久化新的工作记录。
- 对非平凡的持久化工作,明确允许的状态转换、所有权、终止结果,以及脱离卡死状态的有界路径。只使用工作流所需的状态和字段。
- 确保相关故障都有安全的恢复方式或明确的终止结果。优先使用现有的自动恢复机制;仅在现有机制未覆盖风险时,才添加重试、清理器、协调机制或死信处理。
- 限制资源使用,避免单个工作流耗尽共享容量。现有并发限制、租约、速率限制或背压可能已经足够;队列和隔离舱并非必须添加。
- 在能够实施保障的边界处,防止有害的重复副作用。添加去重机制之前,先核实天然幂等性、数据库约束、提供方密钥或其他现有保障的适用范围。
- 确保跨进程、网络、队列、数据库或提供方边界的等待都有时限。遵守现有的有效截止时间,而不是添加相互竞争的超时层;确保不确定的结果能够安全恢复。
- 使用足够的可观测性来诊断相关的用户健康和容量故障;延迟、流量、错误和饱和度是有用的排查起点,而非遥测配额。绝不记录机密、令牌、原始支付详情、完整个人身份信息(PII)、会话 Cookie 或提供方凭据。
可测试性: 23. 将决策与副作用分离。领域策略应能够在不依赖真实网络、时钟、文件、数据库、队列或随机性的情况下进行测试。 24. 在模块、构造函数、函数、处理程序或上下文边界处明确协作者,并优先采用范围精简的契约,描述调用方需要什么,而非依赖项能做的一切。 25. 对有状态行为使用伪对象,对固定响应使用存根,对重要交互使用 mock/spy,并为适配器编写契约测试。代码可被 mock,不等于测试中要大量使用 mock。 26. 不要添加只有想象中的替代实现的抽象,也不要仅仅为了让测试能够访问私有内部实现而将其暴露出来。只有一个清晰替换点的简单代码,胜过层层叠加却无人使用的接口。
可维护性门槛
完成代码更改前,先在心中检查此门槛,并在条件允许时使用本地工具检查:
| 门槛 | 通过条件 |
|---|---|
| 意图 | 读者无需逐行阅读,就能从名称和结构看出代码的作用 |
| 范围 | 更改仅涉及责任范围最小的部分,并避免无关的清理工作 |
| 边界 | I/O、编排、领域策略和呈现没有在无充分理由的情况下纠缠在一起 |
| 类型 | 数据契约足够明确,能让编辑器、编译器或测试发现误用 |
| 注释 | 密集或令人意外的代码在合适层级(类/模块、方法/函数、属性/字段,以及适用时的本地代码块)配有易于理解的注释/文档块;任何 ASCII 图示都与文字说明和代码一致 |
| 测试 | 最可能出现的回归有针对性测试,或明确说明了验证缺口 |
| 错误 | 在能够补充有用上下文的边界处处理失败情况 |
| 默认值 | 严格性、不可变性和环境门控均有明确说明;适当情况下可配置,并有兼容性证据支持采用 |
| 自动化 | 本地和 CI 检查模式执行相同的版本化质量策略,并覆盖受支持的依赖范围 |
| 交接 | 最终回复列出关键文件、已运行的验证,以及任何剩余风险 |
自愈门槛
对于生产运行时更改,请检查下方适用的保障措施。现有机制足够时应予以接受;此表并不要求添加每种状态、信号或恢复组件。
| 门槛 | 通过条件 |
|---|---|
| 身份 | 通过天然幂等性、持久化意图标识、唯一性或其他经过验证的边界保障,防止产生有害的重复副作用 |
| 状态 | 非简单工作对实际使用的状态有明确的转换、终止和恢复行为 |
| 并发 | 共享资源设有唯一性约束、锁、租约、速率限制或工作进程上限 |
| 时间 | 远程调用、任务、锁和待处理状态均设有超时或过期时间 |
| 重试 | 对可重试错误进行分类,限制重试次数,加入抖动,并确保重复副作用不会造成问题 |
| 恢复 | 对卡住、部分完成和失败的状态,可以重试、协调、重新触发或终止,无需开发者手动编辑数据 |
| 降级 | 可选依赖失败时,应用仍能保障用户最重要的任务 |
| 可观测性 | 可用信号能够说明相关的用户影响、工作标识、失败情况和恢复结果 |
| 告警 | 自动化耗尽或用户受到影响时触发告警,而非每次出现预期的瞬时故障时都触发 |
| 测试 | 对最可能发生的生产故障有针对性测试、模拟,或明确说明验证缺口 |
可模拟性检查
完成涉及依赖或副作用的更改前,也请检查:
| 检查项 | 通过条件 |
|---|---|
| 依赖归属 | 读者能看出由谁创建每个外部依赖,以及由谁使用它 |
| 替换能力 | 测试可以替换缓慢、不稳定、昂贵或不可逆的协作者,而无需更改生产代码 |
| 确定性 | 时间、随机性以及进程/环境读取都在边界处受到控制 |
| 契约规模 | 接口或协议只包含调用方实际需要的操作 |
| 生产环境接线 | 正常运行路径仍然简单直接且易于追踪 |
| 测试意图 | 测试断言行为和重要契约,而非偶然的调用顺序 |
| 失败模式 | 无需引发真实副作用,即可模拟依赖的错误路径 |
| 范围 | 此更改提升了可测试性,且没有无关重写或框架变动 |
工作流程
根据更改规模调整此流程。小型编辑只需聚焦上下文并进行验证,无需撰写背景说明、失败图谱或多章节报告。
-
先进行侦察。 阅读本地指南、受影响的代码、相关契约以及正确执行所需的测试。只有在更改依赖于更广泛的接线或配置时,才检查这些内容。
-
明确维护者的需求。 理解新增或变更的职责。只有在有助于消除范围或交接歧义时,才将其写下来。
-
绘制失败图谱。 对于实质性的运行时风险,考虑相关的重复输入、并发、超时、崩溃、过期状态和部分完成情况。确定现有责任方或缺失的保障;只记录实现或评审所需的决策。
-
识别硬依赖。 检查受影响的 I/O、时间、随机性、配置、SDK 和全局状态边界。重用安全的替换点;不要盘点无关依赖。
-
选择适合代码库的最简单边界。 优先使用现有模块和辅助工具。只有在新抽象能够保护一个真实的变更维度时才添加它。优先采用局部约束和现有框架原语;只有在失败模式足够真实、确有必要时,才添加队列、锁、发件箱表、断路器或看门狗。
-
分小步实现。 让差异易于评审。避免顺手格式化、无关迁移、风格变动,以及仅为测试而扭曲代码的做法,例如添加公共 setter 或宽泛的服务定位器。
-
验证行为、失败行为和可读性。 运行相关测试和仓库要求的检查。复用足够的覆盖,并补足影响重大的缺口。像评审陌生人的代码那样重新阅读差异;仅在缺少说明会导致非显而易见的理由遗失时才添加注释。
-
清楚报告。 报告结果、验证情况和剩余风险。只有在设计决策影响重大时才说明;省略空章节和常规流程叙述。
可选辅助工具
将这些辅助工具用作快速代码异味扫描器,而非最终结论:
python3 scripts/analyze_maintainability.py /path/to/project
python3 scripts/analyze_maintainability.py /path/to/project --json
python3 scripts/analyze_app_resilience.py /path/to/project
python3 scripts/analyze_app_resilience.py /path/to/project --json
python3 scripts/analyze_mockability.py /path/to/project
python3 scripts/analyze_mockability.py /path/to/project --json
analyze_maintainability.py会标记大型函数、含糊的名称、薄弱的类型、注释债务和 TODO 漂移。函数范围和函数名称检查仅使用 Python 的 AST;对于其他所有宣称支持的语言,都会有意省略函数调用和函数形式的检查,而不是猜测;薄弱类型信号仍仅限于 Python 注解和保守的 TypeScript 类型位置。analyze_app_resilience.py会标记可能缺少幂等性、重试/退避不足、外部调用没有明显超时、日志上下文不足、吞掉错误,以及没有恢复机制的待处理状态。analyze_mockability.py会标记可能硬编码的副作用和全局变量,供人工或 Agent 检查。
将它们的输出视为供人工审查的提示。扫描结果安静并不能证明代码优质、健壮或易于模拟;扫描结果嘈杂也不能证明代码有问题。如需更深入的审查,可选择改用 templates/maintainability-review.md、templates/resilience-review.md 或 templates/mockability-review.md;不要求采用其格式和章节。
指导不再适用时
相比打包提供的模式,应优先遵循已验证的本地不变量和符合习惯用法的框架行为。当它们与建议冲突或建议不起作用时,应检查已安装版本以及相关官方文档或可信的一手资料。说明尚未解决的限制。提出修正或删除规范技能的建议,并附上冲突规则、来源/版本,以及具体的回归问题或反例;不要悄悄编辑已安装的副本,也不要将发现的问题转变为无关的重构。
阅读指南
| 需求 | 阅读 |
|---|---|
| 默认做法背后的原则 | references/principles.md |
| 拆分函数、模块和职责 | references/decomposition.md |
| 有用的开发者注释和语言特定示例 | references/commenting.md |
| 可维护性、健壮性和可测试性的审查发现及严重程度排序 | references/review-rubric.md |
| 为其他 Agent 或团队成员制定计划 | references/implementation-plans.md |
| 严格默认设置、危险副作用、兼容性和可执行质量门禁 | references/guardrails-and-quality-gates.md |
| 自愈应用原则、故障映射、工作身份和状态机 | references/resilience.md |
| 作业、队列、webhook、cron、卡住的工作和重复工作 | references/jobs-and-queues.md |
| 重试、超时、幂等 API、outbox/inbox、saga 和协调 | references/distributed-systems.md |
| 日志、指标、追踪、告警和仪表板 | references/observability.md |
| 隔离依赖项、副作用、时间、随机性和配置 | references/dependency-boundaries.md |
| 选择 mock、stub、fake、spy 和契约测试 | references/test-doubles.md |
| 三类关注点中常见的陷阱和反模式 | references/gotchas.md |
| 来源影响和改编说明 | references/source-notes.md |
注意事项
- 小函数并不一定更易维护。过度拆分可能和冗长函数一样,让整体逻辑难以理解。
- DRY 不是合并碰巧相似代码的命令。共享代码应有共同的变更原因。
- “整洁”的代码仍可能有误。先保留行为并验证,再润色结构。
- 通用抽象往往比明确的重复代码更难长期维护。等出现真实差异后再抽象。
- 注释无法补救误导性的名称或混乱的边界。应先重命名或重构。
- Agent 生成的代码常常能通过测试,却不符合项目本地惯例。应用通用建议前,先遵循代码仓库的规范。
- 更严格的默认设置即使看起来只是配置,也可能改变行为并造成破坏性影响。先盘点受影响的路径,再分阶段推出。
- 单元测试全部通过,也不能代替代码仓库配置的格式化程序/代码检查器检查、分析、兼容性测试,或可用时安全的代码自动修改试运行;这些检查各自能发现不同的维护问题。
- 重试不安全的操作,可能造成你原本想修复的故障。添加重试前,先确保操作具备幂等性。
- 如果锁在工作进程完成前就过期,仅保证任务唯一还不够。应协调锁 TTL、任务超时、可见性超时和重试窗口。
pending不是恢复策略。每个非终止状态都需要明确的负责人和过期状态处理路径。- 死信队列不是垃圾桶。它们需要告警、检查字段、重新投递规则,以及有害消息处理策略。
- 可观测性并非越多越好。高基数、包含机密信息或无人查询的遥测数据会增加成本和风险,却无法改善故障恢复。
- 为每个类都定义接口会让代码更难浏览。只在替换或所有权边界确实存在时添加契约。
- 对时钟、随机数生成器、环境变量和全局上下文的隐式读取,造成的测试故障往往不亚于网络调用。
- Monkeypatching 有时很有用,但不应成为替换核心生产代码中协作者的唯一方式。