利用大语言模型辅助软件开发的实践方法


基本信息


导语

随着大语言模型(LLM)能力的提升,软件开发的协作模式正在发生实质性改变。本文作者结合自身实践,探讨了如何将 AI 从简单的代码补全工具转变为深度参与架构设计与逻辑实现的“结对编程”伙伴。通过梳理具体的交互策略与工作流,文章旨在帮助开发者更高效地利用 LLM 解决复杂工程问题,在保证代码质量的同时显著提升研发效率。


评论

中心观点 文章主张开发者应从“代码编写者”转变为“系统架构师与审查者”,利用大语言模型(LLM)作为初级程序员来处理具体实现细节,从而通过人机协作大幅提升软件开发的效率与质量。

支撑理由与评价

  1. 认知负载的转移与架构能力的提升

    • 作者观点:LLM擅长处理具体的语法、库调用和样板代码,这使得人类开发者可以将精力集中在更高层次的系统设计、业务逻辑和边缘情况处理上。
    • 你的推断:这标志着软件开发门槛的形态发生了根本性变化。未来的核心竞争力不再是“记忆API”,而是“精准描述需求”和“鉴别代码质量”。
    • 事实陈述:GitHub Copilot等工具的官方研究显示,在某些基准测试中,编码速度提升了近55%。
  2. 迭代式交互优于一次性生成

    • 作者观点:成功的LLM编程不是“魔法一键生成”,而是通过不断的Prompt迭代、重构和验证来完成的。文章强调将大任务拆解为小块,逐步让LLM实现。
    • 评价(内容深度):这一点非常有见地。它揭示了LLM的本质是概率预测引擎,而非逻辑推理引擎。通过缩小上下文窗口,可以提高生成的准确性。这与传统的“敏捷开发”在形式上不谋而合,但执行单元变成了AI。
  3. 审查者的角色至关重要

    • 作者观点:LLM生成的代码必须经过严格的审查。文章建议开发者应像对待初级工程师提交的代码一样,带着怀疑的态度去检查每一行代码。
    • 评价(实用价值):这是目前最落地的建议。许多初学者容易陷入“盲目复制粘贴”的陷阱。文章强调的“审查者”心态是防止AI引入隐蔽Bug(如安全漏洞、逻辑死循环)的防火墙。

反例与边界条件

  1. 复杂系统与长程依赖的失效

    • 边界条件:当涉及高度复杂的分布式系统架构,或者代码中存在跨多个文件的深层状态依赖时,LLM往往会“产生幻觉”或忽略上下文。
    • 反例:在重构一个拥有10年历史的遗留系统时,LLM可能无法理解隐式的业务规则,反而会引入破坏性变更。
  2. 调试与黑盒问题的困境

    • 边界条件:LLM擅长生成代码,但不擅长解决环境配置问题或非确定性的并发Bug。
    • 反例:当遇到一个由于底层驱动版本冲突导致的诡异报错时,LLM通常会给出通用的、无效的建议(如“尝试重启”或“重新安装”),浪费开发者时间。
  3. 边际效益递减

    • 反例:对于非常简单的脚本(如5分钟写完的脚本),打开IDE、编写Prompt、审查代码的时间可能超过直接手写的时间。

可验证的检查方式

  1. 代码覆盖率与Bug率对比实验

    • 指标:选取两组开发能力相当的开发者,一组使用LLM,一组不使用。在完成相同功能模块后,对比单元测试覆盖率、Linting工具报错数以及QA阶段发现的Bug数量。
    • 观察窗口:3个Sprint(约6周)。
  2. 技术债务累积速度

    • 指标:监控使用LLM生成的代码在后续维护中的修改频率。如果LLM代码倾向于“快速实现”但缺乏扩展性,会导致技术债务偿还周期缩短。
    • 检查方式:代码审查中标记的“Refactor Needed”数量占比。
  3. 上下文切换频率

    • 指标:记录开发者在IDE和LLM Chat窗口之间的切换次数。过高的切换频率可能意味着Prompt工程效率低下,或者LLM无法理解意图。

