深入理解程序运行时异常:哪些能 try-catch,哪些绝对不能

在实际开发中,很多工程事故并不是因为“异常发生了”,而是因为异常被错误地处理了。 尤其常见的误区是:

“只要 try-catch 住,程序就安全了。”

这是错误的。

本文将系统讲清楚:

  • 程序运行中到底有哪些“异常”
  • 为什么有些异常可以 try-catch,有些不可以
  • 各主流语言(Java / JS / iOS)的设计哲
  • 工程实践中真正正确的异常处理方式

一、先给结论(TL;DR)

不是所有运行期错误都应该、也都能够被 try-catch。

从工程角度看,运行期问题可以分为两大类:

分类 是否应 try-catch 本质
可恢复异常 ✅ 可以 程序仍处于可控状态
不可恢复错误 ❌ 不可以 / 不应该 运行环境已损坏

区分标准只有一个:

👉 异常发生后,程序是否仍然处于“安全、可预测”的状态?


二、第一类:可以 try-catch 的异常(可恢复异常)

1. 什么是“可恢复异常”

可恢复异常通常具备以下特征:

  • 程序控制流仍然存在
  • 内存、栈、线程结构完好
  • 异常是“业务或环境层面”的问题
  • 通过逻辑处理可以修复、降级或提示用户

try-catch 的本质并不是“修复错误”,而是控制流跳转


2. 常见示例

Java / Kotlin

try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("除零异常");
}

常见可捕获异常:

  • IllegalArgumentException
  • NullPointerException
  • IOException
  • SQLException
  • 业务自定义异常(如 BizException)

这些异常的共同点是:

JVM 仍然可以安全地继续执行后续代码


JavaScript / TypeScript

try {
  JSON.parse("invalid json");
} catch (e) {
  console.error("JSON 解析失败");
}

可捕获的包括:

  • 运行期逻辑错误
  • throw new Error()
  • Promise.reject() / async-await 异常

但注意:

  • 语法错误(parse 阶段)无法 try-catch

Objective-C / Swift

@try {
    NSArray *arr = @[];
    NSLog(@"%@", arr[1]);
}
@catch (NSException *exception) {
    NSLog(@"数组越界");
}

⚠️ 但在 iOS 中:

Apple 并不鼓励使用 try-catch 来处理 NSException

原因后文会详细说明。


3. 适合 try-catch 的典型场景

  • 用户输入不合法
  • 网络请求失败
  • 文件不存在 / 权限不足
  • 数据解析失败(JSON / XML)
  • 明确可预期的业务异常

一句话总结:

这是一个“可能发生、而且你已经预期到”的问题


三、第二类:不可以 try-catch 的异常(致命错误)

1. 本质区别(非常关键)

这些问题严格来说已经不是“异常”,而是:

程序运行环境本身已经处于不可预测状态

典型特征:

  • 内存被破坏
  • 栈空间耗尽
  • CPU 执行非法指令
  • 操作系统强制终止进程

在这种情况下:

继续运行程序,本身就是一种错误


2. 典型示例(语言无关)

① 内存访问错误(C / C++)

int *p = NULL;
*p = 10; // SIGSEGV
  • 空指针解引用
  • 野指针
  • double free ➡️ 结果:

进程被 OS 直接终止,try-catch 根本不存在


② 栈溢出(Stack Overflow)

void recursive() {
    recursive();
}
  • 栈帧无法再分配
  • JVM 无法保证执行环境
  • catch 往往来不及执行

③ 系统级崩溃信号

信号 含义
SIGSEGV 段错误
SIGABRT 进程被强制终止
Illegal Instruction CPU 指令非法
Watchdog Kill 系统杀死进程

这些错误由 操作系统 直接处理,而不是语言运行时。


3. JVM 中的 Error(非常容易被误用)

try {
    // do something
} catch (Throwable t) {
    // 极其危险
}

在 Java 中:

类型 是否应捕获 说明
Exception 业务或环境问题
Error JVM 无法保证安全

典型 Error:

  • OutOfMemoryError
  • StackOverflowError
  • InternalError

官方建议:永远不要 catch Error


4. iOS / Objective-C 的特殊说明(重点)

在 iOS 中,大量崩溃来自:

  • EXC_BAD_ACCESS
  • 野指针
  • 访问已释放对象
NSArray *arr = @[];
arr[1]; // 直接崩溃

Apple 官方明确说明:

NSException 不应用于控制业务流程

原因是:

  • 抛出异常时,对象状态可能已经被破坏
  • catch 后继续执行 = 不确定行为

四、为什么 try-catch 捕获不了这些错误?

一句话答案

try-catch 只能处理“语言级控制流异常”, 而处理不了“运行环境级崩溃”。

对比理解

维度 Exception Crash / Error
控制者 语言运行时 OS / CPU
是否安全
是否应恢复 可以 不应该

五、一个极其常见的工程误区

catch (Exception e) {
    // 什么都不做
}

这是反模式

  • 隐藏真实 bug
  • 程序处于半失败状态
  • 资源未释放
  • 后续逻辑更危险

“吞异常”比“崩溃”更可怕


六、正确的工程实践原则

1️⃣ try-catch 的边界原则

只在你“能处理”的地方 catch

能补救 → catch
不能补救 → 抛出 / 终止

2️⃣ 永远不要做的事情

  • catch Throwable
  • catch Error
  • 用异常做业务分支
  • 捕获后忽略

3️⃣ 面对不可恢复错误的正确姿势

手段 目的
Crash 日志 定位问题
崩溃上报 评估影响
防御式编程 降低发生概率
单元测试 提前发现

七、终极总结

可以 try-catch 的,是“程序还能继续跑的异常”;
不可以 try-catch 的,是“程序已经不该继续跑的错误”。

理解这一点,

  • 你会写出更健壮的代码
  • 你会少写很多“自以为安全”的 bug
  • 你会真正理解语言设计者的取舍