主題 Effective Kotiln Item 7: Prefer null or Failure result when the lack of a result is possible
有時一個 function 不一定能產生它期望的結果,而且這個錯誤是可以預判的,通常稱作副作用,以下是一些常見的例子:
處理這種情況有兩種主要機制:
這兩者之間存在一個重要的區別。異常不應該被用作標準的傳遞信息方式。所有的異常都表示特別的、特殊的情況。應該在預期不到的狀況才使用。 Effective Java 也說到 Use Exceptions for Only Exceptional Circumstances。
null 或 Failure 都非常適合於表示預期的錯誤,他代表著函式除了 Happy Path 外,也有某種錯誤的可能。這樣對開發者更明確、高效,且可以用預期的方式處理。這就是為什麼規則是:當我們預期出錯時,我們應該優先回傳 null 或 Failure,這表明了有發生異常的可能,讓開發者會處理他。以下範例
inline fun <reified T> String.readObjectOrNull(): T? {
//...
if (incorrectSign) {
return null
}
//...
return result
}
inline fun <reified T> String.readObject(): Result<T> {
//...
if (incorrectSign) {
return Result.failure(JsonParsingException())
}
//...
return Result.success(result)
}
class JsonParsingException : Exception()
即然有預期錯誤的可能,就應該在回傳型別表達出來。這時就需要 Result Type, 如果你有聽過 Either,那跟 Result type 的概念相同。雖然書中介紹自已設計一個 Result Type, 但我們沒有要這麼 Hardcore, 就找了個 Kotlin-Result (Github 826☆)
這裡是用正常是 OK<V>
錯誤是 Err<V>
並封在 Result 這個 Type
fun checkPrivileges(user: User, command: Command): Result<Command, CommandError> {
return if (user.rank >= command.mininimumRank) {
Ok(command)
} else {
Err(CommandError.InsufficientRank(command.name))
}
}
或是直接 Catch Db Exception 轉成 Err<Throwable>
val result: Result<Customer, Throwable> = runCatching {
customerDb.findById(id = 50) // could throw SQLException or similar
}
如果一來回傳值就代表了我們預期的所有可能性,開發者雖然不喜歡,但也會被強迫的要去處理這樣的副作用。
編按: Kotlin 在 Effective Kotlin 推出時還沒有 Result type,不過即使時至今日,Kotlin 內建的 Result type 也不太常被使用。不太好使 (因為他不太像一個 Monad)