在 AI 参与软件开发越来越深入之后,一个过去看似普通的工程规范问题变得更重要了:单个代码文件到底应该有多长?
传统工程讨论文件行数时,重点通常是人类维护成本、代码可读性、模块边界和 review 体验。到了 AI 编程场景,这个问题又多了一层含义:文件大小会影响 AI 构造上下文、定位修改点、理解局部语义、复用已有代码以及控制改动范围的能力。
这并不意味着"超过某个行数,AI 就无法处理"。现代模型的上下文窗口已经很大,工具链也能帮助 AI 搜索、跳转、读取局部片段。但在实际开发中,文件越大,AI 的任务执行质量往往越不稳定。原因不只是 token 数增加,而是上下文选择和语义消歧的难度增加了。
本文讨论的是一个更具体的问题:在代码主要由 AI 生成、后续也经常由 AI 修改的前提下,新增文件限制在 1000 行以内,和放宽到 3000 行以内,会对 AI 输出质量和编写成本产生什么影响。
结论
如果目标是长期、持续地让 AI 参与开发,我更倾向于使用这样的规则:
新增业务代码文件建议控制在 1000 行以内。超过 1000 行需要有明确理由。超过 2000 行默认应该拆分。3000 行更适合作为绝对红线,而不是常规目标。
这个判断不是因为 1000 行是一个天然正确的数字,而是因为它通常接近一个 AI 能够较稳定地完整理解、修改和复核的工程单元。3000 行文件不是不能处理,但它会让 AI 在上下文选择、局部推理和改动控制上承担更多不确定性。
如果是一次性生成、很少维护、逻辑简单的代码,3000 行上限未必有明显问题。如果是长期演进的业务系统,尤其是需求频繁变化、多人或多个 AI 会话持续接手的项目,1000 行软上限通常更有利。
文件大小影响的不是"能不能读",而是"能不能读稳"
讨论这个问题时,常见的反驳是:AI 处理局部任务时,并不需要读取整个大文件。它可以通过搜索、调用点、错误日志和函数名定位到相关片段。因此,一个 3000 行文件并不一定比三个 1000 行文件消耗更多上下文,也不一定导致输出质量下降。
这个观点有合理之处。
AI 在真实编码过程中,通常不会机械地从第一行读到最后一行。更常见的流程是:
- 根据需求关键词搜索相关符号;
- 读取函数、类型或错误行附近的片段;
- 查看调用点、测试、协议、数据模型;
- 根据已有信息判断是否需要扩大阅读范围;
- 最后在相对局部的上下文里生成补丁。
因此,如果任务非常明确,例如"修改某个常量""修复某个函数中的边界条件""根据报错行调整类型",那么大文件未必带来明显额外成本。只要定位信号足够强,AI 可以只读取相关的几十到几百行。
问题在于,"选择哪些内容相关"本身就是任务的一部分,而且这一步会出错。
文件越大,里面越容易出现相邻但不相关的逻辑、命名相似但语义不同的函数、共享状态、历史兼容分支和隐式副作用。AI 不是在一个已经被完美标注的语义图里工作,而是在有限上下文、局部搜索结果和代码结构暗示之间做判断。大文件增加的不是纯粹的读取成本,而是检索、筛选、消歧和保持正确局部模型的成本。
1000 行代码大约会消耗多少上下文
从 token 预算看,1000 行普通业务代码通常大约会消耗 10k 到 20k tokens。更稀疏的代码可能低至 6k 到 10k tokens;复杂 UI、测试、fixture、JSON、schema 或包含大量字符串的代码,可能达到 20k 到 40k tokens。
如果给代码加上行号、工具输出、错误日志和周边文件,实际占用还会继续上升。
一个实用估算是:
- 每 1000 行普通业务代码,按 15k tokens 预算;
- 每 1000 行复杂 UI、测试或数据代码,按 25k tokens 预算。
这意味着,一个 1000 行文件通常仍然是比较适合 AI 完整阅读和稳定推理的单位。一个 3000 行文件完整读入时,可能消耗 30k 到 60k tokens,复杂场景下甚至更多。模型未必读不下,但需求说明、相邻文件、测试、错误日志、计划和生成补丁也都需要上下文空间。长上下文能够容纳更多内容,不等于每个细节都会被同等质量地使用。
更重要的是,token 数只是表层指标。真正影响输出质量的是:这些 token 中有多少是任务相关信息,以及 AI 是否能正确判断哪些信息应该被赋予更高权重。
上下文稀释:大文件让信号变得更浑
所谓上下文稀释,并不是说 AI 无法读取 3000 行代码,而是说相关信息会被更多无关信息包围。模型在生成补丁时,需要从上下文中提取对当前任务最重要的约束。当文件中存在大量相邻但不相关的逻辑时,这些约束会变得不够清晰。
例如,一个大文件里可能同时存在:
- 用于提交前校验的逻辑;
- 用于草稿保存的校验逻辑;
- 用于模板生成的校验逻辑;
- 用于历史兼容的校验逻辑;
- 用于测试或 preview 的临时逻辑。
它们的函数名可能非常相似,状态字段也可能相似。AI 搜索关键词后,确实能找到很多结果,但找到很多结果不等于找到了正确上下文。相似代码越多,模型越容易把入口函数、辅助函数、历史分支和当前业务路径混在一起。
小文件的价值不只是减少 token。文件名、目录、类型边界和模块拆分本身就是语义索引。一个名为 PromptValidationService 的文件,会比一个 3000 行的 CreateView 文件更明确地告诉 AI:这里大概率是提示词校验的主要位置。
这类结构化边界对 AI 很重要。它降低了"应该读哪里"的不确定性,也降低了"哪些信息更重要"的判断成本。
局部修改不再局部:代码结构会暗示改动范围
AI 写代码时会根据现有结构推断合理的修改位置和修改范围。如果一个文件里把 UI、状态管理、网络请求、缓存、埋点、错误处理、重试逻辑和数据转换全部放在一起,那么 AI 会自然地认为这些逻辑可以在同一个文件里继续扩展。
这会带来一个问题:任务本身是局部的,但代码结构向 AI 暗示它可以做更大范围的局部联动。
例如,需求是"给图片生成流程增加失败重试"。如果项目中有清晰的结构:
ImageGenerationServiceRetryPolicyImageGenerationViewModel
AI 更可能把重试策略放在 service 或 policy 层,并让 view model 调用它。
如果所有逻辑都在一个巨大的 view 文件里,AI 看到按钮状态、请求逻辑、错误提示、轮询、loading 状态都在同一处,就更容易直接在 view 文件里追加重试逻辑。这个结果并不是模型随机犯错,而是现有结构给它提供了错误的工程暗示。
文件边界越清晰,AI 越容易把修改限制在正确层次。文件越大、职责越混杂,AI 越容易把"能改的地方"误认为"应该改的地方"。
重复实现概率上升:已有能力变得不可发现
AI 重复实现已有 helper,是大文件中很常见的问题。
原因通常不是模型完全没有搜索,而是已有能力的可发现性不足。一个 3000 行文件中可能已经有类似函数:
normalizeInputTextsanitizePromptprepareSubmissionPayloadcleanUserPrompt
如果需求描述是"提交前 trim prompt",AI 可能搜索 trim 或 prompt,但未必能准确发现 sanitizePrompt 就是应该复用的函数。函数藏在大文件中部时,它只依赖函数名和少量调用点被发现;如果它被放在一个独立文件 PromptSanitizer 中,文件名、目录和类型名都会提供额外的检索信号。
也就是说,拆分文件不是单纯为了审美整洁,而是在为未来的 AI 和人类维护者建立更强的语义索引。
已有能力越容易被发现,AI 越可能复用它。已有能力越分散在大文件内部,AI 越容易重新写一套近似逻辑。
质量差异和成本差异的大致量级
在同样业务逻辑变更的前提下,1000 行上限和 3000 行上限的差异可以粗略理解为:
| 维度 | 新增文件不超过 1000 行 | 文件不超过 3000 行 |
|---|---|---|
| 首次生成成本 | 略高,需要更多边界设计 | 较低,容易一口气生成 |
| 后续理解成本 | 较低 | 较高 |
| 修改点定位 | 更稳定 | 更依赖搜索质量和命名质量 |
| 漏改、误改概率 | 较低 | 更容易波动 |
| 重复实现概率 | 较低 | 较高 |
| Review 成本 | 较低 | 较高 |
| 长期维护成本 | 更可控 | 更容易累积复杂度 |
从经验估算看,1000 行方案可能让首次生成成本增加 5% 到 20%。这部分成本主要来自文件拆分、命名、导入导出、边界设计和测试组织。
但在后续维护阶段,1000 行方案通常能降低 15% 到 40% 的 review 和理解成本。在复杂业务模块中,它还可能显著降低返工成本,因为 AI 更少漏掉隐含约束,也更少把修改扩散到无关逻辑。
对于简单 CRUD、配置页、静态表单,1000 行和 3000 行的差异可能并不大。对于状态复杂的编辑器、工作流、支付订阅、生成链路、协作系统、权限体系和异步任务系统,差异会明显扩大。
大文件什么时候是合理的
不应该把 1000 行理解成绝对规则。某些文件超过 1000 行是合理的,甚至拆分后会更差。
常见例外包括:
- 生成代码;
- 静态数据映射;
- schema、fixture、mock 数据;
- 高度内聚的解析器或算法;
- 单一职责明确但实现细节较多的测试文件;
- 临时迁移代码;
- 由框架约束导致必须集中表达的配置文件。
一个典型例子是 AB 实验配置文件。
假设一个文件有 5000 行,里面写满了不同业务的实验配置。每个配置都有唯一 key,结构高度一致,任意两个配置之间完全解耦,没有共享状态、控制流、生命周期或隐式副作用。AI 的任务只是找到某个实验配置,修改一个字段,或者按照已有模板新增一个配置。
在这种场景下,文件尺寸通常不会明显影响 AI 的上下文消耗和输出质量。AI 不需要读取完整 5000 行,它只需要搜索目标 key 或相关关键词,读取目标配置附近的几十行,再参考 schema 或相邻样例即可完成修改。这个文件虽然很大,但它本质上更像"代码形式的数据表",而不是交织在一起的业务逻辑。
这也是很多人认为"大文件不会降低 AI 输出质量"的来源之一。他们讨论的往往是这种理想情况:相关内容可以被准确检索,相关片段之外的内容确实不重要,修改不会产生跨区域副作用。在静态映射、路由表、fixture、schema、mock 数据、AB 实验配置等场景里,这个判断经常成立。
但这个结论不能直接推广到普通业务代码。普通业务代码中的函数、状态和分支往往存在隐式关系。即使代码表面上分散在同一个文件的不同位置,它们也可能共享缓存、订阅状态、异步任务、权限判断、错误处理或 UI 生命周期。此时 AI 不能只靠一次搜索命中就确认上下文充分。
此外,如果一个 3000 行文件具有清晰分区、稳定命名、明确入口和充分测试,那么 AI 处理局部任务时也可以保持较高质量。大文件的问题不是行数本身,而是行数常常伴随职责混杂、语义边界弱和检索噪声增加。
因此,合理的工程规范应该允许例外,但例外需要说明原因。
Swift extension 的文件边界
Swift 里有一个很典型的例子,可以更清楚地说明文件大小为什么会影响 AI 输出质量。
假设当前有一个巨型类,例如一个复杂的 view model。它包含状态、校验、网络请求、生成流程、订阅处理、埋点和错误恢复等逻辑。我们有三种代码分布方式:
- 把这个巨型类的所有代码都放在一个文件中;
- 把这个巨型类按职责拆成多个 extension,但所有 extension 仍然放在同一个文件中;
- 把每个 extension 放在单独的文件中。
第一种方式最不利于 AI。所有逻辑连续堆在同一个类体里,职责边界弱,搜索结果噪声高。AI 即使能找到目标函数,也更容易读到大量相邻但不相关的状态和分支。
第二种方式已经明显更好。比如:
swift
final class EditorViewModel {
// Core state and initialization.
}
extension EditorViewModel {
// Prompt validation.
}
extension EditorViewModel {
// Image generation.
}
extension EditorViewModel {
// Subscription handling.
}
这种写法至少让代码有了职责分区。AI 可以通过 MARK 注释、extension 顺序、协议 conformances 和方法命名判断某段代码的大致职责。相比一个连续 3000 行的类体,它更容易阅读,也更容易局部修改。
但第二种方式的局限在于:所有 extension 仍然共享同一个文件上下文。AI 搜索 validate、generate、state 这类关键词时,命中的仍然是同一个巨型文件里的不同区域。工具读取片段时,也很容易把相邻 extension 或同文件中的其他职责一起带入上下文。文件内部有了分区,但文件级检索边界没有变。
第三种方式通常更适合 AI 协作:
text
EditorViewModel.swift
EditorViewModel+PromptValidation.swift
EditorViewModel+ImageGeneration.swift
EditorViewModel+Subscription.swift
EditorViewModel+Analytics.swift
这里的关键变化不是代码逻辑变了,而是文件边界变成了更强的语义索引。当任务是"修改 prompt 校验逻辑"时,AI 更容易优先读取 EditorViewModel+PromptValidation.swift,而不是打开整个 EditorViewModel.swift。它通常只需要读取主类定义、目标 extension、少量调用点和测试,就能形成足够上下文。
第三种方式相比第二种方式,主要优势有几个:
- 检索结果更干净,搜索命中的是具体职责文件,而不是巨型文件里的某个区域;
- 文件名直接表达语义,
+PromptValidation比同文件第 1800 行附近的 extension 更容易被识别; - 上下文天然局部化,AI 不需要把其他职责一起读进来;
- 改动范围更容易被约束,AI 更少顺手改到相邻业务;
- 已有 helper 更容易被发现和复用,重复实现概率更低。
这说明,即使类没有真正拆成多个类型,仅仅把 extension 分散到不同文件,也会改变 AI 构造上下文的方式。第二种方式改善的是文件内部结构,第三种方式进一步改善的是文件级检索边界。
当然,Swift 里第三种方式也有现实成本。private 和 fileprivate 成员的可见性与文件边界有关。同文件 extension 可以访问更多私有细节,拆到不同文件后,某些访问会失效。开发者可能需要把部分状态或方法调整为 internal,或者提炼出更明确的内部接口。
这既是成本,也是信号。如果拆分后发现每个 extension 都必须直接读写同一大批私有状态,说明这个巨型类的职责耦合本来就很重。文件拆分会把这种耦合暴露出来。好的拆分不应该只是把行数切开,而应该让每个 extension 围绕明确职责工作,并通过少量清晰接口访问共享状态。
推荐规则
对于 AI 生成代码占比较高、并且后续仍会频繁由 AI 修改的项目,我建议采用以下规则:
- 新增业务代码文件默认不超过 1000 行。
- 超过 1000 行时,需要说明为什么暂不拆分。
- 超过 2000 行时,默认必须拆分,除非属于生成代码、数据文件、fixture 或其他明确例外。
- 3000 行作为绝对红线,而不是推荐上限。
- 存量文件不强制按该规则立即重构,避免为了满足规范制造不必要风险。
- 拆分时按职责、生命周期、状态归属和复用边界拆,而不是机械按行数切片。
最后一点尤其重要。为了降低行数而把一个内聚逻辑硬拆成多个互相跳转的小文件,并不会提高 AI 友好性。AI 需要的是清晰边界,而不是碎片化。
好的拆分应该让文件名、目录结构、类型名和公开接口共同表达语义。这样 AI 在后续处理任务时,可以更快判断应该读哪里、应该改哪里、哪些已有能力应该复用。
总结
文件行数对 AI 输出质量的影响,本质上不是一个简单的 token 问题。
大文件确实可以通过搜索和局部阅读来处理。在任务边界清楚、定位信号强、结构清晰、测试充分的情况下,3000 行文件未必明显降低质量。对于 AB 实验配置这类"独立记录集合",即使文件达到 5000 行,也可能仍然适合局部处理。但在真实业务开发中,很多任务并没有精确函数名,需求常常以业务语言表达,修改也常常牵涉状态、生命周期、调用链和隐式副作用。
这时,文件大小会影响 AI 构造正确上下文的能力。1000 行软上限的价值,是把文件边界变成一种稳定的语义索引,帮助 AI 更可靠地定位、理解、修改和复用代码。Swift extension 的例子也说明了这一点:同样是一个巨型类,extension 放在同一文件中只是改善了内部结构;extension 分散到不同文件后,文件名和文件边界会进一步帮助 AI 做上下文选择。
因此,我不认为 1000 行是不可突破的硬规则,但我认为它是一个适合 AI 协作的默认约束。3000 行可以作为例外上限或红线,不应该成为新增业务代码的常规目标。
对 AI 编程来说,真正重要的不是让模型"读得下更多代码",而是让它在更少噪声、更清楚边界、更强语义索引的上下文里工作。文件行数规范的价值,也正在这里。