探索与实践 —— 用生成式 AI 理解遗留代码库
一次技术探索的真实记录 
随着生成式 AI 的快速发展,其在代码处理领域的潜力引发了广泛关注。尤其是面对复杂的遗留代码库,它似乎为开发者打开了一扇新的大门:通过 AI 辅助,快速解析和优化代码。然而,在理论与现实之间,往往隔着一条充满挑战的探索之路。
我最初的灵感来自 2024 年 10 月 技术雷达 发布的最新报告,其中提到了一种引人注目的技术趋势:利用生成式 AI 理解和重构遗留代码库。报告指出,这项技术的目标在于通过生成式 AI 的强大语言能力,将复杂的遗留代码抽象为更易理解的文档或重构建议,进而帮助开发者重新激活那些被遗忘的项目资产。
12.利用生成式AI理解遗留代码库
试验
生成式 AI (Gen AI) 和大型语言模型 (LLMs) 能帮助开发人员编写和理解代码。尤其是在处理遗留代码库时,这种帮助显得尤为有用,特别是当文档质量差、过时或具有误导性时。自我们上次讨论此话题以来,利用生成式 AI 理解遗留代码库的技术和产品得到了进一步的发展,我们已经成功实践了一些方法,尤其是在帮助大型机现代化改造的逆向工程中。我们使用的一个特别有前景的技术是 retrieval-augmented generation (RAG)方法,其中信息检索基于代码库的知识图谱。知识图谱可以保留代码库的结构化信息,而这往往超出了 LLM 从文本代码中推导的内容。这对于那些不具备自我描述性和一致性的选留代码库尤其有帮助。另一个提升代码理解的机会是,图谱可以通过现有的或 AI 生成的文档、外部依赖关系、业务领域知识等内容进一步丰富,从而让 AI 的工作更加轻松。
对于我来说,这无疑触动了痛点。作为一名技术爱好者,手头积压了不少“历史遗留项目”(比如那些年刚学 Java 时开发的 Minecraft 插件)。这些项目在开发之初充满激情,但随着时间推移,代码变得越来越难以维护。面对这些“历史债务”,如果生成式 AI 能够提供一种高效的解析方式,岂不是开发者的福音?
于是,我决定尝试用生成式 AI 破解这些代码的奥秘,我来看看怎么个事。
生成式 AI 与遗留代码:目标与痛点 
我的目标是什么?
对遗留代码库的探索,主要集中在以下几点:
快速理解:通过生成式 AI,从代码中提取核心逻辑、功能描述以及可能的改进方向。
提升效率:减少人力对大量代码逐行分析的依赖,加速开发周期。
迭代优化:不仅理解代码,还希望生成式 AI 为重构提供灵感和建议,甚至将部分重构工作交给 AI 完成。
我遇到的痛点?
尽管生成式 AI 功能强大,但在处理复杂代码库时,仍然存在一些常见的瓶颈:
信息读取的不完整性:将代码以压缩包形式上传时,生成式 AI 无法全面解析文件结构。
上下文窗口限制:即便将项目转化为文本格式,超长的代码文本依然超出了 AI 模型的处理范围。
工具局限:现有工具在支持超长文档、处理全局逻辑上表现不足。
这些问题,使得实现目标的过程远比预期复杂,下面是我做过的一些探索与尝试。
探索历程:从失败到最终方案 
第一步:直接上传项目,碰壁的开始
我最初的尝试十分直接:将项目打包成压缩文件,然后上传给大语言模型,期待它能解析出文件夹结构、代码功能甚至可能的优化建议。然而,AI 模型返回的结果却让我困惑:
- 它只能看到压缩包中的部分文件,且对项目全貌一无所知。
- 输出结果片面且割裂,完全没有办法帮助我理解代码逻辑。
问题根源:生成式 AI 对文件夹和压缩包缺乏原生支持,它的“视野”被格式限制住了。
第二步:用 project2txt,将项目转化为文本
吸取了第一次的教训后,我手动编写了一个小工具 project2txt。它的功能很简单:将整个项目展开,并将所有代码文件内容合并成一个超长的纯文本文件。理论上,这种方法可以让 LLM 获取项目的所有信息。
Python 代码
import os
def output_file_name(output_path, file_name, file_index):
return f"{output_path}/{file_name}_{file_index:02}.txt"
def write_file_contents(folder_path, max_file_size=10000 * 1024):
whitelist_file_type = ('.java', '.gradle', '.yml')
file_index = 1
current_file_size = 0
if not os.path.exists('output'):
os.mkdir('output')
output_file = output_file_name("output", "project", file_index)
out_file = open(output_file, 'w', encoding='utf-8')
for root, _, files in os.walk(folder_path):
for file in files:
if file.endswith(whitelist_file_type):
file_path = os.path.join(root, file)
header = f"%File-Head%: {file_path}\n\n"
content = ""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
except Exception as e:
content = f"Error reading file {file_path}: {e}"
entry = header + content
entry_size = len(entry.encode('utf-8'))
if current_file_size + entry_size > max_file_size:
out_file.close()
file_index += 1
current_file_size = 0
output_file = output_file_name("output", "project", file_index)
out_file = open(output_file, 'w', encoding='utf-8')
out_file.write(entry)
current_file_size += entry_size
out_file.close()
print(
f"All specified files have been written to output_XX.txt with size limit {max_file_size} bytes per file.")
_folder_path = 'project-folder'
write_file_contents(_folder_path)
结果如何?
虽然模型可以读取完整的项目内容,但新的问题随之而来:上下文窗口限制。
- LLM 的窗口限制:生成式 AI 能处理的上下文内容是有限的(通常在几千到几十万 tokens),当文本长度超出限制时,模型只能读取其中一部分。
- 核心信息丢失:因为上下文截断,模型无法对项目提供完整而准确的总结。
第三步:尝试 256k 超长上下文模型
为了解决上下文窗口限制,我将目光投向了支持超长上下文的混元模型(256k tokens 上下文能力)。它的设计初衷正是为了处理超长文本,比如大规模文档和代码库。理论上,它应该能完美解决我的问题。
然而现实再一次让我失望:
- 高昂的运行成本:256k 模型的调用费用远高于常规模型,这对个人开发者来说并不友好。
- 效果未达预期:尽管模型接收了完整内容,但输出的逻辑性和精准度并没有显著提高。
仅仅靠“塞更多数据”来解决问题并不现实,内容的提炼和优化同样重要,因此我们需要更聪明的策略来辅助其发挥作用。
第四步:用 Dify 分块,仍然不够理想
我尝试了 Dify,一个基于工作流的低代码 AI 平台。它允许用户将大规模文档分块处理,再通过模型逐块分析,最终整合输出结果。这种方法有效避开了上下文窗口的限制。
但实际使用中的问题依然存在:
缺乏全局视角:分块后的模型输出,难以连接成完整的逻辑流。
工具局限:平台本身的 Bug 和功能限制,影响了我的体验。
尽管 Dify 为解决超长文档处理提供了一种思路,但它在代码库场景中依然力不从心。
第五步:MapReduce 框架的启发
在 11 月 9 日,我看到了这样一则新闻:
大模型的记忆限制被打破了,变相实现“无限长”上下文。
最新成果,来自清华、厦大等联合提出的LLMxMapReduce长本文分帧处理技术。
来源:量子位
于是,我尝试了 LLM_MapReduce,一个开源框架,基于最新提出的 LLMxMapReduce 的分布式思想:
- Map 阶段:将项目文件分解成多个模块,并行处理。
- Reduce 阶段:整合模型的输出,生成全局总结。
这一方法在理论上完美契合我的需求,但实际操作中依然面临挑战:
部署复杂:框架需要本地模型支持,我需要 Ollama 或者 vLLM,而且项目依赖 Pytorch 环境。我受限于网络条件,无法完成完整的部署。(不足 10 Mbps 的流量网我有点破防)
云端支持不足:框架似乎并不适配云端第三方 LLM 服务,需要自己编写相关适配器。
因此我选择放弃这个方案。但尽管尝试失败,MapReduce 的分布式思想为我未来的开发提供了方向性指引。
最终方案:Cursor + Codebase 的组合拳
在多次试错后,我选择了 Cursor 和 Codebase 的组合方案。
-
Cursor 的核心能力:
作为一款高效的代码编辑工具,它可以快速处理代码片段,并在上下文切换上表现优异。 -
Codebase 的云端整合:
借助 RAG(检索增强生成)技术,Codebase 能够检索大型代码库并生成连贯的分析报告,突破了传统上下文限制。
尽管 Codebase 的云端操作存在代码泄露风险,但对于我的开源项目来说,这种风险在可接受范围内。
通过编写完备的提示词,我把基础的重构工作交给了 Cursor 自主完成,过程中我只需要少量的 review 参与,这帮助我 大大缩短 了项目代码重构的周期(原本项目已经乱的无法维护了,可以称为史山了),但目前还做不到真正的全自动重构,必须要架构师参与干涉,不干涉的话重构效果并不会太理想。
最后:技术的魅力在于创造与重塑
在重构遗留代码的过程中,我逐渐意识到,真正被重构的,是我们的思维方式。技术是一面镜子,它照见的不仅是问题的复杂性,也映射出我们对未来的想象力和对本质的追求。最终,技术的边界与我们认知的边界相连,而每一次探索,都在重塑我们看待世界的方式。
如果你喜欢我在 LINUX DO 发布的原创文章,不妨点个关注吧 ~ 大家的关注就是我更新的动力!下期见 ~