欢迎光临
我们一直在努力

AI 辅助研发内部复盘(3/5):上下文工程与认知解码

摘要

在AI辅助研发的前两篇复盘中,我们分别探讨了“三层控制框架”和“人机协作边界”。然而,当我们真正深入到动辄百万行代码的老项目(Legacy Code)改造现场时,会发现一个更为本质的挑战:认知鸿沟。AI并不懂你的业务历史,不理解你的数据血缘,更猜不透前人留下的那些“玄学”代码背后的苦衷。直接将AI投入到这样的环境中,无异于让一个不懂路况的赛车手去开一辆刹车失灵的老爷车。

本文作为系列复盘的第三篇,将聚焦于“上下文工程(Context Engineering)”与“认知解码”。我们将跳出简单的Prompt技巧,深入探讨如何通过系统化的手段,将人类工程师对老项目的理解“编译”成AI可执行的指令。文章包含三个核心实战代码案例:基于AST的遗留系统依赖分析、利用Embedding技术构建项目级知识库以实现RAG(检索增强生成)、以及通过自动化契约测试守护老项目的接口兼容性。通过这些案例,我们将展示如何将“理解”工程化,如何让AI真正“读懂”老项目,从而实现从“盲改”到“精修”的质变。


第一章:认知的瓶颈——为什么AI在老项目面前显得“弱智”?

在老项目改造中,我们常感到AI“不好用”。它生成的代码风格不符、逻辑错误,甚至凭空捏造API。这并不是大模型变笨了,而是我们给它的上下文(Context)严重不足。

