一、异常的定义与分类
异常,说白了就是程序运行过程中出现的意外情况。我做了这么多年开发,见过太多因为异常处理不当导致线上事故的案例。嗯,咱们先把这个基础概念理清楚。
1.1 什么是异常
异常是程序执行过程中发生的非正常事件。它会打断正常的指令流。我个人习惯把异常理解为「程序生病了」——有些病能自己扛过去(可恢复),有些病必须送医院(不可恢复)。
核心定义:异常 = 程序执行时遇到的、无法在当前上下文继续执行的问题。
1.2 异常的分类
我一般把异常分成三大类。你想想看,这样分在实际开发中特别好用:
| 分类 | 特点 | 典型例子 | 处理方式 |
|---|---|---|---|
| 系统异常 | 底层系统抛出,不可控 | 内存溢出、栈溢出 | 记录日志+重启 |
| 业务异常 | 业务逻辑违反约束 | 参数校验失败、权限不足 | 返回错误码+提示用户 |
| 运行时异常 | 代码缺陷导致 | 空指针、数组越界 | 修复代码+增加防御 |
我在项目中遇到过最坑的一次,就是系统异常和业务异常混在一起处理。结果线上出了内存泄漏,业务层还在拼命重试,最后整个集群都挂了。所以分类一定要清晰。
二、同步异常与异步异常
这个区分特别重要。很多新手搞混了,导致异常捕获不到。我刚开始做混合开发时也踩过这个坑。
2.1 同步异常
同步异常发生在代码的同步执行过程中。说白了就是「当场就炸」——你写了个空指针,程序跑到那行直接就崩了。
// 同步异常示例
try {
let result = JSON.parse(invalidJsonString);
// 如果 invalidJsonString 格式不对,这里直接抛异常
} catch (error) {
console.error('JSON解析失败:', error.message);
// 同步异常可以被 try-catch 捕获
}
同步异常的特点:
- 可以被 try-catch 直接捕获
- 异常发生时,当前执行栈立即中断
- 错误信息包含完整的调用栈
2.2 异步异常
异步异常就麻烦多了。它发生在回调、Promise、事件循环里。你想想看,代码已经执行过去了,异常才冒出来,try-catch 根本抓不到。
// 异步异常示例 - 错误的写法
try {
setTimeout(() => {
throw new Error('异步错误');
}, 1000);
} catch (error) {
// 这里永远捕获不到!
console.log('捕获到异常:', error);
}
// 正确的写法
setTimeout(() => {
try {
throw new Error('异步错误');
} catch (error) {
console.error('在回调内部捕获:', error);
}
}, 1000);
避坑指南:我曾经在项目中用 try-catch 包裹了整个异步函数,以为能捕获所有异常。结果线上跑了三天,日志里一条异常都没有——其实异常全丢了。后来排查才发现,Promise 里的 reject 必须用 .catch() 处理。
2.3 异步异常的处理模式
我个人习惯用三种模式处理异步异常:
- 回调模式:错误优先回调(error-first callback),Node.js 的经典做法
- Promise 链:用 .catch() 统一处理
- async/await:用 try-catch 包裹,但要注意 await 后面的异常才能捕获
// Promise 链处理
fetch('/api/data')
.then(response => response.json())
.catch(error => {
// 统一处理网络异常
console.error('请求失败:', error);
});
// async/await 处理
async function loadData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
// 这里能捕获到异步异常
console.error('加载数据失败:', error);
throw error; // 或者返回默认值
}
}
三、混合开发中的异常来源分析
混合开发(Hybrid App)的异常来源特别复杂。我做过几个大型混合项目,总结下来主要有四个来源。
3.1 原生层异常
原生代码(Java/Kotlin/OC/Swift)抛出的异常。比如:
- 内存不足导致 Native 崩溃
- JNI 调用参数类型不匹配
- 原生组件渲染失败
这类异常最难排查,因为错误信息往往只有个信号量(SIGSEGV、SIGABRT)。
3.2 Web 层异常
H5 页面里的 JavaScript 异常。包括:
- 语法错误、类型错误
- DOM 操作异常
- 网络请求失败
- 第三方 SDK 报错
3.3 桥接层异常
这是混合开发特有的异常来源。JS 和 Native 通信时最容易出问题:
// 桥接异常示例
// Native 端注册的方法
@JavascriptInterface
public void shareImage(String base64Data) {
// 如果 base64Data 过大,这里会 OOM
byte[] data = Base64.decode(base64Data);
// ...
}
// JS 端调用
window.NativeBridge.shareImage(largeBase64String);
// 如果图片太大,Native 端直接崩溃,JS 端完全不知道
我的经验:桥接层异常有个特点——异常发生在哪一端,错误信息就留在哪一端。所以一定要在两端都加日志。我曾经排查过一个闪退问题,查了三天才发现是 JS 传了个超长字符串给 Native,Native 端解码时内存溢出了。
3.4 环境差异异常
不同手机、不同系统版本、不同 WebView 内核导致的差异:
- Android 系统 WebView 版本不一致
- iOS WKWebView 和 UIWebView 行为差异
- 低端机性能不足导致超时
- 系统字体、屏幕尺寸适配问题
四、异常来源全景图
下面这张图是我自己整理的混合开发异常来源结构。每次做项目我都会拿它来对照排查:
五、异常处理的基本原则
讲了这么多,最后总结几条我这些年摸爬滚打总结出来的原则:
- 不要吞异常:catch 了什么都不做,等于掩耳盗铃。至少打个日志。
- 区分异常类型:业务异常要提示用户,系统异常要记录完整上下文。
- 异步异常要特殊处理:别指望 try-catch 能包打天下。
- 桥接层加超时:Native 和 JS 通信一定要设超时,防止死等。
- 日志要带上下文:光记个「出错了」等于没记。要记设备信息、用户操作路径、参数值。
一个小技巧:我习惯在项目初期就搭好异常采集框架。别等到上线了再补,那时候已经漏掉很多关键信息了。异常处理不是功能,是基础设施。
嗯,这一章的内容就到这。异常处理的基础打牢了,后面讲具体采集方案时你才能理解为什么这么设计。记住一句话:异常不可怕,可怕的是你不知道异常发生了。
公众号:蓝海资料掘金营,微信deep3321