1 分钟

保持生成代码可维护:乏味架构规则

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

保持生成代码可维护:乏味架构规则

为什么生成代码更难维护

生成代码会改变日常工作。你不仅在实现功能,还在引导一个能快速创建大量文件的系统。速度是真实的,但小的不一致会很快放大。

单独看生成的输出通常没问题。代价会在第二次、第三次改动时显现:你分不清一段代码该放在哪儿,你在两个地方修同样的行为,或者因为不知道影响范围而不敢动某个文件。

“聪明”的结构代价高,因为它难以预测。自定义模式、隐藏的魔法和过度抽象在第一天看起来合理。到了第六周,下一个改动会变慢,因为你要先重新学会那个技巧才能安全更新。有了 AI 辅助生成,这类聪明设计也会混淆未来的生成,导致逻辑重复或在上面叠加新的一层。

乏味架构恰恰相反:简单的边界、朴素的命名和明显的默认值。它不是追求完美,而是选择一种即便疲惫的队友(或未来的你)也能在30秒内看懂的布局。

一个简单目标:让下一个改动变得容易,而不是让代码看起来多聪明。通常这意味着每类代码(UI、API、数据、通用工具)都有一个明确位置,文件名能预测文件作用,并尽量减少诸如自动注入、隐藏全局或元编程之类的“魔法”。

举例:如果你让 Koder.ai 添加“团队邀请”功能,你希望 UI 放在 UI 区,API 增加一条路由到 API 区,邀请数据存到数据层,而不是为这个功能发明新文件夹或模式。正是这种乏味的一致性让以后编辑便宜可控。

乏味架构规则

生成代码会变贵当它提供了多种做同一件事的方法。乏味架构规则很简单:让下一次改动可预测,即便第一次构建感觉不够巧妙。

你应该能快速回答这些问题:

  • 这个功能在哪里?
  • 我该把新文件放在哪里?
  • 我该怎么命名?
  • 从 UI 到数据最简单的路径是什么?

规则

选一个朴素的结构并在所有地方坚持它。当工具(或队友)建议花哨模式时,默认回答是“不要”,除非它能真正解决痛点。

经久耐用的实用默认值如下:

  • 每个文件夹、每个文件只承担一个职责。如果一个文件有两个修改理由,就拆分它。
  • 可预测性胜过灵活性。相同类型的东西每次都放同一个位置。
  • 优先使用你技术栈的标准做法,而不是自造小框架。
  • 让常见路径显而易见。新贡献者应无需询问就能猜到正确位置。
  • 拒绝“魔法”。避免隐藏行为、严重依赖反射的技巧或过度聪明的抽象。

一个简短的心智测试

想象一个新开发者打开你的仓库,需要加一个“取消订阅”按钮。他们不该先学一套自定义架构。应该能找到清晰的功能区、明确的 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-caseuser-profile-card.tsxbilling-settings
  • React 组件:PascalCaseUserProfileCard
  • 函数与变量:camelCasegetUserProfile
  • 常量:SCREAMING_SNAKE_CASEMAX_RETRY_COUNT

按职责命名,而不是按当前实现命名。user-repository.ts 是职责,postgres-user-repository.ts 是实现细节,可能会变。只有在确实有多种实现时才用实现后缀。

避免像 mischelpers 或巨大的 utils 这样的“杂物抽屉”。若某函数仅被一个功能使用,就把它放到该功能附近。若是共享的,让名字描述能力(date-format.tsmoney-format.tsid-generator.ts)并保持模块小巧。

让导航更快的约定

当路由、处理器和组件遵循模式时,你可以不用搜索就找到文件:

  • 路由:routes/users.ts,路径如 /users/:userId
  • 处理器(HTTP):handlers/users.get.tshandlers/users.update.ts
  • 服务(业务规则):services/user-profile-service.ts
  • 数据访问:repositories/user-repository.ts
  • UI 组件:components/user/UserProfileCard.tsx

如果你使用 Koder.ai(或任何生成器),把这些规则放进 prompt 并在编辑时保持一致。关键是可预测性:只要你能猜到文件名,未来的改动就更便宜。

无巧思的默认设置(经验法则)

创建乏味的 CRM 基础
把清晰的规格在 Koder.ai 中变成带有 React 前端、Go 与 PostgreSQL 后端的 CRM 基础。

生成代码第一天看起来很厉害,但到第 30 天可能很痛苦。选择那些即便有点重复也让代码明显的默认值。

先减少魔法。除非有明确需要,否则跳过动态加载、反射式技巧和自动注入等特性。这些特性会隐藏东西的来源,使调试和重构变慢。

