グローバル変数使わないといけないときってありますよね。たとえば、テストのときの初期化に必要ですよね(今回はこれでハマった)
あとで私がハマった現実的なシチュエーションを説明します(たぶん、同じことでハマった人100人くらいはいる)がひとまずクイズ形式で気楽にいきましょう。
クイズ
皆さんは以下のmain関数の出力は何だと思いますか?
package main
import (
"fmt"
)
var global string
func main(){
global = "initial state"
fmt.Println(global)
}
「馬鹿にしてるのか?」という声が聞こえてきそうですね。
念のため答えは
initial state
です。
じゃあ、次はこちらです。(次からimportとかは省きます)答えを予想してくださいね?
var global string
func main(){
global = "initial state"
global ,err := double()
if err != nil{}
fmt.Println(global)
}
func double()(string,error){
return "double",nil
}
まだ、簡単です。
答えは
double
です。
さて、ここで出力されたglobal
は本当にグローバルで定義されたglobal
なのでしょうか?
ちなみにこのような書き方をする例としてグローバルに定義した*sql.DB
なんかがあります。(私はこういう書き方をした場合には未定義のerrだけが新しく定義されると思ってました。)
次が本題になります。
var global string
func main(){
global = "initial state"
global ,err := double()
if err != nil{}
mistake()
fmt.Println(global)
}
func double()(string,error){
return "double",nil
}
func mistake(){
global = "mistake"
}
答えはmistake
とdouble
のどちらだと思いますか?
正解は
double
です。
mistake()
のなかのglobal
とmain関数の中で:=
を使ってしまったglobal
とは別の変数として扱われるようです。こうなるとmain関数の中からグローバルなglobal
を参照する方法はあるんでしょうか...
いつ困るか?
こんなクイズみたいな状況いつ困んねんって感じですがかなり現実的な状況でかなり困りました。
下の例を見てください。テストで使う*sql.DB
を初期化をするためのsetUp
関数を定義しています。
でも、これだとグローバルなDb
とは別のsetUp
関数の中だけの新しいDb
が定義されてグローバルなDb
は更新されないんですよね。
var Db *sql.DB
func TestMain(m *testing.M){
var t *testing.T
setUp(t)
code := m.Run()
tearDown()
os.Exit(code)
}
func setUp(t *testing.T){
Db, err := sql.Open("mysql", "mysql:mysql@/mission")
if err != nil {
t.Error("Error: Could not connect database",err)
}
}
なので、こう書かなくてはなりません。(ベストプラクティスかどうかは知らない。)
var Db *sql.DB
func TestMain(m *testing.M){
var t *testing.T
setUp(t)
code := m.Run()
tearDown()
os.Exit(code)
}
func setUp(t *testing.T){
var err error
Db, err = sql.Open("mysql", "mysql:mysql@/mission")
if err != nil {
t.Error("Error: Could not connect database",err)
}
}
これでSetup関数の中でDb
が新しく定義される問題を回避することができ、テスト全体で共通のDb
を使えるようになりました。
まとめ
言語の仕様を理解していないと思わぬところでハマってしまいますね。