实际应用建议

  1. 建立“AI-First”的代码规范

    • 不要让AI写“面条代码”。在Prompt中强制要求代码风格、类型注解和文档字符串标准。利用LLM生成代码的同时生成对应的单元测试,这是验证逻辑正确性的最快方式。
  2. 警惕“知识幻觉”

    • 在使用不熟悉的库或新框架时,LLM可能会编造不存在的API。必须养成查阅官方文档与AI输出对照的习惯,特别是对于版本号敏感的依赖项。
  3. 将Prompt作为资产

    • 不要只把Prompt当作临时的指令。将高质量的、能解决特定架构问题的Prompt保存下来,形成团队的私有知识库。这实际上是在将隐性的编程经验转化为显性的Prompt工程能力。

总结 这篇文章准确地捕捉到了软件工程范式转移的早期信号。它没有过分吹捧AI的“全能”,而是务实将其定位为“初级程序员”工具。其最大的价值在于强调了人类判断力在AI辅助编程中的核心地位。然而,文章可能低估了在大型遗留代码库中引入AI的摩擦成本。对于行业而言,这种工作模式将加速“平庸码农”的淘汰,同时对资深架构师的需求将不降反升,因为只有他们懂得如何驾驭、约束和校准AI的输出。


代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 示例1:使用LLM进行代码自动补全
def code_completion():
    """
    模拟LLM代码补全功能
    输入部分代码片段,返回可能的补全建议
    """
    # 模拟的代码片段
    code_snippet = "def calculate_sum(a, b):\n    "
    
    # 模拟LLM生成的补全建议(实际应用中会调用API)
    completion = "return a + b"
    
    # 组合完整代码
    full_code = code_snippet + completion
    print("补全后的代码:")
    print(full_code)
    
    # 验证代码可运行性
    exec_globals = {}
    exec(full_code, exec_globals)
    print("测试结果:", exec_globals['calculate_sum'](3, 5))

code_completion()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 示例2:LLM辅助代码审查
def code_review():
    """
    使用LLM进行代码质量检查
    输入代码片段,返回改进建议
    """
    # 待审查的代码
    code = """
def process_data(data):
    result = []
    for i in range(len(data)):
        if data[i] % 2 == 0:
            result.append(data[i] * 2)
    return result
"""
    
    # 模拟LLM的审查建议(实际会调用API)
    review_suggestions = [
        "1. 建议使用列表推导式简化循环",
        "2. 变量名可以更具描述性,如'even_numbers'",
        "3. 可以添加类型注解提高代码可读性"
    ]
    
    print("代码审查建议:")
    for suggestion in review_suggestions:
        print(suggestion)
    
    # 展示改进后的代码
    improved_code = """
def process_data(data: list[int]) -> list[int]:
    \"\"\"处理数据,返回偶数乘以2的结果\"\"\"
    return [x * 2 for x in data if x % 2 == 0]
"""
    print("\n改进后的代码:")
    print(improved_code)

code_review()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 示例3:LLM辅助单元测试生成
def generate_tests():
    """
    使用LLM自动生成单元测试
    输入函数定义,返回测试用例
    """
    # 目标函数
    def calculate_discount(price, discount_rate):
        if discount_rate < 0 or discount_rate > 1:
            raise ValueError("折扣率必须在0-1之间")
        return price * (1 - discount_rate)
    
    # 模拟LLM生成的测试用例
    test_cases = [
        {"input": (100, 0.2), "expected": 80},
        {"input": (50, 0.5), "expected": 25},
        {"input": (100, 1.1), "expected": "ValueError"},
        {"input": (100, -0.1), "expected": "ValueError"}
    ]
    
    print("生成的测试用例:")
    for i, case in enumerate(test_cases, 1):
        print(f"测试用例{i}: 输入{case['input']}, 预期{case['expected']}")
    
    # 运行测试
    print("\n运行测试结果:")
    for case in test_cases:
        try:
            result = calculate_discount(*case['input'])
            assert result == case['expected']
            print(f"✓ 通过: {case['input']} -> {result}")
        except Exception as e:
            expected = case['expected']
            if isinstance(expected, str) and type(e).__name__ == expected:
                print(f"✓ 通过: {case['input']} 正确抛出{expected}")
            else:
                print(f"✗ 失败: {case['input']} -> {e}")

