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

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

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

用异常代替返回码

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

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应付了事。

相关推荐
meng半颗糖43 分钟前
vue3+tpescript 点击按钮跳转新页面直接通过链接预览word
前端·vue.js·word
击败不可能1 小时前
vue做任务工具方法的实现
前端·javascript·vue.js
觉醒大王1 小时前
医学好投的普刊分享
前端·论文阅读·深度学习·学习·自然语言处理·学习方法
小二·1 小时前
Python Web 开发进阶实战:AI 编排引擎 —— 在 Flask + Vue 中构建低代码机器学习工作流平台
前端·人工智能·python
【赫兹威客】浩哥1 小时前
【赫兹威客】框架模板-前端bat脚本部署教程
前端·vue.js
sww_10261 小时前
智能问数系统(二):数据分析师Python
java·前端·python
一人の梅雨2 小时前
义乌购商品详情接口进阶实战:批发场景下的精准解析与高可用架构
java·服务器·前端
bin91532 小时前
(文后附完整代码)html+css+javascript 弓箭射击游戏项目分析
前端·javascript·css·游戏·html·前端开发
翱翔的苍鹰2 小时前
完整的“RNN + jieba 中文情感分析”项目的Gradio Web 演示的简单项目
前端·人工智能·rnn
有味道的男人3 小时前
如何使用招标网API获取项目详情?
java·服务器·前端