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

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

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

用异常代替返回码

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

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

相关推荐
青衫码上行29 分钟前
【Java Web学习 | 第1篇】前端 - HTML
java·前端·学习
元直数字电路验证29 分钟前
HTML 标签及推荐嵌套结构
前端·javascript·html
charlie11451419130 分钟前
HTML 理论笔记
开发语言·前端·笔记·学习·html·1024程序员节
知识分享小能手1 小时前
uni-app 入门学习教程,从入门到精通,uni-app 企业项目实战:鲁嗑瓜子项目开发知识点(9)
前端·javascript·学习·微信小程序·小程序·uni-app·vue
阿金要当大魔王~~1 小时前
uniapp img 动态渲染 的几种用法
java·服务器·前端·1024程序员节
one.dream1 小时前
用webpack 插件实现 img 图片的懒加载
前端·webpack·node.js
打小就很皮...1 小时前
Steps + Input.TextArea + InfiniteScroll 联调优化
前端·react.js
皓月Code1 小时前
第四章、路由配置
前端·javascript·react.js·1024程序员节
Mr.Jessy2 小时前
JavaScript学习第六天:函数
开发语言·前端·javascript·学习·html·1024程序员节
code_YuJun3 小时前
管理系统——应用初始化 Loading 动画
前端