generate_tests()

案例研究

1:Cursor + GPT-4 重构遗留代码库

1:Cursor + GPT-4 重构遗留代码库

背景: 某 SaaS 初创公司拥有一个运行了 5 年的 Node.js 后端服务,核心逻辑由已离职的 CTO 编写,代码中包含大量复杂的业务逻辑,但缺乏注释和文档。新接手的团队对业务理解不深,且不敢轻易修改代码以免引入 Bug。

问题: 开发效率极低,任何新功能的开发都需要花费数天时间阅读和理解现有代码。团队面临“技术债务”堆积的风险,且由于缺乏单元测试,重构风险巨大。

解决方案: 开发团队引入了 AI 编程工具 Cursor。他们将整个代码库导入 Cursor 的上下文中。首先,他们要求 LLM(GPT-4)“解释这段复杂函数的用途并生成详细注释”;随后,利用 LLM 生成针对核心模块的单元测试,确保测试覆盖率达到 80% 以上;最后,在 LLM 辅助下,将原本混乱的回调函数重构为现代的 Async/Await 语法。

效果: 团队在两周内完成了原本预计需要两个月才能完成的代码梳理和重构工作。新成员通过 AI 解释代码,上手时间从 2 周缩短至 3 天。由于补充了完整的测试用例,后续开发的 Bug 率下降了 40%,团队开发信心显著提升。


2:独立开发者使用 Copilot 构建 MVP

2:独立开发者使用 Copilot 构建 MVP

背景: 一位具有产品思维但编程能力有限的独立开发者,想要验证一个“AI 自动生成周报”的市场需求。他需要快速构建一个包含前端、后端和数据库的 Web 应用,且预算有限,无法雇佣外包团队。

问题: 最大的障碍是“启动时间”和全栈开发的复杂性。如果从零学习数据库设计和 API 编写,产品上线时间将推迟数月,可能错过市场窗口。此外,编写样板代码极其枯燥,容易消磨开发者的热情。

解决方案: 开发者使用 GitHub Copilot 作为结对编程助手。他专注于编写业务逻辑的核心伪代码,而让 Copilot 补全具体的 SQL 语句、API 接口代码以及前端 CSS 样式。在遇到不懂的库(如 Next.js 的特定功能)时,他直接在编辑器中通过自然语言询问 Copilot,让其生成示例代码并直接应用。

效果: 该 MVP(最小可行性产品)在 3 天内开发完成并上线。开发者仅编写了约 30% 的代码,其余 70% 的样板代码和错误处理均由 AI 生成。通过快速验证,他收集到了首批 100 个用户反馈,证明了产品方向的可行性。


3:企业内部工具链的“翻译官”模式

3:企业内部工具链的“翻译官”模式

背景: 一家中型金融科技公司的数据团队主要使用 Python 进行数据分析,而工程团队主要使用 Go 语言开发微服务。当数据团队需要将一个验证过的数据清洗算法交付给工程团队集成到生产环境时,经常出现沟通不畅和实现细节偏差的问题。

问题: 数据团队不熟悉 Go 的并发模型和语法,工程团队又不理解 Python 的 Pandas 逻辑。跨语言协作导致了大量的代码审查时间和反复的“翻译”工作,且容易在算法移植过程中产生数值精度错误。

解决方案: 团队采用 LLM(如 GPT-4 或 Claude 3.5 Sonnet)作为“翻译中介”。数据团队编写核心 Python 逻辑,然后提示 LLM:“将此 Python 函数转换为惯用的 Go 语言代码,并处理大整数精度问题,同时添加必要的错误处理。” 工程团队随后审查 AI 生成的 Go 代码,将其作为基础进行微调和集成。

