你的 LLMs 并不做真正的 OOP,而且它是结构性的。
Source: Dev.to
请提供您希望翻译的完整文本(除代码块、URL 和源链接外),我将把它翻译成简体中文并保持原有的 Markdown 格式。
生成式 AI 每天都在写代码
类、服务、模型、控制器。乍一看,一切都很正常。它可以编译,能够通过测试,并且“完成任务”。
然而,却存在一个反复出现的问题:
LLM 生成的代码通常封装性差。
不是“有点差”。
结构上封装性差。
类里充斥着 getter 和 setter,几乎没有行为,业务逻辑散落各处。简而言之:面向数据的代码,而非面向对象的代码。
为什么会这样?
更重要的是:使用 AI 时如何做得更好?
Source: …
OOP 最初的含义(以及我们忘记的)
当我们今天谈论面向对象编程时,常常想到:
- 类
- 私有属性
- getter / setter
- 接口
但这 不是 最初的愿景。
对 Alan Kay(被视为 OOP 之父之一)而言,核心思想不是类,而是 消息。
“对我而言,面向对象仅意味着消息传递、本地保留与保护以及隐藏状态过程,并且对所有事物进行极端的后期绑定。”
换句话说:
- 对象 相互通信
- 它们 自行保留状态
- 它们 隐藏内部逻辑
- 它们 松耦合
他使用的类比是生物学的:自主的细胞在不暴露内部器官的情况下相互作用。
LLM 生成的内容
Typical example generated by an AI:
public class User {
private String email;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
它很简洁。根据许多教程,它是“最佳实践”。但它不是封装。
为什么?
- 内部状态被暴露
- 内部类型被固定
- 缺少验证
- 业务逻辑被推到外部
结果:行为最终出现在服务、控制器中,甚至更糟……在各处被复制。我们称之为贫血类:一个只有访问器的简单数据袋。
Getter / Setter 的虚假安全感
Getter 和 setter 给人封装的幻象,但实际上:
- 它们暴露内部结构
- 它们导致强耦合
- 它们冻结实现决策
更改字段、其类型或其逻辑会迅速导致广泛的破坏。在面向对象编程中,暴露状态几乎总是抽象泄漏。
对对象提出更好的问题
而不是这样问:
if (user.getEmail() == null) {
// logic here
}
而是这样问:
if (user.canBeContacted()) {
// logic here
}
这已经是进步了:
- 行为已本地化
- 业务规则位于对象中
- 实现可以演进
但我们可以走得更远。
消息与事件方法
在 Alan Kay 的设想中,对象并不说明 它是什么;它响应 被请求的内容。
而不是读取状态:
- 你发送一个意图
- 对象自行决定
- 状态保持内部
事件驱动或面向消息的模型正好实现了这一点:
- 内部状态转换
- 强解耦
- 逻辑集中于一处
这并不是“更复杂”。而是 更明确。
为什么大型语言模型在真正的封装上如此挣扎
这并不是因为 AI “不好”。而是结构性原因。
- 它们从现有代码中学习 —— GitHub 上充斥着 CRUD、DTO、贫血类。
- Getter / Setter 在统计上占主导,因此它们“可能”出现并被生成。
- 业务行为具有上下文性;LLM 在局部表现出色,但在全局一致性方面较弱。
- 面向消息的代码更简洁但更抽象,缺乏明确意图时更难推断。
AI 并不了解你的领域。它只是外推模式。
如何更好地使用 AI 编写面向对象代码
解决方案不是停止使用 AI。解决方案是 更好地引导它。
当你生成一个类时,问问自己(也问模型)以下问题:
- 这个类是 在做某事,还是仅仅用于传输数据?
- 我是 向对象提问,还是 读取它的状态?
- 行为是 局部化 还是分散的?
- 我能在不破坏调用方的情况下更改实现吗?
如果答案是 “no”,那么它可能并不是真正的面向对象。
真正的问题不在于 AI
问题在于:
- 我们已经把贫血的面向对象(OOP)标准化了
- 我们把封装和可见性混淆了
- 我们用数据结构取代了行为
大型语言模型仅仅 复制了我们多年来所产生的内容。
结论
封装不是:
- 私有字段
- 公共 getter
- 被动模型
封装是:
- 对其状态负责的对象
- 本地化业务规则
- 使用消息而非直接访问
- 最小耦合
AI 可以帮助,但 它永远无法取代良好的建模。