1.1 老项目的“三座大山”

  • 隐式知识(Tacit Knowledge):代码只记录了“做什么”,却没有记录“为什么这么做”。比如一个奇怪的if判断,可能是为了兼容2015年的某个特定浏览器,或者是绕过一个已修复的数据库Bug。AI看不见Git提交记录里的讨论,也无法阅读早已删除的Jira工单。

  • 熵增与腐烂(Entropy & Rot):老项目充满了“技术债”。变量命名随意(a, b, temp)、函数职责混乱、注释与代码脱节。AI基于统计概率生成代码,它会倾向于生成“看起来正确”的通用代码,而不是符合当前项目混乱现实的代码。

  • 长尾依赖(Long-tail Dependencies):老项目往往依赖特定版本的库、特定的操作系统环境或特定的硬件配置。AI的训练数据通常偏向主流和最新的技术栈,对这些长尾、陈旧的配置缺乏认知。

  • 1.2 从“提示词”到“上下文工程”

    解决上述问题的关键,在于从“如何问问题(Prompting)”转变为“如何构建环境(Context Engineering)”。

    • 提示词是线性的、临时的。

    • 上下文工程是立体的、持久的。它包括:

      • 规则层:定义什么是“好代码”(如 CLAUDE.md)。

      • 知识层:提供项目专属的背景知识(如业务术语表、架构决策记录 ADR)。

      • 记忆层:让AI记住之前的对话和修改历史。

      • 检索层:在庞大的代码库中实时检索相关信息(RAG)。

    接下来的三个案例,将分别展示如何构建这四个层面,以攻克老项目改造的难题。


    第二章:案例一——基于AST的依赖分析与“理解”工程化

    场景痛点:

    接手一个庞大的单体Java老项目,需要重构核心支付模块。但该模块调用了数十个其他模块的Service,且很多调用是隐式的(如反射、XML配置)。人工梳理依赖关系需要数周,且极易遗漏。

    解决方案:

    利用抽象语法树(AST)进行静态代码分析,并将分析结果转化为AI可理解的“知识图谱”。这不仅是理解项目的过程,也是为AI构建“记忆层”的过程。

    代码实现:

    Step 1: 编写AST解析脚本,提取调用关系

    我们使用 tree-sitter 库,因为它对多种语言支持良好,且适合在Python环境中处理。

    # ast_analyzer.py
    from tree_sitter import Language, Parser
    import json
    import os

    # 1. 加载语言库(需提前编译)
    # git clone https://github.com/tree-sitter/tree-sitter-java.git
    # gcc -o java.so -shared -fpic java/src/parser.c -Ijava/src
    JAVA_LANGUAGE = Language('./build/java.so', 'java')
    parser = Parser()
    parser.set_language(JAVA_LANGUAGE)

    def analyze_java_file(file_path):
    """解析单个Java文件,提取类名、方法名和被调用的其他方法"""
    with open(file_path, 'rb') as f:
    source_code = f.read()

    tree = parser.parse(source_code)
    root_node = tree.root_node

    result = {
    "file": file_path,
    "classes": [],
    "methods": [],
    "calls": [] # 存储方法调用关系
    }

    # 查询类定义
    class_query = JAVA_LANGUAGE.query("""
    (class_declaration
    name: (identifier) @class_name
    ) @class_def
    """)
    # 查询方法定义
    method_query = JAVA_LANGUAGE.query("""
    (method_declaration
    name: (identifier) @method_name
    parameters: (formal_parameters) @params
    body: (block) @body
    ) @method_def
    """)
    # 查询方法调用
    call_query = JAVA_LANGUAGE.query("""
    (method_invocation
    object: (identifier)? @receiver
    name: (identifier) @method_name
    arguments: (argument_list) @args
    ) @call
    """)

    # 提取类
    for node, name in class_query.captures(root_node):
    if name == "class_name":
    result["classes"].append(node.text.decode())

    # 提取方法和调用
    current_method = None
    for node, name in method_query.captures(root_node):
    if name == "method_name":
    current_method = node.text.decode()
    result["methods"].append(current_method)

    for node, name in call_query.captures(root_node):
    if name == "call":
    # 简化逻辑:记录调用者和被调用者
    call_info = node.text.decode()
    result["calls"].append({
    "caller": current_method,
    "callee": call_info
    })

    return result

    def scan_project(project_path):
    """扫描整个项目"""
    project_knowledge = []
    for root, _, files in os.walk(project_path):
    for file in files:
    if file.endswith(".java"):
    full_path = os.path.join(root, file)
    analysis_result = analyze_java_file(full_path)
    project_knowledge.append(analysis_result)
    return project_knowledge

    if __name__ == "__main__":
    # 假设项目路径为 /legacy-project
    knowledge_base = scan_project("/legacy-project")
    # 将分析结果保存为JSON,作为AI的上下文知识库
    with open("project_knowledge.json", "w") as f:
    json.dump(knowledge_base, f, indent=2)
    print("项目知识图谱构建完成,共分析 {} 个文件。".format(len(knowledge_base)))

    Step 2: 将知识图谱转化为AI的“系统提示”

    有了 project_knowledge.json,我们可以在每次与AI交互时,加载相关的上下文。

    # ai_context_builder.py
    import json

    def build_context_for_refactoring(target_module, knowledge_base_path="project_knowledge.json"):
    """
    为目标模块构建AI上下文
    """
    with open(knowledge_base_path, 'r') as f:
    kb = json.load(f)

    context = "你是一个资深的Java架构师,正在进行老项目重构。以下是当前项目的依赖分析报告:\\n\\n"

    # 筛选与目标模块相关的信息
    relevant_info = []
    for item in kb:
    # 简单匹配:如果文件路径包含目标模块名
    if target_module in item["file"]:
    summary = f"文件: {item['file']}\\n"
    summary += f" 定义的类: {', '.join(item['classes'])}\\n"
    summary += f" 定义的方法: {', '.join(item['methods'])}\\n"
    # 找出外部调用
    external_calls = [c['callee'] for c in item['calls'] if c['caller']]
    if external_calls:
    summary += f" 调用了外部方法: {', '.join(set(external_calls))}\\n"
    relevant_info.append(summary)

    if not relevant_info:
    return "未找到相关模块信息。"

    context += "\\n—\\n".join(relevant_info)
    context += "\\n\\n请根据上述依赖关系,分析重构风险,并提出兼容方案。"
    return context

    # 示例:为支付模块构建上下文
    prompt = build_context_for_refactoring("com/example/payment")
    print(prompt)
    # 将此 prompt 发送给 AI…

    Step 3: AI 基于上下文的分析输出

    现在,AI不再盲目建议,而是基于事实进行分析:

    AI分析:

    根据依赖报告,PaymentServiceImpl.processPayment() 方法调用了 InventoryServiceOld.deduct()。

    风险点:InventoryServiceOld 已被标记为 Deprecated,且在新版本中参数顺序发生了变化(旧:itemId, count;新:count, itemId)。

    建议:在重构 PaymentServiceImpl 时,切勿直接调用新的 InventoryService。应先创建一个适配层(Adapter Pattern),在适配层中处理参数转换,以确保对 PaymentServiceImpl 的调用方透明。

    案例价值:

    此案例展示了“理解工程化”。我们没有指望AI自己去“猜”依赖关系,而是通过AST工具将隐式的代码关系显式化,并喂给AI。这使得AI的分析从“直觉”变成了“有据可依”。


    第三章:案例二——基于RAG的项目知识库与“记忆”工程化

    场景痛点:

    老项目文档缺失,仅有的一些Wiki也已过时。新人入职或老项目改造时,需要反复询问老员工。我们希望建立一个“项目问答机器人”,能够随时解答关于代码逻辑、业务背景的问题。

    解决方案:

    利用向量数据库(Vector Database)和嵌入模型(Embedding Model),构建一个检索增强生成(RAG)系统。将代码、注释、Git提交信息、零散文档全部向量化,存入知识库。当用户提问时,先检索相关知识,再让AI基于检索结果作答。

    代码实现:

    Step 1: 数据预处理与向量化

    我们需要将不同类型的知识(代码、Git Log、文档)清洗并切块(Chunking)。

    # rag_data_preparation.py
    import os
    import git
    import tiktoken
    from langchain.text_splitter import RecursiveCharacterTextSplitter
    from langchain_community.vectorstores import Chroma
    from langchain_community.embeddings import HuggingFaceEmbeddings

    # 1. 加载代码和文档
    def load_code_files(project_path):
    docs = []
    for root, _, files in os.walk(project_path):
    for file in files:
    if file.endswith(('.java', '.xml', '.properties', '.md')):
    path = os.path.join(root, file)
    with open(path, 'r', encoding='utf-8', errors='ignore') as f:
    content = f.read()
    docs.append({"source": path, "content": content})
    return docs

    # 2. 加载Git提交历史(挖掘隐性知识)
    def load_git_history(repo_path):
    repo = git.Repo(repo_path)
    commits = list(repo.iter_commits())
    history_docs = []
    for commit in commits[:200]: # 限制数量
    message = commit.message.strip()
    diff = commit.diff(commit.parents[0] if commit.parents else None, create_patch=True)
    for item in diff:
    if item.diff:
    history_docs.append({
    "source": f"Commit: {commit.hexsha[:8]}",
    "content": f"提交信息: {message}\\n代码变更:\\n{item.diff.decode('utf-8', 'ignore')}"
    })
    return history_docs

    # 3. 文本切块
    def split_documents(docs):
    text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    length_function=len,
    separators=["\\nclass ", "\\npublic ", "\\nprivate ", "\\n# ", "\\n// ", "\\n\\n", "\\n", " ", ""]
    )
    chunks = []
    for doc in docs:
    splits = text_splitter.split_text(doc["content"])
    for split in splits:
    chunks.append({"source": doc["source"], "content": split})
    return chunks

    # 4. 创建向量库
    def create_vector_store(chunks, persist_directory="./chroma_db"):
    embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
    vectorstore = Chroma.from_texts(
    texts=[chunk["content"] for chunk in chunks],
    metadatas=[{"source": chunk["source"]} for chunk in chunks],
    embedding=embeddings,
    persist_directory=persist_directory
    )
    vectorstore.persist()
    print(f"向量库创建完成,共存储 {len(chunks)} 个文本块。")

    if __name__ == "__main__":
    project_path = "/legacy-project"
    print("开始加载代码文件…")
    code_docs = load_code_files(project_path)
    print(f"加载了 {len(code_docs)} 个文件。")

    print("开始加载Git历史…")
    git_docs = load_git_history(project_path)
    print(f"加载了 {len(git_docs)} 条提交记录。")

    all_docs = code_docs + git_docs
    print("开始切块…")
    chunks = split_documents(all_docs)
    print(f"切分得到 {len(chunks)} 个文本块。")

    print("开始创建向量库…")
    create_vector_store(chunks)
    print("项目知识库构建完毕!")

    Step 2: 构建RAG问答系统

    有了向量库,我们就可以实现一个简单的问答接口。

    # rag_qa_system.py
    from langchain_community.vectorstores import Chroma
    from langchain_community.embeddings import HuggingFaceEmbeddings
    from langchain.chains import RetrievalQA
    from langchain_community.chat_models import ChatOllama # 假设使用本地Ollama运行的模型

    def setup_qa_chain(persist_directory="./chroma_db"):
    embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
    vectorstore = Chroma(persist_directory=persist_directory, embedding_function=embeddings)

    # 使用本地大模型,保护代码隐私
    llm = ChatOllama(model="qwen2:7b-instruct")

    qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=vectorstore.as_retriever(search_kwargs={"k": 5}), # 检索最相关的5个块
    return_source_documents=True
    )
    return qa_chain

    def ask_question(qa_chain, question):
    print(f"问题: {question}")
    result = qa_chain.invoke({"query": question})
    print("\\n答案:")
    print(result["result"])
    print("\\n参考来源:")
    for doc in result["source_documents"]:
    print(f"- {doc.metadata['source']}")

    if __name__ == "__main__":
    qa_chain = setup_qa_chain()

    # 示例问题1:关于业务逻辑
    ask_question(qa_chain, "为什么UserServiceImpl里的getUserInfo方法要判断null?")

    # 示例问题2:关于历史包袱
    ask_question(qa_chain, "三年前是谁修改了PaymentService,为什么要加那个try-catch?")

    案例价值:

    此案例实现了“记忆工程化”。AI不再仅仅依赖训练时的通用知识,而是拥有了针对当前项目的“私人记忆”。通过RAG,我们解决了大模型“幻觉”问题,让AI的回答有了确切的依据(Source Documents)。这对于理解老项目的“历史原因”至关重要。


    第四章:案例三——契约测试与“验证”工程化

    场景痛点:

    在老项目中修改代码,最怕“改坏”了。特别是修改公共模块或接口时,可能会影响下游未知的调用方。传统的单元测试只能保证内部逻辑正确,无法保证对外契约的稳定性。

    解决方案:

    引入契约测试(Contract Testing),并利用AI自动生成和维护测试用例。我们将Pact框架与AI结合,让AI理解接口定义,并生成各种正常和异常的测试场景。

    代码实现:

    Step 1: 定义契约(Pact文件)

    契约文件描述了消费者(Consumer)对提供者(Provider)的期望。

    # pacts/UserServiceClient-UserService.json
    {
    "consumer": { "name": "UserServiceClient" },
    "provider": { "name": "UserService" },
    "interactions": [
    {
    "description": "获取用户信息,用户存在",
    "request": {
    "method": "GET",
    "path": "/users/123",
    "headers": { "Accept": "application/json" }
    },
    "response": {
    "status": 200,
    "headers": { "Content-Type": "application/json" },
    "body": {
    "id": "123",
    "name": "张三",
    "age": 30,
    "role": "admin"
    }
    }
    },
    {
    "description": "获取用户信息,用户不存在",
    "request": {
    "method": "GET",
    "path": "/users/999"
    },
    "response": {
    "status": 404
    }
    }
    ]
    }

    Step 2: 利用AI生成测试桩(Provider States)

    老项目中,数据库环境复杂,很难构造测试数据。我们可以利用AI根据契约自动生成测试桩代码。

    # ai_test_stub_generator.py
    import json

    def generate_spring_test_stub(pact_file_path):
    """
    根据 Pact 文件生成 Spring Boot 的测试桩代码
    """
    with open(pact_file_path, 'r') as f:
    pact = json.load(f)

    provider_name = pact['provider']['name']
    interactions = pact['interactions']

    test_code = f"""
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
    import org.springframework.test.web.servlet.MockMvc;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

    @WebMvcTest({provider_name}Controller.class)
    class {provider_name}ContractTest {{

    @Autowired
    private MockMvc mockMvc;

    // AI Generated Test Stubs based on Pact Interactions
    """

    for interaction in interactions:
    desc = interaction['description'].replace(' ', '_')
    req = interaction['request']
    res = interaction['response']

    test_code += f"""
    @Test
    void {desc}() throws Exception {{
    // Given: Provider State Setup
    // TODO: AI suggests setting up DB state here based on '{interaction['description']}'
    // e.g., if description contains '用户存在', ensure user 123 is in DB.

    // When & Then
    mockMvc.perform({req['method']}("{req['path']}")
    .accept("{res['headers'].get('Content-Type', 'application/json')}"))
    .andExpect(status().is({res['status']}));

    // Additional assertions for response body
    """
    if 'body' in res:
    # 简单示例:验证JSON字段存在
    for key in res['body']:
    test_code += f" .andExpect(jsonPath(\\"$.{key}\\").exists());\\n"
    else:
    test_code += " }\\n"

    test_code += "}\\n"
    return test_code

    # 生成测试代码
    stub_code = generate_spring_test_stub("pacts/UserServiceClient-UserService.json")
    print(stub_code)

    # 将代码写入文件
    with open("UserServiceContractTest.java", "w") as f:
    f.write(stub_code)

    Step 3: 自动化验证流水线

    我们将上述过程集成到CI/CD流水线中:

  • AI 扫描:每次代码提交,AI 扫描接口定义的变化。

  • 契约更新:如果接口变化,AI 辅助更新 Pact 文件。

  • 测试生成:AI 根据新的 Pact 文件生成测试桩。

  • 执行验证:运行契约测试,确保老接口的行为未被破坏。

  • 案例价值:

    此案例实现了“验证工程化”。契约测试是保护老项目的“安全阀”。通过AI自动生成测试桩,我们解决了老项目测试数据难构造、测试代码维护成本高的痛点。这使得我们可以放心大胆地使用AI进行重构,因为任何破坏兼容性的行为都会被契约测试立即捕获。


    第五章:综合复盘——构建AI时代的“理解-约束-验证”闭环

    将上述三个案例串联起来,我们构建了一个完整的AI辅助老项目改造闭环:

  • 理解阶段(案例一):利用AST分析工具,将代码的静态结构转化为知识图谱。这解决了“项目长什么样”的问题。

  • 记忆阶段(案例二):利用RAG技术,将代码、文档、历史记录向量化,构建项目专属知识库。这解决了“为什么这样写”的问题。

  • 验证阶段(案例三):利用契约测试和AI生成的测试代码,建立自动化防护网。这解决了“改了会不会坏”的问题。

  • 在这个闭环中,AI不再是一个孤立的聊天窗口,而是深度嵌入到研发流程的各个角落。人类工程师的角色也从“代码编写者”转变为“上下文管理者”和“质量守门人”。

    5.1 关键心得

    • 垃圾进,垃圾出(GIGO):AI的输出质量完全取决于输入的上下文质量。花时间构建高质量的上下文(规则、知识库、测试),是所有工作的前提。

    • 工具链思维:不要指望一个Chatbot解决所有问题。需要将AI能力与AST解析器、向量数据库、CI/CD流水线等传统工具链深度集成。

    • 持续迭代:上下文工程不是一劳永逸的。随着项目的演进,知识库需要更新,规则文件需要调整,测试需要补充。

    5.2 团队落地建议

  • 建立项目知识库:立即开始为你的老项目构建RAG知识库。这是ROI最高的投资。

  • 标准化规则文件:在团队内统一 CLAUDE.md 或 .cursorrules 的格式和内容,确保AI行为的一致性。

  • 推广契约测试:在涉及核心接口的项目中,强制引入契约测试,并将其作为AI辅助重构的安全基线。

  • 结语

    老项目改造是一场艰苦的战役,但AI为我们提供了前所未有的武器。通过本篇复盘介绍的上下文工程技术,我们可以将“理解”这一最困难、最耗时的环节工程化、自动化。我们不再是盲人摸象,而是拥有了一张清晰的地图和一个强大的导航系统。

    在未来的复盘中,我们将进一步探讨如何利用AI进行架构级别的重构决策,以及如何管理AI辅助开发带来的新型技术债。请持续关注本系列,与我们一同探索AI时代的软件工程之道。

    免责声明:本文涉及的代码与方案均为技术探讨与经验总结。在实际生产环境中应用,请务必结合您的具体业务场景、安全合规要求进行充分测试与风险评估。文中提及的第三方库及工具,请遵守其相应的开源协议及使用规范。

    赞(0)
    未经允许不得转载:171主机测评 » AI 辅助研发内部复盘(3/5):上下文工程与认知解码
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址