表題の通り、Goのお作法に「インターフェースで受けて、実体で返せ」というものがあります。
戻り値はなるべく具体的な型で書き、受け取る側が必要なインターフェースを定義して受け取るべき、というものです。
例えば 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型で返すのだと思います。