# 异常处理 + 错误码

> 来源: Java开发手册（嵩山版）— 二(一) 错误码 + 二(二) 异常处理

## 错误码规约

### 【强制】

1. **快速溯源、沟通标准化**: 错误码回答谁的错、错在哪
2. **不体现版本号和错误等级**: 以不断追加方式兼容
3. **全部正常返回 00000**
4. **5 位字符串**: 错误来源(A/B/C) + 四位数字编号
   - A = 用户端错误
   - B = 当前系统错误
   - C = 第三方服务错误
5. **编号不与业务/组织架构挂钩**: 先到先得
6. **避免随意定义新错误码**: 使用已有相近错误码
7. **错误码不直接输出给用户**: 用户提示信息与错误码分开

### 【推荐】

8. 业务独特信息由 `error_message` 承载
9. 第三方错误码向上抛出时允许转义（C → B）

### 【参考】

10. 三级宏观错误码: 一级(A0001/B0001/C0001) → 二级 → 三级
11. 后三位与 HTTP 状态码无关
12. 纯数字不利于跨文化协作
13. 数字编排不利于感性记忆

---

## 异常处理

### 【强制】规则

### 1. 预检规避 RuntimeException

Java 类库中可通过预检查规避的 RuntimeException 不应通过 `catch` 处理。

> **正例**: `if (obj != null) {...}`  
> **反例**: `try { obj.method(); } catch (NullPointerException e) {...}`

### 2. 异常不做流程控制

异常设计的初衷是解决意外情况，处理效率比条件判断低很多。

### 3. 区分异常类型

catch 时分清稳定代码和非稳定代码，对非稳定代码尽可能区分异常类型。

### 4. 异常必须处理

捕获了却什么都不处理而抛弃之是不允许的。不想处理则上抛。最外层必须转化为用户可理解的内容。

### 5. 事务 catch 后手动回滚

> **说明**: 抛出异常被 catch 后，如需回滚，注意手动回滚事务。

### 6. finally 关闭资源

finally 块必须对资源对象、流对象进行关闭，有异常也要做 try-catch。JDK7+ 可用 try-with-resources。

### 7. finally 中不 return

> **反例**:
> ```java
> try { return ++x; }
> finally { return ++x; } // 直接返回 2，丢弃 try 的返回值
> ```

### 8. 捕获与抛出异常匹配

捕获异常与抛异常必须完全匹配，或者捕获的是抛异常的父类。

### 9. RPC/二方包用 Throwable 拦截

在调用 RPC、二方包、或动态生成类的方法时，捕捉异常必须使用 `Throwable` 类。

> **说明**: 类冲突时可能抛 `NoSuchMethodError`（不是 Exception 的子类）。

### 【推荐】规则

### 10. 返回值 null 需注明

方法返回 null 时，必须添加注释说明什么情况下返回 null。防止 NPE 是调用者的责任。

### 11. 防止 NPE 常见场景

1. 返回基本数据类型时自动拆箱
2. 数据库查询结果可能为 null
3. 集合元素即使 isNotEmpty 取出也可能为 null
4. 远程调用返回对象需判空
5. Session 数据建议 NPE 检查
6. 级联调用 `obj.getA().getB().getC()` 易 NPE

> **正例**: 使用 JDK8 的 `Optional` 类

### 12. 用业务自定义异常

避免直接 `new RuntimeException()`，更不允许抛 `Exception` 或 `Throwable`。推荐 `DAOException` / `ServiceException` 等。

### 【参考】

### 13. 不同场景的异常处理方式

| 场景 | 方式 |
|------|------|
| 公司外 http/api 开放接口 | 使用 `errorCode` |
| 应用内部 | 推荐异常抛出 |
| 跨应用 RPC 调用 | Result 封装（isSuccess/errorCode/errorMessage） |
| 应用内部 | 直接抛出异常 |
