[Golang] 程式設計教學:錯誤處理 (Error Handling)

【分享本文】
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

    前言

    在實際情境中運行的程式,即使程式本身沒有臭蟲 (bug),仍然要面對許多可能的錯誤 (error)。例如,想要將某個字串轉成數字,但字串本身不是合法的數字;想要讀取某個外部檔案,卻權限不足;想要解析某個 XML 檔案,但該 XML 檔案內有錯誤的格式。我們不能天真地認定程式不會發生錯誤,而要詳細考慮可能發生的錯誤,撰寫相關的程式碼。

    處理錯誤的策略

    Go 程式對錯誤的處理有兩大類方式:

    一般錯誤:拋出錯誤物件,交由後續的程式自行處理 嚴重錯誤:引發 panic 事件,將程式提早結束

    要採用那一種方式沒有制式的規定,完全依賴套件設計者的權衡考量。例如,在 Go 程式中,除以零是一種嚴重錯誤,如下例:

    package main
     
    import (
        "fmt"
    )
     
    func main() {
        fmt.Println(3 / 0)
    }
    

    然而,開啟檔案時引發錯誤,不會直接讓程式當掉,而會由使用者決定下一步要如何處理:

    package main
     
    import (
        "log"
        "os"
    )
     
    func main() {
        file, err := os.Open("file.txt")
        if err != nil {
            log.Fatal(err)
        }
     
        // Do something on file object.
    }
    

    由於要從嚴重錯誤中回復較困難,對大部分的情境來說,使用一般錯誤是較佳的的選擇。

    一般錯誤

    在 Go 裡面,沒有 throw ,也沒有 trycatchfinally 這種針對錯誤處理的特殊區塊,錯誤物件也是一個值,不會將程式強迫中止,而由使用者決定下一步要怎麼做。如下例:

    package main
     
    import (
        "errors"
        "fmt"
    )
     
    func main() {
        err := errors.New("Some error")
        if err != nil {
            fmt.Println(err)
        }
     
        fmt.Println("More message")
    }
    

    在這個例子中,我們將錯誤值印出,但程式沒有提早結束。

    在 Go 程式中,錯誤物件通常會和其他值一併回傳,由套件使用者自行檢查及決定下一個步驟。如下例:

    package main
     
    import (
        "fmt"
        "log"
        "strconv"
    )
     
    func main() {
        n, err := strconv.Atoi("hello")
        if err != nil {
            log.Fatal(err)
        }
     
        fmt.Println(n)
    }
    

    如果我們很確定程式不會有錯誤,我們也可以忽略錯誤物件。如下例:

    package main
     
    import (
        "fmt"
        "strconv"
    )
     
    func main() {
        n, _ := strconv.Atoi("123")
     
        fmt.Println(n)
    }
    

    另外一種錯誤處理的變化形態是回傳布林值,例如,我們在檢查 map 的鍵/值對時。見下例:

    package main
     
    import (
        "log"
    )
     
    func main() {
        m := map[int]string{
            1: "one",
            2: "two",
            3: "three",
        }
     
        v, ok := m[1]
        if !ok {
            log.Fatal("Unable to retrieve key/value pair")
        }
     
        if !(v == "one") {
            log.Fatal("Wrong value")
        }
    }
    

    錯誤介面

    如果想實作自己的錯誤物件,Go 提供一個公開介面,只要符合該介面的要求即可。

    type error interface {
        Error() string
    }
    

    嚴重錯誤

    如果想引發嚴重錯誤,使用 panic 函式即可。見下例:

    package main
     
    import (
        "fmt"
    )
     
    func main() {
        panic("Some error")
     
        // It didn't occur.
        fmt.Println("More message")
    }
    

    在這個例子中,程式遇到 panic 所在的地方即中止程式,不會執行後續的指令。

    從嚴重錯誤中回復

    如果想要從嚴重錯誤中回復,要用 recover 函式,並且要搭配特定的語法。見下例:

    package main
     
    import (
        "fmt"
    )
     
    func main() {
        defer func() {
            err := recover()
            if err != nil {
                fmt.Println("Recover from panic")
            }
        }()
     
        panic("Some error")
     
        // It didn't occur.
        fmt.Println("More message")
    }
    

    濫用 recover 會使得程式可讀性較差,不會將其常態性使用。

    【分享本文】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email
    【追蹤新文章】
    Facebook Twitter Plurk