《代码整洁之道》第 7 章 错误处理
第 7 章 错误处理
7.1 使用异常而非返回码
在很久以前,许多语言都不支持异常。这些语言处理和汇报错误的手段都有限。你要么设
置一个错误标识,要么返回给调用者检查的错误码。代码清单7-1中的代码展示了这些手段。
// 代码清单7-1 DeviceContollerjava public class DeviceController { ... public void sendShutDown{) { DeviceHandle handle = getHandle(DEV1); // Check the state of the device if (handle != DeviceHandle.INVALID) { // Save the device status to the record field retrieveDeviceRecord(handle); // If not suspended, shut down if (record.getStatus() != DEVICE_SUSPENDED) { pauseDevice(handle) ; clearDeviceWorkQteue(handle) ; closeDevice(handle) ; } else { logger.log ("Device suspended. Unable to shut down") ; } } else { logger.log ("Invalid handle for: " + DEV1.toString()); } } ... }
这类手段的问题在于,它们搞乱了调用者代码。调用者必须在调用之后即刻检查错误。不幸的是,这个步骤很容易被遗忘。所以,遇到错误时,最好抛出一个异常。调用代码很整洁,其逻辑不会被错误处理搞乱。
代码清单 7-2 展示了在方法中遇到错误时抛出异常的情形。
// 代码清单7-2 DeviceController.java (采用异常处理) public class DeviceController { ... public void sendShutDown() { try { tryToShutDown(); } catch (DeviceShutDownError e) { logger.log(e); } } private void tryToShutDown() throws Devi ceShut DownError { DeviceHandle handle = getHandle(DEV1); DeviceRecord record = retrieveDeviceRecord(handle); pauseDevice(handle); clearDeviceWorkQueue(handle); closeDevice(handle); } private DeviceHandle getHandle (DeviceID id) { ... throw new DeviceShutDownError ("Invalid handle for: " + id. toString()) ; ... } ... }
注意这段代码整洁了很多。这不仅关乎美观。这段代码更好,因为之前纠结的两个元素设备关闭算法和错误处理现在被隔离了。你可以查看其中任一元素,分别理解它。
7.2 先写 Try-Catch-Finally 语句
异常的妙处之- -是,它们在程序中定义了一个范围。执行 try-catch-finally 语句中 try 部分的代码时,你是在表明可随时取消执行,并在 catch 语句中接续。
在某种意义上,try 代码块就像是事务。catch 代码块将程序维持在一种持续状态, 无论 try 代码块中发生了什么均如此。所以,在编写可能抛出异常的代码时,最好先写出 try-catch-finally 语句。这能帮你定义代码的用户应该期待什么,无论 try 代码块中执行的代码出什么错一样。
7.4 给出异常发生的环境说明
你抛出的每个异常,都应当提供足够的环境说明,以便判断错误的来源和处所。在 Java 中,你可以从任何异常里得到堆栈踪迹(stack trace);然而,堆栈踪迹却无法告诉你该失败操作的初衷。
应创建信息充分的错误消息,并和异常一起传递出去。在消息中,包括失败的操作和失败类型。如果你的应用程序有日志系统,传递足够的信息给 catch 块,并记录下来。
7.5 依调用者需要定义异常类
对错误分类有很多方式。可以依其来源分类:是来自组件还是其他地方?或依其类型分
类:是设备错误、网络错误还是编程错误?不过,当我们在应用程序中定义异常类时,最重
要的考虑应该是它们如何被捕获。
来看一个不太好的异常分类例子。下面的try-catch-finally语句是对某个第三方代码库的
调用。它覆盖了该调用可能抛出的所有异常:
ACMEPort port = new ACMEPort(12) ; try { port.open() ; } catch (DeviceResponseException e) { reportPortError(e) ; logger.log("Device response exception", e); } catch (ATM1212UnlockedException e) { reportPortError(e) ; logger.log("Unlock exception", e); } catch (GMXError e) { reportPortError(e); logger.log("Device response exception") ; } finally { ... }
语句包含了一大堆重复代码,这并不出奇。在大多数异常处理中,不管真实原因如何,我们总是做相对标准的处理。我们得记录错误,确保能继续工作。
7.7 别返回 null 值
这种代码看似不坏,其实糟透了!返回 null 值,基本上是在给自己增加工作量,也是在给调用者添乱。只要有一处没检查 null 值,应用程序就会失控。
对于从应用程序深处抛出的 NullPointerException 异常,你到底该作何反应呢?
7.8 别传递 null 值
在大多数编程语言中,没有良好的方法能对付由调用者意外传入的 null 值。事已如此, 恰当的做法就是禁止传入 null 值。这样,你在编码的时候,就会时时记住参数列表中的 null 值意味着出问题了,从而大量避免这种无心之失。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
2021-08-02 *a++ 和 *++a