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

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

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

用异常代替返回码

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

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

相关推荐
charlee4434 分钟前
行业思考:不是前端不行,是只会前端不行
前端·ai
Amodoro2 小时前
nuxt更改页面渲染的html,去除自定义属性、
前端·html·nuxt3·nuxt2·nuxtjs
Wcowin2 小时前
Mkdocs相关插件推荐(原创+合作)
前端·mkdocs
伍哥的传说2 小时前
CSS+JavaScript 禁用浏览器复制功能的几种方法
前端·javascript·css·vue.js·vue·css3·禁用浏览器复制
lichenyang4532 小时前
Axios封装以及添加拦截器
前端·javascript·react.js·typescript
Trust yourself2433 小时前
想把一个easyui的表格<th>改成下拉怎么做
前端·深度学习·easyui
三口吃掉你3 小时前
Web服务器(Tomcat、项目部署)
服务器·前端·tomcat
Trust yourself2433 小时前
在easyui中如何设置自带的弹窗,有输入框
前端·javascript·easyui
烛阴3 小时前
Tile Pattern
前端·webgl
前端工作日常3 小时前
前端基建的幸存者偏差
前端·vue.js·前端框架