掘金 人工智能 07月18日 10:35
AI编程在OOP场景下探索
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文探讨了在Spring AI工程中遇到的ChatClient空指针异常,分析了代码实现与单元测试中的问题。通过与不同AI模型的交互,逐步定位并解决了因组件更新导致知识滞后和API不兼容的挑战。最终,文章提出了基于OOP原则的适配器模式重构方案,成功隔离了Spring AI框架,实现了代码的可插拔性和可测试性,并分享了AI编程在OOP场景下的探索意义、版本API知识更新滞后的挑战以及人机协同的进化路径,展望了自适应OOP系统的未来。

💡 **ChatClient空指针异常的根源分析**:文章指出,在Spring AI项目中,ChatClient为null是导致空指针异常的主要原因。这通常发生在Mock单元测试环境下,表明ChatClient的初始化或注入存在问题。业务逻辑代码中,ChatClient通过构造函数注入,但单元测试中的Mock设置未能正确模拟其构建过程,导致实际调用时ChatClient为空。

🔧 **适配器模式解决框架耦合与可测试性问题**:为了解决Spring AI框架更新带来的兼容性问题以及提高代码的可测试性,引入了适配器模式。通过定义`ChatAdapter`接口和Spring AI的`SpringAiChatAdapter`实现,将对`ChatClient`的直接依赖解耦。`TaskMcpCallServerServices`服务类转而依赖于抽象的`ChatAdapter`,使得单元测试可以轻松地Mock适配器,从而验证服务逻辑,也为未来替换其他AI库奠定了基础。

🧠 **AI辅助编程的挑战与人机协同的价值**:文章展示了AI在代码生成和重构过程中的能力,同时也暴露了AI模型知识更新滞后、对上下文理解有限等问题。例如,AI模型可能推荐已弃用的API或无法理解隐式依赖。这凸显了专家级判断和人机协同的重要性,通过增强型提示工程、验证循环和可解释性增强,可以最大化AI的价值,并弥补其不足。

🚀 **AI编程在OOP场景下的未来展望**:AI与OOP的融合将推动自适应软件系统的发展,实现动态类加载、演化式设计等。开发者需要从代码编写者转变为系统架构师和AI训练师,构建新的认知桥梁,以应对快速变化的软件开发环境和不断涌现的新技术。

背景

     我们Spring AI工程引用代码如下,由于基于Mock单元测试下ChatClient总是为空,异常是

Cannot invoke "org.springframework.ai.chat.client.ChatClient.prompt(String)" because "this.chatClient" is null

     Spring AI 1.0.0的正式发布时间为2025年5月20日

业务逻辑代码

@Servicepublic class TaskMcpCallServerServices implements AiService, ChatService {private final ChatClient chatClient;    /**     * Constructs a new TaskMcpCallServerServices instance.     * @param aiClientBuilder the ChatClient builder used to create the chat client     */public TaskMcpCallServerServices(ChatClient.Builder chatClientBuilder) {this.chatClient = chatClientBuilder.build();    }    @Overridepublic ChatClient getChatClient() {return chatClient;    }    @Override    /**     * Gets AI response for the given prompt by calling the MCP server.     * @param prompt the input prompt to send to the AI     * @return ResponseEntity containing the AI response     */public ResponseEntity<String> getAiResponse(String prompt) {String response = this.chatClient                .prompt(prompt)                .call()                .content();return ResponseEntity.ok(response);    }}

单元测试代码

