九、把异常当回事,代码才靠谱

把异常当回事,代码才靠谱

《代码整洁之道》第七章专门讲错误处理,核心观点很明确:错误处理不是边角料,而是代码健壮性的核心,必须认真设计。糟糕的错误处理会让代码混乱不堪,而优雅的处理方式能让主逻辑清晰可读。

用异常代替返回码

以前写代码总爱用返回码表示错误,比如:

java 复制代码
// 反例:返回码让调用者被迫处理错误
public int deletePage(String pageId) {
    if (pageId == null) return -1;
    if (!pageExists(pageId)) return -2;
    // 执行删除逻辑
    return 0;
}

// 调用者必须嵌套判断
int code = deletePage("123");
if (code == 0) {
    // 处理成功
} else if (code == -1) {
    log.error("pageId为空");
} else if (code == -2) {
    log.error("页面不存在");
}

这种方式会导致调用者代码充满if-else,主逻辑被错误处理淹没。改用异常后清爽很多:

java 复制代码
// 正例:异常分离错误处理和主逻辑
public void deletePage(String pageId) throws InvalidPageIdException, PageNotFoundException {
    if (pageId == null) throw new InvalidPageIdException("pageId不能为空");
    if (!pageExists(pageId)) throw new PageNotFoundException("页面不存在: " + pageId);
    // 执行删除逻辑
}

// 调用者集中处理异常
try {
    deletePage("123");
    // 处理成功逻辑
} catch (InvalidPageIdException e) {
    log.error(e.getMessage());
} catch (PageNotFoundException e) {
    log.error(e.getMessage());
}

异常的优势:错误处理代码从主逻辑中抽离,调用者可以选择立即处理或向上传递,灵活性更高。

先写Try-Catch-Finally,再填逻辑

作者建议"先写try-catch-finally",这是个反直觉但有效的技巧。比如要实现一个读取文件并解析的功能:

java 复制代码
// 先搭好异常处理框架
public String parseFile(String path) {
    FileReader reader = null;
    try {
        // 后续填读取逻辑
    } catch (FileNotFoundException e) {
        log.error("文件不存在: " + path, e);
        throw new ParsingException("解析失败", e);
    } catch (IOException e) {
        log.error("读取失败: " + path, e);
        throw new ParsingException("解析失败", e);
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                log.warn("关闭文件失败", e);
            }
        }
    }
}

先确定异常边界和资源释放方式,再填充核心逻辑,能避免遗漏错误处理。这本质是把"异常安全"作为前提,而不是事后补救

别返回null,别传递null

返回null是很多bug的根源,比如:

java 复制代码
// 反例:返回null让调用者防不胜防
public List<User> getUsers() {
    if (dbConnection == null) return null; // 潜在NPE风险
    // 查询数据库
}

// 调用者忘记判空就会炸
List<User> users = getUsers();
users.forEach(u -> log.info(u.getName())); // NPE!

更安全的做法是返回空集合或抛出异常:

java 复制代码
// 正例:返回空集合或抛异常
public List<User> getUsers() {
    if (dbConnection == null) return Collections.emptyList(); // 空集合更安全
    // 或者抛异常:throw new DbConnectionException("数据库连接未初始化");
    // 查询数据库
}

同理,也别在参数中传递null,可以用Objects.requireNonNull提前校验:

java 复制代码
public void createUser(User user) {
    Objects.requireNonNull(user, "user不能为null");
    Objects.requireNonNull(user.getName(), "用户名不能为null");
    // 执行创建逻辑
}

null的问题:它不携带任何信息,出了问题很难定位根源。用空集合、特殊值或异常,能传递更丰富的上下文。

异常处理的其他实践

  • 异常要包含上下文 :别只抛new Exception("错误"),要说明"什么操作失败,原因是什么",比如new PaymentFailedException("订单123支付失败,余额不足")

  • 使用 unchecked 异常:可控异常(checked exception)会导致接口僵化,一旦新增异常,所有调用者都得改。优先用非可控异常,让调用者自主决定是否处理。

  • 错误处理也是"一件事"try块里只放可能出错的核心逻辑,catch块专注处理一种错误,别在里面塞额外逻辑。

代码对比:混乱 vs 整洁

混乱的错误处理:

java 复制代码
public void processOrder(String orderId) {
    if (orderId == null) {
        System.out.println("订单ID为空");
        return;
    }
    Order order = orderDao.getById(orderId);
    if (order == null) {
        System.out.println("订单不存在");
        return;
    }
    if (!order.isPaid()) {
        System.out.println("订单未支付");
        return;
    }
    // 处理订单逻辑
}

整洁的错误处理:

java 复制代码
public void processOrder(String orderId) {
    try {
        validateOrderId(orderId);
        Order order = getValidatedOrder(orderId);
        // 处理订单逻辑
    } catch (InvalidOrderIdException | OrderNotFoundException | UnpaidOrderException e) {
        log.error("处理订单失败: " + orderId, e);
    }
}

private void validateOrderId(String orderId) {
    if (orderId == null || orderId.isEmpty()) {
        throw new InvalidOrderIdException("订单ID不能为空");
    }
}

private Order getValidatedOrder(String orderId) {
    Order order = orderDao.getById(orderId);
    if (order == null) throw new OrderNotFoundException("订单不存在: " + orderId);
    if (!order.isPaid()) throw new UnpaidOrderException("订单未支付: " + orderId);
    return order;
}

核心差异:整洁的代码把错误判断和主逻辑分离,通过异常传递错误信息,调用者能快速定位问题,主流程一目了然。

错误处理的终极目标是:让正常流程的代码像"没有错误可能"一样简洁,而错误情况的处理又足够清晰,能快速排查问题 。这需要把异常当"一等公民"来设计,而不是随便加个try-catch应付了事。

相关推荐
大家的林语冰40 分钟前
《前端周刊》尤大开源 Vite+ 全家桶,前端工业革命启动;尤大爆料 Void 云服务新产品,Vite 进军全栈开发;ECMA 源码映射规范......
前端·javascript·vue.js
jiayong231 小时前
第 8 课:开始引入组合式函数
前端·javascript·学习
田八1 小时前
聊聊AI的发展史,AI的爆发并不是偶然
前端·人工智能·程序员
zhanghongbin011 小时前
AI 采集器:Claude Code、OpenAI、LiteLLM 监控
java·前端·人工智能
IT_陈寒1 小时前
Python的列表推导式里藏了个坑,差点让我加班到凌晨
前端·人工智能·后端
吴声子夜歌1 小时前
ES6——正则的扩展详解
前端·mysql·es6
天***88522 小时前
Edge 浏览器离线绿色增强版+官方安装包,支持win7等系统
前端·edge
漫游的渔夫2 小时前
别再直接 `json.loads` 了!AI 返回的 JSON 坑位指南
前端·人工智能
软件工程师文艺2 小时前
从0到1:Claude Code如何用React构建CLI应用
前端·react.js·前端框架
M ? A2 小时前
Vue 迁移 React 实战:VuReact 一键自动化转换方案
前端·vue.js·经验分享·react.js·开源·自动化·vureact