news 2026/10/1 12:01:45

AI代码生成为何在异常处理上“打太极”?调教方法与日志规范实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI代码生成为何在异常处理上“打太极”?调教方法与日志规范实战

H38 这期,我想聊聊异常处理和日志规范。上周我把一块核心链路的异常处理代码交给 AI 去生成,当时心想:以现在大模型的能力,写个 try-catch、打几个日志还不是手到擒来。结果代码出来之后我盯着屏幕看了半天,越看越不是滋味——AI 比我想象中更保守,保守到让我怀疑它是不是在故意“打太极”。

先说结论:AI 不是不会写异常处理,而是它默认选择了“最不容易被骂”的写法,而不是“最正确”的写法。这不是模型能力不够,而是训练目标和人类工程师的代码评审标准存在偏差。这篇文章我会拿实际生成结果说话,分析 AI 保守的三个典型表现,然后给出我在项目里总结的调教方法:提示词怎么设计、日志规范怎么喂给 AI、静态检查怎么兜底。如果你正在用 AI 辅助写业务代码,或者你负责团队日志规范落地,这篇文章应该能帮你省掉不少排查时间。

1. 我发现 AI 在异常处理上“怂”得有点过分

1.1 测试场景:让 AI 生成文件下载模块

我先交代一下背景。项目里有个文件导出功能,逻辑不复杂:读本地文件、校验大小、转成下载流,过程中可能遇到文件不存在、权限不足、磁盘 IO 异常。我让 AI 直接生成这个函数,要求是“异常处理合理,日志清晰”,没有给更多约束。

AI 给的版本大概长这样:

def download_file(source_path): try: with open(source_path, "rb") as f: data = f.read() except Exception as e: logger.info("download failed: %s", e) return None return data

这段代码能用,但放在生产环境就是事故。文件不存在和磁盘损坏被混在一个 except 里,错误信息用 info 级别打出去,然后返回 None,调用方根本不知道到底是“文件不存在”还是“磁盘坏了”,只会看到一个空结果。我把这段代码拿给团队里一个刚入职的同学看,他的第一反应是:“这代码看起来没什么问题啊,捕获了异常还打了日志。”问题就出在这里——AI 给的写法是“看起来没问题”,而不是“真正可靠”。

1.2 保守的三种典型表现

我把 AI 生成过的异常处理代码归了一下类,发现它的保守集中在三个地方:

第一,异常捕获过宽。AI 特别习惯写except Exception,把所有异常吞进同一个分支。我让 AI 生成依赖外部服务的调用,它甚至写了except Exception后 return None,把连接超时、认证失败、数据解析错误全混在一起。这种写法在教材里常见,但在真实系统里等于屏蔽了故障。

第二,日志级别严重偏低。明明是不可恢复的故障,AI 经常用 info 或者压根不打日志。我让 AI 给支付回调生成日志,它把“验签失败”记成 warning,把“重复回调”记成 info,把“DB 写入失败”记成 error 之后,后面又跟了一句“不影响主流程”。在告警规则里,error 才是触发通知的门槛,AI 为了“不打扰人”,把所有错误都压到了 warn 以下。

第三,倾向于吞异常而不是抛出。AI 特别不愿意让异常继续向上传播。它宁可返回一个默认值、空列表、None,也不愿意让上层感知到失败。表面上看程序没有崩溃,实际上数据错了、流程断了,而且很难追溯到源头。

1.3 这种保守带来的真实问题

我在本地试过把 AI 写的异常处理代码塞进一个压测脚本里,模拟文件读取异常,结果所有失败都被静默吞掉,监控面板上看不到任何一条 error 日志,只有业务数据对不上时才发现问题。排查成本翻了至少三倍。

更麻烦的是日志的检索性。AI 打的日志缺乏关键上下文,比如没有 traceId、没有错误码、没有文件名、没有异常堆栈。生产环境一天几千万条日志,单纯一句“download failed: No such file or directory”,根本定位不了是哪个用户、哪个批次、哪台机器上的哪个文件。这就引出一个核心观点:AI 帮我们写代码,但我们对异常处理和日志规范的思考不能省略。它给的是一种“统计意义上的安全”,而不是“工程意义上的正确”。

