Edited at

"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型で返すのだと思います。