☰
AI代码生成偏爱保守?从异常处理与日志规范看工程实践
2026/10/26 12:59:46 网站建设 项目流程

最近在重构一个中型Java服务的异常处理模块,顺手也想把日志规范统一一下。按我的习惯,这种“模板型”代码先让AI跑一版,我再改改就行。但这一回,AI给的方案让我意外到停下来专门研究了一下——它比我想象中保守太多了。我本来以为它会给点“新东西”,比如基于java.lang.Throwable的智能异常树、自动计算重试退避时间的工具类、或者一套很有设计感的日志切面。结果它给我的就是最基础的try-catch、log.error、再抛一个BizException,规规矩矩,一点惊喜都没有。

后来我又试了几轮,发现它其实是刻意收敛了。它不主动做“看起来创新”的写法,而是把所有可能出问题的点都避开,只输出最稳妥、最公认的模板代码。这个特点放在异常处理和日志规范这两个主题下,反而很有意思——因为这两个话题恰恰是工程里最容易被“自由发挥”搞砸的地方。有些同事喜欢到处catch然后print堆栈,有些把ERROR当成万能级别,有些在日志里拼接用户手机号。AI这种“保守偏见”,恰好是把工程基础盘往回拉了一手。

这篇文章就围绕这个现象展开,聊三件事:AI为什么会给出保守的异常处理和日志建议,这种保守背后有什么工程逻辑,以及我们怎么利用这个特性把事情办得更稳妥。适不适合看?适合正在做接口开发、微服务调优,或者刚接手老项目日志的病人。当然,如果你只是好奇AI生成代码的脾气,也能当个乐子看。

1. 意外发现:AI给出的异常处理为何如此“老成”?

1.1 我最初的设想和AI的实际输出

我让AI写一个“下载文件时网络异常自动重试”的例子。我脑子里的预期是:它会给出一个带指数退避的循环,区分IOException和TimeoutException,再根据异常类型决定是重试还是直接失败,甚至可能给个熔断器的简化版。结果AI给的是这个:

public byte[] downloadFile(String url) { try { // 省略实际下载逻辑 return doDownload(url); } catch (IOException e) { log.error("下载文件失败, url={}", url, e); throw new BusinessException("FILE_DOWNLOAD_FAILED", e); } }

没有重试、没有退避、没有区分异常类型。我追问了一句“不加重试吗?”,它倒是给我补了一个三层for循环,但每重试一次前都检查一下请求是否幂等,还特意注释“重试可能导致重复请求,请确认接口幂等后再开启”。这态度明显是“先把丑话说前面”。

我后来拿这个例子问AI为什么这么保守,它的回复基本可以总结成一句话:在不清楚业务上下文的情况下,默认选择最无害的路径。重试可能造成重复订单,抛RuntimeException可能中断事务,直接吞异常会导致问题排查困难——这些都是踩坑概率最高的点。AI在训练阶段见过太多这类翻车样例,所以它在生成代码时会有意绕过这些高风险设计。

1.2 AI的“安全第一”倾向从何而来

大模型生成代码的行为,本质上是对海量代码库的概率模拟。GitHub上开源项目的代码里,最主流的写法就是try-catch+ 日志 + 统一异常封装,这种模板的样本数量远大于那些精妙的错误处理框架。模型读到这样的样本越多,输出时就越倾向于复现它。

但这里还有一个更关键的因素:大模型的对齐机制。厂商在训练模型时,会特意抑制模型输出“有副作用”的代码。比如一段代码如果会在短时间内高频重试、可能耗尽连接池、可能掩盖系统缺陷,模型会被打分系统惩罚。久而久之,模型就学会了“不做事就不出错”。这在异常处理领域体现得尤其明显——保守不是能力问题,而是策略问题。

注意:AI的保守本质上是概率分布下的均值。它不是理解了你的业务,只是选择了历史上最少出错的路线。

既然它保守是常态,那我们就没必要跟它较劲。正确做法是在提示词里把需要它激进的地方明确指出来,让它在自己的保守框架内做文章。举个例子,如果你确实需要重试逻辑,就告诉它“接口幂等,可以进行最多3次指数退避重试”,它立刻就能生成一段很规范的重试代码。

2. 日志规范:AI比我的“野路子”更懂行业惯例?

2.1 一次日志存根引发的讨论

我以前写代码有个坏习惯:在catch块里直接e.printStackTrace(),或者随便logger.error("错了")。这次重构时,我把一段典型代码贴给AI,问它哪里有问题。它给了我非常详细的review,大意是:

  • printStackTrace会输出到标准错误流,在生产环境根本没法采集和检索。
  • 日志消息里没有请求ID和业务参数,出问题后没法做链路分析。
  • 捕获Exception后没有输出完整堆栈,只打了e.getMessage(),排查时只能看到一句话。
  • 没有区分WARN和ERROR,把可恢复的异常也当成系统级错误。

说实话,这些规范我心里都清楚,但从来没有一次性整理成文。AI等于把我脑子里零散的“常识”组织成了一篇可执行的评审意见。那一刻我意识到,AI在日志规范方面的“保守”,其实是它把行业最佳实践当成了默认值。

2.2 从“能用”到“可观测”:日志规范的关键要素

那次AI给我的整改意见被我整理成了一张表,之后团队就按这个标准检查代码:

维度推荐做法反例
日志级别ERROR只保留影响业务/需要立即介入的异常;WARN表示可恢复问题;INFO记录关键流程节点所有异常都打ERROR
上下文信息通过MDC写入traceId、userId、订单号,便于过滤和链路追踪光打印异常堆栈,不知道哪个用户触发
敏感信息token、密码、身份证号不得输出;手机号/地址需要脱敏日志里直接打客户手机号
占位符使用log.error("下单失败, userId={}, orderId={}", userId, orderId, e)字符串拼接"userId=" + userId
堆栈保留异常处理时log.error(..., e),确保堆栈完整log.error(e.getMessage())

这套规范单看每一条都不稀奇,但真正难的是让所有开发者在几百个类里保持一致。AI的价值是它能把规则翻译成具体的判断标准——比如它可以帮你检查某段代码里有没有漏打关键参数,或者有没有把不该打的敏感字段混进日志。这比打开SonarQube看一堆规则列表要直观得多。

3. 正确姿势:如何引导AI生成更合适的异常处理与日志代码

3.1 提示词里把约束写清楚

想让AI在保守的基础上满足你的特殊需求,关键是把“业务边界”表达清楚。我试过最有效的提示词模板包含以下要素:

  • 异常类型和对应策略:哪些异常需要上报、哪些需要吞掉、哪些需要重试。
  • 日志级别和上下文:在哪个环节打什么级别的日志,需要包含哪些字段。
  • 返回值风格:方法调用方希望收到什么类型的结果(可选值、默认值、还是抛出异常)。
  • 边界条件:比如幂等性、并发上限、超时阈值。

来看一个示例提示词:

请为下面的方法补充异常处理,要求: 1. 只捕获FileNotFoundException和SocketTimeoutException,其他异常向上抛; 2. 捕获FileNotFoundException时,返回Optional.empty(); 3. 捕获SocketTimeoutException时,记录WARN日志,包含method和traceId,并重试1次; 4. 重试前校验请求唯一性参数requestId,避免重复提交; 5. 不允许吞异常,不允许使用printStackTrace。

同样的方法,AI按照这五条约束生成的代码就不是那个“保守模板”了,而是一段有明确策略的异常处理逻辑。因为所有风险点都被预先排除,模型反而敢于放开手写。

3.2 让AI参与代码评审和规则制定

除了让它写代码,我后来发现让AI做“评审专家”也很好用。把一段新测代码扔给它,让它站在“团队技术负责人”的视角指出异常处理和日志规范的问题。它的输出往往比大多数同事严格。

另外一个更进阶的操作:让AI生成一份《异常处理与日志规范建议书》。给它你的技术栈(比如Spring Boot、Logback、ELK),让它输出一版结构化规范,包括异常码设计、日志级别矩阵、常用MDC字段清单。得到初稿后,你只需要做一次人工裁剪,就能当团队文档用。这个方法特别适合那些有规范但没时间写文档的小团队。

4. 工程实践中的平衡:AI保守是好事,但别全盘照收

4.1 异常处理的原则:什么时候该保守,什么时候该冒险

AI默认的选择是“记录日志 + 抛异常”,这在很多场景下是正确的,但千万不能变成万能公式。比如一个定时任务清理临时文件,如果某次清理失败,你并不想把整个任务中断——记录一条WARN然后跳过,继续处理下一个文件,才是合理的。反过来,用户支付接口如果出现不确定的异常,你却选择吞掉继续执行,后果就会很严重。这时候必须fail-loud,让调用方感知到失败。

判断依据其实很朴素:这个异常吞掉之后,系统是能继续提供正确服务,还是会在不知道的情况下产生错误结果?如果是前者,保守地记录日志没问题;如果是后者,必须激进地暴露问题。

AI缺少业务语义,它只能给你一个最通用的默认值。你需要做的就是告诉它什么是“可恢复的”,什么是“不可恢复的”。比如你可以写“方法内部catch到IOException时返回null,调用方会做兜底处理”,这样AI生成的就是“保守但符合预期”的代码。

4.2 如何把AI建议内化为团队规范

光靠AI生成零散的代码建议,效果有限。我建议把AI产出的规范沉淀到项目里,具体做法:

  • 把梳理好的《异常处理规范》放进仓库的docs/目录,作为代码评审的必读项。
  • 在CI流程里加上日志规范检查,比如用grep拦截printStackTrace、用正则检查非法log.info级别过载,或者接入静态代码扫描工具。
  • 在Code Review模板里新增一栏“异常处理与日志上下文检查”,强制评审者确认这段代码是否包含了traceId和异常堆栈。

这样AI承担的是“起草者”的角色,真正保证规则落地的是流程。

4.3 实战排查实录:AI建议在真实环境中的表现

有一件我印象很深的“翻车”事。团队按AI的建议,把所有捕获到Exception的地方统一打ERROR级日志。结果一个定时任务每天凌晨拉取第三方数据,对方服务偶尔会短时闪烁,每次失败都打ERROR,日志大小一晚上涨了几个GB。磁盘告警之后,我们分析发现那些异常其实都是同一类连接超时,而且下一轮任务会自动补偿。也就是说这个场景里,打ERROR反而是噪音。

后来我们把AI再接回来,这次我给它描述完整背景:“这是补偿型任务,单次失败在一分钟内会自动重试,需要降低日志级别,同时避免重复堆栈刷屏。”AI给了一个很务实的方案:对同一异常类型和相同message做一分钟内的去重,超过阈值才升级为ERROR。这个设计比单纯降低级别更合理,因为它保留了真正的故障信号,同时滤掉了重复噪音。

5. 常见问题速查表与独家避坑技巧

5.1 让AI写异常处理时常见的翻车场景

我整理了几次使用AI生成异常处理代码时踩过的坑,以及对应的解法,方便你对照检查:

常见情况AI的默认表现你需要补充的提示
只说“帮我处理异常”直接catch (Exception e) {},要么打印要么空路径明确列出要捕获的具体异常类型和异常后的动作
要求“重试”生成固定次数循环,没考虑幂等和退避明确说明接口幂等、重试间隔策略、最大次数
要求“忽略某类异常”可能把Throwable也一起吞了写“仅忽略BusinessException中的XX码,其余继续抛”
要求“返回默认值”返回null却把原始异常丢了要求“返回Optional.empty()并保留DEBUG日志”
要求“统一异常处理”把所有异常包装成RuntimeException并丢堆栈指定异常基类和转换规则,保留原始cause

5.2 日志格式化与敏感信息泄漏

AI在日志规范上的保守不代表它永远不会犯错。我发现如果提示词里不提到敏感信息,AI有可能把方法入参整个打印出来,比如“用户登录成功,手机号=138****5678,密码=abc”。这种日志一旦泄露,问题就大了。所以每次让AI生成日志代码前,我都会在提示词里加一句“禁止输出任何敏感字段,包括手机号、身份证、token、密码”。

另外如果你用的是Log4j2,要注意日志格式化字符串漏洞的问题。AI生成的代码如果直接将外部输入拼进logger.error的模式字符串,就会埋雷。正确做法是坚持使用参数化占位符。

最后分享一个小技巧:把AI当成“保守的评审委员会”,而不是“创新的代码生成器”。当我需要处理异常或梳理日志时,我先自己写一版“野路子”,然后让AI来挑毛病。它的保守眼光正好能指出那些可能引发线上事故的要害点。等它挑完毛病,我再根据业务实际决定哪些地方要改、哪些地方保持原样。这套流程跑过几次之后,我发现自己的代码质量确实被它“拉踩”着提高了一个档次。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询