优先显式导入和清晰的依赖。如果一个文件需要某个东西,就直接导入它。如果模块需要组装,把它做在一个可见的地方(例如单一的组合文件)。读者不应当猜测哪个先运行。

把配置变得乏味且集中。把环境变量、功能开关和全局设置放在一个模块并使用一致命名。不要因为方便就把配置散落到随机文件中。

保持团队一致性的经验法则:

  • 显式优于隐式(导入、路由、DI、副作用)。
  • 如果省 10 行但引入一个新概念,就不要做。
  • 保持一套做事方式(一个日志工具、一个配置模块)。
  • 偏好简单数据流而不是隐藏的观察者或事件链。
  • 调试时,先删除那些过于聪明的代码。

错误处理是聪明实现最伤人的地方。选一种模式并到处使用:从数据层返回结构化错误,在一个地方把它们映射到 HTTP 响应,并在 UI 边界翻译为面向用户的消息。不要在不同文件里抛出三种不同的错误类型。

如果你用 Koder.ai 生成应用,预先要求这些默认:显式模块组装、集中配置和统一的错误模式。

UI、API 与数据之间的边界

UI、API 与数据之间的清晰界线能把改动限制在局部。大多数神秘 bug 都出在某一层开始做另一层的事时。

UI:展示状态,收集输入

把 UI(通常是 React)当作渲染界面和管理仅限 UI 的状态的地方:哪个标签页打开、表单错误、加载指示器和基本输入处理。

把服务端状态分离:抓取的列表、缓存的个人资料、以及任何必须与后端保持一致的数据。当 UI 组件开始计算总价、校验复杂规则或决定权限时,逻辑会在多个屏幕间蔓延,改动成本变高。

API:精简且稳定的形状

保持 API 层可预测。它应把 HTTP 请求翻译为对业务代码的调用,然后把结果翻译回稳定的请求/响应格式。避免直接把数据库模型放到网络传输中。稳定的响应让你能在不破坏 UI 的情况下重构内部实现。

一个简单且好用的路径:

  • UI 用带类型的请求/响应对象调用 API 客户端。
  • API 处理器验证输入并调用服务方法。
  • 服务包含业务规则与工作流。
  • 仓库把数据库查询封装成小方法。

数据:把查询隐藏在仓库后面

把 SQL(或 ORM 逻辑)放在仓库边界后面,这样应用其他部分就“不知道”数据如何存储。在 Go + PostgreSQL 中,通常是像 UserRepoInvoiceRepo 这样的仓库,提供小而清晰的方法(GetByIDListByAccountSave)。

具体例子:添加折扣码。UI 渲染一个字段并显示更新后的价格。API 接受 code 并返回 {total, discount}。服务判断代码是否有效以及折扣如何叠加。仓库负责读取与持久化所需的数据行。

按步骤:让生成代码可维护

生成的应用看起来能很快“完成”,但结构决定以后改动的成本。先决定乏味规则,然后只生成足够证明这些规则的代码。

实用的搭建流程

先做一次简短的规划。如果用 Koder.ai,Planning Mode 是个把文件夹地图和少量命名规则写进去的好地方,然后再生成任何东西。

接着按这个顺序执行:

  1. 把地图和规则写下来。 选择边界(例如:ui/api/data/features/)和几条命名规则。
  2. 生成一个薄的垂直切片。 选一个触及 UI、API 和存储的小功能,比如“创建联系人”。目标是端到端路径,而不是完备性。
  3. 立刻重构以匹配边界。 把代码移动到规划好的文件夹,重命名不清楚的文件,删除重复。把“什么都做”的函数拆成 UI、处理器和数据访问。
  4. 增加第二个功能来测试形态。 选个相似的,比如“列出联系人”。如果你被迫破坏规则,说明边界可能不对。
  5. 尽早锁定约定。 添加简短的 CONVENTIONS.md 并把它当成契约。一旦代码库增长,改名和改文件夹会变得昂贵。

现实检验:如果新来的人不能在不问的情况下猜到把“编辑联系人”放在哪儿,架构还不够乏味。

示例场景:添加功能而不制造混乱

放心重构
在重构出问题时,使用快照和回滚来安全试验结构。

想象一个简单的 CRM:联系人列表页和联系人编辑表单。你很快做出第一个版本,一周后需要给联系人加“标签”。

把应用当作三个乏味的箱子:UI、API 和数据。每个箱子都有明确且直白的名称,这样“标签”改动会很小。

