I. 引言
背景
大型语言模型(LLM)已成为软件开发领域的变革性技术,能够自动化并加速包括代码生成、补全和翻译在内的各种任务。¹ 开发领域见证了专门的代码 LLM 的激增,例如 CodeLlama、GPT-4、CodeGeeX 和 Amazon CodeWhisperer,它们越来越多地被集成到开发人员的工作流程中。¹ 这些模型在根据自然语言描述生成代码片段方面表现出卓越的能力,有可能提高生产力,并使新手程序员也能阐明需求并获得功能性代码。² 像 HumanEval 这样的性能基准显示,这些模型的能力随着时间的推移取得了显著进步。²
问题陈述
尽管取得了这些进步,但在将 LLM 应用于开发或修改大型代码库时,出现了一个重大挑战。正如从业者所阐述和研究观察到的那样,虽然 LLM 可以快速生成初始代码段(例如,最多几百行),但随着代码库的增长,它们的性能通常会大幅下降 [用户查询]。修改变得更慢,生成或修改的代码质量往往会下降,表现出复杂性增加、可维护性降低和潜在的不正确性等问题。¹ 这种现象在处理超过几百行的代码时尤其明显,限制了 LLM 在构建和演进大型、真实世界软件系统方面的实际效用。⁶ 核心问题似乎是一个可伸缩性瓶颈,即模型难以在大型、复杂的代码结构中维持上下文和连贯性。
提出的解决方案与假设
本报告研究了一种缓解这些限制的潜在策略:应用成熟的软件重构原则来更有效地构建 LLM 生成的代码。具体来说,它检验了这样一个假设:采用诸如“按概念拆分”(主要解释为关注点分离 (SoC) 和单一职责原则 (SRP))以及领域驱动设计 (DDD) 等技术可以创建更易于管理的代码结构。其核心前提是,通过将代码库分解为更小、定义明确、概念上连贯的模块或上下文,LLM 或许能够更有效地运行,即使在整个项目扩展时也能保持更好的上下文理解并生成更高质量的代码。这种结构化的方法可能克服在大型项目中使用 LLM 时观察到的性能下降和质量问题。
存在一个潜在的矛盾:LLM 在快速生成初始代码方面表现出优势,但在面对大多数软件项目所特有的持续演进和扩展时却步履维艰。¹ 这表明,旨在加速的工具遇到了其旨在加速的过程中固有的复杂性障碍。困难不仅在于生成速度,还在于模型在复杂性增加时有效保留和利用上下文的能力。⁷ 这表明架构结构可能是一个目前在大型 LLM 辅助开发工作流程中未被充分强调的关键因素。
报告目标与结构
本报告的主要目标是批判性地评估使用 SoC/SRP 和 DDD 重构技术来增强 LLM 辅助软件开发的可伸缩性和可维护性的可行性和有效性。这包括:
- 记录 LLM 在大规模代码生成和修改方面的已知局限性。
- 解释与代码结构和复杂性管理相关的 SoC/SRP 和 DDD 的核心原则。
- 探索将这些重构方法应用于 LLM 生成的代码的潜力。
- 分析重构后的代码结构可能如何影响 LLM 的性能和上下文理解。
- 比较 SoC/SRP 与 DDD 在此背景下的相对优缺点。
- 识别在 LLM 生成的代码上实施这些重构策略所面临的挑战。
- 综合这些发现以提供整体评估。
报告接下来将检查 LLM 的局限性,详细说明重构原则,分析其应用和影响,比较各种方法,讨论挑战,最后评估整体可行性并提出建议。
II. LLM 在大规模代码生成中的局限性
大型语言模型尽管在生成代码方面表现出色,但在处理大型或复杂的代码库时,会暴露出一些特别严重的局限性。这些局限性涵盖了性能下降、代码质量问题以及修改现有系统的困难。
A. 性能下降和上下文窗口约束
影响 LLM 在大型代码库上性能的一个基本约束是有限的“上下文窗口”——模型在生成或修改过程中可以同时处理的最大文本量(代码、指令、历史记录)。虽然现代 LLM 拥有越来越大的上下文窗口,例如 Claude 3.5 Sonnet 提到的 200K token 容量⁶,但性能问题仍然可能出现。有证据表明,即使有大的窗口,超过某些阈值(例如,Sonnet 3.5 的 100K token)也可能导致性能下降。⁶
对于大型软件项目来说,这个限制的实际影响是显著的,因为这些项目不可避免地包含比最大上下文窗口所能容纳的多得多的代码。这迫使开发人员手动筛选相关的代码片段以提供给 LLM,否则模型可能会在信息不完整的情况下运行。当必要的上下文超过窗口时,LLM 可能会有效地“忘记”或未能考虑代码库的关键部分,导致输出不一致、逻辑错误或先前建立的信息被破坏,使得 LLM 在需要对整个系统状态有深入、持久理解的任务中变得不可靠。⁷
B. 生成代码中的代码质量问题
除了上下文限制之外,LLM 生成的代码通常还存在各种质量缺陷,并且随着规模和复杂性的增加,这些缺陷往往会恶化。
- 冗余和低效: 研究表明,LLM 生成的代码可能包含冗余,执行不必要的计算,或采用次优的算法和数据结构。¹ 这些低效可能导致最终软件出现实际的性能问题,例如执行时间增加和内存消耗更高。¹ 这很可能是因为 LLM 的训练目标优先考虑根据其训练数据中的模式预测下一个可能的 token 序列,而不是优化全局效率或算法优雅性。一般的逻辑和性能相关的低效被认为是常见问题。¹
- 可维护性和可读性: LLM 生成的代码经常被标记为难以维护、不必要的复杂且缺乏可读性。¹ 这与代码“又长又烂”的轶事观察结果一致 [用户查询]。结构不良、命名不一致、缺乏注释或逻辑过于复杂,使得人类开发人员难以理解、调试和安全地修改代码。此外,这种缺乏清晰度也可能阻碍后续与相同或其他试图修改代码的 LLM 的交互。一些分析表明,AI 助手通常缺乏“全局观”,通过结构不良的代码导致技术债务。⁸
- 正确性和可靠性: 确保 LLM 生成代码的功能正确性仍然是一个重大挑战。³ 研究表明,LLM 生成的代码可能比人类编写的代码包含更多的错误和安全漏洞。³ 模型可能会误解任务要求,难以处理边缘情况,或生成带有细微逻辑缺陷的代码。¹ 这需要花费大量时间进行生成后的验证、测试和调试,可能抵消生产力收益。¹ 这些问题的普遍存在常常导致开发人员发现生成的代码在没有大量返工的情况下无法使用。³ 正在探索分析内部 LLM 状态的开放式方法来主动评估可靠性 ³,但黑盒验证仍然很常见。
- 处理复杂性: LLM 在处理复杂的软件系统时面临固有的困难,特别是那些涉及复杂控制流、数据依赖或专业领域知识的系统,例如在高性能计算 (HPC) 中。⁵ 现有模型通常缺乏深入的程序分析能力,无法完全理解并正确生成或修改这些复杂上下文中的代码。⁵ 虽然 LLM 越来越多地用于代码分析任务,如摘要和模式识别 ⁹,但它们准确推理大型复杂系统行为的能力仍然有限。正确性、效率和安全性是 AI 生成代码分析的突出领域。¹⁰
这些记录在案的质量问题——低效、可维护性差、错误——不仅仅是孤立事件。它们往往随着代码库的增长而复合。少量冗余或复杂性在小型脚本中可能是可以容忍的 ¹,但当 LLM 生成数千行代码时,这些小缺陷会迅速累积。这种累积会产生显著的技术债务 ⁸,使整个系统变得脆弱、难以推理,并且难以可靠地修改——无论是对人类还是对后续的 LLM 交互而言。这种复合效应很可能显著导致了在超过某个复杂性阈值后观察到的 LLM 效用的急剧下降。
核心冲突似乎在于 LLM 固有的局部优化倾向(基于直接上下文预测下一个最佳 token)与大型软件系统中全局一致性要求之间的矛盾。主要基于序列预测训练的 LLM,本身并不优先考虑架构完整性、长程依赖或遵守软件设计原则。¹ 重构的目的恰恰是强加这种缺失的全局结构,可能创建更小、更连贯的“局部”上下文,这可能更符合 LLM 的操作优势。
C. 修改现有大型代码库的挑战
使用 LLM 修改现有大型代码库带来了超出初始生成的具体挑战:
- 上下文理解: 如前所述,有限的上下文窗口严重妨碍了 LLM 理解大型项目完整范围和复杂性的能力,导致修改可能在局部看似合理,但在全局上不一致或不正确。
- 保持一致性: 在没有全面理解系统架构、依赖关系和不变量的情况下,确保 LLM 在系统某一部分引入的更改不会与系统的其他部分冲突或破坏它们是很困难的。LLM 输出的不确定性会进一步使这个问题复杂化。⁹
- 集成工作: LLM 生成或修改的代码通常需要大量的人工干预,包括调试、重构和测试,然后才能安全地集成到主代码库中。¹ 这种集成开销会消耗大量的开发人员时间,削弱了使用 LLM 预期的速度优势。从业者对增加这些调试和重构工作的低效表示担忧。¹
III. 用于复杂性管理的代码重构原则
重构是重新构造现有计算机代码的过程——改变其构造——而不改变其外部行为。它是一种在代码编写完成后改进代码库设计的规范化技术。与管理 LLM 生成代码中观察到的复杂性相关的两组关键原则是关注点分离/单一职责原则和领域驱动设计。
A. 按概念拆分(关注点分离 & 单一职责原则)
“按概念拆分”的思想与旨在通过模块化管理复杂性的基本软件工程原则密切相关。
- 核心思想: 关注点分离 (SoC) 原则主张设计软件时将其分解为不同的部分或模块,每个部分处理一个特定的关注点(例如,用户界面、业务逻辑、数据访问)。⁸ 这些模块应尽可能独立运行,最大限度地减少重叠。¹¹ 单一职责原则 (SRP) 是 SOLID 原则之一,是 SoC 的一个具体应用。⁸ 它规定一个类或模块应该只有一个改变的理由,这意味着它应该对软件功能的单一、明确定义的部分负责,并且该职责应完全由该类封装。¹² 本质上,每个单元应该“只做一件事,并且只做一件事”。¹² 这与“上帝对象”等反模式形成鲜明对比,后者中单个类处理不同的职责(例如,UI、逻辑、数据库调用),导致结构脆弱且难以维护。¹³
- 好处: 遵守 SoC 和 SRP 会带来许多对管理复杂性至关重要的好处。它促进了模块化,将大问题分解为更小、可管理的单元。⁸ 这导致耦合减少(模块之间的相互依赖性降低)和内聚增加(模块内的元素紧密相关)。¹¹ 因此,代码变得更容易理解、维护、测试和重用。⁸ 当关注点分离良好时,可以更独立地开发、更新和推理各个部分,从而减轻处理系统特定部分所需的脑力负荷。¹¹ 这直接解决了大型、非结构化代码库(包括 LLM 生成的代码库)中常见的可维护性和复杂性问题。
- 技术: 实现 SoC/SRP 涉及应用诸如模块化设计、最小化组件间耦合 ¹¹、最大化组件内内聚 ¹⁵ 以及有意识地避免违反这些原则的反模式(如上帝对象、意大利面条式代码(高度纠缠的逻辑)和过度的复制粘贴编程 ¹³)等技术。指导重构以实现更好抽象的一个常用启发式方法是马丁·福勒推广的“三法则”,该法则建议只有当代码出现三次或更多次时才应将其重构为可重用组件,从而防止过早或不必要的抽象。⁸ 其他相关原则包括 DRY(不要重复自己)¹¹ 和 Unix 的“做好一件事”哲学,这在概念上与 SRP 一致。¹⁸
B. 领域驱动设计 (DDD)
领域驱动设计提供了一种更全面的方法,特别适用于在复杂业务领域内运行的软件系统。
- 核心思想: DDD 是一种软件开发方法,其核心是创建核心业务领域的复杂模型,捕捉其错综复杂的过程、规则和逻辑。¹⁹ 其目标是使软件设计与它所代表的业务现实紧密结合,使软件成为领域理解的直接体现。²⁰ DDD 强调技术团队和领域专家之间的协作,以迭代地完善这个模型。²⁴ 它在组织混乱的业务逻辑至关重要的复杂领域中特别有价值。²¹
-
战略设计: DDD 的一个关键方面,特别是对于大型系统,是战略设计,它侧重于领域模型的宏观组织。¹⁹ 关键模式包括:
-
限界上下文 (Bounded Contexts): DDD 通过将大型领域模型划分为多个限界上下文来处理复杂性。²¹ 限界上下文定义了一个明确的边界(例如,子系统或团队的职责),在该边界内,特定的领域模型及其相关语言是精确和一致的。²⁰ 这种划分允许大型系统的不同部分独立演进,并在各自边界内具有清晰的模型完整性,从而缓解了因不同业务领域之间术语或概念冲突而引起的问题。²⁵
- 通用语言 (Ubiquitous Language): 在每个限界上下文中,DDD 提倡开发一种通用语言——一种源自领域模型的共享、严谨的词汇表,由领域专家和开发团队在讨论、文档以及重要的是在代码本身中一致使用。²⁰ 这确保了清晰度,减少了歧义,并将软件实现直接与业务领域联系起来。²⁰
-
上下文映射图 (Context Maps): 这些图表可视化了大型系统中不同限界上下文之间的关系和集成,阐明了依赖关系和交互模式(例如,共享内核、客户/供应商、防腐层)。²²
-
战术设计: DDD 还提供了一套用于在限界上下文中实现领域模型的构建块模式。¹⁹ 这些包括:
-
聚合 (Aggregates): 聚合是一组相关的领域对象(实体和值对象),在数据更改时被视为单个单元。¹⁹ 每个聚合都有一个根实体,它是聚合外部唯一可访问的成员。聚合定义了一致性边界,确保跨越聚合内多个对象的业务规则被原子地执行。²⁵
- 实体 (Entities) 和值对象 (Value Objects): DDD 区分了实体(由其随时间持续存在的唯一身份定义的对象)和值对象(由其属性定义的对象,通常是不可变的,身份不是主要关注点)。¹⁹ 这种区分有助于准确地建模领域概念。
-
仓库 (Repositories): 这些为持久化和检索聚合提供了一个抽象层,将领域模型与特定的数据存储技术解耦。¹⁹
-
对复杂性的好处: DDD 通过战略设计划分为限界上下文和战术设计的建模模式,为管理大型业务领域固有的复杂性提供了一个强大的框架。¹⁹ 它有助于创建更模块化、可维护且与它们所服务的业务需求紧密结合的系统。对边界和清晰定义的强调对于管理沟通和系统复杂性至关重要,尤其是在大型或长期运行的项目中。²⁶
虽然 SoC/SRP 和 DDD 都旨在管理复杂性,但它们在不同的抽象层次上运作。SoC/SRP 为组织模块或类中的代码提供了基础性的、通常是局部的指导方针,侧重于技术职责。¹³ 相比之下,DDD,特别是战略设计,提供了一种更高级别的、以领域为中心的策略,用于根据业务能力划分整个系统。²¹ 重要的是,DDD 通常在其限界上下文和聚合中使用 SoC/SRP 原则作为良好战术实现的一部分。它们不是相互排斥的,而是实现结构良好系统的互补方法。
此外,认识到重构通常不是一次性活动,而是一个持续改进的过程至关重要。⁸ 诸如“先让它工作,再让它正确,最后让它快”和“童子军规则”(让代码比你发现时更整洁)等原则强调持续优化以保持代码质量和管理技术债务。¹¹ 这种持续性在考虑 LLM 生成的代码时尤其重要,这些代码可能需要迭代的人工指导或 LLM 辅助优化才能达到并保持理想的结构。
IV. 将重构方法应用于 LLM 生成的代码
将像领域驱动设计这样具体的、复杂的重构策略直接应用于管理 LLM 生成代码的可伸缩性,似乎是一个显式研究和既定最佳实践正在形成而非完全成熟的领域。关于 LLM 在代码生成中应用的调查涵盖了数据整理、评估和下游任务等各种主题,但对这一特定交叉点的详细探索似乎不太普遍。¹ 对自动化 LLM 代码中低效检测和重构工具的需求进一步表明这是一个活跃的开发领域。¹ 开发人员正在积极探索如何在大型现有项目的背景下利用 LLM 进行重构。²⁹
尽管缺乏广泛的专门研究,但 SoC/SRP 和 DDD 的原则可以通过几种方法在概念上应用于 LLM 生成的代码:
- 生成后重构: 最直接的方法是让人类获取 LLM 生成的初始代码,并根据 SoC/SRP 原则(例如,提取类,确保单一职责)或 DDD 原则(例如,识别潜在的限界上下文、聚合,并相应地重构代码)手动进行重构。这要求开发人员分析 LLM 的输出,识别潜在的概念或领域边界(这些边界可能被模糊或混合),并强加所需的结构。
- 引导生成: 一种更主动的方法是尝试在生成过程本身中引导 LLM。这可能涉及制作包含明确架构约束的复杂提示。例如,提示可以指示 LLM 专门为“订单处理”限界上下文生成代码,详细说明预期的聚合(如订单)和值对象,并要求生成的服务或类遵守 SRP。此处的成功在很大程度上取决于 LLM 理解和遵循复杂架构指令的能力。
- 迭代优化: 这涉及一个循环,其中 LLM 可能被用来建议甚至执行对现有代码(本身可能是 LLM 生成的)的特定重构任务,并在人工监督下进行。²⁹ 例如,开发人员可以要求 LLM “将这个大方法重构为更小的方法,每个方法都有单一职责”或“识别这个类中潜在的值对象”。然后,人类开发人员将审查并验证 LLM 的建议或操作。
一个关键的潜在协同作用在于,根据 SoC/DDD 原则结构化的代码可能天生更适合 LLM 处理。由符合 SRP 的类或 DDD 的限界上下文定义的清晰边界和集中职责可以创建更小、语义上更连贯的代码单元。这些定义明确的单元可能更符合 LLM 有限的上下文窗口和处理能力,即使在整个项目变大时,也能实现更有效的迭代开发和修改。注意到 AI 助手有时会因为缺乏“全局观”而导致技术债务;通过 SoC/DDD 强加架构结构可以作为一种关键的对策,充当“避免技术债务的基石”。⁸
然而,应用这些策略提出了一个实际问题:是应该首先重构通常混乱的初始 LLM 输出,为后续的 LLM 交互创建一个更好的基础,还是投入复杂的提示和指导来鼓励 LLM 从一开始就生成结构良好的代码?前者需要大量的前期手动工作,可能抵消 LLM 的速度优势,而后者则挑战了当前 LLM 能力和提示工程的极限。⁸ 这表明通常可能需要一种混合方法:初始 LLM 生成,然后是人工主导的战略重构(可能由 LLM 辅助执行战术任务),创建一个结构化的基线,后续的 LLM 交互可以更有效地在此基础上构建。
思考 LLM 与结构化代码之间的交互,使用 SoC/DDD 进行重构可以被视为不仅仅是代码清理。它主动构建了一个上下文的“脚手架”。通过强加清晰的边界(模块、类、限界上下文)和分离关注点 ⁸,代码库被预处理成逻辑上不同的单元。这种结构为 LLM 提供了导航辅助,帮助它在其固有限制内更有效地运行,特别是在处理大型、未分化上下文的困难方面。⁶ 重构后的结构实质上创建了 LLM 可以更连贯地处理的可管理块。
V. 重构结构对 LLM 性能的影响
假设使用关注点分离、单一职责原则和领域驱动设计等原则来构建 LLM 生成的代码,会对 LLM 的性能和有效性产生积极影响,特别是在大型和不断演进的代码库的背景下。
A. 增强的上下文管理
根据 SoC/SRP(产生更小、更集中的类或模块)或 DDD(产生不同的限界上下文和聚合)良好结构化的代码,自然地将代码库分割成更易于管理的单元。当 LLM 被赋予在特定的、定义明确的模块或限界上下文中工作的任务时,与该任务直接相关的代码更有可能适应其操作上下文窗口。
这种改进背后的机制涉及减少 LLM 的认知负荷。清晰的边界最大限度地减少了模型需要同时处理的不相关代码和信息的数量。这使得 LLM 能够将其注意力和计算资源集中在手头的特定任务上,可能导致更高保真度的输出,并减少在大型、非结构化上下文中观察到的上下文损坏或信息丢失的风险。⁷ 这些定义明确的单元之间的显式依赖关系(例如,模块之间的接口、限界上下文之间的关系)可能通过在提示中有针对性地包含或利用诸如检索增强生成 (RAG) 之类的技术来动态提供相关的跨边界信息而得到更有效的管理。¹⁰
B. 改进的代码理解和相关性
具有清晰职责 (SRP) 并与业务领域 (DDD) 对齐的代码库拥有更明确的语义结构。通过其定义的角色和边界,单个代码段的目的变得更加清晰。这种增强的清晰度可能提高 LLM “理解”代码并生成与现有设计和逻辑更相关、更一致的添加或修改的能力。
例如,与处理多个不相关任务的单体 ApplicationManager 相比,负责修改遵守 SRP 的 UserService 类的 LLM 在更清晰的功能范围内操作。类似地,限制在 Ordering 限界上下文中的指令比应用于未分化代码库的模糊指令提供了更强的领域约束。这种改进的结构和语义清晰度可能导致 LLM 在处理代码时产生更可靠的内部表示,可能与生成输出中更高的质量和正确性相关,这与探索内部 LLM 状态以获取可靠性信号的研究一致。³
C. 专注生成和修改的潜力
重构使 LLM 能够进行更有针对性的干预。开发人员可以将模型引导到特定的类、模块、聚合或特定的限界上下文中操作,而不是可能将大量、未分化的代码块输入 LLM 的上下文。然后,提示可以只包含必要的上下文:正在修改的代码单元、其直接依赖项(例如,接口、关键协作者)以及所需更改的自然语言描述。
这种专注的方法减少了提供给 LLM 的输入中的噪声和歧义,增加了生成尊重既定架构的准确、局部化更改的可能性。它允许开发人员在更大的系统内以更可控和可预测的方式利用 LLM 的代码生成能力。
一个经过良好重构的代码库可以作为一种隐式提示。一致的组织、对设计原则的遵守、有意义的命名约定(例如 DDD 的通用语言 ²⁰)以及清晰的边界充当了结构性线索。在包含具有类似模式的代码的大量数据集上训练的 LLM ¹,很可能识别并遵循这些既定结构。这种固有的结构一致性可以引导 LLM 的预测朝着生成自然融入现有架构的代码方向发展,即使在每个提示中没有明确的架构指令。
此外,通过模块化和清晰边界(SoC/SRP 和 DDD 固有)实现的隔离可以有效减少潜在 LLM 错误的“爆炸半径”。通过最小化组件之间的耦合 ¹¹,LLM 在一个封装良好的模块或限界上下文 ¹ 中引入的错误或缺陷不太可能传播并在整个系统中引起意外后果。与在高度耦合的“意大利面条式代码” ¹³ 或单体的“大泥球” ²⁰ 中排除故障相比,这种遏制简化了调试和纠正。
VI. 比较分析:SoC/SRP 与 DDD
虽然关注点分离/单一职责原则 (SoC/SRP) 和领域驱动设计 (DDD) 都旨在通过模块化来管理复杂性,但它们在范围、重点和对 LLM 交互的潜在影响方面有所不同。
- 范围和抽象级别: SoC/SRP 主要在较低的抽象级别上运作,关注单个类或模块的技术职责分离。¹³ 它们提供了一种通用的、技术驱动的方法来组织代码。相比之下,DDD,特别是其战略设计方面,在更高的系统级别上运作。¹⁹ 它根据业务领域和能力来划分系统,创建限界上下文。²¹ 这使得 DDD 在概念上更接近于架构设计,而 SoC/SRP 更接近于详细设计。
- 关注点: SoC/SRP 的主要关注点是技术内聚和耦合。¹¹ 它们旨在确保代码单元具有单一、明确定义的技术目的。DDD 的主要关注点是业务领域。¹⁹ 它旨在使软件结构与业务逻辑和流程保持一致,使用通用语言 ²⁰ 并围绕核心领域概念进行建模。²⁵
-
对 LLM 的影响:
-
SoC/SRP: 应用 SoC/SRP 可以创建更小、更集中的代码单元(类/模块)。这对于在 LLM 有限的上下文窗口内工作非常有益,并可以提高局部代码生成的清晰度。⁷ 然而,它本身可能无法为 LLM 提供足够的宏观上下文来理解跨模块的交互或整体业务流程。
-
DDD: DDD 的限界上下文 ²¹ 提供了更高级别的结构单元。这可能有助于 LLM 理解系统的不同业务部分以及它们之间的关系(通过上下文映射图 ²²)。在限界上下文内操作可以为 LLM 提供强大的领域约束,从而可能产生更符合业务需求的输出。然而,单个限界上下文可能仍然很大,并且其内部实现仍然需要 SoC/SRP 原则才能实现有效的局部上下文管理。
-
实施复杂性: 与 DDD 相比,SoC/SRP 通常更容易理解和应用。它们是许多开发人员熟悉的基础设计原则。⁸ DDD,特别是战略设计,需要更深入地理解业务领域,与领域专家的协作,以及对限界上下文、聚合和通用语言等概念的把握。¹⁹ ²⁰ ²⁴ 这使得 DDD 的实施更具挑战性,尤其是在没有明确领域专业知识或组织支持的情况下。
- 协同作用: 正如前面提到的,这些方法不是相互排斥的。一个强大的方法通常涉及在 DDD 定义的限界上下文和聚合内部应用 SoC/SRP 原则。¹³ ²⁵ DDD 提供了宏观结构(系统如何根据业务进行划分),而 SoC/SRP 提供了微观结构(每个部分的内部组织方式)。对于 LLM 来说,这种组合可能提供两全其美的效果:DDD 的限界上下文提供了业务领域约束和更高层次的结构,而 SoC/SRP 确保了这些上下文中的代码被组织成 LLM 可以有效处理的可管理的、集中的单元。
总之,SoC/SRP 提供了基本的代码级组织,这可能有助于 LLM 的局部上下文管理。DDD 提供了一个更全面的、以业务为中心的框架,用于构建大型系统,这可能有助于 LLM 的领域理解和更高层次的上下文。对于旨在利用 LLM 处理大型、复杂、面向业务的系统的团队来说,结合使用这两种方法可能是最有益的,利用 DDD 进行战略划分,利用 SoC/SRP 进行战术实现。
VII. 挑战与考量
虽然通过重构(使用 SoC/SRP 或 DDD)来构建 LLM 生成的代码以提高可伸缩性很有希望,但也存在一些挑战和需要考虑的因素:
- 重构成本与 LLM 速度: 应用严格的重构原则,无论是手动还是 LLM 辅助,都需要时间和精力。¹ 这可能会抵消使用 LLM 进行快速初始代码生成所带来的速度优势。在快速原型设计与构建长期可维护系统之间需要权衡。⁸
- LLM 理解架构指令的能力: 让 LLM 直接生成符合复杂架构模式(如 DDD)的代码仍然是一个挑战。LLM 可能难以一致地应用高级设计原则或理解限界上下文等抽象概念的细微差别。这通常需要仔细的提示工程和人工验证。⁸
- 识别重构边界: 特别是对于 DDD,识别 LLM 生成的代码中(或现有代码中)正确的限界上下文和聚合可能很困难,尤其是在没有明确领域专业知识的情况下。LLM 本身可能无法可靠地识别这些高级别的业务边界。
- 维护重构结构: 即使代码库最初被重构,后续由 LLM(或人类)进行的修改也可能无意中破坏既定的结构,重新引入耦合或违反职责分离,从而导致架构漂移。⁸ 需要持续的警惕和潜在的自动化检查来维护设计完整性。
- 工具支持: 目前缺乏专门用于根据 SoC/DDD 原则自动重构 LLM 生成代码的成熟工具。虽然存在通用的重构工具和一些用于 LLM 代码分析的新兴工具 ¹⁰,但将设计原则应用于 LLM 输出的特定任务仍然主要依赖于人工。
- 评估开销: 验证 LLM 生成或重构的代码是否正确遵守了设计原则并保持了功能正确性,这本身就需要付出努力。³ 这增加了开发过程的开销。
- 领域专业知识(对于 DDD): 有效应用 DDD 严重依赖于对业务领域的深入理解以及与领域专家的合作。²⁴ 在 LLM 辅助的工作流程中整合这种专业知识可能需要新的协作模式。
克服这些挑战可能需要一种多方面的方法,结合改进的 LLM 能力(更好地理解架构)、更智能的开发工具(用于 LLM 辅助重构和验证)、仔细的提示策略以及人类开发人员在指导、验证和维护架构完整性方面的持续监督。
VIII. 结论与建议
大型语言模型为加速软件开发提供了巨大的潜力,但在处理大型代码库时,它们面临着显著的可伸缩性挑战,表现为性能下降和代码质量问题,如复杂性增加、可维护性降低和潜在的不正确性。¹ ⁶ 这些问题似乎源于 LLM 有限的上下文窗口和它们在大型、非结构化代码中维持全局一致性的困难。⁷
应用成熟的软件重构原则,特别是关注点分离/单一职责原则 (SoC/SRP) 和领域驱动设计 (DDD),为缓解这些挑战提供了一条有希望的途径。通过将代码库分解为更小、定义明确、概念上连贯的单元(无论是技术模块/类还是业务限界上下文),重构可以创建更易于 LLM 处理的结构。⁸ ¹⁹
- SoC/SRP 通过创建更小、更集中的代码单元来提供基本的、技术层面的组织,这可以直接帮助管理 LLM 的上下文窗口限制并提高局部代码生成的清晰度。¹³
- DDD 提供了一个更高层次的、以业务为中心的结构,通过限界上下文将系统与业务领域对齐。²¹ 这可以增强 LLM 对业务逻辑的理解,并在更宏观的层面上指导其生成和修改。
重构后的结构有望通过以下方式提高 LLM 的性能:增强上下文管理(使相关代码适应上下文窗口)、改进代码理解(提供更清晰的语义结构)以及实现更专注的生成和修改(将 LLM 定向到特定单元)。⁷ ¹⁰ 这种结构还可以作为一种隐式提示,引导 LLM 生成更符合既定架构的代码,并限制潜在错误的传播范围。
然而,实施这些策略并非没有挑战。重构所需的前期投入可能会抵消 LLM 的速度优势,LLM 理解和遵循复杂架构指令的能力仍在发展中,并且需要持续努力来维护架构完整性。⁸
建议:
- 采用混合方法: 对于大型项目,考虑采用混合方法。利用 LLM 进行初始代码生成或特定任务,然后进行人工主导的战略重构(使用 DDD 识别限界上下文),并可能利用 LLM 辅助进行战术重构(应用 SoC/SRP 在上下文中)。
- 优先考虑结构: 认识到架构结构不仅仅是事后的想法,而是有效利用 LLM 处理大型代码库的关键推动因素。尽早投入结构化设计(无论是手动还是通过引导式 LLM 生成)。⁸
- 迭代重构: 将重构视为一个持续的过程,而不是一次性活动。定期审查和重构 LLM 生成或修改的代码,以防止架构漂移并管理技术债务。¹¹
- 战略性提示: 尝试使用包含明确架构约束(例如,目标模块、职责、限界上下文)的提示来引导 LLM 生成更结构化的代码,但要准备好进行验证和手动调整。
- 人类监督至关重要: 尽管 LLM 不断进步,但人类开发人员在定义架构、验证 LLM 输出、执行复杂重构和确保整体系统完整性方面仍然至关重要。¹
- 探索工具: 关注能够自动化或辅助基于设计原则的代码分析和重构的新兴工具,特别是那些为 LLM 生成的代码量身定制的工具。¹⁰
通过有意识地将 SoC/SRP 和 DDD 等重构原则整合到 LLM 辅助的开发工作流程中,团队可以更好地利用 LLM 的优势,同时减轻其在处理大型、复杂软件系统时的局限性,从而构建更具可伸缩性、可维护性和可靠性的应用程序。
或许您还需要下面的文章: