十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI编程时代,如何用批判性思维守住代码质量底线?

AI编程时代,如何用批判性思维守住代码质量底线? 不知道你有没有遇到过这种情况让 AI 写了一段代码本地一跑居然通过了但上线之后却出了事故或者 AI 给了一个看起来很专业的修复方案照着改完却发现另一个功能挂了。问题并不一定出在 AI 本身而在于我们使用 AI 时丢失了开发者的批判性思维。AI 编程助手已经成为越来越多开发者的日常工具但“能跑”和“正确”之间往往隔着一整条验证链。这篇文章会围绕“使用 AI 但不失去批判性思维”这个主题先讲清楚为什么 AI 输出不能直接当答案再拆解四个核心思考原则最后用一个完整的代码案例展示如何审查和修正 AI 生成的代码。无论你是刚开始用 AI 写代码还是已经在团队里推广 AI 辅助开发这篇文章都能给你一份可落地的检查方法。1. AI 时代为什么批判性思维反而更重要1.1 AI 辅助开发已经变成常态从 GitHub Copilot、Cursor到各类 AI 编程插件和 Agent 工具AI 正在深度参与需求分析、代码生成、错误排查、代码审查等环节。它的效率提升是实实在在的重复样板代码可以秒出常见框架的写法能自动补全报错之后还能快速给出排查建议。但效率提升并不等于质量提升。我在平时 review 代码时发现很多同学把 AI 的输出直接当作“标准答案”少了确认、验证和追问的过程。结果就是一段代码看起来格式规范、注释完整但业务逻辑是错的或者依赖了一个根本不存在的 API。AI 不是搜索引擎更不是权威文档。它的本质是基于概率的文本生成模型它给出的代码在“形式上”可能非常专业但在“事实上”不一定正确。1.2 什么是“使用 AI 时的批判性思维”批判性思维不是否定 AI也不是所有输出都怀疑一遍。它指的是把 AI 的输出看作是“候选人答案”而不是“最终答案”。具体来说你在接受 AI 输出之前至少要完成以下几件事验证代码能不能编译测试能不能通过边界情况有没有覆盖追问AI 给出这个方案的理由是什么它是否了解完整上下文核验它提到的 API、配置项、版本号是否真实存在来源是否可靠判断这段代码会对线上产生什么影响是否有安全、并发、数据一致性风险这套流程和代码审查Code Review的逻辑一模一样。只是过去我们审查的是同事的代码现在审查的对象换成了 AI。1.3 为什么批判性思维容易被丢掉很多开发者不是没有能力审查 AI 输出而是“不想查”或者“忘了查”。原因大概有三个。第一是效率压力。既然 AI 已经帮我们写完了再花时间去验证好像违背了使用 AI 的初衷。但实际上如果 AI 生成的代码有隐藏问题后期排查的耗时可能是前期验证的好几倍。第二是信任惯性。AI 生成的内容语气确定、格式完整天然容易让人放松警惕。尤其当 AI 连续几次给出正确结果后使用者会形成“它应该没问题”的惯性。第三是反馈闭环缺失。AI 生成代码后如果测试覆盖不够问题不会立刻暴露。等到线上出了故障你已经很难想起来这段代码是 AI 写的还是自己写的。所以越是用 AI 辅助开发越需要把“验证”从可选项变成必选项。2. AI 辅助开发的典型场景与隐藏风险2.1 场景一自动生成代码最常见的用法是把需求描述给 AI让它输出完整代码。这个场景的风险点在于 AI 对项目上下文的理解是有限的。举一个我实际见过的例子。团队里用 AI 生成了一段订单状态更新的代码AI 直接用了字符串拼接 SQL看起来逻辑没问题但一旦订单号来源于外部参数就存在 SQL 注入风险。而且 AI 并不知道项目里已经统一封装了 BaseMapper所以生成的代码风格也和现有工程不一致。代码生成阶段需要重点检查依赖的类是否真实存在、是否有合适的异常处理、是否考虑了并发和数据一致性、是否符合团队现有编码规范。2.2 场景二错误信息排查把报错信息粘贴给 AI让它帮忙分析原因这是很多开发者的日常操作。AI 确实能提供一些有价值的排查方向但也存在明显风险。第一个风险是上下文不足。你只给 AI 贴了一行报错它并不知道项目用的什么框架、什么版本、什么配置自然只能给出一个泛泛的答案。第二个风险是“看似合理但方向错误”的建议。AI 可能会引导你去改一个根本没问题的配置反而把问题带偏。正确做法是先自己看完整堆栈想清楚报错发生的位置和触发条件再拿这些信息去和 AI 讨论。AI 可以当排错助手但不应该当“背锅侠”。2.3 场景三代码审查与重构建议用 AI 做代码审查可以发现一些重复代码、命名不规范、结构臃肿的问题。但 AI 的审查缺乏业务语义它不知道哪些逻辑是客户要求的硬性约束也不知道哪些看似冗余的判断是为了兼容历史数据。我见过一个例子AI 建议把一个字段的非空校验删掉理由是这个字段在数据库层已经设置了 NOT NULL。单看代码确实没问题但业务方反馈说这个接口经常被外部系统调用非空校验是为了在入口层提前拦截异常数据而不是等数据库报错。AI 的建议虽然合理却忽略了业务背景。所以AI 的重构建议可以参考但最终是否采纳必须结合你对业务的理解来判断。2.4 场景四技术方案设计更高阶的用法是让 AI 帮忙做技术选型和方案设计。这个场景的风险最大因为方案设计需要结合现有系统、团队能力、运维成本和长期演进而这些信息 AI 一无所知。更需要注意的是AI 在描述技术方案时非常自信甚至会编造一些不存在的特性对比。如果你只依赖它的输出做决策很可能会被带偏。涉及方案选型时必须去查官方文档看版本差异甚至做最小原型验证。3. 保持批判性思维的四个核心原则3.1 原则一验证优先把 AI 输出当“待验证代码”我给团队定的规矩是AI 生成的代码默认状态是“待验证”而不是“可提交”。验证不是随便跑一下而是带着问题去验证。拿到 AI 输出的代码后我会按顺序问自己几个问题这段代码能编译通过吗核心逻辑有没有单元测试覆盖边界条件空值、超长、重复请求有没有处理是否存在并发访问时数据不一致的问题有没有安全漏洞注入、越权、敏感信息泄露只要有一个问题没有明确答案就不应该合并到主干代码。你可以把这些问题做成一份审查清单每次用 AI 生成代码后逐项打勾。3.2 原则二保持追问让 AI 给出依据而不是结论很多人在和 AI 协作时只问“怎么做”不问“为什么”。比如不推荐帮我写一个分页查询。推荐我要实现一个分页查询但不确定用 limit 还是游标分页表有 500 万数据请对比两种方案在深分页场景下的性能差异说明原理。当你要求 AI 给出依据时它不一定会完全正确但这个“追问”的过程会促使你思考这个方案成立的前提是什么在什么条件下会失效有没有更合适的替代方案把 AI 当作一个可以随时讨论的同事而不是一个输出机器。这样你才能在协作中保持主动思考。3.3 原则三明确边界知道哪些事不能交给 AIAI 可以帮你生成代码、整理文档、梳理思路但有些事情必须由人来负责业务规则的正确性判断AI 不知道业务方的真实意图。安全与合规决策涉及认证、授权、数据删除、生产变更的方案必须人工审查。生产环境的变更操作AI 给出的命令不能直接在生产环境执行。最终质量责任无论代码是不是 AI 写的出了问题责任在提交代码的人。这一点尤其重要。很多团队出现线上事故后复盘发现代码是 AI 写的但没有人真正 review 过。责任感不能因为引入了 AI 工具就转移。3.4 原则四建立反馈闭环用结果验证思考批判性思维不能只停留在“我觉得有问题”要落到反馈闭环上。完整的反馈闭环包括AI 产出代码 → 人工审查 → 自动化测试 → 代码合并 → 灰度发布 → 线上监控。每一环都在验证前一步的判断是否正确。如果代码经过测试没发现 bug不代表它一定正确只能说明测试覆盖到的场景没有出问题。所以在关键业务上我会要求补充更多异常场景的测试比如重复请求、并发提交、依赖服务超时等。4. 实战案例一次 AI 生成代码的批判性审查与修正光讲理论不够下面用一个完整的案例演示怎么用批判性思维审查 AI 生成的代码。4.1 案例背景假设我们有一个订单支付回调接口。第三方支付平台在用户完成支付后会回调我们的系统通知订单支付成功。你让 AI 生成一段处理逻辑它给出了下面的代码。4.2 AI 生成的第一版代码// 文件路径src/main/java/com/example/order/OrderPayService.java Service public class OrderPayService { Autowired private JdbcTemplate jdbcTemplate; public void handlePayCallback(String orderId, double amount) { Order order queryOrder(orderId); if (order null) { throw new RuntimeException(order not found); } // 直接更新订单状态为 PAID String sql UPDATE t_order SET status PAID, amount amount WHERE order_id orderId ; jdbcTemplate.execute(sql); } private Order queryOrder(String orderId) { String sql SELECT * FROM t_order WHERE order_id orderId ; return jdbcTemplate.queryForObject(sql, Order.class); } }这段代码从语法上看没有问题但用批判性思维审查一遍问题非常多。4.3 批判性审查逐条发现问题我把审查过程拆成几个问题对应前面提到的核心原则。问题一是否存在安全漏洞queryOrder和handlePayCallback都使用了字符串拼接 SQL。如果orderId来自外部请求攻击者可以构造恶意参数造成 SQL 注入。即使在实际场景中订单号可能来自内部签名校验之后这种写法仍然是高危反模式。问题二金额字段类型是否合理amount使用了double。金额计算在 Java 中推荐使用BigDecimal因为double存在精度丢失问题。比如0.1 0.2在二进制浮点数中就不是精确的0.3。涉及真金白银的业务绝对不能这样写。问题三状态流转是否可控代码直接把状态更新为PAID没有检查订单当前的支付状态。如果支付平台因为网络原因重复回调这段代码会把已经取消的订单重新置为已支付或者重复处理两次回调。问题四并发场景下是否安全如果两条回调请求同时到达两个请求都查询到订单状态是UNPAID然后都执行更新就可能出现重复处理。代码里没有任何乐观锁或版本号机制。问题五异常处理是否合理代码抛出的是RuntimeException外部无法区分“订单不存在”“状态异常”“金额不一致”等不同情况也就无法针对业务异常做差异化处理。问题六是否缺少幂等控制支付回调天然是幂等敏感的。一个健壮的系统必须保证同一笔支付回调即使被通知多次也只生效一次。4.4 修正后的代码针对上面的问题修正后的代码应该做到使用参数化 SQL 防注入、金额使用 BigDecimal、增加状态机校验、使用乐观锁防止并发重复处理、区分业务异常与系统异常、补充完整日志。// 文件路径src/main/java/com/example/order/OrderPayService.java Service Slf4j public class OrderPayService { private final JdbcTemplate jdbcTemplate; public OrderPayService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } Transactional public void handlePayCallback(String orderId, BigDecimal amount) { // 1. 参数校验 if (orderId null || orderId.isBlank()) { throw new IllegalArgumentException(orderId 不能为空); } if (amount null || amount.compareTo(BigDecimal.ZERO) 0) { throw new IllegalArgumentException(amount 必须大于 0); } // 2. 查询订单使用参数化 SQL 防止注入 Order order queryOrder(orderId); if (order null) { throw new BusinessException(订单不存在); } // 3. 状态校验只有 UNPAID 状态才能流转到 PAID if (!UNPAID.equals(order.getStatus())) { throw new BusinessException(当前订单状态不允许支付回调处理); } // 4. 金额校验回调金额必须等于订单应付金额 if (amount.compareTo(order.getPayAmount()) ! 0) { throw new BusinessException(回调金额与订单金额不一致); } // 5. 乐观锁更新防止并发重复处理 int rows jdbcTemplate.update( UPDATE t_order SET status ?, pay_time ?, version version 1 WHERE order_id ? AND status UNPAID AND version ?, PAID, LocalDateTime.now(), orderId, order.getVersion() ); if (rows ! 1) { throw new BusinessException(订单状态已变化请勿重复处理); } // 6. 记录处理日志 log.info(支付回调处理成功, orderId{}, amount{}, orderId, amount); } private Order queryOrder(String orderId) { String sql SELECT order_id, status, pay_amount, version FROM t_order WHERE order_id ?; return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(Order.class), orderId); } }对应的表结构可以这样设计CREATE TABLE t_order ( id BIGINT AUTO_INCREMENT PRIMARY KEY, order_id VARCHAR(64) NOT NULL UNIQUE, status VARCHAR(16) NOT NULL, pay_amount DECIMAL(12,2) NOT NULL, version INT NOT NULL DEFAULT 0, pay_time DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP );4.5 用测试验证修改结果修正代码只是第一步还需要用测试验证行为。下面是核心测试思路// 文件路径src/test/java/com/example/order/OrderPayServiceTest.java SpringBootTest class OrderPayServiceTest { Autowired private OrderPayService orderPayService; Test void shouldRejectWhenOrderAlreadyPaid() { // 准备数据库中订单 status PAID // 执行再次调用 handlePayCallback // 断言抛出 BusinessException且订单金额没有被修改 } Test void shouldRejectWhenAmountNotMatch() { // 准备数据库中订单 pay_amount 100.00 // 执行传入 amount 99.99 // 断言抛出 BusinessException } Test void shouldUpdateSuccessWhenStatusIsUnpaid() { // 准备数据库中订单 status UNPAID // 执行调用 handlePayCallback // 断言订单状态变为 PAIDversion 加 1 } }测试注释里的“准备”“执行”“断言”需要结合你实际的测试数据库来补齐。你可以使用 H2 内存数据库也可以使用 Testcontainers 启动一个真实的 MySQL 容器。无论用哪种方式目的都是一样的用可重复的自动化测试把“批判性审查”沉淀成机器可执行的验证。5. 把批判性思维落实到日常工作流5.1 需求输入给 AI 足够的上下文很多人抱怨 AI 生成的代码不符合预期其实问题出在提问太宽泛。AI 不了解你的技术栈、版本、业务约束和代码风格自然只能生成“通用答案”。推荐你用一个结构化的提问模板我正在使用 [框架名 版本]数据库为 [数据库类型 版本]需要实现 [功能描述]。 约束条件 1. [业务规则例如“只有 UNPAID 状态可以流转到 PAID”] 2. [技术约束例如“金额使用 BigDecimal”“SQL 必须参数化”] 3. [异常处理要求例如“区分业务异常与系统异常”] 请输出 1. 核心代码实现 2. 关键设计思路 3. 可能出现的边界情况把上下文交代清楚AI 的输出质量会明显提升。更重要的是这个写 prompt 的过程本身就是在梳理你对需求的理解。5.2 产出审查把 AI 输出当“第一轮草稿”无论 AI 输出多完整都建议遵循一个固定的审查流程编译检查确认代码能通过编译。逻辑走查逐行读一遍看是否有明显逻辑错误。边界测试补充空值、重复请求、并发场景的测试。安全审查检查注入、越权、敏感信息泄露等风险。业务确认与需求方核对业务规则是否符合预期。这个流程不需要每次都完整跑一遍但至少前两项应该是默认动作。如果你用 AI 生成的是 SQL一定要额外执行 EXPLAIN 查看执行计划如果你用 AI 生成了配置文件要确认每一项配置在目标环境有实际效果。5.3 验证闭环用自动化测试兜底在团队里我特别强调“用测试约束 AI 输出”。具体做法是先写测试用例再让 AI 去实现。如果你先让 AI 写实现再补测试很容易跟着实现思路走测试的独立性会被削弱。比如要实现一个订单金额计算功能你可以先把下面的测试思路贴给 AI// 测试要求 // 1. 空订单列表返回 0 // 2. 单笔订单金额正确汇总 // 3. 含优惠金额时最终金额不超过订单总额 // 4. 金额保留两位小数且不丢失精度有了明确的测试约束AI 生成的代码会更有针对性。而你在审查时也拥有了可验证的标准。5.4 记录沉淀把 AI 踩坑经验变成团队规范每一个被 AI 坑过的瞬间都值得变成一条规范。比如凡是涉及金额的代码必须使用 BigDecimal。凡是外部回调接口必须有幂等控制。凡是 SQL必须使用参数化查询或 ORM 封装方法。凡是删除或更新操作必须先备份或验证 WHERE 条件。这些规范可以写在团队的开发文档里也可以做成代码审查清单。当你从“个人经验”上升到“团队规范”时AI 辅助开发的整体质量才会有质的提升。6. 常见问题与排查思路以下是我在团队推广 AI 辅助开发时经常遇到的问题和排查思路问题现象可能原因解决思路AI 生成了不存在的 API模型基于训练数据中的过时知识生成内容到官方文档核对类名、方法签名锁定实际依赖版本AI 修复建议掩盖了根因上下文不足AI 只看到了局部代码贴完整日志和代码要求 AI 分析根因不要只给“绕过方案”自己 review 了但线上还是出问题审查清单不完整缺少并发、安全、幂等视角引入标准审查清单覆盖数据一致性、安全、异常处理AI 代码风格和项目不一致没有在 prompt 中指定项目规范和约束在 prompt 中声明包结构、框架、统一返回类等规范AI 推荐了一个很新的依赖但没人用过模型倾向给出“看起来先进”的方案以官方文档和团队已用技术栈为准先做技术小样验证测试通过但代码有隐藏 bug测试主要覆盖正常路径缺少边界用例补充异常场景测试重复请求、空值、超时、并发排查时有一个很重要的原则永远不要因为“AI 说没问题”就直接跳到下一步。验证的终点是代码运行结果和线上监控数据而不是 AI 的结论。7. AI 协作的工程最佳实践7.1 个人层面把验证变成肌肉记忆我建议每个使用 AI 编程的人给自己定三条强制规则第一AI 生成的核心代码必须手写对应的核心测试。第二涉及金额、权限、删除、更新的代码必须经过一次完整走查。第三AI 给出的配置项必须去官方文档确认之后再写入工程。这三条规则不需要别人监督但能在关键时刻挽回损失。7.2 团队层面建立 AI 辅助开发规约如果团队已经重度使用 AI 编程工具建议把规则写成文档让全员遵守AI 生成的代码必须经过人工评审禁止跳过 Code Review 直接合并。涉及安全敏感操作的代码至少需要一名高级工程师审查。AI 生成的 SQL 必须查看执行计划禁止直接在生产环境执行。关键接口必须补充幂等、并发、异常场景测试。生产环境的任何变更必须经过人工复核和备份验证。这些规约不是限制开发效率而是保证在 AI 辅助开发的高效之下依然保有质量底线。7.3 安全边界不能完全交给 AI 的环节有几类工作我建议永远不要全权交给 AI哪怕它看起来表现很好权限模型的设计认证、授权、越权判断这类逻辑必须人工确认。数据删除与批量更新AI 给出的 SQL 可能漏了 WHERE 条件风险极高。加密密钥与密钥存储涉及生产敏感信息必须有专门的安全方案。生产环境操作命令任何 rm、drop、update、restart 操作前都要人肉二次确认。AI 在这些环节可以作为辅助提供思路和检查清单但最终决策和执行必须由人负责。7.4 可维护性让 AI 输出更容易被审查最后提一个容易被忽略的点AI 的代码能不能被高效审查和代码本身的可读性直接相关。让 AI 生成代码时明确提出这些要求函数尽量拆小一个函数只做一件事。命名清晰避免 a、b、temp 这种无意义命名。错误信息描述具体便于定位问题。核心逻辑加注释说明设计原因而不是重复代码行为。不引入项目中没有使用过的新依赖除非经过评审。可读性好的代码审查效率高出错的概率也低。8. 总结AI 不会替你思考回到文章标题用 AI但不失去批判性思维。这句话的本质是——AI 可以帮你写代码、查资料、排查错误但它不会替你承担思考的责任。AI 输出的每一段代码都值得你像一个严格的 Code Reviewer 那样去审视。你可以信任它但信任的前提是验证。你可以依赖它但依赖的边界取决于你对业务、安全、架构的理解深度。下一步你不需要学什么高深的新技术只需要从今天开始给 AI 的每一个输出养成“先验证、再使用”的习惯。可以准备一份自己的审查清单也可以把你踩过的 AI 坑写在评论区让更多人避雷。真正决定代码质量的永远不是生成它的人或工具而是提交它在审查时有多认真。
返回列表