异常处理基础:捕获、抛出、日志与调试技巧

张开发
2026/4/12 14:49:20 15 分钟阅读

分享文章

异常处理基础:捕获、抛出、日志与调试技巧
文章目录前言一、捕获别啥都抓也别啥都不抓1.1 最烦人的就是那种裸try-catch1.2 finally块别乱用try-with-resources真香二、抛出异长得抛得有价值别瞎扔2.1 自定义异长不是炫技是刚需2.2 包装异长时记得保留现场三、日志从记了等于没记到结构化输出3.1 传统日志已经out了结构化日志才是版本答案3.2 日志级别别乱设debug≠info≠error四、调试技巧从printf大法到AI辅助4.1 生产环境调试别只会打日志4.2 AI辅助调试2026年真香警告五、一些保命的小技巧5.1 全局异长处理器是底线5.2 异长别用来做流程控制写在最后P.S. 目前国内还是很缺AI人才的希望更多人能真正加入到AI行业共同促进行业进步增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow教程通俗易懂高中生都能看懂还有各种段子风趣幽默从深度学习基础原理到各领域实战应用都有讲解我22年的AI积累全在里面了。注意教程仅限真正想入门AI的朋友否则看看零散的博文就够了。前言朋友们写代码这么多年我发现一个真理出bug不可怕可怕的是你压根不知道bug在哪裸奔上周我带的那个应届生线上服务挂了2小时才发现是空指针问他咋没日志他说我以为不会报错… 我当时真的差点当场圆寂~~不扯了直接开整。这篇我把我踩过的坑、背过的锅、还有2025年最新的异长处理套路全倒给你们。一、捕获别啥都抓也别啥都不抓1.1 最烦人的就是那种裸try-catch我见过太多新手写这种代码try{// 一堆业务逻辑doSomething();}catch(Exceptione){// 空的啥也没有}这种写法在圈内叫吞咽异常跟把报错吞肚子里一样程序是不崩了但你也不知道它到底咋了。2025年了这种代码要是被CRCode Review抓到绝对会被钉在耻褥柱上正确的姿势是啥精确捕获Java 21 和 Python 3.13 现在都推这个理念。比如说你要读文件就抓FileNotFoundException别上来就抓个Exception全集。抓得太泛会把一些你根本处理不了的系统异长也给吞了比如OutOfMemoryError这种你抓了也救不回来纯属给自己挖坑。我之前有个项目数据库偶发连接超时我一开始偷懒抓了Exception结果后来真出了内存泄漏日志里毛都没有排查到凌晨4点… 血得教训1.2 finally块别乱用try-with-resources真香老程序员都知道finally是用来释放资源的但2025年了谁还手写finally啊Java 7就出了try-with-resourcesPython有上下文管理器C#有using语句这些语法糖不香吗// 老古董写法容易出事儿BufferedReaderbrnull;try{brnewBufferedReader(newFileReader(file.txt));// 读取操作}catch(IOExceptione){e.printStackTrace();}finally{if(br!null){try{br.close();// 这还可能抛异长}catch(IOExceptione){// 无限套娃...}}}// 2025年标准写法一步到位try(BufferedReaderbrnewBufferedReader(newFileReader(file.txt))){// 读取操作自动关闭绝绝子}catch(IOExceptione){logger.error(文件读取跪了: {},e.getMessage(),e);}看到没代码行数直接砍半而且close()方法自动调用哪怕中间抛异长也不怕资源泄漏。这个特性在微服务架构里特别重要你要是不关资源网关层分分钟给你 connection pool 打满然后整个服务雪崩…二、抛出异长得抛得有价值别瞎扔2.1 自定义异长不是炫技是刚需很多小伙伴以为用Java自带的RuntimeException扔出去就完事了太天真了生产环境出问题你抛个RuntimeException: error occurred运维大哥看到日志直接懵圈——这是啥哪报的严不严重2025年的最佳实践是建立异长层级体系。我自己项目的套路是这样的BaseException (所有业务异长的爹) ├── ServiceException (服务层问题) │ ├── PaymentFailedException (支付相关) │ ├── UserNotFoundException (用户不存在) │ └── InvalidParameterException (参数校验失败) └── IntegrationException (第三方集成问题) ├── NetworkTimeoutException (网络超时) └── ThirdPartyErrorException (下游报错)这样设计有啥好处调用方能做智能决策比如说支付场景遇到NetworkTimeoutException这种瞬态异长可以直接重试但要是InsufficientBalanceException余额不足重试100次也没用直接拒绝就完事了。Python 3.11 还出了ExceptionGroup可以把多个异长打包一起抛这个在异步编程里简直是神器。2026年的新趋势是异长聚合一次性告诉你哪几个环节都出了问题而不是让你像剥洋葱一样一层层debug。2.2 包装异长时记得保留现场捕获底层异长往上抛的时候千万别搞这种try{database.query();}catch(SQLExceptione){thrownewBusinessException(数据库挂了);// 原始异长呢}这叫异长链断裂你把底层的SQLException给吃了上层根本看不到root cause。正确的做法是try{database.query();}catch(SQLExceptione){thrownewBusinessException(数据库查询失败userIduserId,e);// 带上cause}或者用Python的raise … from语法try:low_level_call()exceptConnectionErrorase:raiseServiceUnavailableError(支付接口无响应)frome这样日志里会显示The above exception was the direct cause of the following exception异长链条清清楚楚。2025年的SLF4J 2.0 和 Logback 1.5 都优化了这种链式异长的展示再也不用在一堆堆栈里翻找原始报错了。三、日志从记了等于没记到结构化输出3.1 传统日志已经out了结构化日志才是版本答案还在用log.error(“用户” userId “登录失败”)这种字符串拼接这种写法在2025年属于反模式为啥因为没法检索啊ELKElasticsearchLogstashKibana或者Grafana Loki里你只能用正则去匹配这种文本性能拉胯不说还容易漏。现在的版本答案是结构化日志也就是JSON格式。2026年的生产环境无论你是用Log4j2、Logback还是Python的structlog都得往这个方向靠。对比一下就懂了// 传统写法机器读不懂 2025-04-12 14:30:45 [ERROR] 用户87654登录失败IP192.168.1.102原因是密码错误 // 结构化写法JSON格式直接入库 { timestamp: 2025-04-12T14:30:45Z, level: error, event: login_failed, user_id: 87654, ip_address: 192.168.1.102, reason: invalid_password, trace_id: abc-123-def-456, latency_ms: 145 }看到没字段全是key-valueElasticsearch直接索引Kibana里点两下就能过滤出过去1小时所有登录失败的北京用户效率提升不是一点半点。据我统计用了结构化日志后排查问题的MTTR平均修复时间能从原来的2小时降到15分钟以内3.2 日志级别别乱设debug≠info≠error很多人记日志全凭心情这不行2025年的12-Factor App规范里明确说了日志级别必须严格区分DEBUG开发调试用生产环境默认关闭开关打开后能看到详细流程INFO关键业务流程节点比如订单创建成功、“支付回调收到”WARN非预期但可恢复的情况比如请求重试第3次、“缓存击穿回查数据库”ERROR必须人工介入的问题比如数据库连接断开、“核心下游服务无响映”重点来了不要记敏感信息2025年GDPR和国内数据安全法都严得一匹密码、身份证号、银行卡号绝对不能出现在日志里。上周有个大厂因为这个被罚了200万血得教训日志里可以记user_id但绝不能记password_hash记住了还有不要同时log又throw我见过这种代码try{process();}catch(Exceptione){log.error(处理失败,e);// 记一次thrownewBusinessException(处理失败,e);// 又抛出去上层又记一次}结果日志里同一个异长出现8次排查的时候满屏重复心态直接爆炸。要么你记了包装成自定义异长往上抛要么你直接抛让上层统一记别两头都搞四、调试技巧从printf大法到AI辅助4.1 生产环境调试别只会打日志线上出问题本地复现不了咋办2026年了别再用System.out.println了那是原始人做法现在的神器是远程调试分布式追踪。比如说你有个微服务架构请求从网关→A服务→B服务→C服务→数据库中间哪环出问题用传统的grep日志你得在5个服务的日志文件里翻来覆去还得对时间戳简直要命。解决方案是OpenTelemetry Trace ID。每个请求进网关时生成一个唯一的trace_id然后透传到下游所有服务。日志里统一带上这个字段Jaeger或者SkyWalking里一搜整个调用链可视化哪步耗时3秒、哪步报错了一目了然。我还喜欢在关键位置记上下文日志Contextual Logging不只是说这里错了还要把当时的状态快照记下来。比如log.error(订单状态流转失败当前状态{}, 目标状态{}, 操作人{}, 请求参数{},currentStatus,targetStatus,operatorId,params);这种日志一出基本不用翻代码直接就能定位业务逻辑哪错了。2025年的Log4j2 2.24 甚至支持异步上下文用ThreadLocal或者Scoped Values自动注入trace_id代码里不用手动传爽得一批4.2 AI辅助调试2026年真香警告朋友们2026年最火的调试姿势是AI-assisted debugging。Copilot Chat、Amazon CodeWhisperer、JetBrains的AI Assistant这些工具不只是写代码快调试bug也是一绝我现在的workflow是本地复现不了直接把异长堆栈丢给AI它能帮你分析可能的root cause甚至给你建议修改方案。还有Rubber Duck Debugging橡皮鸭调试法——你把问题描述给AI听很多时候讲着讲着你自己就发现问题了跟对着同事讲一样但AI不会嫌你烦~~还有Binary Search Debugging二分查找调试。线上偶发bug不知道是哪行代码出的问题把代码分成两半注释掉一半看还出不出问题不出就锁定另一半再对半砍… 最多10次就能定位到具体代码行。这种技巧在排查Heisenbug海森堡bug观察时就不出现时特别好使。五、一些保命的小技巧5.1 全局异长处理器是底线Spring Boot、Django、Flask这些框架一定要配置全局异长处理器Global Exception Handler。总有些异长是你try-catch漏掉的比如某个边角料功能抛了个你没见过的异长要是没全局兜底用户看到的就是满屏的Whitelabel Error Page或者直接500 Internal Server Error体验稀烂。全局处理器里要做这几件事记日志带上trace_id返回友好的错误提示别暴露堆栈给前端发送告警钉钉/飞书/企业微信通知我之前配了一个线上只要出ERROR级别的异长飞书群里立马弹消息附带trace_id和简要信息响应速度直接拉满。5.2 异长别用来做流程控制这句话我得加粗异长是异长情况不是业务逻辑我见过有人这么写try{useruserService.findById(userId);}catch(UserNotFoundExceptione){usercreateDefaultUser();// 用异长控制分支}这种写法性能差抛异长代价高而且语义混乱。正确的做法是OptionalUseruserOptuserService.findById(userId);UseruseruserOpt.orElseGet(()-createDefaultUser());异长是用来处理非预期情况的不是用来写if-else的记住这句话能少走很多弯路。写在最后异长处理这事儿说简单也简单说难也难。核心就三点捕获要精确抛出要有意义日志要结构化。2025-2026年的趋势是向着可观测性Observability发展不只是异长还要结合metrics指标和tracing追踪形成三位一体的监控体系。我之前写过一篇《智能体监控基础》跟这篇是姊妹篇感兴趣的可以去翻翻。异长处理是代码的免疫系统写好了能救命写不好… 那就准备好凌晨被运维电话轰炸吧你平时写代码遇到过最离谱的异长是啥欢迎在评论区分享咱们一起吐槽~~ 觉得有用的点个赞转发给那个总写catch(Exception e){}的同事救救孩子完P.S. 目前国内还是很缺AI人才的希望更多人能真正加入到AI行业共同促进行业进步增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow教程通俗易懂高中生都能看懂还有各种段子风趣幽默从深度学习基础原理到各领域实战应用都有讲解我22年的AI积累全在里面了。注意教程仅限真正想入门AI的朋友否则看看零散的博文就够了。

更多文章