オレの書くGoは間違っていた
Go言語には"Accept interfaces, return structs"というお作法がある。
このお作法の存在に気づいていなかったし、そうでなくても正しく動いてくれていた。
Goを3年ほど書いてきてようやくお作法を理解したので紹介したい。
これまでのぼくの書き方
以下にこれまでぼくが書いていたコードのinterface定義の例とその実装の例を示す。
Example
structはExample
interfaceの定義を満たしており、DoSomething
関数を持つ。
interfaceのありがたみはinterfaceさえ参照すれば、それを満たすstructがどんな関数を持っているのか知ることができる点だとばかり思っていた。
interfaceを引数に取る関数に依存性の注入などを行ったときに返り値と引数の型が異なってしまうため、NewExample
関数の返り値はinterfaceを返せばいいものだと思っていた。
しかも、structがinterfaceの実装を満たしていなかった場合におまじないの行とNewExample
の行で同じエラーが発生していた。(意味あるのかな?と思っていた)
package example
var _ ExampleInterface = (*Example)(nil) // おまじない
type ExampleInterface interface {
DoSomething() error
}
type Example struct {}
func NewExample() ExampleInterface {
return &Example{}
}
func (e *Example) DoSomething() error {
return nil
}
これが正しいGoの書き方だ
なんとGo公式にinterfaceを関数の返り値にするのはダメだと書かれており、それに従うと以下のような実装になる。
NewExample
関数がstructを返すようになったので、おまじない行と重複したエラーが発生することはなくなった。これでおまじないが成立するわけだ。
ただ、この方法だと前述のように依存性の注入を行ったときにNewExample
は*Example
を返し、受け取る引数はExampleInterface
を必要としているので型が異なってしまう。
- func NewExample() ExampleInterface {
+ func NewExample() *Example {
return &Example{}
}
これを解決するためにwireなどのDIツールで以下のように記述する。
これでwire.Build
関数にNewExampleSet
を渡せばNewExample
関数の返り値が*Example
でもExampleInterface
を受け取りたい関数とマッチしてくれる。
+ var NewExampleSet = wire.NewSet(
+ wire.Bind(new(ExampleInterface), new(*Example)),
+ NewExample,
+ )
まとめ
Accept interfaces, return structsというお作法について理解した。
関数の引数にinterfaceを受け取ると、定義を満たすstructであればすり替え可能なためにテスト時のmockのしやすさにつながる。
Goの魅力はこれに限らないと感じるので、もっと深く知っていきたい。
Discussion
この記事についてGoのコントリビュータの方がツイートしてます。@tweet
お返事が遅くなってしまい申し訳ございません。教えていただきありがとうございます。
コメントいただいた件について記事冒頭にメッセージを表示させていただきました。