go vet の shadow を知る

この記事はEureka Advent Calendar 2016 12日目の記事です。
11日目は二川さんさんのネイティブエンジニアが半年間Webエンジニアとして働いてきた話でした。
 
こんにちは。エウレカでGo言語エンジニアをしている 田野です。
 
ここ1週間のうちに Dave Cheneyさんと2度もお会い出来る機会がありホクホクしています。お帰りの際に地下鉄の駅までお見送りさせていただいたのですが、とても良い旅行だったとお話されていました。
 
ところで先日社内にて go vet の話題になりまして、特に shadow チェックという検査が存在するのですが、この意味が全くわからなかったので調べました。
 
調べてみるとたしかにバグの可能性をよく検出してくれそうで便利だと感じました。反面、期待通りの検出を厳密にしてくれないケースもあり、使うときは注意が必要と感じました。
 
今回はその件についてブログに書きたいと思います。

go vet とは?

Go言語の標準ツールのひとつです。vet – The Go Programming Language

Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string. Vet uses heuristics that do not guarantee all reports are genuine problems, but it can find errors not caught by the compilers.

要約しますと、

  • コンパイラが検出しないエラーを検出します。
  • そのエラー自体が本当に問題化するかは別として、経験則的に問題となりやすいGo言語のソースコードの記述を抽出します。(例: 書式文字列とあわない Printf のコールなど…文字列なのに %d を指定したりなど。)

shadow

shadowを説明するサンプルとして、main.go という名前で例をつくりました。

x := 0

{
    var x int
    x++
}

fmt.Println(x) // 0

The Go Playground
 
このプログラミングはブロック内にて、xが再宣言されています。
このままプログラムを実行しても、コンパイルエラーは起きずに Println0 を表示します。
 
ですが実際には、x++が何かの仕様を体現していた可能性があります。その場合は、 var x int と再宣言することが不具合と考えられます。
 
このような状況について vet コマンドはこのようなエラーを出力します。

$ go tool vet -shadow main.go
main.go:11: declaration of "x" shadows declaration at main.go:8

shadowの定義

shadow の定義ですが、 shadowのコード go/shadow.go golang/go · GitHub の中に記載があります。

This file contains the code to check for shadowed variables.
A shadowed variable is a variable declared in an inner scope
with the same name and type as a variable in an outer scope,
and where the outer variable is mentioned after the inner one
is declared.
(This definition can be refined; the module generates too many
false positives and is not yet enabled by default.)

訳すとこのような感じです。
 
shadow された変数とは、外側のスコープで定義している変数を、内側のスコープで同名・同型で定義された変数であり、内部スコープで宣言された後、外部スコープで変数が使用される場合を指します。
shadow の定義はさらに改善が必要で、現状では多くのエラーの可能性を出力してしまうため、デフォルトではオフになっています)

shadowの検出ケースに対する注意

shadow のエラーが何かの不具合を示しているとは必ずしも限らないようです。
 
以下のコードを subfunc.go という名前で用意しました。 The Go Playground

x := 1

add := func() int {
    x := 2
    return x
}

fmt.Println(add()) // 2
_ = x

このケースでは、add関数の内部スコープで宣言された x は外部スコープで宣言された x と全く無関係であるように見えます。
 
しかし、 vet はこちらをエラーとしてしまいます。

$ go tool vet -shadow subfunc.go
subfunc.go:11: declaration of "x" shadows declaration at subfunc.go:8

このように、必ずしも問題があるケースを示しているとは限らない、というのが shadow のエラーで注意するべきところです。
 
この挙動が上記のソースコード内のコメントにて書いてある 「shadowの定義をもっとよく改善出来るはず」 という点です。

まとめ

社内で go vet をかけたら shadowed valuable のエラーが大量でてきた…なんて話を以前聞いていたのですが、 shadow とはなんだろうと思い調べてみました。
 
僕個人的には、vet 自体は最初にもある通り「経験則から問題とおぼしきものをチェックする」という定義づけなので、テストコードの様にCIでマージ前にかならず動作させるものというより、個人がコードを書いていて保存時に gofmt などと一緒に動かすほうが良いかなと考えています。
 
ちなみに、上記に示した shadow が検出する良いパターン・悪いパターンについては、 Guillaume’s Thoughts: Scope and Shadowing in Go に良くまとまっていたのでご参照ください。時間があれば翻訳してもよいか筆者へ尋ねてみたいと思っています。
 
明日は香取さんのDeep Learningは1週間で始められる!です。Deep Learning、僕も勉強したいな!

  • このエントリーをはてなブックマークに追加

エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!

Recommend

Reduxから見えてきた希望と課題

大学生エンジニアが春休みにエウレカで働いてみた! 〜短期インターン体験記〜