adding-dependencies
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAdding dependencies
添加依赖项
REQUIRED BACKGROUND: the skill.
principal-engineering必备背景知识: the skill.
principal-engineeringOverview
概述
A dependency is a hire, not a snippet: with the feature come its defects, its release rhythm, its transitive tree, and its maintainer's attention span. Core principle: exhaust what you already have, vet what you take, pin what you took, and record why.
依赖项就像雇佣的员工,而非一段简单代码:引入功能的同时,也会带来其缺陷、发布节奏、传递依赖树以及维护者的精力限制。核心原则:穷尽现有资源,审慎引入新依赖,锁定已引入的依赖,并记录决策原因。
Exhaust what you already have
穷尽现有资源
Work down the list and stop at the first level that holds; good answers often compose two levels, an existing dependency for the hard part with a few lines of your own around it:
- The need itself. Speculative need is no need; skip it and say so in a line.
- This codebase. A helper, type, or pattern a few files away does the job; reuse it. Writing a second copy of something the repo already contains is the same defect bans for data.
keeping-one-source-of-truth - The standard library.
- The platform. A database constraint over application code, a native control over a widget library, the runtime's own primitive over a wrapper.
- A dependency the project already carries. Its transitive tree is already paid for. Two boundaries hide here: a transitive you start using is a NEW direct dependency (declare it, pin it to the already-resolved version, vet it lightly since the code already ships), and note which class the reuse crosses, because a dev-tool's transitive promoted into runtime changes who pays for it: every consumer's install, not just CI.
- A few lines of your own. Owning twenty lines beats owning a stranger's repository, when twenty lines is truly all it takes.
- Only past all six: a new dependency, vetted below.
按以下优先级依次排查,找到可行方案后即可停止;合理的解决方案通常结合两个层级,比如用现有依赖处理核心逻辑,再自行编写少量代码补充:
- 需求本身:无根据的需求不算需求;直接跳过并简单说明原因。
- 当前代码库:只需复用几文件之外的辅助函数、类型或模式即可满足需求;重复编写仓库中已有的代码,与原则禁止的数据冗余属于同一类问题。
keeping-one-source-of-truth - 标准库
- 平台能力:优先使用数据库约束而非应用层代码、原生控件而非组件库、运行时原生原语而非封装层。
- 项目已有的依赖项:其传递依赖树的成本已被承担。这里有两个注意点:开始使用的传递依赖会成为新的直接依赖(需声明它,锁定到已解析的版本,由于代码已上线可简化审查),同时要记录复用跨越的类别,因为将开发工具的传递依赖提升为运行时依赖会改变成本承担方:从仅CI环境变为所有用户的安装流程。
- 自行编写少量代码:如果确实只需20行代码就能实现,那么自己维护这20行代码胜过维护陌生人的整个仓库。
- 只有在以上6种方式都不可行时:才引入经过以下审查的新依赖项。
Vetting the one you take
审查待引入的依赖项
- Cost the whole hire: the transitive tree it drags in, the install and build weight, the license against the project's, the security history (advisories, and a supply-chain score where a scanner runs).
- Check the pulse: recent releases, how issues get answered, how many people can merge. A load-bearing package with one exhausted owner is a risk you are choosing.
- Read the part you will call. Grounding applies to other people's code too: the API surface you depend on and its failure modes, not the README's promises.
- Prefer the tool with one job over the framework with forty; the other thirty-nine come along anyway, in weight and in attack surface.
- Record the decision. A new dependency is a decision: what it is for, what else was weighed, and the condition under which it leaves (via where installed). A tree full of unexplained packages is a decision log nobody wrote.
recording-decisions
- 评估整体成本:包括它引入的传递依赖树、安装和构建负担、许可证与项目许可证的兼容性、安全历史(漏洞公告,以及扫描工具给出的供应链评分)。
- 检查活跃度:近期发布情况、问题响应速度、可合并代码的维护者数量。一个承载核心功能但仅有一位疲惫维护者的包,是你主动选择的风险。
- 阅读你将调用的代码部分:对他人代码也要务实:关注你依赖的API表面及其故障模式,而非README中的承诺。
- 优先选择单一功能工具,而非拥有40个功能的框架;额外的39个功能无论如何都会带来体积和攻击面的负担。
- 记录决策:引入新依赖是一项决策:要记录它的用途、对比过的其他选项,以及它被移除的条件(通过已安装的工具记录)。充满未解释依赖的项目,就像一份从未撰写的决策日志。
recording-decisions
The project declares its posture
项目需明确依赖策略
Dependency tolerance is the project's to declare, like its risk tiers: fully self-contained (some apps rightly ban external code wholesale), a curated allowlist, or vet-and-add. The project's rules or CLAUDE.md state which; when nothing does, ask what the system must never depend on and treat the answer as the declaration. A posture is honored even when inconvenient; changing it is a recorded decision, not an npm install.
An undeclared posture is not a hard gate: when nobody can answer today, proceed under a stated assumed posture, record the assumption in the decision, and track the declaration question with an owner. Blocking a deadline on a question with no answerer is not diligence; shipping under an unstated assumption is not honesty; the assumed-and-tracked middle is both.
依赖容忍度由项目决定,就像其风险等级一样:完全自包含(部分应用完全禁止外部代码是合理的)、 curated 白名单,或者审查后添加。项目的规则或CLAUDE.md会明确说明;如果没有,需确定系统绝对不能依赖的内容,并将其作为策略声明。即使不便也要遵守策略;更改策略是一项需记录的决策,而非简单执行npm install。
未明确的策略并非硬性障碍:如果当前无人能给出答案,可在明确的假设策略下推进,将假设记录在决策中,并指定负责人跟进策略声明的问题。因无答案的问题而阻碍交付不是勤勉;在未声明的假设下交付也不诚实;而“假设并跟进”的中间方案兼顾两者。
Pin and prove
锁定并验证
- Lockfiles are committed and versions are pinned; the build that worked today must work tomorrow, and an unpinned in CI or a base image is a time bomb with someone else's clock.
latest - An update is a change like any other: read the release notes BEFORE a major bump (breaking changes and migrations first), run the tests against it (), and give a major bump its own commit so the blame trail stays readable. Grouped patch bumps may travel together.
testing-changes - Vendored code carries its origin and version in the tree; you cannot update what you cannot date.
- 提交锁文件并锁定版本;今日能正常构建的版本明日也必须能正常构建,CI或基础镜像中未锁定的版本就像由他人掌控计时的定时炸弹。
latest - 更新与其他变更一样:在大版本升级前阅读发布说明(优先关注破坏性变更和迁移指南),针对新版本运行测试(遵循规范),并为大版本升级单独提交一次代码,以便清晰追踪变更责任。小版本补丁升级可批量处理。
testing-changes - Vendored code需在代码树中记录其来源和版本;无法确定版本的代码也无法更新。
Removal
移除依赖项
- A dependency whose job disappeared leaves in the same change that removed the job; a package kept "in case" is the speculative need from level 1, in reverse.
- Down to one small call site, consider owning those lines instead.
- Unmaintained but load-bearing is a risk item with an owner and a plan, never a hope.
- 功能已消失的依赖项,应与移除该功能的变更一同处理;保留“以防万一”的包,本质是反向的第1类无根据需求。
- 如果仅剩下一个小型调用点,考虑自行实现这部分代码。
- 无人维护但承载核心功能的依赖项是需指定负责人和应对计划的风险项,而非寄希望于运气。
Common mistakes
常见错误
- The tiny-utility reflex. Trees do not grow by big decisions; they grow one small package at a time, each individually reasonable.
- A framework installed to call one function.
- Depending on a package's internals or private paths; only the public contract is a promise.
- Importing a transitive dependency as if it were yours: undeclared today, gone on the next lockfile refresh. If you need it, declare it.
- Adding a dependency to avoid reading the code already in the repo (level 2, skipped).
- Vendoring without provenance, then wondering which version the copy was.
- Updating everything at once and discovering which bump broke the build by bisecting your own weekend.
- 盲目引入微小工具包的习惯。依赖树并非由重大决策构建而成;而是由一个个看似合理的小型包逐步积累壮大。
- 为调用一个函数而安装整个框架。
- 依赖包的内部实现或私有路径;只有公开契约才是可靠的承诺。
- 将传递依赖当作自有依赖直接导入:今日未声明,下次刷新锁文件时就可能消失。如果需要它,请明确声明。
- 为避免阅读仓库中已有的代码(跳过第2步)而添加新依赖。
- 引入vendored code但未记录来源,之后无法确定其版本。
- 一次性更新所有依赖,然后花费整个周末排查是哪个版本升级导致构建失败。