用途
“约定优于配置”技能用于在组织项目结构、建立工程模式或降低配置开销时提供指导。当用户提出“我应该如何组织这个”“这是什么……”(原文在此处截断)等组织与结构类问题时,可参考该技能。
其核心理念是:采用合理的默认值与一致的约定,让决策只做一次并被统一应用;仅对确实需要偏离默认值的情况保留显式配置。
为什么在生产环境重要
每一项配置选项都是一项需要有人去做出、记录、理解并维护的决策。配置错误的系统是生产事故的主要成因之一,例如错误的环境变量、YAML 中的拼写错误、服务之间相互冲突的设置。
约定驱动的系统是可预测的。新团队成员可以查看一个模块,就知道其他每个模块如何运作,不会出现“这个服务的做法不一样,因为 18 个月前有人这样配置过”的情况。
配置应是例外,而不是默认。当一切都可配置时,就没有任何东西被标准化。
主要规则
- 尽早建立项目约定并文档化。文件结构、命名模式、错误处理风格、测试组织方式——一次决定、处处遵循。将这些写入一份持续维护的文档(例如 CLAUDE.md 或 CONTRIBUTING.md)。
- 直接沿用框架约定。如果框架把路由放在 routes/、模型放在 models/、测试放在 tests/,就照做。除非框架约定确实不适用,否则不要自创布局。
- 可预测地命名。如果 users 表对应的处理程序叫 users.rs,测试就应在 users_test.rs,迁移应引用 users,API 端点应为 /users。可预测的命名能免去查找。
- 默认采用常见情况。配置选项应具有适用于 90% 用例的合理默认值,仅对真正变化的 10% 要求显式配置。
- 尽量减少环境相关配置。开发、预发与生产之间的差距应尽可能小。环境相关配置是“在我机器上能跑”类缺陷的主要来源。
- 把配置当作代码。配置文件应像代码一样被版本控制、审查与测试。如果改动某个配置值可能导致故障,就应像改代码一样严格对待。
反模式
- 配置驱动架构:系统行为由成百上千个没人能完全看懂的 YAML 旋钮决定。
- 不一致的项目结构:每个模块的组织方式都不同,因为“开发者偏好那样”。
- 重复造轮子:在标准工具已存在且足够用时,仍自建构建脚本、测试运行器或部署工具。
- 一切皆功能开关:对已经做出的决定仍包一层功能开关,“以便以后能改”。
- 未文档化的约定:代码库中存在但未写下来的模式,导致新成员在不知情下违反。
示例
过度配置:每个处理程序都各自指定中间件栈。
-- Over-configured: every handler specifies its own middleware stack
app.route("/users", handler=users, middleware=[auth, logging, rate_limit, cors])
app.route("/orders", handler=orders, middleware=[auth, logging, rate_limit, cors])
app.route("/products", handler=products, middleware=[logging, cors]) # oops, forgot auth
约定做法:默认中间件全局应用,例外显式声明。
-- Convention: default middleware applied globally, exceptions are explicit
app.use([auth, logging, rate_limit, cors]) # convention: all routes get this
app.route("/users", handler=users)
app.route("/orders", handler=orders)
app.route("/health", handler=health, skip=[auth]) # explicit exception
过度配置:每个测试都自行指定数据库设置。
-- Over-configured: every test specifies database setup
def test_create_user():
db = connect("postgres://localhost:5432/test")
db.migrate()
db.seed("users.sql")
...
约定做法:测试框架负责设置,测试只做测试。
-- Convention: test framework handles setup, tests just test
def test_create_user(db): # db fixture provided by convention
user = create_user(db, name="Alice")
assert user.name == "Alice"
边界
- 约定不等于僵化。约定应随团队与项目成长而演进,需定期评审和更新。
- 有些东西必须配置。数据库 URL、API 密钥、用于灰度发布的功能开关以及环境相关设置确实需要配置。该原则是减少不必要的配置,而非消除所有配置。
- 约定必须可被发现。只存在于某个开发者脑子里的约定不是约定,而是陷阱。要把它们写下来。
- 与灵活性存在张力:在面向多样用例的库和框架中,更多配置是合适的;在应用代码中,越少越好。
代码审查清单
- 本次变更是否遵循项目既有的模式与约定?
- 如果引入了新模式,该模式是否已文档化并说明理由?
- 是否存在可被合理默认值替代的配置选项?
- 文件/模块结构是否与项目其余部分一致?
- 新成员能否根据项目约定预测这段代码的位置与结构?
相关技能
- boy-scout-rule:用于增量维护约定。
- separation-of-concerns:用于结构约定本身。
- kiss:当约定应默认选择更简单的方案时。
应用前提
如果 .agents/stack-context.md 存在,请先阅读它。应用该原则时使用所检测技术栈的惯用模式。对于框架相关的细节,使用 context7 MCP 或网络搜索——不要猜测。