框架约定如何减少文档需求
框架约定能让应用更易理解、减少冗长文档。了解约定通常覆盖的范围、何时失效,以及如何只记录那些例外。

当约定取代文档意味着什么
框架约定是框架“默认的做事方式”——框架默默鼓励甚至直接要求的做法。与其让每个团队自己发明文件夹布局、命名规则或请求/响应流程,框架提供了一套共享模式。如果你遵循这些模式,其他开发者就能预测文件的位置和行为,而不需要冗长的说明文档。
团队为什么要写文档
大多数文档并不是因为大家热爱写文档而存在。文档用于解决几类反复出现的问题:
- 入职: 帮助新开发者了解从哪里开始以及项目如何组织
- 一致性: 防止每个人用不同的方式解决相同问题
- 记录决策: 捕获为什么选择某种方法(通常是在权衡之后)
约定特别擅长解决前两项。当“把 X 放哪”和“把 Y 命名为何”已经被框架决定时,需要解释和争论的就少了。
约定会减少文档,但不会完全取代它
“约定取代文档”并不意味着项目不再需要文档。它的意思是大量基础指引从文字转为可预测的结构。你不再需要通过阅读 wiki 来知道控制器放哪里,而是因为框架期望控制器在某个位置(工具、生成器和示例也会强化这一点),你可以直接推断出来。
结果是关于显而易见事项的文档减少,而更多精力用在记录真正与项目相关的内容:业务规则、非同寻常的架构选择和刻意的例外。
你将从这篇文章收获什么
本文面向希望在不维护庞大文档站点的前提下获得更清晰代码库和更快入职速度的开发者、技术负责人和以产品为中心的团队。
你会了解框架约定如何创建“隐式文档”、约定通常规范哪些内容、约定何时失效以及哪些内容仍然值得显式记录——以便在文档减少的同时,清晰度提升。
为什么约定有效:共享默认优于长篇解释
“约定优于配置”意味着框架为你做出合理选择——前提是你遵循它的约定。与其写(和读)大量设置说明,团队依赖于每个人都能识别的共享默认值。
一个简单比喻
可以把它想像成在一个国家开车,大家都同意靠右行驶、红灯停车、遵守标准路牌。
你可以为每个路口写详细手册(“看到红色八边形就停;绿灯就走…”),但没有必要——因为约定已经被广泛认知并稳定执行。
框架约定也如此:它们把“我们这里的做法”变为可预测的行为。
默认值省去了逐条解释的需要
当框架有默认值时,你不必为每一个小决策写文档。框架(以及你的团队)可以假定如下模式:
- 文件放置位置(控制器在一个文件夹、模板在另一个)
- 命名方式(一个
User模型映射到users数据) - 常见功能的接入方式(路由、验证、环境配置)
这种共享基线把文档从“设置 X 的每一步”缩减为“我们遵循框架默认,除非另有说明”。它也降低了入职时的认知负荷:新开发者更常能正确猜测,因为代码与他们在其他项目中见过的匹配。
权衡:更少的灵活性,更高的一致性
约定并非没有代价。缺点是你有时要放弃非常规的文件结构、自定义命名或高度定制的工作流。
优点是:一致性——更少争论、更少惊讶、更少只有老员工才知道的“部落知识”。团队能够更快推进,因为他们把时间花在构建而不是解释上。
当约定被广泛共享时效果最佳
约定只有在大家已经知道它们或能一次学会并到处复用时才会节省写文档的成本。
这就是为什么流行框架如此强大的原因:它们的约定被广泛教授、广泛使用并在许多代码库中重复出现。当你的项目紧贴这些共享默认时,代码会默认变得可理解,所需的书面说明也会大幅减少。
框架约定通常规范的五类内容
框架约定是共享的捷径。它们规范了每个新队员第一天常问的问题:“这放哪?”和“我该怎么命名?”当答案可预测时,你可以用少量一致的默认值替代大量文档。
1)文件夹与文件结构
大多数框架会推动一种可识别的项目结构:UI 所在、路由所在、数据访问所在、测试所在。这种一致性很重要,因为人们不用看指南就能区分“渲染页面的部分”和“与数据库交互的部分”。
最好的约定让常见任务成为肌肉记忆:新增屏幕时,你已经知道它属于哪个文件夹。
2)命名约定
命名规则减少了诸如“我们的控制器在 X,需要在 Y 中注册”的解释。名字本身就暗示职责。
常见示例:
- 页面/组件以它们渲染的内容命名(并采用可预测的大小写规则)
- 测试以其覆盖的单元命名
- 文件名与导出匹配(以便搜索按预期工作)
3)路由与 URL
许多 Web 框架把文件映射到路由(或使路由易于推断)。如果你能从文件名猜出 URL,或者反过来,你就不需要为每个功能单独写一份路由文档。
约定还会设定对动态路由、嵌套路由和 404 处理的期望,因此“如何添加新端点”有标准答案。
4)数据访问模式
约定通常定义“数据代码”所在:模型、仓库、服务、迁移、模式文件。即便应用很小,约定好的数据访问位置也能防止随意在 UI 代码里散落数据库调用。
5)常见脚本与命令
标准命令(运行、测试、构建、lint、格式化)消除了歧义。新开发者不应该需要查 wiki 就能知道如何启动项目——npm test(或等价命令)应该是显而易见的选择。
当这五个领域保持一致时,代码库本身就能回答大多数“我们这里怎么做?”的问题。
约定如何把代码库变成一张地图
一个试图用文字描述“如何一切工作”的 wiki 往往会随着文件夹移动、命名变化和新功能加入而逐渐过时。约定颠覆了这个思路:你不再读长篇解释,而是读结构。
可预测的位置让定位变得轻松
当框架(和你的团队)就文件位置达成一致时,仓库就像城市街区一样易于导航。
如果你知道 UI 组件放在 components/,页面视图放在 pages/,API 处理函数放在 api/,你就不会再问“X 在哪?”因为第一次猜测通常就是对的。即便不对,你的搜索范围也被限制了:不是到处都是,而是在少数预期位置之一。
名称如同路标
约定让文件名和符号携带含义。新人可以从位置和命名推断行为:
- 名为
user.controller的文件很可能处理请求逻辑 UserService类可能包含业务规则- 名为
migrations/的文件夹可能包含有序且只运行一次的数据库修改
这种推断把“给我讲清楚架构”这类问题拆成更小且更易回答的问题(“这个服务允许直接调用数据库吗?”),从而更容易记录。
模板保持地图一致
强化地图的最快方式是脚手架。起始模板和生成器默认会以“正确”的形状创建新功能——包括文件夹、文件名、样板代码,通常还有测试。
这很重要,因为约定只有在被一致应用时才有效。模板是护栏:它把每个新路由、组件或模块推入预期结构,使代码库无需更多 wiki 页面仍然可读。
如果你维护内部脚手架,可以在短小的入职页面(例如 /docs/getting-started)中链接它们,让文件树完成其余工作。
“隐式文档”的真实案例
框架约定常常像静默的内置说明。与其写一页解释“东西放哪”或“如何接入”的文档,框架已经替你做了决定——团队学会从结构中读取答案。
Ruby on Rails:"放在这里就能工作"
Rails 因“约定优于配置”而闻名。举个简单例子:如果你创建了 OrdersController,Rails 会假设存在匹配的视图文件夹 app/views/orders/。
这个单一约定可以替代一大段原本需要解释的内容:
- HTML 模板应该放在哪里
- 一个 URL 如何找到正确的控制器动作
- 控制器如何选择匹配的模板
结果:新人可以通过遵循文件夹模式添加页面,而无需询问“这个文件放哪?”
Django:为常见工作提供可预测结构
Django 鼓励一致的“app”结构。看到一个 Django 应用时,开发者会期待在 models.py 找到数据结构,在 views.py 找到请求处理,在 templates/ 找到 HTML。
你可以写很长的指南来描述项目的组成,但 Django 的默认设置已经教会了这些。当要更改页面外观时,开发者会去 templates/;当要调整存储的数据时,便从 models.py 开始。
结果:修复更快,寻找时间更少,“哪个文件控制这个?”的问题减少。
Next.js:无需路由手册的路由
Next.js 通过让路由直接反映文件夹结构来减少文档。将文件放在 app/about/page.tsx(或旧版的 pages/about.tsx),就自动得到一个 /about 页面。
这免去了关于如何注册路由、如何一致命名路由以及如何在不破坏导航的情况下添加新页面的文档需求。
结果:入职更简单——人们可以通过扫描目录发现网站结构。
同一理念,不同生态
Rails、Django 和 Next.js 看起来不同,但原理相同:共享默认把项目结构变成说明。当每个人信任相同的约定时,代码库本身能回答许多“我们这里如何做这件事?”的问题——无需维护额外文档。
当约定失效(困惑回归)
框架约定工作良好时感觉“隐形”。你可以猜测文件位置、命名,以及请求如何流经应用。当代码库偏离这些共享默认时,困惑会回归。
约定在侵蚀时的信号
早期会出现一些模式:
- 过多自定义文件夹,不符合框架的常规结构(例如,每个功能都创建新的顶层目录但没有清晰规则)
- 命名不一致:一处用
UserService,另一处用UsersManager,再一处用user_service - 临时模式在屏幕或端点间变化(“我们这里不一样,因为…”),而没有稳定指南
这些并非必然错误——但它们意味着新队员不能再依赖框架的“地图”。
一次例外如何变成许多例外
大多数约定崩坏始于合理的局部优化:“这个功能特殊,所以放到这里”或“这种命名更易读”。问题是例外具有传染性。一旦第一个例外存在,下一位开发者会把它当作先例:
- 第二个功能复制了该自定义文件夹,因为它已存在
- 第三个功能稍作改动以适应,因为第二个不太合适
- 很快就有三种“可接受”的做法
到那时,约定不再是约定——而变成了部落知识。
真正的代价:时间、错误和会议
当约定模糊时,入职变慢,因为人们无法预测去哪儿找东西。日常任务耗时增加(“哪个文件夹是真正的?”),错误增加(接错模块、使用错误命名模式、重复逻辑)。团队为此补偿,开更多同步会议、写更长的 PR 说明、添加最终变成过时的“快速文档”。
保持清晰的简单规则
只有在有明确理由时才自定义——并留下书面说明。
这个说明可以很轻量:在不寻常结构附近放简短注释,或在 /docs/decisions 页面写一段简短条目,说明发生了什么、为什么值得,以及未来应遵循的标准做法。
仍需记录的内容:例外
框架约定能删除很多解释性页面,但并不能免除责任。仍需文档记录的部分是那些项目刻意不同于多数开发者假定的地方。
记录决策,而不是基础行为
跳过对标准框架行为的重复解释。相反,捕捉影响日常工作的决策:
- 你选择了什么(以及没有选择什么)
- 有什么改变(以及何时改变)
- 为什么改变(权衡、约束、由事件驱动的修复)
示例:“我们在 /src/features 下使用功能文件夹而不是分层文件夹(/src/components、/src/services),因为所有权对应团队并减少跨团队耦合。”这一句能防止数周的缓慢漂移。
在代码附近留下简短的“例外说明”
当例外在局部很重要时,把说明放在局部。文件夹内的短 README.md 或文件顶部的简短注释通常比中央 wiki 更有效。
合适候选:
- 一个因某种原因破坏了常规项目结构的目录
- 一个必须以非通常顺序初始化的模块
- 看起来“错误”的命名,但在知道约束后才合理
保持这些说明简短且可操作:什么不同、为什么不同、接下来该怎么办。
建一个小型“项目规则”页面
放一页轻量说明(通常在 /docs/project-rules.md 或根 README),只列出 5–10 条关键选择,能帮助人们避免踩雷:
- 与框架默认不同的命名约定
- 预期的项目结构(仅在偏离时说明)
- 添加新功能或端点的“黄金路径”
这不是完整手册——只是共享的护栏。
快速上手:如何运行和测试
即便有约定,当人们无法运行应用时入职也会受阻。添加一节简短的“如何运行/测试”,与标准命令和你的实际设置一致。
如果约定命令是 npm test,但你的项目实际需要 npm run test:unit,要明确写出来。
通过代码评审保持文档更新
将文档视作变更的一部分,才能保持准确。在评审时问:“这是否引入了新的例外?”如果是,要求在同一个 PR 中包含相应说明(本地 README、Project Rules 或根快速上手)。
通过自动化而不是更多文档来强制约定
如果约定是代码库的“共享默认”,自动化是让它们生效的方法。与其指望每个开发者记住 wiki 页面的规则,不如把规则做成可执行的——让项目自我强制执行。
保持团队一致性的自动检查
一个良好设置会及早且悄然地捕获偏离:
- 格式化: 在保存时和 CI 中自动格式化(例如 Prettier、gofmt、black),让风格争论消失。
- Lint 规则: 防止常见错误并强制命名约定(例如 React hooks 规则、未使用导入、如果你不允许默认导出则阻止默认导出)。
- 测试命名与结构: 强制
*.spec.ts、describe/it词语或必需断言等模式,使测试易读一致。 - 文件夹边界: 阻止违反预定架构的导入(例如“功能不能互相导入”或“UI 不能导入服务器代码”)。可以用 ESLint 规则、TypeScript 路径限制或自定义脚本实现。
这些检查把“请记住……”的段落替换为简单结果:代码要么符合约定,要么不符合。
快速失败:在合并前捕获问题
自动化的优势在于快速失败:
- 问题在本地开发或Pull Request 中就被发现,而不是几周后
- 审查者少花时间查规范,多看产品逻辑
- 新人通过明确一致的错误和修复学习约定
保持规则精简且与框架一致
最好的规则集是小而无聊的。从框架默认开始,只添加能保护清晰度的规则(命名、结构、边界)。每多一条规则就是多一件人们必须理解的事,所以像对待代码一样对待新检查:当它解决了反复出现的问题才添加;当它不再有用就删除。
把测试当成活的文档(只要面向人类编写)
当代码库遵循框架约定时,测试可以不只是“证明工作正常”。它们可以解释系统应如何工作,用接近实现本身的位置和普通话描述。
写能读成故事的测试
一个有用的规则:每个测试应描述一个端到端行为。如果有人能通过浏览测试名就理解系统承诺了什么,你就减少了单独文档的需要。
优秀测试通常遵循简单节奏:
- Arrange: 设置现实的起始点
- Act: 执行一个动作
- Assert: 检查重要结果
更好的是使用反映用户意图的命名:
signing_in_with_valid_credentials_redirects_to_dashboardcheckout_fails_when_shipping_address_is_missing
这些名字就是“文档”,因为失败的测试会迫使人们更新说明。
用验收测试描述用户流程
验收(或功能)测试很适合记录产品从用户角度的行为。
验收测试可以描述的行为示例:
- 用户注册、确认邮箱并进入欢迎页
- 管理员创建折扣码并在结账时生效
这些测试回答了“当我做 X 会发生什么?”这类新队员首先关心的问题。
单元测试用于边界情况和规则
单元测试在记录“微小但重要”的规则时很有用:
- 四舍五入行为
- 验证规则
- 权限检查
- 棘手的边界情况(时区、限制、空状态)
当规则不是从框架约定中能直接看出来时,单元测试尤其有价值。
保持夹具与示例数据小巧且有意义
示例数据也可以成为活文档。一个小而命名良好的夹具(例如 user_with_expired_subscription)比 wiki 中一段文字更快地教授领域概念。
要点是克制:保持夹具最小、可读并针对单一概念,这样它们才是值得信赖的示例,而不是另一个需要维护的系统。
起始模板:传播约定的最快方式
起始模板(和背后的生成器)是把“我们这里怎么做”变为人们实际遵循的最快方式。与其让每个团队成员记住正确的文件夹、脚本和工具,不如把这些决定烙印在起始仓库里。
模板、生成器与起始套件:不同速度,同一目标
- 模板 提供可复制的基线(例如“新服务”“新前端应用”)。
- 生成器(CLI 工具)可以问几个问题,然后创建一致的文件、命名和接线。
- 起始套件 通常不仅包含代码结构,还包含 CI、lint、测试和部署的默认配置。
三者都减少了“文档债”,因为约定被编码在起点,而非写在会漂移的 wiki 上。
在实践中,这也是像 Koder.ai 这样的工具能发挥作用的地方:当你通过聊天驱动的工作流生成一个新的 React 应用、Go 后端、PostgreSQL 模式或 Flutter 客户端时,你可以通过让默认输出匹配你的约定来把团队保持在单一的“黄金路径”上(然后把源码导出到你的仓库)。
标准化设置以避免每个仓库都不同
入职困惑多数不是业务逻辑问题,而是关于东西放哪以及如何运行。一个好的模板使常见任务在各仓库间一致:相同脚本、相同文件夹名、相同检查命令、相同的 PR 期望。
如果只能做一件事,就要达成一致:
- 可预测的文件夹(例如
/src、/test、/docs(仅用于例外)) - 通过 package 脚本提供一种运行、测试和 lint 的方式
- 默认 CI 流水线自动运行这些脚本
轻量“新项目”清单
把它做得够小以免团队跳过:
- 文件夹结构与命名规则
- 一条命令完成设置(例如
install+dev) test、lint与format脚本- 每个 PR 都运行的 CI
- 基本 README:目的、前置条件以及人们需要的 3–5 个命令
别让模板僵化:模板本身可能成为问题
最大风险是因为“去年好用”就复制旧模板。过时的依赖、遗留脚本或被弃用的模式会通过模板迅速传播。
把模板当作产品:给它版本、定期审查,并在约定改变时更新它。(如果平台支持快照与回滚——例如 Koder.ai 支持——使用它来安全地迭代起始模板,而不破坏每个人的基线。)
一个实用检查表:在不牺牲清晰度的前提下减少文档
减少文档并不等于让人猜测。这意味着把“主路径”做得足够一致,让大多数问题自有答案,只有真正不寻常的部分需要书写下来。
1)做一个快速自查(找出真实摩擦点)
查找人们在 Slack、PR 评论、站会或入职环节反复问的地方。一些提示:
- “这个文件放哪?”
- “我们该怎么命名这个东西?”
- “如何添加新页面/作业/端点?”
- “为什么这个模块的做法不一样?”
如果某个问题被问到两次以上,你可能不需要多写文字——而是需要一个约定。
2)选择:采用框架默认,或记录有意偏离
对于每个重复问题,决定:
- 我们在和框架对抗: 切回框架的默认约定(路由、文件布局、命名、错误处理)。默认设置已经被生态记录。
- 我们有充分理由偏离: 保留偏离,但把它显式化并易于发现。
一个有用规则:如果偏离不能节省真实时间或降低真实风险,它很可能不值得长期困惑。
3)创建一页小型“约定与例外”页面
保留一页简短页面(例如 /docs/conventions),列出:
- 每个人应假定的 5–10 条约定
- 少量例外(附理由与示例)
限制内容为新人第一周需要的东西。如果页面开始膨胀,通常说明你应该简化代码库而不是写更多文档。
4)设定节奏:每季度回顾约定
应用会演化。安排轻量的季度回顾:
- 出现了哪些新模式?
- 哪些例外已成为“常态”(应升级为约定)?
- 哪些约定被忽视(以及为什么)?
要点
尽可能优先使用框架默认,只记录不同之处——清晰、简短并集中在一处。
常见问题
“框架约定取代文档”到底是什么意思?
框架约定是框架期望你遵循的默认模式——文件夹结构、命名、路由、数据访问和常用命令。当你遵循这些约定时,其他开发者可以推断出文件在哪里以及如何工作,而无需阅读项目特定的文档。
为什么团队会写那么多文档?
因为随着代码库变化,用文字保持文档准确很难。文档主要用于:
- 帮助新人入职
- 保持团队内工作一致性
- 记录重要决策与权衡
约定通过让结构可预测来覆盖前两项。
是否意味着我们可以完全停止写文档?
不是的。约定减少了关于显而易见内容的文档(文件放哪里、路由如何接入),但你仍需记录项目特有的内容:业务规则、刻意的偏离和关键决策。想法是“更少的文档,但更高价值的文档”。
约定通常会标准化哪些内容?
它们规范常见的“第一天”问题:
- 这段代码放在哪?(文件夹与文件布局)
- 应该叫什么?(命名)
- 请求如何流动?(路由/控制器模式)
- 数据逻辑放哪?(模型/服务/迁移)
- 如何运行/测试/构建?(脚本与命令)
当这些都是可预测的,仓库就能自解释。
约定如何把代码库变成“隐式文档”?
当代码遵循已知模式时,目录树和文件名就像路标。新人可以按预期导航(例如“模板在 templates/ 中”,“迁移在 migrations/ 中”),而不是阅读可能已过时的长篇架构说明。
起始模板和生成器如何减少文档债务?
它们把约定编码为默认输出,避免依赖记忆。好的脚手架会生成:
- 正确的文件夹和文件名
- 预期的连接(路由、注册、导入)
- 基础测试与脚本
这防止了偏离,使“地图”在各功能间保持一致。
有哪些预警信号表明约定正在瓦解?
当开发者无法预测文件位置或命名时就能看出来。常见信号:
- 许多自定义顶层文件夹但规则不清晰
- 命名不一致(
UserServicevsUsersManagervsuser_service) - 大量一次性模式(“我们这里不一样,因为…”)且没有稳定准则
届时团队会用 Slack 解释、写更长的 PR 描述,或产生陈旧的“快速文档”。
我们应如何处理对框架约定的例外?
只有在确有回报时才自定义,然后留下轻量说明:
- 在不寻常的文件夹里放一个短小的
README.md - 在“奇怪”设置附近写一条简短注释
- 在
/docs/decisions或类似位置记录
记录应说明变了什么、为什么变、以及今后的标准做法。
即使有强约定,哪些文档仍然值得保留?
即使有强约定,仍值得写的小量实用文档包括:
- 快速上手: 精确的运行/测试/格式化命令(尤其当它们不同于默认时)
- 项目规则: 5–10 条约定以及仅有的偏离(包含原因)
- 决策记录: 对影响未来工作的权衡的简短说明
保持精简,并在代码评审时要求当变更引入新例外时更新相应文档。
如何用自动化来强制约定,从而少写“请记住…”式的文档?
将约定变为可执行:
- 格式化工具(本地和 CI 中运行)
- lint 规则用于命名与模式
- 测试与测试命名约定
- 导入边界(阻止不该发生的依赖)
当检查在本地或 PR 中失败时,开发者会立即学会规则——审查者也能把精力放到产品逻辑上。