@ExtendWith(MockitoExtension.class)class TaskMcpCallServerServicesTest {    @Mock    private ChatClient.Builder chatClientBuilder;    @Mock    private ChatClient chatClient;    @InjectMocks    private TaskMcpCallServerServices taskMcpCallServerServices;    @Test    void testGetAiResponse() {        // Given        String testPrompt = "test prompt";        String expectedResponse = "test response";                when(chatClientBuilder.build()).thenReturn(chatClient);        // When        ResponseEntity<String> actualResponse = taskMcpCallServerServices.getAiResponse(testPrompt);        // Then        assertEquals(expectedResponse, actualResponse.getBody());    }
}

实践

Qwen3-Thinking模型

看上去解决问题思路没有问题,但实际还是不能解决ChatClient空指针问题。

基于Trae+Germin2.5 Flash模型对话如下:

我们查看修改后代码,并不理想

@RestController@RequestMapping("/tools")public class ToolController {private final AiService aiService;public ToolController(AiService aiService, ChatService chatService) {this.aiService = aiService;    }    /**     * 使用阿里云对话服务(AMAP)进行文本对话     *     * @param prompt 用户输入的对话内容     * @return ResponseEntity 包含对话结果的HTTP响应     */    @GetMapping("")public ResponseEntity<String> amap(String prompt) {return aiService.getAiResponse(prompt);    }}

Claude4

存在如下问题,大模型的知识还是跟不上组件更新

java: 找不到符号     符号:   类 CallPromptSpec     位置: 接口 org.springframework.ai.chat.client.ChatClient

TongYi Lingma-Qwen3-thinking再次尝试

请对当前类引用类型ChatClient进行完全隔离,ChatClient来自Spring AI框架,我们期望隔离它,并且可以替换为其他AI类库的实现,请基于OOP编程原则修改相关关联代码。

单元测试没有修复,第二轮对话修复单元测试问题

最终Qwen3达到期望的结果

/** * 聊天客户端适配器接口 * 提供统一的方法调用规范,可扩展支持不同AI框架的实现 */public interface ChatAdapter {    /**     * 执行AI对话请求     * @param prompt 用户输入内容     * @return 返回处理结果     */    String chat(String prompt);}
/** * Spring AI框架适配器实现 */@Componentpublic class SpringAiChatAdapter implements ChatAdapter {    private final ChatClient chatClient;    public SpringAiChatAdapter(ChatClient chatClient) {        this.chatClient = chatClient;    }    @Override    public String chat(String prompt) {        return chatClient.prompt(prompt).call().content();    }}
@Servicepublic class TaskMcpCallServerServices implements AiService {    private final ChatAdapter chatAdapter;    /**     * Constructs a new TaskMcpCallServerServices instance.     * @param chatAdapter the ChatAdapter instance to use     */    public TaskMcpCallServerServices(ChatAdapter chatAdapter) {        this.chatAdapter = chatAdapter;    }    @Override    /**     * Gets AI response for the given prompt by calling the MCP server.     * @param prompt the input prompt to send to the AI     * @return ResponseEntity containing the AI response     */    public ResponseEntity<String> getAiResponse(String prompt) {        String response = this.chatAdapter.chat(prompt);        return ResponseEntity.ok(response);    }}

单元测试代码

@ExtendWith(MockitoExtension.class)class TaskMcpCallServerServicesTest {    @Mock    private ChatAdapter chatAdapter;    @InjectMocks    private TaskMcpCallServerServices taskMcpCallServerServices;    @BeforeEach    public void before()    {        MockitoAnnotations.openMocks(this);        taskMcpCallServerServices = new TaskMcpCallServerServices(chatAdapter);    }    @Test    void testGetAiResponse() {        // Given        String testPrompt = "test prompt";        String expectedResponse = "test response";                when(chatAdapter.chat(testPrompt)).thenReturn(expectedResponse);        // When        ResponseEntity<String> actualResponse = taskMcpCallServerServices.getAiResponse(testPrompt);        // Then        assertEquals(expectedResponse, actualResponse.getBody());    }}

Trae中Germin 2.5 Flash

新生成文件,编译不通过,并且多次修复未果

Trae基于DeepSeek V3  0324实践

存在单元测试问题,第二轮修复单元测试成功

/*** Interface defining the contract for AI client implementations.* Provides abstraction for different AI service providers.*/public interface AiClient {    /**     * Gets AI-generated response for the given prompt.     * @param prompt the input text to send to the AI service     * @return the AI response content     */String getResponse(String prompt);}/*** Service class for handling MCP server calls and AI responses.* Implements AiService interface to provide AI response generation functionality.*/@Servicepublic class TaskMcpCallServerServices implements AiService {private final AiClient aiClient;    /**     * Constructs a new TaskMcpCallServerServices instance.     * @param aiClient the AI client implementation     */public TaskMcpCallServerServices(AiClient aiClient) {this.aiClient = aiClient;    }    @Override    /**     * Gets AI response for the given prompt by calling the MCP server.     * @param prompt the input prompt to send to the AI     * @return ResponseEntity containing the AI response     */public ResponseEntity<String> getAiResponse(String prompt) {String response = this.aiClient.getResponse(prompt);return ResponseEntity.ok(response);    }}

直接让Claude4 重构

重构基本是成功的,还使用简单工厂,但新生成UnitTest依赖的API存在版本问题,最终效果如下

总结

一、AI编程在OOP场景下的探索意义

1. 代码生成与模式识别的范式突破

2. 重构与演化的智能辅助

3. 领域特定语言(DSL)的生成

二、版本API知识更新滞后的挑战

1. 知识衰减的典型场景

2. 上下文提示词的局限性

三、专家级综合判断的必要性

1. 领域知识的不可替代性

2. 认知推理链的构建

四、人机协同的进化路径

1. 增强型提示工程

2. AI与专家的协作范式

3. 持续学习的工程实践

五、未来展望:自适应OOP系统

最终,OOP与AI的融合将走向自适应软件系统

这一进程要求开发者从“代码编写者”转型为“系统架构师+AI训练师”,在OOP的抽象层次与AI的生成能力之间构建新的认知桥梁。

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Spring AI ChatClient 空指针异常 适配器模式 单元测试 AI编程 OOP
相关文章