学习如何利用“乏味架构”规则保持生成代码可维护:清晰的文件夹边界、一致的命名和能减少未来返工的简单默认设置。

生成代码会改变日常工作。你不仅在实现功能,还在引导一个能快速创建大量文件的系统。速度是真实的,但小的不一致会很快放大。
单独看生成的输出通常没问题。代价会在第二次、第三次改动时显现:你分不清一段代码该放在哪儿,你在两个地方修同样的行为,或者因为不知道影响范围而不敢动某个文件。
“聪明”的结构代价高,因为它难以预测。自定义模式、隐藏的魔法和过度抽象在第一天看起来合理。到了第六周,下一个改动会变慢,因为你要先重新学会那个技巧才能安全更新。有了 AI 辅助生成,这类聪明设计也会混淆未来的生成,导致逻辑重复或在上面叠加新的一层。
乏味架构恰恰相反:简单的边界、朴素的命名和明显的默认值。它不是追求完美,而是选择一种即便疲惫的队友(或未来的你)也能在30秒内看懂的布局。
一个简单目标:让下一个改动变得容易,而不是让代码看起来多聪明。通常这意味着每类代码(UI、API、数据、通用工具)都有一个明确位置,文件名能预测文件作用,并尽量减少诸如自动注入、隐藏全局或元编程之类的“魔法”。
举例:如果你让 Koder.ai 添加“团队邀请”功能,你希望 UI 放在 UI 区,API 增加一条路由到 API 区,邀请数据存到数据层,而不是为这个功能发明新文件夹或模式。正是这种乏味的一致性让以后编辑便宜可控。
生成代码会变贵当它提供了多种做同一件事的方法。乏味架构规则很简单:让下一次改动可预测,即便第一次构建感觉不够巧妙。
你应该能快速回答这些问题:
选一个朴素的结构并在所有地方坚持它。当工具(或队友)建议花哨模式时,默认回答是“不要”,除非它能真正解决痛点。
经久耐用的实用默认值如下:
想象一个新开发者打开你的仓库,需要加一个“取消订阅”按钮。他们不该先学一套自定义架构。应该能找到清晰的功能区、明确的 UI 组件、单一的 API 客户端位置和一条数据访问路径。
这条规则在像 Koder.ai 这样的即兴编码工具下尤其管用:你可以快速生成,但每次都把输出引导到相同的乏味边界。
生成代码往往增长很快。保持可维护性的最安全方式是用一个乏味但可猜的文件夹地图,让任何人都能猜到改动归属。
一个适用于许多 web 应用的小型顶层布局:
app/ 屏幕、路由与页面级状态components/ 可复用的 UI 组件features/ 每个功能一个文件夹(billing、projects、settings)api/ API 客户端代码与请求辅助server/ 后端处理器、服务和业务规则这让边界一目了然:UI 在 app/ 和 components/,API 调用在 api/,后端逻辑在 server/。
数据访问也应当乏味。把 SQL 查询和仓库代码放在后端附近,而不是散落在 UI 文件里。在 Go + PostgreSQL 的组合中,一个简单规则是:HTTP 处理器调用服务,服务调用仓库,仓库与数据库对话。
共享类型和工具应有明确归处,但保持精简。把跨切类型放 types/(DTO、枚举、共享接口),把小工具放 utils/(日期格式化、简单校验器)。如果 utils/ 开始像第二个应用,说明这些代码更应该放回某个功能文件夹。
把生成的文件夹当作可替换的。
generated/(或 gen/)并尽量避免直接编辑它。features/ 或 server/,这样重新生成不会覆盖它。例子:如果 Koder.ai 生成了 API 客户端,把它放在 generated/api/,然后在 api/ 写薄薄的包装层,用来添加重试、日志或更清晰的错误信息,而不用触碰生成文件。
生成代码很容易堆起来。命名是让它在一个月后仍可读的关键。
选一种命名风格并不要混用:
kebab-case(user-profile-card.tsx、billing-settings)PascalCase(UserProfileCard)camelCase(getUserProfile)SCREAMING_SNAKE_CASE(MAX_RETRY_COUNT)按职责命名,而不是按当前实现命名。user-repository.ts 是职责,postgres-user-repository.ts 是实现细节,可能会变。只有在确实有多种实现时才用实现后缀。
避免像 misc、helpers 或巨大的 utils 这样的“杂物抽屉”。若某函数仅被一个功能使用,就把它放到该功能附近。若是共享的,让名字描述能力(date-format.ts、money-format.ts、id-generator.ts)并保持模块小巧。
当路由、处理器和组件遵循模式时,你可以不用搜索就找到文件:
routes/users.ts,路径如 /users/:userIdhandlers/users.get.ts、handlers/users.update.tsservices/user-profile-service.tsrepositories/user-repository.tscomponents/user/UserProfileCard.tsx如果你使用 Koder.ai(或任何生成器),把这些规则放进 prompt 并在编辑时保持一致。关键是可预测性:只要你能猜到文件名,未来的改动就更便宜。
生成代码第一天看起来很厉害,但到第 30 天可能很痛苦。选择那些即便有点重复也让代码明显的默认值。
先减少魔法。除非有明确需要,否则跳过动态加载、反射式技巧和自动注入等特性。这些特性会隐藏东西的来源,使调试和重构变慢。
优先显式导入和清晰的依赖。如果一个文件需要某个东西,就直接导入它。如果模块需要组装,把它做在一个可见的地方(例如单一的组合文件)。读者不应当猜测哪个先运行。
把配置变得乏味且集中。把环境变量、功能开关和全局设置放在一个模块并使用一致命名。不要因为方便就把配置散落到随机文件中。
保持团队一致性的经验法则:
错误处理是聪明实现最伤人的地方。选一种模式并到处使用:从数据层返回结构化错误,在一个地方把它们映射到 HTTP 响应,并在 UI 边界翻译为面向用户的消息。不要在不同文件里抛出三种不同的错误类型。
如果你用 Koder.ai 生成应用,预先要求这些默认:显式模块组装、集中配置和统一的错误模式。
UI、API 与数据之间的清晰界线能把改动限制在局部。大多数神秘 bug 都出在某一层开始做另一层的事时。
把 UI(通常是 React)当作渲染界面和管理仅限 UI 的状态的地方:哪个标签页打开、表单错误、加载指示器和基本输入处理。
把服务端状态分离:抓取的列表、缓存的个人资料、以及任何必须与后端保持一致的数据。当 UI 组件开始计算总价、校验复杂规则或决定权限时,逻辑会在多个屏幕间蔓延,改动成本变高。
保持 API 层可预测。它应把 HTTP 请求翻译为对业务代码的调用,然后把结果翻译回稳定的请求/响应格式。避免直接把数据库模型放到网络传输中。稳定的响应让你能在不破坏 UI 的情况下重构内部实现。
一个简单且好用的路径:
把 SQL(或 ORM 逻辑)放在仓库边界后面,这样应用其他部分就“不知道”数据如何存储。在 Go + PostgreSQL 中,通常是像 UserRepo 或 InvoiceRepo 这样的仓库,提供小而清晰的方法(GetByID、ListByAccount、Save)。
具体例子:添加折扣码。UI 渲染一个字段并显示更新后的价格。API 接受 code 并返回 {total, discount}。服务判断代码是否有效以及折扣如何叠加。仓库负责读取与持久化所需的数据行。
生成的应用看起来能很快“完成”,但结构决定以后改动的成本。先决定乏味规则,然后只生成足够证明这些规则的代码。
先做一次简短的规划。如果用 Koder.ai,Planning Mode 是个把文件夹地图和少量命名规则写进去的好地方,然后再生成任何东西。
接着按这个顺序执行:
ui/、api/、data/、features/)和几条命名规则。CONVENTIONS.md 并把它当成契约。一旦代码库增长,改名和改文件夹会变得昂贵。现实检验:如果新来的人不能在不问的情况下猜到把“编辑联系人”放在哪儿,架构还不够乏味。
想象一个简单的 CRM:联系人列表页和联系人编辑表单。你很快做出第一个版本,一周后需要给联系人加“标签”。
把应用当作三个乏味的箱子:UI、API 和数据。每个箱子都有明确且直白的名称,这样“标签”改动会很小。
一个干净的布局可能是:
web/src/pages/ContactsPage.tsx 和 web/src/components/ContactForm.tsxserver/internal/http/contacts_handlers.goserver/internal/service/contacts_service.goserver/internal/repo/contacts_repo.goserver/migrations/现在“标签”变得可预测:先更新模式(新表 contact_tags 或 tags 列),然后逐层修改:仓库读写标签、服务校验、处理器暴露字段、UI 渲染和编辑它。不要把 SQL 偷放到处理器里,或把业务规则塞进 React 组件。
如果后来产品要求“按标签过滤”,你大多会在 ContactsPage.tsx(UI 状态与查询参数)和 HTTP 处理器(请求解析)工作,而仓库处理查询。
测试和夹具也应保持小且靠近代码:
server/internal/service/contacts_service_test.go 测试像“标签名在单个联系人中必须唯一”的规则server/internal/repo/testdata/ 放最小夹具web/src/components/__tests__/ContactForm.test.tsx 测试表单行为如果你用 Koder.ai 生成这些,导出后同样适用:保持文件夹乏味,保持命名直白,修改不再像考古学。
生成代码第一天看起来整洁,但之后成本高昂的常见原因不是“坏代码”,而是缺乏一致性。
一个代价高的习惯是让生成器每次都发明结构。一个功能带来自己的文件夹、命名风格和辅助函数,久而久之你会有三种做同一件事的方式。选一个模式、写下来,并把任何新模式当作有意识的变更,而不是默认。
另一个陷阱是混层。当 UI 组件直接访问数据库,或 API 处理器构建 SQL 时,小改动会变成跨全局的危险编辑。保持边界:UI 调 API,API 调服务,服务 调数据访问。
过早过度使用通用抽象也会增加成本。一个通用的 “BaseService” 或 “Repository” 框架看起来漂亮,但早期抽象通常是猜测。当现实变化时,你反而要和自己的框架作斗争,而不是交付功能。
经常重命名和重组是另一种债务。如果文件每周都在移动,人们就不再信任布局,快速修复会降落在随机地方。先稳定文件夹地图,然后在计划好的批次里重构。
最后,对“平台代码”要小心:没有真实用户需求的共享库和自制工具只有在反复证明有用时才值得投入。在那之前,保持默认直接简单。
如果新的人打开仓库,他应该能快速回答一个问题:“我该把这个加到哪儿?”
把项目交给一个队友(或未来的你),让他们添加一个小功能,比如“在注册表单加一个字段”。如果他们不能很快找到合适位置,说明结构没做好。
检查三个明显的归处:
如果平台支持,保留回滚路径。快照和回滚在你实验结构时尤其有用,能提供安全回退的办法。
当你停止争论风格,开始做出几条能坚持下去的决定时,可维护性提升最快。
写下一小组约定,消除日常犹豫:文件放哪、怎么命名、如何处理错误与配置。保持简短到能在一分钟内读完。
然后做一次清理,让现有代码符合这些规则,并停止每周重排。频繁重组会让下次改动更慢,即便代码看起来更漂亮。
如果你在用 Koder.ai(koder.ai)构建,保存这些约定作为起始 prompt 有帮助,这样每次生成都会落到同一结构上。工具可以很快,但正是这些乏味的边界让代码易于变更。