一个干净的布局可能是:

  • web/src/pages/ContactsPage.tsxweb/src/components/ContactForm.tsx
  • server/internal/http/contacts_handlers.go
  • server/internal/service/contacts_service.go
  • server/internal/repo/contacts_repo.go
  • server/migrations/

现在“标签”变得可预测:先更新模式(新表 contact_tagstags 列),然后逐层修改:仓库读写标签、服务校验、处理器暴露字段、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 中定义文件夹、命名和 UI‑API‑数据 的边界。

如果新的人打开仓库,他应该能快速回答一个问题:“我该把这个加到哪儿?”

两分钟导航测试

把项目交给一个队友(或未来的你),让他们添加一个小功能,比如“在注册表单加一个字段”。如果他们不能很快找到合适位置,说明结构没做好。

检查三个明显的归处:

  • UI 改动在一个显而易见的位置。
  • 从 UI 容易找到对应的 API 路由/处理器。
  • 数据模型与数据库改动有明确位置。

值得在代码审查中执行的规则

  • UI、API 与数据各有归处,例外很少见。
  • 名称像标签一样易读,不像谜题。
  • 分层越界要被指出(例如 UI 访问数据库)。
  • 默认拒绝巧妙捷径。

如果平台支持,保留回滚路径。快照和回滚在你实验结构时尤其有用,能提供安全回退的办法。

下一步:保持乏味,保持低成本

当你停止争论风格,开始做出几条能坚持下去的决定时,可维护性提升最快。

写下一小组约定,消除日常犹豫:文件放哪、怎么命名、如何处理错误与配置。保持简短到能在一分钟内读完。

然后做一次清理,让现有代码符合这些规则,并停止每周重排。频繁重组会让下次改动更慢,即便代码看起来更漂亮。

如果你在用 Koder.ai(koder.ai)构建,保存这些约定作为起始 prompt 有帮助,这样每次生成都会落到同一结构上。工具可以很快,但正是这些乏味的边界让代码易于变更。

常见问题

生成代码中的“朴素架构”是什么意思?

为生成的代码采用一套简单的目录结构、直白的名称,并让从 UI 到 API 再到数据层只有一条清晰路径。目标是让任何人无需先学习自定义模式,就能把改动放到正确的位置。

为什么 AI 生成代码的可维护性更难保证?

生成代码可以快速增加文件,因此细小的不一致会迅速蔓延。可预测的结构能避免第二次、第三次修改变成在重复逻辑和陌生文件夹中搜寻的过程。

生成的 Web 应用应使用哪些文件夹?

一个实用的起点是:用 app/ 存放页面和路由,用 components/ 存放可复用 UI,用 features/ 存放功能专属代码,用 api/ 存放客户端请求,用 server/ 存放后端逻辑。每个功能都保持相同的边界划分。

我应如何命名生成的文件和代码?

始终使用同一种风格:文件和文件夹用 kebab-case,React 组件用 PascalCase,函数用 camelCase,常量用 SCREAMING_SNAKE_CASE。按职责命名文件,例如 user-repository.tsmoney-format.ts

如何分离 UI、API 和数据库代码?

让 UI 代码专注于渲染和输入,让 API 处理程序验证请求并返回稳定的响应,把业务规则放在服务中,把 SQL 或 ORM 调用放在仓储中。每一层都应调用下一层,而不是代替它完成工作。

我应该直接编辑生成的文件吗?

将生成的输出放在专用的 generated/gen/ 文件夹中,避免直接编辑。把自定义包装器或适配器写在该文件夹外,这样重新生成代码时不会丢失你的改动。

哪些“聪明”的模式应避免使用?

避免隐藏的全局变量、自动连接、动态加载,以及为了节省几行代码就引入新概念的抽象。优先采用直接导入、集中式配置和清晰可见的模块设置。

如何搭建一个易维护的生成式项目?

从一个小型端到端功能开始,例如创建联系人。生成它,将不清晰的内容移到规划好的文件夹中,删除重复项,然后构建一个类似的第二个功能,看看这种结构是否依然自然。

如何保持 Koder.ai 输出的一致性?

在每次改动前,向生成器提供明确规则:目录结构、命名风格、层级边界和错误处理模式。使用 Koder.ai 时,你可以将这些规则保存在规划提示词中,让新工作遵循相同的结构。

如何检查我的项目是否易于修改?

让某人在没有指导的情况下添加一项小改动,例如注册字段。如果他们能快速找到 UI、API 处理程序、服务和数据位置,项目就易于维护。否则,应在结构进一步膨胀前简化目录图。

Related posts