Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save evalphobia/4a63e685774ebb16b8b7 to your computer and use it in GitHub Desktop.
Save evalphobia/4a63e685774ebb16b8b7 to your computer and use it in GitHub Desktop.
Go言語の地雷(原題: Golang landmines)

Goでよくやってしまうミスが3つあります。
私はそのミスを、分かりやすいように簡略した書き方ではなく、巷でよく見かける書き方のままここで説明します。
3つのミスの全てが、私の知る限り少なくとも1回づつ、Kubernetesの過去のコードレビューにありました。

1: ループ変数がループ外のスコープになっている

この各行はいったい何を行っているのでしょうか。想像してから下へスクロールしてください。

func print(pi *int) { fmt.Println(*pi) }

for i := 0; i < 10; i++ {
  defer fmt.Println(i)
  defer func(){ fmt.Println(i) }()
  defer func(i int){ fmt.Println(i) }(i)
  defer print(&i)
  go fmt.Println(i)
  go func(){ fmt.Println(i) }()
}

解答:

func print(pi *int) { fmt.Println(*pi) }

for i := 0; i < 10; i++ {
  defer fmt.Println(i) // OK: 9 ... 0 を出力します
  defer func(){ fmt.Println(i) }() // NG: "10" を10回出力します
  defer func(i int){ fmt.Println(i) }(i) // OK
  defer print(&i) // NG: "10" を10回出力します
  go fmt.Println(i) // OK: 0 ... 9 をランダムな順に出力します
  go func(){ fmt.Println(i) }() // NG: 完全に予測不可能
}

for key, value := range myMap {
  // Same for key & value as i!
}

みなさんはこれらの変数がループ内のスコープになっていると思うでしょうが、
Goは全てのイテレーションで同じメモリ領域を使用します。
このことは上の keyvalue , i のアドレスはループから逃げることが出来ないということを意味しています。
無名関数(クロージャ) func() { /* do something with i */ } は変数のアドレスを取る微妙な方法です。

2: nilインターフェースはインターフェースのnilポインタとは違うこと

type Cat interface {
  Meow()
}

type Tabby struct {}
func (*Tabby) Meow() { fmt.Println("にゃー") }

func GetACat() Cat {
  var myTabby *Tabby = nil
  // おおっと、myTabbyに値を代入し忘れました
  return myTabby
}

func TestGetACat(t *testing.T) {
  if GetACat() == nil {
    t.Errorf("本物のネコを返却し忘れています!")
  }
}

どうなるでしょうか? 上記のテストはnilポインタを検出しません!
これはインターフェースがポインタの役目を果たすので、GetACatは事実上、nilポインタへのポインタを返却します。
GetACatのようなことをしないでください。そうすればあなた(と同僚)はもっと幸せになれます。
これはerror型の値で とても 簡単に起こります。
http://golang.org/doc/faq#nil_error

3: あなたの正気を隠す有害なもの

var ErrDidNotWork = errors.New("失敗しました")

func DoTheThing(reallyDoIt bool) (err error) {
  if reallyDoIt {
    result, err := tryTheThing()
    if err != nil || result != "成功しました" {
      err = ErrDidNotWork
    }
  }
  return err
}

上の関数は 必ず nilエラーを返却します。
内部のerr変数が関数スコープ変数を「隠してしまう」からです。
var result stringを追加し、 := を使うのをやめれば修正されます。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment