Communitygithub.com

AndrewIngram/use-dependencies-idiomatically

以惯用方式使用项目所依赖的库和框架:学习每个库的模型,而不是重新发明轮子或对抗框架。在编写通用辅助函数(重试、缓存、分页、并发、解析、校验、日期计算)之前,或在基于某个库或框架构建时(ORM、数据抓取客户端、UI工具包、状态管理器、工作流引擎、测试运行器),请先使用此技能。

use-dependencies-idiomatically 是什么?

该技能提供了一套系统性的方法来检查、选择和惯用地使用项目中的依赖库,以避免常见的反模式,如“重新发明轮子”、“对抗框架”和“未充分利用”库的功能。它的核心原则是:每个依赖库都针对特定的问题模型设计,其API编码了该模型——它拥有什么、留给开发者什么、保证了什么、以及期望在哪里被扩展。技能文档详细描述了编写依赖代码的六个步骤:从重新陈述问题、检查已存在解决方案(从框架本身到标准库)、学习所安装版本的文档模型、选择合适工具、按库的方式编写代码,到处理摩擦(如与库模型冲突)的决策树。它还定义了两类“代码气味”——“对抗库”的气味(如破坏保证、使用私有API、打补丁、重复状态)和“未充分利用库”的气味(如重复造轮子、手写状态管理、未使用声明式API)。该技能指导用户根据GUIDANCE.md、REVIEWING.md、AUDITING.md和UPGRADING.md等配套文件进行审查、审计、添加和升级依赖。最终输出应包括:承载你所编写行为的库特性、为何没有内置方案的原因、被排除的依赖及原因、以及保留的气味及其理由。该技能适用于Claude Code、Cursor等AI编码助手,帮助它们生成更质量更高、更符合库设计意图的代码。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/AndrewIngram/swe-skills/tree/HEAD/skills/engineering/use-dependencies-idiomatically

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

以惯用方式使用依赖项

每个依赖库都是为特定形状的问题而构建的。其API编码了该模型:它拥有什么、留给你什么、保证了什么,以及期望在何处被扩展。编写惯用的解决方案,即库自己的文档和规范示例会展示的那种方案,当问题具有不同形状时(阻抗失配),请为该任务挑选正确的工具。应避免的失败是重新发明轮子、对抗框架,以及悄悄未充分利用已为该场景构建的便利功能。

摩擦是一种气味:在明显路径上的强制类型转换、补丁或变通方法意味着模型被误读或工具是错误的。代价最高的违规行为没有任何摩擦:它们能通过类型检查,看起来是局部性的,同时却破坏了库存在所旨在提供的保证,因此要有目的地去寻找它们。

编写使用依赖项的代码

默认路径。审查、审计、添加和升级是其分支(如下)。

  1. 重新陈述问题。 注意XY问题:任务通常以解决方案的形式出现,无论是你还是用户提出的。将其重新陈述为一般性问题:

    • “对上传重试三次”是一个重试策略。
    • “我需要每个账户最新发票的 row_number() over (partition by account_id)”是按组分组的TOP-N,而ORM可能用无窗口函数的方式来建模。

    “库不能做X”是一个需要在步骤3中验证的说法,无论谁提出来。

    完成条件:你能够用库的文档会使用的词语来命名该问题。

  2. 找到已经解决了的地方,从最近的开始:

    1. 你所处的框架:工作流引擎的重试策略和定时器、GraphQL客户端的分页和存储更新器、测试运行器的模拟定时器。
    2. 项目声明的依赖项(package.json、pyproject.toml、go.mod、Cargo.toml)以及代码库如何导入它们。现有用法展示了团队的约定,这可能与库的设计不同。
    3. 项目的共享包,这些包通常以项目希望的方式封装了一个依赖项。
    4. 所用运行时版本的标准库(engines、.nvmrc、requires-python)。涵盖范围广泛的标准库比人们记住的要多:Python的itertools、collections、functools、pathlib;现代Node的fetch、AbortSignal.timeout、util.parseArgs、node:test。

    完成条件:每个来源都已检查,并且你有一个简短的清单,可能为空。

  3. 从已安装版本的文档中学习模型。 从锁文件中获取版本;只有匹配该版本的来源才算数。按顺序阅读:

    1. 该项目针对此依赖项的指导,按GUIDANCE.md找到并保留。它是关于团队有意决策的权威来源,也是库模型的缓存:当它记录的版本与锁文件不同时,或者某个声明决定了你的方法时,请在下面的来源中确认它。
    2. 一个参考检出:将依赖项的仓库克隆到项目中,或由诸如opensrc的工具获取,通常AGENTS.md或CLAUDE.md中会有指向。根据锁文件检查其标签。其文档、示例和测试是预期用途的最佳记录。
    3. 已安装的包:导出的类型及其文档注释,通常去掉了文档和示例。
    4. 在线资源,当上述两者都没有文档时:该主版本的官方文档,包括概念和指南页面以及API参考;然后是该仓库在匹配标签上的上游代码。

    只构建在公共API之上:文档描述的内容,通过声明的入口点(如exports映射、__all__、没有前导下划线的名称)访问。阅读源码以理解行为;你仅在其中发现的东西都是私有的。Semver保护公共API,不保护其他任何东西。你对库的记忆是一个需要对照文档检查的假设,而你偶然打开的运行时文件不能替代文档。

    完成条件:在编写或判断使用它的代码之前,你能够从文档中回答:

    • 它为什么而构建,它故意遗漏了什么?
    • 它拥有什么(存储、缓存、生命周期、重试、事务),它生成或要求你声明什么(代码生成输出、模式、类型),每个部分是为了你代码中的哪一部分?
    • 它在系统哪些部分之间维护什么保证(数据掩码、工作流确定性、事务隔离),什么会悄悄地破坏它?它通常位于你自己的模块之间。
    • 哪些公共API和文档化的扩展点(选项、插件、中间件、钩子、逃生舱)涵盖了这一点,规范示例看起来是什么样的,包括哪个模块导入什么?
    • 它附带了哪些检查(lint插件、编译器或代码生成验证、严格类型模式),项目运行它们吗?
  4. 选择合适的贴合方式:

    • 问题与模型匹配: 直接使用它。
    • 问题部分落在主API之外: 对该部分使用文档化的逃生舱(ORM中的原始SQL、无头组件、自定义解析器)。逃生舱属于设计的一部分。
    • 已安装版本缺少该功能: 如果变更日志显示较新版本有,请按UPGRADING.md提议升级。
    • 阻抗失配: 使用模型匹配的依赖项,或使用我们拥有的接缝后面的普通代码,这些代码应远离此依赖。被安装或附近有使用,并不使一个库成为正确的工具。

    完成条件:你已经选择了一个方案,并能从模型中证明其合理性。

  5. 以库的方式,在其公共API上编写代码。 第一个能工作的版本通常不是惯用版本。当相邻代码和规范示例不一致时,遵循规范示例并告知用户偏差。在编写过程中运行库自己的检查;如果项目关闭了它们,请说明打开它们能捕获什么。

    完成条件:库的检查通过,以下气味不适用(或者每个适用的气味都是你将报告的选择),并且库的维护者会认为该代码是其库按设计使用的代码。

  6. 当摩擦持续存在时,首先调整我们的设计以适应库的模型;其约束通常编码了一个教训(工作流引擎的确定性规则、GraphQL客户端的数据掩码)。然后重新审视步骤4。只有在用户同意的情况下才对抗库工作,在提案中和变通方法的注释中命名你所依赖的未文档化行为。模型与产品需求之间的冲突由用户决定。

气味:对抗库

每一个气味都会让你回到步骤4。按库存在所提供的内容排序:被破坏的保证比其他所有都重要,即使今天没有任何东西被破坏;而编码了模型的团队规则则具有模型的严重性。

  • 破坏的保证:生成的代码在为其生成的模块之外使用(另一个组件的片段类型)、读取另一个模块的掩码数据、一个片段或模式在无关的调用者之间共享、确定性工作流内部的非确定性。这些能通过类型检查,所以要特意搜索它们。
  • 在我们代码与库类型相遇处的强制类型转换或类型检查器抑制(as、any、@ts-expect-error、# type: ignore)。
  • 私有API:内部路径(pkg/dist/…、pkg._internal)、下划线名称、未文档化的导出、强制转换到库对象的私有字段、仅在源代码中发现的行为。不稳定或实验性API有文档但不在Semver范围内:当文档建议在这里使用时才使用,并说明原因。
  • 改变库行为的补丁(包管理器补丁、猴子补丁、分叉)。使库做其文档已经承诺的事情的补丁是一个正确操作的bug修复:将补丁只限于修复,并链接上游问题。
  • 重复状态:库拥有的某事物的第二个真相来源,例如镜像到存储状态的GraphQL缓存,或框架已经跟踪的重试计数。
  • 在库提供回调、事件或钩子的地方,使用定时器、标志或效果链在库“之后”运行代码。
  • 在每个调用点覆盖的默认值,或者撤销每个调用行为的包装器。
  • 主要重命名其封装的API的浅层包装器。应用删除测试:如果删除它只是将调用移回库,则它没有发挥作用。
  • 因为整个看起来比需要的更多而重建库功能的一部分。

气味:未充分利用库

代码在公共API上工作,但一个为此场景构建的便利功能本可以使其更短或更声明式。要找到它,请阅读你传递的每个选项对象的类型以及库针对步骤1中问题的指南。

  • 多个调用构成一个操作:存在upsert时的先查找后创建、循环中的写入存在批量API时。
  • 重塑输出的粘合代码,而查询或钩子接受选择或投影选项。
  • 手写重建库跟踪的状态:加载、挂起、脏状态或重试标志。
  • 每个调用点相同的选项,而存在默认值、共享配置、主题或工厂。
  • 对于库允许你声明的事物的命令式代码:模式、变体、关系、策略。

比例是双向的:当几行清晰的普通代码就能完成工作时,普通代码就是惯用的。

审查、审计、添加和升级

  • 审查他人的改动:阅读REVIEWING.md。
  • 审计代码库对依赖项的使用:阅读AUDITING.md。
  • 添加一个包,或升级依赖项或运行时:首先阅读UPGRADING.md。
  • 查找、编写或更新项目对依赖项的指导:阅读GUIDANCE.md。

回复内容: 承载你所编写行为的库特性;对于你自己编写的通用代码,你检查了什么以及为什么没有合适的方案;每个你跳过的依赖项,以及其模型中不匹配的部分;每个你保留的气味及其原因。当你学到了关于依赖项的持久知识时,请按GUIDANCE.md添加你建议的指导。

相关技能