关于 convention-over-configuration
面向软件项目的开发技能,指导团队采用约定优于配置的原则。它倡导记录并统一可预测的默认做法,同时为确有差异的情况保留显式配置。
主要功能
- 01记录项目约定
- 02遵循框架惯例
- 03采用可预测的命名
- 04优先使用合理默认值
- 05将配置作为代码审查
- 06识别配置反模式
适用场景
- 01统一各模块的项目结构
- 02减少不必要的项目配置
- 03检查代码是否遵循约定
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ jordancoin/ codingskills --skill convention-over-configuration安装后在 Claude Code 中输入/convention-over-configuration确认已加载。
详细介绍
约定优于配置
开始应用前
如果存在 .agents/stack-context.md,请先阅读。针对检测到的技术栈,采用符合其习惯的模式。有关框架特定的细节,请使用 context7 MCP 或网络搜索——不要猜测。
原则
采用合理的默认值和一致的模式,让决策只需制定一次,便能在各处应用。只有在确实需要偏离默认值时,才进行显式配置。
这对生产环境为何重要
每个配置选项都是一项决策,必须有人做出、记录、理解并维护。配置错误是导致生产事故的主要原因之一——环境变量有误、YAML 中出现拼写错误、服务之间的设置相互冲突。
由约定驱动的系统具有可预测性。新团队成员只需查看一个模块,就能了解其他模块的工作方式。不必担心“这个服务的实现不一样,是因为 18 个月前有人做了那样的配置”。
配置应该是例外,而不是默认做法。当一切都可以配置时,就没有什么是标准化的。
规则
- 尽早确立项目约定并记录下来。 文件结构、命名模式、错误处理风格、测试组织方式——一次决定,处处遵循。将这些内容写入一份持续更新的文档(例如 CLAUDE.md 或 CONTRIBUTING.md)。
- 照常采用框架约定。 如果框架规定路由放在
routes/、模型放在models/、测试放在tests/,就遵循这些约定。除非框架的约定确实不适用,否则不要自行设计布局。 - 采用可预测的命名。 如果用户表对应的处理程序叫作
users.rs,测试就应放在users_test.rs中,迁移应引用users,API 端点应为/users。可预测的命名是一种能省去搜索的约定。 - 默认采用常见情况。 配置选项应提供合理的默认值,满足 90% 的使用场景。只有对确实存在差异的 10% 场景,才要求显式配置。
- 尽量减少特定环境的配置。 开发、预发布和生产环境之间的差异应尽可能小。特定环境的配置是导致“在我的机器上能运行”这类 Bug 的主要原因之一。
- 将配置视为代码。 配置文件应像代码一样纳入版本控制、接受审查并经过测试。如果更改某个配置值可能导致服务中断,就应像审查代码变更一样严格审查。
反模式
- 配置驱动的架构: 系统行为由 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:用于约定应默认采用更简单的选项时
评论
支持 Markdown