正常优先
以 Go 语言的函数ReadFile
为例。定义如下:
func ReadFile(filename string) ([]byte, error)
这个函数会将error
返回。事实上,大部分 Go 语言 API 都有将error
返回的特征。(推荐的)调用方式如下:
//...
data, err = ReadFile("example.dat");
if (! err) {
//...
}
//...
一些编程语言的 API,会提醒使用者“异常可能会出现”,暗示使用者“马上去做异常处理”。然而异常处理的设计过程会中断正常逻辑思路。如果异常处理链过长,回归到正常逻辑的时候可能一脸懵逼。
因此,异常处理的首要原则是“正常优先”。也就是说,如果代码写到这里才发现“可能需要设计异常处理”(即异常处理不在原有的设计预期之中),那就无视之(最多留个注释),优先完成正常逻辑。
这里的“异常设计”是广义的,并不限定在try {...} catch (exception) {...}
的形式上。准确来说,“异常设计”它还包含了“容错设计”。以下列 JavaSript 代码为例:
// add.js
export default function add (a, b) {
return a + b;
}
这个函数的作用是计算两个数的和。请注意:JavaScript 不会验证参数和返回值的类型。所以,如果这个函数是个 API,调用者可能会传入类型不正确的参数,得到不正确的结果。例如:
// index.js
import add from './add.js';
console.log(add('3' + 4)); // 34
具备“异常设计”的代码是:
// add.js
export default function add (a, b) {
if (typeof(a) !== 'number' || typeof(b) !== 'number') {
throw 'Error: parameters must be number.'
}
return a + b;
}
例子中的if (condition) {...}
严格来说属于“容错设计”,本文中将其归类为“异常设计”(调用参数异常),也应当遵守上文提及的“正常优先”原则。
自用忽略
异常设计的第2个原则是“自用忽略”。也就是说,如果开发者设计 API 只会被自己调用,则不要设计异常处理。
为自用 API 设计异常处理有两大弊端:
- 浪费时间。开发者调用自己设计的 API,通常思路明确,不会产生错误。即使设计了异常处理,也几乎不可能被执行。设计过程本身就是在浪费时间。
- 可能会屏蔽 IDE 的异常提示,增加调试难度。现在的 IDE 都具有强大的错误追踪功能,有的甚至能精确定位到源文件的行。设计不当的异常处理,会影响 IDE 的异常捕获,反而降低了开发效率。
如果自用 API 将要被发布,可能会被其他开发者使用,这时就需要考虑异常处理的问题了。
量力而行
异常设计的第3个原则是“量力而行”。也就是说,对因信息缺失或环境限制而无法处理的异常,或抛出(throw)或终止(terminate)或无视(ignore)。
例如:
- 抛出:上文的
add
。 - 终止:程序执行时,首先从指定位置读取配置文件,则读取失败(文件不存在、IO设备异常等),则直接终止程序。
- 无视:这个在Web前端中十分常见,若 fetch API 失败(本地网络切换、服务器访问量过大等),则可以无视之。因为用户可以自行刷新网页解决这类问题。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。