2. 为什么 AI 会这么保守:从训练到对齐的“不敢错”

2.1 训练语料的统计惯性

AI 不是天生保守,而是它从训练数据里学到的“主流”就是保守的。开源代码里充满了except Exception: pass、print(e)、return None,尤其是教程项目、示例代码和短期脚本,异常处理大多处于“能跑就行”的状态。大模型在生成时本质上是在做 token 级别的概率采样,它会优先选训练语料中出现频率最高的模式,而不是最优质的实现。

我做过一个小统计:让同一个模型生成 20 段不同的异常处理代码,其中 14 段都包含except Exception,只有 3 段区分了具体的异常类型。这不是模型不会,而是它觉得“写 Exception 最安全”——因为语料里就是这么多。

2.2 对齐训练带来的“讨厌风险”

这里要说一点背景。语言模型在上线前都会做一轮对齐,目标之一是让模型给出的回答更“无害”。在这个目标的驱动下,模型会倾向于避开可能引发争议、报错或无法编译的形式。具体到代码生成,它就表现为:宁可多捕获一些异常,也不想漏掉某个异常导致程序崩溃;宁可少打 error 日志,也不想因为误报打扰运维。

这不是模型故意偷懒,而是它在用“不惹麻烦”的策略来满足对齐目标。但工程代码恰恰需要适当的“惹麻烦”:该抛的异常要抛,该打 error 的要打,该中断流程的要中断。我们把问题拆开看,就是 AI 在“让程序继续跑”和“让问题暴露出来”之间,总是选前者。

2.3 AI 对日志规范的防御式解读

另一个让我意外的点是,AI 对日志规范的理解也偏保守。我让它按“日志分级明确、便于排查”的标准生成日志代码,它反而把所有日志都降级了。原因很好理解:训练语料和网上博客反复强调“error 级别会触发告警,不要乱用”,AI 学到的教训是“少报错才安全”,所以它就尽量不用 error,甚至把该报 error 的场景也压成 warn 或 info。

这里需要澄清一个误区:日志级别不是越少越好,而是要和动作对应。error 表示“需要人马上看”,warn 表示“不需要马上看但值得注意”,info 表示“正常流程节点”。AI 没有我们团队具体的告警策略,它只能选择一个在所有团队里都不算错的方案——那就是少用 error,最好别用。

2.4 这不是坏事,但需要管理

我并不是说 AI 的保守一无是处。在快速原型、一次性脚本、异常影响面小的场景里,保守的异常处理反而能避免程序崩溃。但在核心业务链路里,这种保守就是隐患。所以真正的问题不是“AI 太笨”,而是“我们没告诉 AI 这条链路的异常策略是什么”。

想明白这一点之后,我就不再对着 AI 输出发脾气了,而是把调教重点放在提示词和规范约束上。让一个保守的初级工程师变成符合团队要求的工程师,靠的是清晰的规范和不断的 review,AI 也一样。

3. 调教 AI 的正确姿势:提示词、规范与护栏

3.1 提示词里写清楚异常边界

我试过无数次“直接让 AI 写一个健壮的下载模块”,结果都不理想。后来发现,不是提示词不够“宏大”,而是缺少可执行的边界。AI 需要知道:哪些异常是可恢复的,哪些是不可恢复的;可恢复的怎么处理,不可恢复的是抛出还是返回特定状态;日志打到什么级别。

我现在的提示词模板大致是这样:

请实现一个文件下载函数,满足以下异常处理要求: 1. FileNotFoundError 视为业务异常,记录 warning 日志,返回错误码 FILE_NOT_FOUND。 2. PermissionError 视为权限问题,记录 error 日志,抛出自定义 BusinessException(BIZ_PERMISSION_DENIED)。 3. OSError 视为系统异常,记录 error 日志并打印堆栈,原样抛出。 4. 所有日志必须包含参数 path、当前用户 userId、方法名。 5. 禁止 catch Exception 这种过宽捕获,禁止吞掉异常,禁止返回 None 表示失败。

把边界写清楚之后,AI 生成的质量明显上升。它不再默认“全 catch 住”,而是知道要区分异常类型并采取不同动作。这里的关键在于,异常处理策略本来就是业务决策,不能指望 AI 替我们做决策。我们要做的是把决策告诉它。

