Upgrade to Pro — share decks privately, control downloads, hide ads and more …

パッケージ外から非公開フィールドを変更する方法.pdf

MakKi
December 18, 2018
320

 パッケージ外から非公開フィールドを変更する方法.pdf

golang.tokyo #20

MakKi

December 18, 2018
Tweet

More Decks by MakKi

Transcript

  1. 自己紹介 • 牧内大輔 • MakKi / @makki_d • 所属:KLab株式会社 ◦

    このビルの22F ◦ オンライン対戦の中継サーバを Goで • gozxing ◦ https://github.com/makiuchi-d/gozxing ◦ 1D/2Dバーコードリーダーライブラリ ◦ JavaのZXingをGoに移植
  2. 今回のお話 • 外部パッケージのstructの内部状態に依存した処理のテスト ◦ 意図した内部状態をお手軽につくりたい • こういうときどうする? ◦ 非公開フィールドを公開しちゃう ▪

    論外 ◦ スタブ・モック ▪ テストのために差し替え可能にする? • gozxingでは元のZXingの構造を残す方針にやや反する ◦ リフレクション ▪ 内部構造に依存(密結合) • 外部パッケージとはいえ同じライブラリ内なので許容 ▪ 今回はこれで
  3. Goのリフレクション • 標準パッケージ “reflect” ◦ https://golang.org/pkg/reflect/ • Value型 ◦ オブジェクトの型と値を持ち、操作するためのもの

    ◦ func ValueOf(i interface{}) Value ◦ func (v Value) FieldByName(name string) Value ◦ func (v Value) Set(x Value) ◦ func (v Value) SetBool(x bool) ◦ ... いけそうだ!
  4. まずは試してみる package sub type A struct { a int }

    func NewA() A { return A{10} } ...(snip)... func main() { a := sub.NewA() v := reflect.ValueOf(a).FieldByName("a") v.SetInt(100) fmt.Println(a) }
  5. まずは試してみる package sub type A struct { a int }

    func NewA() A { return A{10} } ...(snip)... func main() { a := sub.NewA() v := reflect.ValueOf(a).FieldByName("a") v.SetInt(100) fmt.Println(a) } panic: reflect: reflect.Value.SetInt using value obtained using unexported field
  6. ポインタを経由して上書きする方法 • unsafe.Pointer ◦ 任意の型のポインタにキャストできる ◦ uintptrと相互変換 ▪ reflectのValueからも取得できる ▪

    func (v Value) UnsafeAddr() uintptr • 手順 a. 非公開フィールドのポインタ値( uintptr)を取得 b. uintptrをunsafe.Pointer方にキャスト c. 元と同じ型のポインタにキャスト d. 参照先に代入
  7. やってみる package sub type A struct { a int }

    func NewA() A { return A{10} } ...(snip)... func main() { a := sub.NewA() v := reflect.ValueOf(a).FieldByName("a") p := unsafe.Pointer(v.UnsafeAddr()) ptr := (*int)(p) *ptr = 100 fmt.Println(a) }
  8. やってみる package sub type A struct { a int }

    func NewA() A { return A{10} } ...(snip)... func main() { a := sub.NewA() v := reflect.ValueOf(a).FieldByName("a") p := unsafe.Pointer(v.UnsafeAddr()) ptr := (*int)(p) *ptr = 100 fmt.Println(a) } panic: reflect.Value.UnsafeAddr of unaddressable value
  9. unaddressable value • ValueOf関数へ値渡ししている ◦ 一時的なコピーが作られる ◦ 戻り値のValueが指すのはコピーの方 • コピーのアドレスは取れない

    ◦ The Laws of Reflection (The Go Blog) ▪ https://blog.golang.org/laws-of-reflection ◦ Settabilityの説明だけどAddressabilityも似た感じ a := sub.NewA() v := reflect.ValueOf(a).FieldByName("a") A a A a 変数 a FieldByName の戻り値 ValueOf の引数 v ValueOf の戻り値 コピー unaddressable addressable
  10. ValueOfにポインタを渡す • 引数としてコピーされるのはポインタ ◦ 参照先の実体は同じもの • Elem()でポインタ参照先のValueを取得 ◦ Valueが対応するのは変数 aと同じもの

    ◦ コピーではないので Addressable • 戻り値 v もAddressable A a 変数 a FieldByName の戻り値 ValueOf の引数 v ValueOf の戻り値 コピー &a Elem の戻り値 unaddressable addressable a := sub.NewA() v := reflect.ValueOf(&a).Elem().FieldByName("a")
  11. ポインタを経由して上書きする方法 package sub type A struct { a int }

    func NewA() A { return A{10} } ...(snip)... func main() { a := sub.NewA() v := reflect.ValueOf(&a).Elem().FieldByName("a") p := unsafe.Pointer(v.UnsafeAddr()) ptr := (*int)(p) *ptr = 100 fmt.Println(a) }
  12. ポインタを経由して上書きする方法 package sub type A struct { a int }

    func NewA() A { return A{10} } ...(snip)... func main() { a := sub.NewA() v := reflect.ValueOf(&a).Elem().FieldByName("a") p := unsafe.Pointer(v.UnsafeAddr()) ptr := (*int)(p) *ptr = 100 fmt.Println(a) } {100}