当人工智能为你编写React应用却忽视代码整洁性原则时
学习七种良好的代码编写习惯——DRY原则、单一职责原则、保护性条款等——以及AI生成的React代码为何常常违反这些习惯,还有相应的修正方法。
最近有一位客户交给我一个开发披萨订购网站的项目。
起初看来这个项目的范围还算可控。
一个首页。
一个菜单区域。
不同的披萨类别。
每个产品的单独页面。
一个购物车功能。
一个结账流程。
再加上一个用于管理后台各项事务的管理员面板。
人们的自然想法是:
“这应该很快就能完成。”
随后他们决定借助人工智能编程助手来完成工作。
从这时起事情变得有趣起来。
让人工智能承担大部分编码工作
该项目是用React开发的。
人们没有手动输入每一行代码,而是向人工智能发送指令来生成各部分内容。
比如这样的指令:
“创建披萨卡片组件。”
立刻就能生成对应的组件。
接下来是:
“构建购物车。”
已处理。
接着:
“构建结账页面。”
也已处理。
然后:
“创建一个能显示统计数据、订单信息及客户信息的管理员控制面板。”
已完成。
速度实在令人印象深刻。
通常需要数小时才能完成的工作几分钟内就做好了。
最终成果包括:
- React组件
- 表单
- 数据表格
- 控制面板小部件
- API集成
- 加载指示器
- 错误处理机制
- 能自适应屏幕尺寸的布局
而且该应用程序确实可以正常运行。
一切看起来都很不错。
似乎已经找到了理想的开发流程:
想法 → 提示词 → 代码 → 上线
但还缺少一个关键步骤。
审查已构建的内容。
客户从未见过代码本身
从客户的角度来看,一切都很完美。
网站运行流畅。
控制面板功能正常。
菜单显示正确。
订单呈现情况与预期一致。
从外观上看,它十分精致。
那么问题出在哪里呢?
问题从外部是无法察觉的。
实际上,代码库正在悄悄变成一个维护难题。
起初并不明显。
但随着更多功能的添加,某种模式开始显现。
一个念头不断浮现:
“等等……难道没有类似的东西已经被做出来了吗?”
就在那时,一个重要的认识出现了:
AI确实擅长生成可运行的代码。
但它并不会自动产出整洁的代码。
问题一:到处都是重复逻辑
几乎完全相同的逻辑散布在应用程序的各个部分。
例如,有几个独立的函数都在分别计算总数。
其中一个函数的代码如下:
const total = price * quantity;
在其他地方,也有几乎一模一样的代码:
const orderTotal = price * quantity;
而在另一个文件中则是:
const cartAmount = price * quantity;
这三个函数本质上都在执行相同的计算。
从技术层面来看并没有任何问题。
但一旦定价规则需要更改,就必须逐一找到并更新所有这些代码副本。
正是在这时,整洁代码的第一个原则变得清晰起来。
1. DRY原则——避免重复代码
每当出现重复的逻辑时,值得思考的问题是:
能否将其整合为单一来源?
例如:
const calculateItemTotal = (price, quantity) => {
return price * quantity;
};
从那以后,应用程序的每个部分都可以调用同一个共享函数。
目标并非强制在所有地方实现代码复用。
只是为了消除毫无意义的重复代码。
问题#2:单个组件承担了所有功能
有一次,我们打开了其中一个生成的React文件仔细查看。
它的代码量不断增加。
一行又一行,持续膨胀。
那个单一组件负责:
- API请求
- 表单状态管理
- 验证逻辑
- 模态框的显示/隐藏
- 表格渲染
简而言之,有一个文件几乎独自承担了整个应用程序一半的功能。
显而易见的结论是:
“维护这个结构会非常麻烦。”
于是该组件被拆分成了更小的部分。
不再保持原有的结构:
Orders.jsx
↓
800+ lines
而是被重构为类似这样的形式:
Orders
├── OrderFilters
├── OrderTable
├── OrderRow
├── OrderModal
└── useOrders
这一转变直接引出了下一个原则。
2. 单一职责原则
一个组件或函数应当只承担一项明确的任务。
如果无法用一句话描述某个组件的功能,那很可能是它承担了过多的职责。
例如:
OrderTable用于显示订单列表。
那是一个简洁明了的描述。
与之相比:
OrderTable负责获取订单、检查用户权限、计算总计、触发通知,并将所有信息以表格形式展示。
那样的描述可是个危险信号。
问题#3:嵌套的if语句让代码变得混乱
项目中的某些逻辑曾是这样的:
if (user) {
if (user.isActive) {
if (user.isVerified) {
// create order
}
}
}
它确实能正常运行。
但阅读起来十分费力。
于是它被重写为:
if (!user) return;
if (!user.isActive) return;
if (!user.isVerified) return;
// create order
那个版本一眼就能看懂。
这时就需要用到下一个习惯:
3. 提前返回/保护性语句
与其将核心逻辑埋在多层嵌套的条件中,不如尽早处理无效情况并提前退出。
Invalid?
↓
Return
Invalid?
↓
ReturnEverything okay?
↓
Do the actual work
这样一来,主逻辑依然保持简洁、易读,且没有不必要的缩进。
问题 #4:AI为已存在的功能建议了新组件
这个错误有点尴尬。
给AI的提示是:
“创建一个用于删除订单的确认模态框。”
AI的回应是生成了一个全新的组件。
它几乎在未经质疑的情况下就被接受了。
随后对项目进行快速搜索后发现了重要线索:
其实已经存在模态框组件了。
本可以直接重用它。
那一刻让人明白了一个道理:
AI并不知道某个代码库中已经存在什么。
即便系统确实知晓,它仍可能选择创建新内容而非重用现有资源。
更好的做法是给出如下提示:
“在创建新组件之前,先检查现有代码库中是否有可重用的部分。”
这一经验引出了另一条规则:
4. 先重用再创建
在添加以下内容之前:
- 新组件
- 新工具函数
- 新自定义钩子
- 新API辅助工具
应先询问:
“项目中已经存在这样的内容了吗?”
优秀的代码库并非由大量可重用组件构成,而是体现在开发者们会重用现有资源,而非默默复制它们。
问题#5:变量名有时糟糕透顶
人工智能生成的代码速度极快。
人们很容易接受这样的变量名:
const data = ...
const result = ...
const x = ...
const temp = ...
这些名称可以正常编译且不会引发错误。
但几个月后,data实际上指的是什么?
是与披萨相关的数据吗?
还是订单信息?
客户记录呢?
或是与控制面板相关的内容?
因此,随着项目的发展,变量命名应当更加有针对性。
与其这样写:
const data = await getOrders();
不如使用更清晰的版本:
const orders = await getOrders();
再比如:
const x = users.filter(...);
这样写会清晰得多:
const activeUsers = users.filter(...);
由此又得出一条简单的规则:
5. 使用能解释代码含义的名称
具有描述性的命名往往能让注释完全变得多余。
读到类似这样的内容时:
const activeUsers = users.filter(
(user) => user.isActive
);
已经清楚地说明了正在发生什么。
无需再添加:
// Filter users who are currently active
代码本身就已经说明了一切。
问题 #6:对本无需优化的内容进行优化
在这样的项目中,很容易开始产生这样的想法:
“这段代码需要进一步优化。”
这往往会导致人们去研究诸如:
useMemo()
useCallback()
以及一些其他的性能提升技巧。
但在这里值得暂停一下。
优化并不一定是好事。
以一个简单的计算为例:
const total = price * quantity;
仅仅因为存在相应的工具,就毫无理由将其包装在复杂的优化模式中。
这样做只会让代码更难理解。
这又引出了另一个教训:
6. 只有在存在真正问题时才进行优化
不要仅仅因为以下原因就着手优化:
“网上有人说这种技术更快。”
相反,应先找出真正的瓶颈。
某个组件是否频繁重新渲染?
API调用是否缓慢?
某项计算是否确实耗时?
代码包的大小是否过大?
数据库查询效率是否低下?
在采取任何措施之前,先确定真正的问题所在。
只有到那时才应该进行优化。
干净的代码加上不必要的优化只会带来多余的复杂性。
问题#7:不再要求AI“解决所有问题”
这或许是这类项目中最重要的一条经验。
有时人们会忍不住直接说:
“帮我把整个项目整理一下。”
但最好还是先不要这么做。
在那种情况下,“整理”到底意味着什么?
人工智能可以这样回应:
- 重命名变量和文件
- 重新组织文件夹结构
- 引入新的抽象概念
- 合并相关的函数
- 删除它认为不必要的代码
- 重构整体架构
其中一些改动确实能起到帮助作用。
另一些则可能毫无意义。
还有一些甚至可能会悄悄引入错误。
采用更缓慢、分阶段的办法效果更好。
首先可以这样问:
“看看这个项目,暂时先不要做任何改动。”
然后:
“指出所有重复的逻辑部分。”
接着:
“告诉我这些重复内容中哪些真的值得修改。”
只有在那之后才应该对建议进行审查。
然后应用一项更改。
接着进行测试。
这就成了第七条规则:
7. 重构前先理解
绝不要让人工智能在未完全理解代码的情况下对其进行修改。
先从分析开始。
确保理解其逻辑。
再决定该怎么做。
然后进行更改。
最后通过测试来验证。
一个披萨网站所体现的道理
有趣的是,客户很少会问:
“你的代码整洁吗?”
他们通常会问更简单的问题:
“网站能正常使用吗?”
而大多数情况下答案确实是肯定的。
不过,开发者承担的责任远不止第一个可用的版本。
软件一旦发布就很少会保持不变。
最终客户会提出这样的要求:
"我们能添加配送追踪功能吗?"
接着是:
"让我们加上折扣码吧。"
然后是:
"我们需要支持多个分支。"
再然后是:
"为餐厅员工添加账户功能。"
最后是:
"我们希望有一些报表功能。"
没过多久,那个小小的披萨网站就发展成了一个功能完备的系统。
正是在这个时候,整洁的代码结构才真正体现出其价值。
错不在人工智能
在这里应该做到公平对待。
人工智能并非罪魁祸首。
在这样的开发过程中,它确实具有极高的价值。
它能帮助实现:
- 提升工作效率
- 尝试不同的方法
- 处理重复性的编码任务
- 定位错误
- 构建组件框架
- 理性思考解决方案
真正的问题从来不是人工智能在生成代码。
问题在于有时生成的代码在没有经过充分审查的情况下就被接受了。
这是两个截然不同的问题。
结合人工智能的改进型编码工作流程
完成这样的项目后,调整工作方式是很有必要的。
整个流程大致如下:
Understand the requirement
↓
Explore existing code
↓
Plan the solution
↓
Ask AI for implementation
↓
Review the generated code
↓
Simplify
↓
Test
↓
Refactor if necessary
人工智能仍然承担着大部分的实际编码工作。
但更多的思考任务则回归到开发者身上。
这种平衡似乎正是整个软件开发领域的发展方向。
AI负责编写代码,但代码库仍属于你
这就是整个体验带来的核心启示。
一旦AI开始为你生成代码,人们很容易产生这样的想法:
“这是AI写的,它肯定明白自己在做什么。”
但这种想法并不成立。
代码库的所有权始终在你手中。
未来需要持续维护它的人是你。
出现漏洞时必须去排查问题的人也是你。
需要向他人解释其运作原理的人还是你。
数月后再次回顾时仍需理解其逻辑的人也是你。
说不定将来会有其他开发者打开这个项目并疑惑:
“采用这种方法的理由是什么?”
理想情况下,代码本身就应该让这种逻辑清晰可见。
七项值得坚持的优质代码习惯
总结整个经历后,以下内容让我印象深刻:
1. DRY原则
除非有充分理由,否则避免在多个地方重复相同的逻辑。
2. 单一职责原则
确保每个函数或组件的功能都明确且单一。
3>尽早返回原则
减少嵌套结构,使主要逻辑路径更易于理解。
4>先复用再创建原则
在开发新内容之前,先在代码库中寻找现有解决方案。
5>良好的命名原则
选择能够清晰表达变量或函数含义的名称。
6>有依据地优化原则
除非存在可量化的性能问题,否则不要随意增加复杂性。
7>重构前先理解原则
AI可以提出修改建议,但哪些建议值得采纳由您决定。
总结
在开始这个披萨网站项目时,我原以为AI辅助的主要好处会是:
极高的速度。
现在回想起来,我发现还有更大的好处。
AI让开发者有更多精力去处理那些真正需要判断力的开发工作。
我们不必把时间浪费在重复性的基础工作中,而是可以将精力投入到更有价值的问题上:
这真的是正确的做法吗?
能否让它变得更简单?
项目中已经存在类似的功能了吗?
当新的功能加入时,这个设计还能适用吗?
其他开发者也能理解并遵循这个设计吗?
这正是使用人工智能辅助开发时面临的真正难题。
人工智能几乎可以瞬间生成数百行代码。
而确认这些代码确实有存在的必要仍需由我们来完成。
既然人工智能已融入开发流程,或许这就是“整洁代码”的新定义:
让代码运行起来并非终点,能够后续维护它才是。
相关阅读
- 为何人工智能的访问权限而非能力才是真正的依赖风险 —— 本文通过分析Claude和GPT-5.6最近的出口管制事件,指出获取人工智能模型的权限是一个独立于实际能力的易变因素。