3.2 把团队日志规范直接喂给 AI

如果你只是给 AI 一句话“按规范写日志”,它大概率还是按自己那套来。最好的办法是把团队的日志规范文档关键部分直接复制进提示词。我通常会把下面这几条放进去:

  • 日志格式:时间戳 [日志级别] traceId 类名 - 业务消息 上下文字段
  • 级别定义:error 表示需要人工介入的故障;warn 表示可恢复但值得关注;info 表示关键流程节点;debug 表示调试细节。
  • 禁止事项:禁止打日志不带异常堆栈;禁止使用print;禁止在日志中拼接敏感个人信息。

有团队规范做锚点,AI 的“防御式保守”就会被约束到正确的方向上。它不再花心思猜该用 info 还是 error,因为你已经给了它判断标准。

3.3 给例子,不如给原则

我也试过在提示词里贴一大堆“正确示例”,比如优秀的异常处理代码片段。效果有,但不稳定。贴三个以上例子时,AI 容易“抄过头”,把示例里和当前场景无关的细节也搬过来。更稳的做法是给一两个正反例,同时把判断原则写清楚。

比如我常用的一对例子:

# 反例:吞掉异常,日志无堆栈,返回 None except Exception as e: logger.info("failed: %s", e) return None # 正例:区分可恢复与不可恢复,日志带上下文 except FileNotFoundError as e: logger.warning("file not found, use empty", extra={"path": path}) return []

然后明确告诉 AI:“在其余场景里套用这个判断逻辑,而不是复制代码”。这样 AI 更可能学会“决策方式”,而不是“某个具体写法”。

3.4 启动前约定不可触碰的底线

最后这道护栏是审查清单。我会在提示词里要求 AI 在输出最后附上一段“自查说明”,逐条确认自己没有违反以下规则:

  • 是否使用过宽的except Exception?
  • 是否在捕获异常后没有记录日志?
  • 是否在日志中丢失异常堆栈?
  • 是否用 info 级别记录了 error 场景?
  • 是否返回了容易让调用方混淆的默认值?

这一步强烈建议保留。AI 的自查说明不是给我们看的,是给 AI 自己看的。它会为了写出“没违反规则”的自查,主动修正前面的代码,相当于在生成阶段做了一轮自我约束。实测下来,加上自查要求后,AI 生成的异常处理代码出现“吞异常”的次数大幅下降。

4. 实操案例:从保守 AI 到合格初稿的完整流程

4.1 第一步:准备一份可复用的日志规范

这里我给一个简版规范,适用于大多数中大型后端项目。你可以直接抄进提示词或团队文档里:

字段说明示例
ts毫秒级时间戳2025-01-12 21:30:15.123
level日志级别ERROR / WARN / INFO / DEBUG
traceId链路追踪 IDa3f2b9c0d1
module模块名file-download
bizCode业务错误码FILE_NOT_FOUND
message可读消息文件不存在,使用默认列表
stack异常堆栈异常类 + 堆栈首行
kv业务上下文path=/tmp/x.txt, userId=u_1024

规范里还要写清楚:ERROR 必须包含 stack;WARN 可以只带 message;INFO 用于流程节点,绝不携带异常堆栈;所有日志不记录身份证号、手机号等敏感字段。有了这个底子,AI 生成的日志代码才不至于各写各的。

4.2 第二步:设计错误码和上下文模型

很多团队日志乱,是因为错误码体系是乱的。我在项目里习惯给每个业务模块定义错误码前缀,比如文件模块是FILE_,权限模块是AUTH_,支付模块是PAY_。设计错误码之后,再把“异常 → 错误码 → 日志级别 → 用户可读信息”的映射关系做成提示词的一部分。

比如文件下载模块:

异常情况错误码日志级别处理方式
文件不存在FILE_NOT_FOUNDWARN返回空列表
无读取权限FILE_PERMISSION_DENIEDERROR抛出业务异常
磁盘 IO 故障FILE_IO_ERRORERROR抛出系统异常并告警
文件大小超限FILE_SIZE_EXCEEDEDWARN返回参数错误

