异常与类型安全结果(Prefer null or Failure result when the lack of result is possible)
你可能觉得,这一条的中英文对不上啊,的确,这个条目以下的内容并非来自于Effective Kotlin这本书,而是来自我看到的一篇文章,我觉得这篇文章与这一条很契合,并且写得更好。所以就引用这篇文章来说明这一点。
这一条的主要含义在于辨析异常与返回错误结果之间的关系。所谓类型安全的结果,会在后文中有解释。
The first and foremost use of exceptions in Kotlin is to handle program logic errors.
异常指向的是程序中的逻辑错误。
异常应该被用来检测程序中的先决条件和约束条件(preconditions and invariants),这些是编译器无法发现的错误。而require
和check
存在的最大意义就在于此。
As a rule of thumb, you should not be catching exceptions in general Kotlin code.
在Kotlin代码中,不应该捕获异常。
一般而言,在Kotlin中捕获异常被认为是一种“脏代码”。异常应该在顶层的架构被处理,主要目的就是提示bug。异常指向的是程序中的逻辑错误,其实就是程序的bug,这是异常在Kotlin中的首要目的。
然而,很多时候区分程序的逻辑错误和程序有必要处理的“边界”条件是困难的。很多时候这都取决于具体情况。
以标准库中的String.toInt()
为例,输入非数字格式的字符串是程序的逻辑错误还是边界条件呢?严格来讲,所有非数字格式的字符串作为输入都应该视为逻辑错误并抛出NumberFormatException
,如果调用者把这种情况视为一种边界条件,那么他应该捕获NumberFormatException
异常并返回自己想要的默认值,这就是典型的Java的做法。如上所述,这非常不Kotlin,地道的Kotlin style应该是使用String.toIntOrNull()
:
val number = string.toIntOrNull() ?: defaultValue
String.toIntOrNull()
使用null
作为逻辑异常的结果,然后结合Kotlin null-safety,这就very Kotlin-style。
Kotlin的标准库都遵循这种设计规范,甚至Array和List的get操作符都有相应的getOrNull
版本,用null
来表示IndexOutOfBoundsException
逻辑异常的结果。
Use exceptions for logic errors, type-safe results for everything else. Don't use exceptions as a work-around to sneak a result value out of a function.
用异常来表示逻辑错误,用type-safe(主要就是null和sealed class,见下文)的结果来表示其它。不要使用异常来作为函数除正常返回值之后的的另外一种返回结果(异常不是“逃逸”出函数的方式,它只是用来表示逻辑错误)。
经常的,函数会有成功和失败两种结果,此时我们可以借鉴标准库的做法,一个函数抛出异常来指示逻辑错误,另外一个“配对”函数使用null
作为逻辑错误的结果(类似String.toIntOrNull()
和String.toInt()
)。如果函数有多种失败的情形(多种逻辑错误),那么可以考虑使用sealed class作为配对函数的返回值。
sealed class ParsedDate {
data class Success(val date: Date) : ParsedDate()
data class Failure(val errorOffset: Int) : ParsedDate()
}
fun DateFormat.tryParse(text: String): ParsedDate =
try {
ParsedDate.Success(parse(text))
} catch (e: ParseException) {
ParsedDate.Failure(e.errorOffset)
}
结合Kotlin的when
表达式,也可以实现类型安全的Kotlin-style的代码。
最后解释一下什么叫类型安全的结果,其实就是返回null或者sealed class,对于null值,Kotlin有空安全作为保障,我们可以继续使用null值或者通过?:操作符使用默认值、return或者throw;而对于sealed class我们也说它是类型安全的,因为通过when表达式,sealed class的每种值都不会被遗漏,所有结果都会得到相应的处理。
Don't use exceptions if you need local handling of certain failure scenarios in your code, don't use exceptions to return a result value, avoid try/catch in general application code, implement centralized exception-handling logic, handle input/output errors uniformly at an appropriate boundary of your code.
这一小节的主要内容来自于Kotlin and Exceptions。作者是目前的Kotlin Leader,写得简明且深刻,对Kotlin中异常的定位做了非常明晰的说明。