It is my duty to protect my family
Mulan, 2020
台灣人透過文化累積,其實都有防禦性程式設計的概念----防禦駕駛,我們總是得預想每個用路人都會違規,以此保護自己
判斷條件 1,2,3 分別印出不同字串
when(number) {
1 -> {"one"}
2 -> {"two"}
else -> {"three"}
}
我把範例的條件判斷最簡化了,應該很明顯,這種判斷遲早出錯的,根本就只判斷了 1,2 ,如標題所寫,when ... else 判斷是給給預期之外的情境的
為什麼選擇標準函式庫的錯誤比較好呢?
難道是工程師懶惰打 code,台灣也要開始安靜辭職了嗎??不,人若不努力,跟鹹魚有什麼區別,但作為工程師,請聰明的工作,減少大家的工作量
回來主題,工程師們對於標準的錯誤有著相對一致的認知,也就是說和其他工程師合作時,丟出正確的標準錯誤可以讓協作者快速了解情況,而不是每次都得在專案全域搜尋客制的錯誤名稱
當你實現的機制,已經不符合現有錯誤狀態時,請為此聲明客制錯誤,並對客制錯誤下註解,說明哪些情境會觸發,會有哪些影響,舉個例子吧
Kotlin Coroutine 裡面透過 CancellationException 來取消 coroutineScope
@SinceKotlin("1.4")
public actual typealias CancellationException = java.util.concurrent.CancellationException
@InlineOnly
@SinceKotlin("1.4")
public actual inline fun CancellationException(message: String?, cause: Throwable?): CancellationException {
return CancellationException(message).also { it.initCause(cause) }
}
@InlineOnly
@SinceKotlin("1.4")
public actual inline fun CancellationException(cause: Throwable?): CancellationException {
return CancellationException(cause?.toString()).also { it.initCause(cause) }
}
這是一個很好的設計,但這個客制錯誤不僅需要套件使用者了解這個錯誤,還需了解 套件的錯誤傳遞機制
檢查錯誤本身就是一件任務了,什麼意思呢?
如果一個函式裡面已經有 try/ catch/ finally,就不該再有其他任務執行
fun someTask(){
try {
} catch(e:Exception){
Log.e(TAG, e)
throw e
}
}
而如果你的 catch 不對錯誤做處理,就應該把整個 try/ catch刪掉
但如果想用 try/ finally 來關閉資源呢?
可以用 kotlin.io 的 use 或 useline 方法
在 Kotlin 的設計中,我們需要和其他平台語言做互動,而這有可能產生出潛在的風險,比如 JAVA 吧,他廣為人知的就是 NullPointerException,儘管他有對應標示的 @NotNull 註釋,但如果外部函式庫沒有標示,Kotlin 編譯器依然會認定為 NotNull,儘管 null 在 Java 裡傳遞時沒事,但到了 Kotlin 的環境裡就處噴出 NullPointerException
另一個是 Kotlin/Js 的跨平台設置,個人的開發體驗來說還不如直接寫 Js
沒啦讚啦,但還是會有跨平台的小問題
比如 kotlin 可以定義 js 的函式
fun jsTypeOf(o: Any): String {
return js("typeof o")
}
fun jsInfinite(): Int {
return js("Number.POSITIVE_INFINITY")
}
fun jsIntOverFlow(): Int {
return js("2147483647999")
}
儘管在讀的時候還行,但操作的時候就QQ
console.log(jsTypeOf(jsInfinite()))//number
console.log(jsInfinite())//Infinity
console.log(jsInfinite() - 100)//0
console.log(jsTypeOf(jsIntOverFlow()))//number
console.log(jsIntOverFlow())//2147483647999
console.log(jsIntOverFlow() - 100)//-101
真實的 js 結果會是
Number.Positive_Infinity - 100 // Infinity
2147483647999 - 100 //2147483647899
根本就 GG,所以寫跨平台操作時,要特別注意值的轉換
斷言是另一個對錯誤情境的處理工具,和 try/ catch 不同的在於
斷言,是要檢查預期內的情境
錯誤,是要處理預期外的情境
那 require, assert, check 三者差在哪裡呢?
從 assert 開始了解
主要用在測試的時候
@Test
fun `fib works correctly for the first 4 positions`() {
assertEquals(1, fib(0))
assertEquals(1, fib(1))
assertEquals(2, fib(2))
assertEquals(3, fib(3))
}
用於比較期望結果和實際結果是否一致
判斷參數 argument 的,在這邊多帶一個很重要的概念,物件是可以有狀態,他可以保有變數去記錄程式運行時的情境,而函示則無狀態,依照邏輯去完成某個任務
而這裡的 require 最常見的就是用於檢查,函式的傳入參數是否有效
fun isAgeGreaterThanZero(age:Int){
require(age > 0){
"Age shouldn't be a negetive number"
}
}
那當我們輸入了-1時,就會出現
Exception in thread "main" java.lang.IllegalArgumentException: Age shouldn't be a negetive number
at MainKt.isAgeGreaterThanZero(Main.kt:7)
at MainKt.main(Main.kt:3)
at MainKt.main(Main.kt)
而 check. 是被設計來檢查狀態的,一樣拿個例子
class UserConnectState(){
var token:String? = null
private set
fun keepAlive(){
checkNotNull(token){
"Token is null, please login before connect to socket"
}
socket.startPingpong(token)
}
}
Most Taiwanese have the concept of defensive programming ---- Defensive driving, we always predict every possibility that other driver will violate a traffic law
else
is for elsewhen 1,2,3 print different string
when(number) {
1 -> {"one"}
2 -> {"two"}
else -> {"three"}
}
I simplified the check condition, it should be obvious, the check consition will cause issue soon or later, the else branch should throw unExcept error instaed
Why choose standard error is better than custom one?
Is the engineer too lazy, start to quite-quit? Actually not, since all engineer has similar concept to standard error, by throw standard will be easier for other cooperator
If you implement a condition, has error state not fit in any exists Error, please make a custom error, and add comment for it, specify what situation will trigger, what will happen next
In Kotlin Coroutine using CancellationException
to cancel `coroutineScope ``
@SinceKotlin("1.4")
public actual typealias CancellationException = java.util.concurrent.CancellationException
@InlineOnly
@SinceKotlin("1.4")
public actual inline fun CancellationException(message: String?, cause: Throwable?): CancellationException {
return CancellationException(message).also { it.initCause(cause) }
}
@InlineOnly
@SinceKotlin("1.4")
public actual inline fun CancellationException(cause: Throwable?): CancellationException {
return CancellationException(cause?.toString()).also { it.initCause(cause) }
}
It is actually a great design, but it also need library user to know about the error and the error propagerta in the library
What does it mean that handling errors is a task?
If your function already have try/ catch/ finally, it should contain more logic
fun someTask(){
try {
} catch(e:Exception){
Log.e(TAG, e)
throw e
}
}
And if you don't handle error in catch
, consider delete the try/ catch
block
What if you need try/ finally
to close a resource?
using use
or useline
from kotlin.io
In the design of Kotlin, we need to interact with other platform language, and there is potential risk, take Java as example, it is famous about NullPointerException
, although java has @NotNull
, but if the author doesn't mark it, the compiler will assume it is NotNull
, which will cause error in kotlin part
The other one is Kotlin/ js
, personally I recommend to write js directory
There are some issue between those
we can define js function in kotlin
fun jsTypeOf(o: Any): String {
return js("typeof o")
}
fun jsInfinite(): Int {
return js("Number.POSITIVE_INFINITY")
}
fun jsIntOverFlow(): Int {
return js("2147483647999")
}
Although reading seems normal, but if we try to modify it...
console.log(jsTypeOf(jsInfinite()))//number
console.log(jsInfinite())//Infinity
console.log(jsInfinite() - 100)//0
console.log(jsTypeOf(jsIntOverFlow()))//number
console.log(jsIntOverFlow())//2147483647999
console.log(jsIntOverFlow() - 100)//-101
the result of javascript is
Number.Positive_Infinity - 100 // Infinity
2147483647999 - 100 //2147483647899
It is not safe to trust value from other platform without check
Assertion is the other tool for exception handling, the difference between try/ catch
assertion, check for expected situation
Error, handle situation not expected
Then what is the difference between require, assert, check?
Mainly use for test
@Test
fun `fib works correctly for the first 4 positions`() {
assertEquals(1, fib(0))
assertEquals(1, fib(1))
assertEquals(2, fib(2))
assertEquals(3, fib(3))
}
to check is the expected and actual value is same
check the argument, one idea to bring up, object could be stateful, it can keep the state at run time, and function does not
The required here is to check the input argument is valid or not
fun isAgeGreaterThanZero(age:Int){
require(age > 0){
"Age shouldn't be a negative number"
}
}
If we pass in -1
Exception in thread "main" java.lang.IllegalArgumentException: Age shouldn't be a negative number
at MainKt.isAgeGreaterThanZero(Main.kt:7)
at MainKt.main(Main.kt:3)
at MainKt.main(Main.kt)
And check is build for check state
class UserConnectState(){
var token:String? = null
private set
fun keepAlive(){
checkNotNull(token){
"Token is null, please login before connect to socket"
}
socket.startPingpong(token)
}
}