34
11

More than 1 year has passed since last update.

Goのグローバル変数とスコープでハマった話

Last updated at Posted at 2020-11-02

グローバル変数使わないといけないときってありますよね。たとえば、テストのときの初期化に必要ですよね(今回はこれでハマった)
あとで私がハマった現実的なシチュエーションを説明します(たぶん、同じことでハマった人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"
}

答えはmistakedoubleのどちらだと思いますか?
正解は

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を使えるようになりました。

まとめ

言語の仕様を理解していないと思わぬところでハマってしまいますね。

34
11
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
34
11