效果: 跨语言协作的交付周期从 3 天缩短至 4 小时。AI 生成的 Go 代码质量高,不仅语法正确,还自动处理了 Python 中被忽略的并发安全和内存管理问题。这种模式让团队能够专注于各自擅长的领域,消除了语言壁垒带来的摩擦。


最佳实践

最佳实践指南

实践 1:建立精准的上下文环境

说明: 大语言模型(LLM)无法凭空猜测你项目的具体架构或业务逻辑。为了获得高质量的代码建议,必须将相关的代码文件、函数定义或文档直接提供给模型。这相当于在IDE中打开文件,让模型“看到”当前的上下文,从而生成与现有代码风格一致且逻辑兼容的代码。

实施步骤:

  1. 在提问前,使用复制粘贴或上下文引用功能,将相关的类定义、接口或现有函数体输入给模型。
  2. 明确告知模型当前的编程语言、框架版本以及项目依赖。
  3. 如果是修改代码,先提供“之前”的代码块,再描述需要的改动。

注意事项: 避免一次性粘贴过大的文件(如整个node_modules),应聚焦于当前正在编辑的具体模块或依赖项。


实践 2:采用迭代式交互与逐步细化

说明: 软件开发是复杂的逻辑构建过程,指望模型一次性生成完美无缺的大型功能是不现实的。最佳实践是将大任务拆解为小步骤,通过多轮对话逐步引导模型完善代码。这类似于敏捷开发中的迭代,每一步都建立在正确的基础上。

实施步骤:

  1. 将需求拆解:先要求模型生成函数签名或伪代码。
  2. 逐步填充:确认逻辑无误后,再要求模型填充具体的实现细节。
  3. 逐行审查:每生成一个函数,立即检查并指出问题,要求模型修正。
  4. 组合测试:将各个小模块组合后,再进行整体调试。

注意事项: 不要在提示词中堆砌过多复杂的需求。保持对话的连贯性,利用模型的短期记忆来维持上下文。


实践 3:掌握“橡皮鸭”式调试法

实施步骤:

  1. 描述预期行为与实际行为的差异。
  2. 粘贴报错日志和相关的代码片段。
  3. 向模型解释:“我认为这段代码应该做X,但它却做了Y,可能的原因是什么?”
  4. 根据模型的分析建议,检查变量状态、边界条件或并发问题。

注意事项: 确保提供的报错信息是完整的,包括堆栈跟踪,以便模型精准定位问题。


实践 4:利用模型生成测试用例而非仅生成代码

说明: 代码的正确性需要验证。与其仅仅依赖模型写代码,不如利用模型强大的逻辑能力来为你的代码编写测试用例。这不仅能验证代码质量,还能帮助你思考边缘情况。

实施步骤:

  1. 完成核心功能代码编写后,要求模型:“请为这段代码编写单元测试,覆盖正常路径和边缘情况。”
  2. 要求模型使用特定的测试框架(如PyTest, JUnit, Jest)。
  3. 运行测试,如果测试失败,将错误反馈给模型进行修复。
  4. 甚至可以先写测试(TDD风格),让模型根据测试用例去编写实现代码。

注意事项: 模型生成的测试可能存在语法问题或断言逻辑错误,务必人工审查测试代码的有效性。


实践 5:使用自然语言编写文档与注释

说明: 代码的可读性至关重要。利用LLM强大的语言组织能力,让它为你的代码撰写文档、API说明或复杂的行内注释。这能极大地降低团队沟通成本和未来的维护难度。

实施步骤:

  1. 选中一段复杂的逻辑代码,要求模型:“请为这段代码添加详细的注释,解释其算法逻辑。”
  2. 要求模型根据函数签名生成标准的Docstring(如Javadoc或Python Docstring)。
  3. 在完成功能开发后,要求模型生成README文件或API使用指南。
  4. 将生成的文档整合到项目中,并确保准确性。

注意事项: 模型可能会“过度解释”显而易见的代码,或者生成与代码实际行为不符的文档(幻觉),因此必须人工校对。


实践 6:明确约束条件与代码风格

