聲明預期定義

張永翰 | Mar 26, 2024 min read

聲明預期定義 require, assert, check, elvis

對參數或狀態,應儘早聲明預期結果 儘管這些聲明,無法讓我們不必寫文件,但他仍讓開發者享有幾個好處

  1. 對不看文件、註解的協同開發者,預期結果仍會被看見
  2. 如果不符合預期,對丟出 exception 而不是有預期之外的行為
  3. 儘早聲明預期,確保在修改任何內容前就 throw exception ,以免一些操作執行、一些未執行,導致不一致的情況
  4. 程式碼在某種程度上會自我檢查,可不那麼依賴於測試來保證邏輯正確性

儘管放在function body 前段,讓其他開發者更容易看到,但並非每個開發者都會讀 source code ,所以還是得把 require, check 寫在註解裡

  • make them more visible
  • protect your application stability
  • protect your code correctness
  • smart cast variable

require

當函式傳入參數時,通常會有在某些條件下參數才能執行的情況

針對傳入參數,呼叫 require

fun demoRequire(int:Int) {
    require(int > 0){
        "int is smaller than 0"
    }
}

throw IllegalArgumentException

check

確保某些狀態,用於 class 內狀態變數,通常擺在 require後

class DemoAssert(){
    private var isOpen = false
    fun demoCheck(){
        check(isOpen){
            "is not open for now"
        }
    }
}

throw IllegalStateException

assert

我們會知道function執行後應該要達到某些條件,但人總有可能犯錯,常見會出錯的情境是

  1. refactor
  2. other developer change code or implement
  3. we made a mistake
class DemoTest{
    
    @Test
    fun `run. a sample test`(){
        ...
        assertEquals(10, runResult)
    }
}

我們也可以透過 jvm 設置加入 -ea ,將一般 function 寫成

fun pop(num:Int):List<T>{
    ...
    assert(ret.size == num)
    return ret
}

這個寫法不會在 production 時執行,僅會在 run test 的時候執行,因為我們通常不希望用戶遇到錯誤,而如果情境是需要用戶知道出錯的時候,應該要用 check

將 assert 至於一般函式,而不限於測試函式的優點

  1. self check -> 有效率的測試
  2. 預期的結果會在真實的使用案例下測試,而非固定的 test case
  3. 可在真實執行的情境下,測試某個部分
  4. 程式會儘早出錯,離出錯的地方更近,更容易修正

儘管有以上優點,單元測試仍不可被取代

elvis operator

null check with throw or return

fun getString(str:String?){
    val input = str ?: return
}
fun getString(str:String?){
    val input = str ?: throw("invalidate input")
}
fun getString(str:String?){
    val input = str ?: run {
        log("invalidate input")
        return 
    }
}