Slide 1

Slide 1 text

構造体初期化の方法について 2022/08/25 CA.go #09

Slide 2

Slide 2 text

自己紹介 吉瀬遼太 (きせ りょうた) - IU事業部 株式会社CAM - Twitter: @kissessenose - GitHub: https://github.com/mmmommm 今日のサンプルコード: https://github.com/mmmommm/ca.go

Slide 3

Slide 3 text

今回のゴール - オプショナルな値をもつ構造体初期化の際の方法を知ること - 性質を理解し使い分けられるようになること

Slide 4

Slide 4 text

構造体の処理化 構造体にオプショナルな値がある場合、初期化時に渡した時はデフォルト値にしてほしい ケースはよくあると思います。 その時の手法として - functional options pattern - builder pattern (method chaining) の二つのパターンがあります。 今回はその二つのパターンの説明と どこが優れているのかについて紹介していこうと思います。

Slide 5

Slide 5 text

手法を使用しない場合 そもそもこの方法を使うと何が嬉しいのかを先に説明します 例えばクライアント初期化時にconfigが必須でいくつかオプショナルな値が渡せる時 client := s3.NewFromConfig(config, “us-east-1", true) のように愚直に渡すやり方だと - 常に詳しくオプションの値を設定する必要があること - 拡張するたびに引数が増えることになり、変更された際に後方互換性がない - とりあえず渡す状況になる可能性があること - なんの設定値なのか関数を見に行かないとわからないこと などが問題として挙げられます。 拡張の見込みがなくシンプルなもので良いのであればこの方法でも問題はありませんが 拡張前提であればこのような実装はするべきではありません

Slide 6

Slide 6 text

functional option pattern (以後FOP) Functional options are a method of implementing clean/eloquent APIs in Go. Options implemented as a function set the state of that option. FOPは、Goでクリーン/雄弁なAPIを実装するための方法です。 関数として実装されたオプションは、そのオプションの状態を設定します。 さきほどの例で示した aws-sdk-go でもこのパターンを使用しています。 client := s3.NewFromConfig(config, “us-east-1", true) client := s3.NewFromConfig(config, func(o *s3.Options) { o.Region = “us-east-1” o.DisableSSL = true } )

Slide 7

Slide 7 text

実際のコード aws-sdk-goの場合は初期化関数内で無名関数を用いていましたが fopClient := NewClientWithFOP(sample, With○○(true)) のように関数で提供することが多いと思います。

Slide 8

Slide 8 text

builder pattern (以後 BP) Builder pattern separates the construction of a complex object from its representation so that the same construction process can create different representations. BPは、複雑なオブジェクトの構築とその表現を分離し、同じ構築プロセスで異なる表現を作成できるようにします。 特徴としてはメソッドをチェーンした最後に Buildメソッドを呼び出すことです。 client := s3.NewFromConfig(config, “us-east-1", true) client := s3.NewFromConfig(config) .WithRegion(“us-east-1”) .WithDisableSSl(true) .Build()

Slide 9

Slide 9 text

実際のコード bpClient := NewClientWithBP(Premium).With○○(true).Build() このように設定したい値をメソッドチェーンで繋ぎ最後に Buildメソッドを呼び出すことで構造体を生成します。

Slide 10

Slide 10 text

二つの比較 どちらの方法が優れているのか検証するために - メモリ使用のベンチマーク - 保守性・拡張性 の項目で比較してみました

Slide 11

Slide 11 text

メモリ使用のベンチマーク FOPが実行速度、メモリ確保量、アロケーション回数全てで BPを上回っています。 2019年/Go1.13 の時点で検証した記事では逆の結果だったようなのでどこかで改善が行われたのかなーと思いました。

Slide 12

Slide 12 text

Go 1.17/1.18での変更箇所 Go 1.17 implements a new way of passing function arguments and results using registers instead of the stack. Benchmarks for a representative set of Go packages and programs show performance improvements of about 5%, and a typical reduction in binary size of about 2%. Go 1.18 expands the supported platforms to include 64-bit ARM (GOARCH=arm64), big- and little-endian 64-bit PowerPC (GOARCH=ppc64, ppc64le), as well as 64-bit x86 architecture (GOARCH=amd64) on all operating systems. Go 1.17 は、スタックの代わりにレジスタを使用して関数の引数と結果を渡す新しい方法を実装しています 。代表 的な Go パッケージとプログラムのベンチマークでは、パフォーマンスが約 5% 向上し、バイナリ サイズが通常約 2% 削減されました。 Go 1.18 はサポートするプラットフォームを拡大し、 64 ビット ARM (GOARCH=arm64) とビッグ/リトルエンディア ンの 64 ビット PowerPC (GOARCH=ppc64, ppc64le) に加え、すべての OS 上の 64 ビット x86 アーキテクチャ (GOARCH=amd64) をサポートしました。

Slide 13

Slide 13 text

確認

Slide 14

Slide 14 text

Go1.13 increment()にint32を渡している部分で変更が起こっているか確認

Slide 15

Slide 15 text

Go1.17 increment()にint32を渡している部分で変更が起こっているか確認

Slide 16

Slide 16 text

保守性・拡張性 Goでは基本的に関数実行のたびにerrチェックを行うのでメソッドチェーンするBPとは相性が悪いという話 ・ struct に Error というプロパティを持たせそのプロパティにエラーを詰めること ・ 構造体作成の最後に呼び出されるメソッドでエラーを返すようにすること でエラーハンドリングの問題は解決することが可能なので大きな問題ではない   使い分けとしては BP = 構造体を生成するために考えられた実装方法 FOP = オプション設定のために考えられた実装方法   ので場合に応じて使い分けるのが良さそうです。

Slide 17

Slide 17 text

最後に 今回は構造体初期化の方法として選択肢に上がる functional option pattern と builder pattern について話し ました。 今回の発表が誰かの助けになれば幸いです。 ご清聴ありがとうございました 🙇

Slide 18

Slide 18 text

参考資料 - https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md - https://github.com/tmrts/go-patterns/blob/master/creational/builder.md - https://github.com/knsh14/uber-style-guide-ja/blob/master/guide.md#functional-options - https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html - https://www.calhoun.io/using-functional-options-instead-of-method-chaining-in-go/ - https://www.pospome.work/entry/2018/03/12/014109 - https://go.dev/doc/go1.17#compiler - https://tip.golang.org/doc/go1.18#compiler - https://qiita.com/momotaro98/items/51398e728b92261215a5