Go

"Accept interfaces, Return structs"が基本なのに、何でerrorはerrorで返すのか

表題の通り、Goのお作法に「インターフェースで受けて、実体で返せ」というものがあります。

http://idiomaticgo.com/post/best-practice/accept-interfaces-return-structs/

戻り値はなるべく具体的な型で書き、受け取る側が必要なインターフェースを定義して受け取るべき、というものです。

例えば net/http*Client は具体的な型ですが、これを使う関数の引数をそのまま*http.Clientとか書いちゃって、テストしづらくて詰む、なんてことはよくあります。

func doSomething(c *http.Client) { // ←cを丸ごとモック化するのは不可能!!残念!!
    // ...
}

これを見て net/http が最初からインターフェースを定義しておくべきだったんじゃないか、みたいに思うかもしれませんが、そこは冒頭の作法に則っていると考えるべきです。

httpClientの場合、まあ単純なケースだとDoメソッドしか使わないと思います。なので、

type Doer interface {
    Do(req *http.Request) (*http.Response, error)
}

みたいなのを 自分で 定義して、

func doSomething(c Doer) {
    // ...
}

こうすればいいわけですね。

最初からインターフェースで返してしまうと、実体のコードを追うのが難しくなってしまうため、こんなふうに書くわけです。

errorの場合は?

例えばdoSomethingがMyErrorを返すことが分かっている場合、こう書けるじゃんと思うかもしれません。

func doSomething() *MyError {
    // ...
        return new(MyError)
    // ...
}

しかし、Goのお作法的には常に error インターフェースを返すように書きます。これは、考えてみれば当然で、errorを作るのはこの関数だけとは限らないからです。

doSomethingに仕様追加があり、他の関数(エラーが起きるかもしれない)の呼び出しを追加したとしましょう。

func doSomething() *MyError {
    // ...
        return new(MyError)
    // ...
    err := foo() // ←あっ、MyError型を返さない関数だ…
    if err != nil {
        return new(MyError) // 元のエラーを握りつぶすことになっちゃった…
    }
}

常にerrorインターフェースを返すように書いていれば、直接返せるようになります。

func doSomething() error {
    // ...
        return new(MyError)
    // ...
    return foo()
}

それに、error ならこの戻り値がエラーであることが一目瞭然だというメリットもあります。というわけで、errorの場合はreturn structsの作法を無視して、error型で返すのだと思います。