有了这个映射表,AI 不再需要自己“猜测”每个异常该怎么处理,它的保守倾向也就失去了土壤。因为它一旦乱写,很容易违背映射表中的内容。

4.3 第三步:让 AI 按规范生成模块代码

把规范、错误码表和提示词模板组合到一起,生成效果会明显改观。我让同一个 AI 重新生成文件下载模块,这次它的输出已经是这个水准:

def download_file(source_path: str, user_id: str) -> list[bytes]: try: with open(source_path, "rb") as f: data = f.read() except FileNotFoundError as e: logger.warning( "file not found, use default list", extra={"error_code": "FILE_NOT_FOUND", "path": source_path, "user_id": user_id}, ) return [] except PermissionError as e: logger.error( "permission denied", extra={"error_code": "FILE_PERMISSION_DENIED", "path": source_path, "user_id": user_id}, exc_info=e, ) raise BusinessException("FILE_PERMISSION_DENIED") from e except OSError as e: logger.error( "unexpected io error", extra={"error_code": "FILE_IO_ERROR", "path": source_path, "user_id": user_id}, exc_info=e, ) raise return data

注意几个细节:FileNotFoundError 用 WARN,PermissionError 和 OSError 用 ERROR;异常信息通过exc_info=e保留堆栈;业务异常用raise ... from e保留异常链;上下文里带了 path 和 user_id。这套写法放在生产环境里,至少能保证故障可感知、可定位、可追溯。虽然它还是有一些地方可以优化,比如文件大小校验没有做,但作为初稿已经足够合格。

4.4 第四步:用静态检查工具兜底

人不可能每次都逐行 review AI 代码,所以我把一部分检查交给了自动化工具。项目是 Python 的话,我会在 CI 里加 flake8 和 pylint 规则,强制检查except Exception、print、raise后无日志等常见问题。日志侧,我会写一个简单的自定义规则,利用 AST 解析源代码,检查 logger 调用是否带exc_info或stack_info。

在代码审查阶段,我还有一个习惯:让 AI 生成自己的代码评审意见。提示词里明确要求“指出异常处理中所有可能引发生产问题的地方,并给出修改建议”。听起来很绕,但效果不错——AI 在“挑刺模式”下并不保守,它会指出自己生成代码里日志级别不当、异常范围过宽的问题。我们只需要把这份评审意见当参考,再决定是否采纳。等于用 AI 的两种模式互相制衡。

5. 异常处理与日志规范 AI 化后的常见问题速查

5.1 AI 总是用 print 或 e.printStackTrace()

这个问题在 Java 项目里特别常见。AI 生成代码时冷不丁冒出一句e.printStackTrace(),通常发生在 catch 块里。原因很简单:训练语料里大量教材示例用 printStackTrace,AI 学了个正着。

我的处理办法是在提示词里加一句“禁止使用 print 或 printStackTrace,日志必须通过 logger 输出”,同时用静态检查工具兜底。如果项目有统一日志封装,可以直接把LoggerFactory.getLogger的用法贴给 AI,让它按封装类来。实测下来,加提示词之后出现 printStackTrace 的概率能降到几乎为零。

5.2 日志全是 info,看不到 error

如果你发现 AI 生成的代码里全是 info,连异常场景都用 info 记录,别急着怪它懒。它很可能是怕 error 触发告警。我之前处理过一段 AI 生成的回调处理代码,AI 把“验签失败”“重复回调”“数据库写入失败”全部打成了 info,原因写的是“不阻塞主流程”。

这种问题靠提示词就能拉回来:明确告诉 AI,error 级别用于“需要人工介入的故障”,并给出几个场景例子。同时要求它在输出前自查“是否把所有故障都压成了 info”。如果真的想让 AI 形成习惯,可以在日志规范里加一条:每段代码里至少包含一个 error 或 warn 级别的日志分支。反过来逼它思考异常路径。

5.3 异常链丢失:只见 message 不见堆栈

AI 特别习惯写logger.error("xxx" + e.getMessage())或者logger.error(str(e))。这在最坏情况下只能看到一句话,堆栈全部丢失,线上排查等于盲人摸象。我反复踩过这个坑,最后在提示词里固定了一段话:“所有 error 日志必须携带异常堆栈,Python 使用 exc_info=True,Java 使用 logger.error(msg, e) 而不是在 msg 里拼接 e.getMessage()。”

