Runner in the High

技術のことをかくこころみ

Golangのあの動的にDIするやつ

名称が分からないが、実際の例で言うとcloudspannerecosystem/yoにあるこういうやつ。

// YOLog provides the log func used by generated queries.
var YOLog = func(context.Context, string, ...interface{}) {}

これだけ見ると何のために定義されているか分からない。ではなんなのかと言うと、これは後から動的に実装を差し込むために用意されているインターフェイス的なやつらしい。

親しいコードを用意すると以下のよう感じ。

package main

import (
    "fmt"
)

// ロガーのインターフェイス
var Log = func(string) {}

func main() {
    // LogAの実装に差し替え
    Log = func(msg string) {
        fmt.Printf("LogA: %s\n", msg)
    }
    Log("Hello World") // LogA: Hello World
 
    // LogBの実装に差し替え
    Log = func(msg string) {
        fmt.Printf("LogB: %s\n", msg)
    }
    Log("Hello World") // LogB: Hello World
}

Log自体には定義の段階では実装は空だが、それが呼び出されるタイミング(あるいはどこかの初期化のタイミング)で実装を差し込むことで好きな振る舞いを与えられる。普段からImmutableな雰囲気のコードばっかり書いているとこういうmutableな手法はなかなか思いつかないが、こういうのがGoっぽいのかもしれない。

今回の例は変数の型が関数だからいいが、これが構造体のポインタとかになると複雑なアプリケーションではいつ実装が差し込まれるのか分からずSEGVしてしまい危険。絶妙なタイミングで発生するランタイムエラーなバグを埋め込む可能性がある。なので、当然といえば当然だが、意図的に定義と同時に空の実装を与えたり未初期化のときに呼び出されたらエラーログを吐くなどの実装をしておいたほうが無難だろうなとは思う。