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

AI7小时前发布 beixibaobao
3 0 0

摘要

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

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

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


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

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

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

1.1 老项目的“三座大山”

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

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

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

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

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

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

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

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

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

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

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

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


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

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

场景痛点:

接手一个庞大的单体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)))

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

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架构师,正在进行老项目重构。以下是当前项目的依赖分析报告:nn"
    # 筛选与目标模块相关的信息
    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 += "nn请根据上述依赖关系,分析重构风险,并提出兼容方案。"
    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的项目知识库与“记忆”工程化

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

场景痛点:

老项目文档缺失,仅有的一些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// ", "nn", "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)。这对于理解老项目的“历史原因”至关重要。


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

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

场景痛点:

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

解决方案:

引入契约测试(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
      }
    }
  ]
}

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

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流水线中:

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

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

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

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

案例价值:

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


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

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

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

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

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

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

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

5.1 关键心得

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

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

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

5.2 团队落地建议

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

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

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

结语

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

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

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

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

© 版权声明

相关文章