Go言語でエラーを書くときに気をつけること

Go言語では例外という概念はなく、ビルトインでerror型というのが用意されています。

type error interface {
    Error() string
}

処理に失敗した場合等は例外を発生させることはできなく、errorを返すという形をとります。
またそのメソッドや関数を実行した側は、戻り値にerrorが含まれている場合はその値がnilかどうかを確認することで処理が成功したのか失敗したのかが分かります。


JavaC#等では基底クラスのException等を継承して独自の例外を定義して使用することができます。
もちろんGo言語でも以下のようにして簡単に独自のエラーを返すことができます。

errors.New("独自のエラーです。")

ダック・タイピングを採用しているGo言語ではError()メソッドを実装しているだけでerror型として判定されます。したがって以下のように書き方もできます。

type MyError2 string

func (m MyError2) Error() string {
    return string(m)
}
type MyError3 struct {
    Code    string
    Message string
    // ....
}

func (m MyError3) Error() string {
    return fmt.Sprintf("%s : %s", m.Code, m.Message)
}


一般的によくあると思いますが、例えば「ある関数を実行した際errorが返ってきます。このエラーの種類によって処理を分けたい。」というような場合があるとします。
errors.Newが簡単にかけるのでそのままで書いてしまいがちですが、これは他の言語でいうthrow new Exception()したのと同じになります。
「とりあえずエラーは返したよ」という状態です。これではエラーの種類によって処理を分けることは難しいです。(エラーメッセージを確認すればできそうですが、、、)
そこでサンプルで確認してみました。

package main

import (
    "errors"
    "fmt"
)

var (
    MyError = errors.New("MyError1です。")
)

func main() {
    e0 := doAnonymousError()
    e1 := doMyError()

    if e0 == errors.New("AnonymousError") {
        fmt.Println("e0 == AnonymousError")
    } else if e0.Error() == "AnonymousError" {
        fmt.Println("e0.Error() == \"AnonymousError\"")
    }

    if e1 == MyError {
        fmt.Println("e1 == MyError1")
    }
}

func doMyError() error {
    return MyError
}

func doAnonymousError() error {
    return errors.New("AnonymousError")
}
$ go run goerrors1.go
e0.Error() == "AnonymousError"
e1 == MyError1

上で書いたとおり、直接errors.New()したものは比較の際に同じメッセージでerrors.New()しても同じものとは判定されず、エラー文字列を比較しなくてはなりません。
それに対して、varで定義したものを使用した場合は通常の比較で判定することができます。(同じ値を見ているので当たり前でしょうけど)


goのソースコードの中でも直接errors.New()やfmt.Errorf()している箇所があり、エラーの種別を判定できずに困ったので一度確認してみました。もしかすると直接errors.New()している箇所はエラーの種別を判定して処理することなく、「そのままエラーで終わらせろ」というメッセージかもしれません。


先日公開したgo-pop3パッケージでは、処理を分ける箇所については明示的にエラーを定義していたと思います。