interfaceの実装を強制する簡単なテクニックを紹介
Goのinterface
を使って抽象的にオブジェクトを扱いたいときにメソッドの実装漏れってないでしょうか?
抽象化したい構造体が多かったり、interface
のメソッド定義を変更した場合に簡単に発生しますよね。
今回は、それを起こさないために、Goコンパイラにinterface
の実装漏れを伝える簡単なテクニックを共有します。
導入
Doer
というDo()
を実装するだけで使えるinterface
を定義しました。
下記のコードのPerson
はDo()
を実装しているので、Doer
として扱うことができます。
type Doer interface {
Do()
}
type Person struct {
Name string
}
func (p Person) Do() {
fmt.Printf("My name is %s. I am Doer!!!!\n", p.Name)
}
func main() {
p := Person{Name: "John"}
p.Do()
}
実行結果
My name is John. I am Doer!!!!
ここまで問題は無いと思います。
それでは、Doer
にメソッドGreet()
を追加してみましょう。
今までDo()
を実装するだけで、Doer
として扱うことができていたのに、Greet()
も実装する必要が発生しました。
なので、Do()
しか実装していない構造体Person
は、Doer
として扱うことができませんが、プログラムは正しいです。
type Doer interface {
Do()
Greet() // 追加!!!
}
type Person struct {
Name string
}
func (p Person) Do() {
fmt.Printf("My name is %s. I am Doer!!!!\n", p.Name)
}
func main() {
p := Person{Name: "John"}
p.Do()
}
このように1ファイルに収まるシンプルさなら実装漏れにすぐに気づくはずですが、
interface
を実装したい構造体がたくさんあったら...ファイルが分けられていたら...パッケージが分けられていたら...
なかなか気づくのは難しいと思います。
それを探すのってかなり面倒ではありませんか....?
テクニックを紹介
var _ Doer = Person{}
というコードを追加するだけです。
_
は省略を表しているので、メモリも無駄になりません。
これを加えるだけで、コンパイルエラーが発生し、さらにエラーが発生しているファイル名、行列数まで教えてくれるのですぐに見つけることができます。
type Doer interface {
Do()
Greet()
}
var _ Doer = Person{} // たったこれだけ!
type Person struct {
Name string
}
func (p Person) Do() {
fmt.Printf("My name is %s. I am Doer!!!!\n", p.Name)
}
func main() {
p := Person{Name: "John"}
p.Do()
}
./prog.go:5:5: cannot use composite literal (type Person) as type Doer in assignment:
Person does not implement Doer (missing Greet method)
最後に
たった1行追加するだけでinterface
の実装漏れをふせぐことができるこのテクニックを是非使ってみてください!!
実はこれUber
がおすすめしているテクニックでもあります。
他にもGoのテクニックや、Uber
で推奨されているスタイルがまとまっているので気になる方はチェックしてみてください。
コードレビューをする際にめちゃくちゃ使えます!
「Uberで使われているんですよ〜」といえば、納得できそうではないでしょうか?笑
番外編
コメント欄で他のテクニックも頂いたのでまとめてみました。
コンストラクタを書く
@riita10069
私はconstructerを書くのがいいかなと思っています。
コンストラクタを書くことで、同じくコンパイラに実装漏れを検知してもらうことができます。
type Doer interface {
Do()
Greet()
}
// constructor を追加
func NewDoer() Doer {
return Person{}
}
type Person struct {
Name string
}
func (p Person) Do() {
fmt.Printf("My name is %s. I am Doer!!!!\n", p.Name)
}
func main() {
p := Person{Name: "John"}
p.Do()
}