代码生成之后,我会快速扫一眼 catch 块里 logger 的第二个参数。只要发现白底黑字只有一个字符串参数,基本就是丢堆栈了,直接返工。

5.4 怎么让 AI 自己给自己挑毛病

最后分享一个我最常用的技巧:让 AI 双重输出。第一次生成代码,第二次让它对照异常处理和日志规范给代码挑毛病。挑毛病时 AI 会变得异常严格,有时候甚至比人类 review 还苛刻。它可能会指出“这里应该单独 catch ZeroDivisionError”,“这条日志缺少 traceId”,“这个返回值应该用 Optional 而不是 None”。虽然不一定每一条都正确,但能帮我们快速聚焦到高风险点。

为了这个流程更好用,我会在项目里维护一份“异常处理检查清单 Markdown 文件”,里面写着团队约定好的规则。每次调 AI 之前,把这份清单复制进提示词,要求它“先阅读清单,再写代码,最后对照清单自查”。这比每次重新描述规范省事得多,而且 AI 的输出稳定性会随着清单的完善持续提升。

回到开头那个话题。AI 在异常处理和日志规范上确实比我预想的保守,但保守本身不是缺陷,它是一种需要被管理的行为模式。我现在的态度是:把它当成一个特别认真、特别怕犯错但缺乏业务判断的初级工程师,给它足够清晰的规范和边界,它的产出就会从“可用但危险”变成“可靠且合规范”。如果你也在跟 AI 协作写业务代码,不妨从这个角度去调整提示词和规范文档,省下来的排查时间会非常可观。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 12:01:08

Wine+FEX-Emu+DXMT:跨平台运行Windows应用的兼容层实践

1. 从“Madeira”说起:一个跨平台兼容项目的整体设计思路第一次看到“Madeira”这个代号,很多人会以为是某个葡萄酒产区或者旅游项目,但在我实际接触的圈子里,它指向的是一类非常具体的技术实践:在非 Windows 平台上跑…

作者头像 李华
网站建设 2026/10/1 12:01:00

AI生成梯形图的四种技术路径与工程落地指南

1. 这不是“让AI画个梯形图”那么简单——先搞清LD的本质和AI能碰的边界“AI生成梯形图LD”,这七个字在PLC工程师的朋友圈里刷屏快半年了。但很多人一上来就问:“哪个AI工具能一键生成?”,结果试了三款,导出的LD代码要…

作者头像 李华
网站建设 2026/10/1 12:00:40

基于Matlab的楼宇微网虚拟储能日前优化调度建模与实现

1. 楼宇微网里为什么要引入需求侧虚拟储能 接到这个题的时候,我的第一反应不是建模,而是先问自己一个问题:楼宇微网里这块“虚拟储能”到底是什么,值不值得为它多写一百行约束。需求侧虚拟储能系统说白了,就是把楼宇里…

作者头像 李华
网站建设 2026/10/1 11:59:56

计算机答辩不靠背答案:评委提问逻辑与回答框架

计算机答辩这件事,很多人把它当成一场"知识考试",于是把教材从头翻到尾,把论文里的名词解释背得滚瓜烂熟,结果一进答辩教室,老师问的第一个问题就让他懵了——"你为什么选这个题目?"这…

作者头像 李华
网站建设 2026/10/1 11:59:34

Go 语言 encoding/json 标准库深度解析:从 Tag 反射到流式处理

适用版本:Go 1.16 ~ 1.24(文中标注各版本行为差异)1. 背景1.1 为什么 JSON 是 Go 生态的"头号公民"数据格式在 Go 生态中,JSON 的使用频率远超其他序列化格式,几乎每一个网络服务、配置加载、数据交换场景都…

作者头像 李华
网站建设 2026/10/1 11:58:55

DeepSeek开源大模型技术解析与工程落地指南

这个标题存在根本性事实错误,无法作为真实项目进行技术或商业层面的深度拆解。DeepSeek(深度求索)是一家中国人工智能公司,成立于2023年,核心业务聚焦于大模型研发与开源生态建设,其产品包括DeepSeek-VL、D…

作者头像 李华