说明: 默认情况下,模型生成的代码往往是通用的、教科书式的。为了使其符合生产环境标准,必须在提示词中明确技术约束、性能要求和代码风格规范。

实施步骤:

  1. 在生成代码前,明确指定:“使用函数式编程风格”、“不要使用外部库”或“保持时间复杂度在O(n)以内”。
  2. 如果项目有特定的代码规范(如Google Style Guide),将其摘要告知模型。
  3. 要求模型处理特定的边缘情况,例如:“请处理输入为None或空字符串的情况。”
  4. 对于安全敏感的代码,明确要求:“请检查是否存在SQL注入风险。”

注意事项: 约束条件越多,模型生成的代码可能越受限。要在“具体要求”和“给模型发挥空间”之间找到平衡。


实践 7


学习要点

  • 基于《How I write software with LLMs》一文(通常指开发者分享如何利用大模型辅助编程的经验,如构建工具、重构代码或自动化工作流),以下是总结出的关键要点:
  • 将大模型视为初级程序员或结对编程伙伴,而非全知全能的替代者,开发者必须始终主导代码的设计、审查和最终决策。
  • 采用“分而治之”的策略,将复杂的编程任务拆解为极小的、独立的步骤(如编写特定函数或单元测试),以避免模型产生逻辑幻觉或上下文丢失。
  • 优先使用大模型编写测试用例而非直接编写业务代码,通过测试驱动开发(TDD)的方式确保生成的代码符合预期并具备可维护性。
  • 善用“上下文注入”技术,将架构文档、数据结构定义或旧代码片段作为背景信息提供给模型,以生成符合项目规范的代码。
  • 利用大模型处理“脏活累活”,如编写正则表达式、SQL 查询语句、样板代码或进行代码重构,以显著提升开发效率并减少认知负担。
  • 建立严格的验证与迭代闭环,对模型生成的代码保持怀疑态度,始终通过人工审查、静态分析和实际运行来验证其正确性与安全性。

常见问题

1: 目前 LLM 在编写软件方面最有效的使用场景是什么?

1: 目前 LLM 在编写软件方面最有效的使用场景是什么?

A: 根据 Hacker News 社区的讨论,LLM 最擅长处理“上下文相对独立”且“逻辑通用”的任务。主要应用场景包括:

  1. 样板代码生成:编写单元测试、正则表达式、SQL 查询或特定的数据结构(如 JSON Schema)。
  2. 辅助理解:解释复杂的代码库、文档晦涩的 API 或生成代码摘要。
  3. 语言转换:将代码从一种语言迁移到另一种(例如 Python 转 Go),或者将自然语言描述转化为特定的 API 调用。
  4. 脚本编写:编写一次性的数据处理脚本、自动化运维脚本等。 用户普遍认为,LLM 更像是一个“高级搜索引擎”或“初级程序员助手”,目前尚不具备独立完成复杂系统架构的能力。

2: 使用 LLM 编写代码时,如何处理上下文窗口限制和代码遗忘问题?

2: 使用 LLM 编写代码时,如何处理上下文窗口限制和代码遗忘问题?

A: 这是一个核心挑战。常见的解决方案包括:

  1. 模块化开发:将代码库拆分为更小的模块,只将当前正在编辑的文件或相关的几个文件作为上下文发送给 LLM。
  2. 使用 RAG(检索增强生成)工具:利用 Cursor、Copilot 等工具,它们会索引你的代码库,只检索与当前光标位置或问题最相关的代码片段发送给模型,而不是发送整个项目。
  3. 分步交互:避免要求 LLM “重写整个系统”,而是要求它“重构这个函数”或“优化这个类”。
  4. 维护技术文档:将项目的设计文档、架构图作为上下文的一部分提供给 LLM,帮助它理解全局逻辑,而不仅仅依赖代码本身。

3: 随着代码生成工具的普及,初级程序员是否会被淘汰?软件工程师的角色将如何转变?

3: 随着代码生成工具的普及,初级程序员是否会被淘汰?软件工程师的角色将如何转变?

A: 大多数开发者认为角色将发生转变,而非简单的淘汰:

  1. 技能重心转移:未来的价值将更多地体现在“系统设计”、“代码审查”和“问题定义”上,而不是单纯的语法记忆和手写代码速度。
  2. 初级开发者的挑战:初级开发者通常通过修改简单代码和学习现有代码库来成长。如果 LLM 包揽了这些工作,初级开发者可能面临“缺乏练习机会”的困境。因此,初级开发者需要更主动地学习底层原理和架构思维。
  3. 人机协作:工程师将从“Writer”转变为“Editor”和“Architect”。核心能力变成了如何精确地向 LLM 描述需求(Prompt Engineering),以及如何验证 LLM 生成的代码是否存在安全漏洞或逻辑错误。

4: 如何确保 LLM 生成的代码的安全性并避免引入 Bug?

4: 如何确保 LLM 生成的代码的安全性并避免引入 Bug?

A: 信任但验证是必须遵守的原则:

  1. 代码审查:不能直接复制粘贴 LLM 的代码到生产环境。必须像审查同事的代码一样进行逐行审查,特别关注逻辑漏洞、边界条件处理和潜在的注入攻击。
  2. 测试覆盖:在合并代码前,编写或运行全面的单元测试和集成测试。LLM 生成的代码往往在“快乐路径”上表现良好,但在异常处理上可能考虑不周。
  3. 依赖检查:LLM 有时会编造不存在的库或建议使用不安全的旧版本库。务必验证所有引入的依赖包的合法性和安全性。
  4. 简单化原则:如果生成的代码过于复杂或使用了你不熟悉的生僻语法,要求 LLM 解释它或重写为更简单的版本。

5: 目前主流的 AI 编程工具(如 GitHub Copilot, Cursor, ChatGPT/Claude 直接对话)各有什么优缺点?

5: 目前主流的 AI 编程工具(如 GitHub Copilot, Cursor, ChatGPT/Claude 直接对话)各有什么优缺点?

A:

  1. GitHub Copilot
    • 优点:集成度高,在 IDE 中实时补全,干扰小,适合快速编写单行代码或函数。
    • 缺点:上下文感知能力相对较弱,难以理解整个项目的宏观架构,有时会重复代码中的注释或版权信息。
  2. Cursor / Continue
    • 优点:具备“聊天”模式和“引用”功能,可以针对整个项目或特定文件提问,能够理解跨文件的引用关系,适合重构和解释代码。
    • 缺点:有时响应速度不如原生补全工具快,且需要改变部分编程习惯(如习惯于通过对话而非直接打字)。
  3. 直接使用 ChatGPT/Claude 网页版
    • 优点:具备较强的推理能力,适合处理复杂的算法设计、架构决策或生成一次性脚本。
    • 缺点:与 IDE 工作流割裂,需要手动复制粘贴,且无法感知本地的私有代码库(除非手动上传)。

6: LLM 在处理遗留代码或大型项目时面临哪些主要困难?

6: LLM 在处理遗留代码或大型项目时面临哪些主要困难?

A: LLM 在处理遗留代码或大型单体项目时主要面临以下困难:

  1. 上下文容量限制:大型项目代码量巨大,无法将所有文件一次性放入模型的上下文窗口,导致模型难以理解系统的全貌。

思考题

## 挑战与思考题

### 挑战 1: [简单]

问题**: 在使用 LLM 辅助编程时,直接要求模型“写一个贪吃蛇游戏”往往只能得到平庸的代码。请尝试设计一个包含“具体技术栈限制”和“核心功能约束”的 Prompt,使生成的代码能够直接运行且符合你的编码风格(例如:使用 Python + Pygame,不使用全局变量,包含面向对象设计)。

提示**: 思考如何将模糊的需求转化为结构化的指令。考虑在 Prompt 中提供示例代码片段或风格指南,利用“上下文学习”原理来引导模型模仿特定的代码模式。


引用

注:文中事实性信息以以上引用为准;观点与推断为 AI Stack 的分析。



站内链接

相关文章