Goの構造体初期化はFunctional OptionsパターンとBuilderパターンどっちが良さそうか比べた


# はじめに

Goのプロジェクトで任意な設定フィールドが多くある構造体の初期化では[Functional Options パターン](https://github.com/tmrts/go-patterns/blob/master/idiom/functional-options.md)がよく使われます。

Functional Options パターンとは? 

> Functional Options パターンは不透明なOption型を使って内部の構造体に情報を渡すパターンです。 可変長引数を受け取り、それらを順に内部のオプションに渡します。コンストラクタや、公開されたAPIで3つ以上の多くの引数が必要な場合、このパターンを使うと良いでしょう。 (https://github.com/knsh14/uber-style-guide-ja/blob/master/guide.md#functional-options より)

構造体の初期化としてはBuilderパターンも一般的に有名でもちろんGo言語でも実装できます。
特にこの2つのデザインパターンに対して同時に言及した記事がなかったので使い勝手とパフォーマンスの観点で比較してみました。

__Goのバージョン__
`go version go1.13.1 darwin/amd64`

# TL;DR

比較検証した結果、少々乱暴に結論づけると以下になります。

|総合|Functional Optionsパターンの勝ち|
|-------|------|
|使い勝手|Functional Optionsパターンの勝ち|
|パフォーマンス|Builderパターンの勝ち|

# Main Content

## サンプルコードの構成

サンプルコードを作りました。構成は以下のような感じ。

```
$ tree
.
├── application.go
├── builder.go
├── functional_option.go
└── main.go
```

ソースコードのリンクは[こちら](https://github.com/momotaro98/go-codes-for-learning/tree/master/design-pattern/functional_option_and_builder)

## 共通部分

Functional Optionsパターン(以降FOP)とBuilderパターン(以降BP)の比較にて共通で初期化する構造体が以下の`Application`です。

```go:application.go
package main

type Course int

const (
    Basic Course = iota
    Premium
)

func (c Course) String() string {
    switch c {
    case Basic:
        return "Basic"
    case Premium:
        return "Premium"
    default:
        return ""
    }
}

// Application は通信キャリアの契約申し込みをイメージしたものです。
type Application struct {
    Course Course                // ベーシックプランとプレミアプランがある
    SubscribeSupportService bool // サポートサービスのオプション
    SubscribeMovieService bool   // 動画サービスのオプション
    SubscribeBackupService bool  // データバックアップサービスのオプション
}
```

## Functional Options パターン

FOPで`Application`構造体を初期化する実装は以下のような感じになります。

```go:functional_option.go
package main

type Option func(*Application)

func WithSupport(flg bool) Option {
    return func(a *Application) {
        a.SubscribeSupportService = flg
    }
}

func WithMovie(flg bool) Option {
    return func(a *Application) {
        a.SubscribeMovieService = flg
    }
}

func WithBackupService(flg bool) Option {
    return func(a *Application) {
        a.SubscribeBackupService = flg
    }
}

func NewApplicationWithFOP(course Course, ops ...Option) *Application {
    a := Application{Course: course}
    for _, option := range ops {
        option(&a)
    }
    return &a
}
```

## Builder パターン

BPで`Application`構造体を初期化すると以下のような感じ。

```go:builder.go
package main

type ApplicationBuilder interface {
    WithSupport(flg bool) ApplicationBuilder
    WithMovie(flg bool) ApplicationBuilder
    WithBackupService(flg bool) ApplicationBuilder
    Build() *Application
}

type appBuilder struct {
    course Course
    subSupport bool
    subMovie bool
    subBackup bool
}

func (b *appBuilder) WithSupport(flg bool) ApplicationBuilder {
    b.subSupport = flg
    return b
}

func (b *appBuilder) WithMovie(flg bool) ApplicationBuilder {
    b.subMovie = flg
    return b
}

func (b *appBuilder) WithBackupService(flg bool) ApplicationBuilder {
    b.subBackup = flg
    return b
}

func (b *appBuilder) Build() *Application {
    return &Application{
        Course: b.course,
        SubscribeSupportService: b.subSupport,
        SubscribeMovieService: b.subMovie,
        SubscribeBackupService: b.subBackup,
    }
}

func NewApplicationWithBP(course Course) ApplicationBuilder {
    return &appBuilder{
        course: course,
    }
}
```

## main.go

```go:main.go
package main

import (
	"fmt"
)

func main() {
	// Functional Option Pattern (FOP)
	fopApp := NewApplicationWithFOP(Premium,
		WithBackupService(true),
		WithSupport(true),
		WithMovie(false),
	)

	// Builder Pattern (BP)
	bpApp := NewApplicationWithBP(Premium).
		WithBackupService(true).
		WithSupport(true).
		WithMovie(false).
		Build()

    fmt.Printf("%+v\n", fopApp) // &{Course:Premium SubscribeSupportService:true SubscribeMovieService:false SubscribeBackupService:true}
    fmt.Printf("%+v\n", bpApp)  // &{Course:Premium SubscribeSupportService:true SubscribeMovieService:false SubscribeBackupService:true}
}
```

## 使い勝手について

上記の`main.go`の見た目で比較してわかるように[こちらの記事](https://qiita.com/weloan/items/56f1c7792088b5ede136)などで考慮されている柔軟性に関してはどちらも同じ性質を持っていそうです。
`functional_option.go`と`builder.go`を比較するとBPの方がBuidlerのインターフェースと構造体を用意する必要があり少しFOPよりも複雑になってしまっています。

## パフォーマンスについて

ベンチマークをとってみます。

```go:bench_test.go
package main

import (
	"testing"
)

// Functional Options Pattern (FOP)
func BenchmarkFunctionalOption(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = NewApplicationWithFOP(Premium,
			WithBackupService(true),
			WithSupport(true),
			WithMovie(false),
		)
	}
}

// Builder Pattern (BP)
func BenchmarkBuilder(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = NewApplicationWithBP(Premium).
			WithBackupService(true).
			WithSupport(true).
			WithMovie(false).
			Build()
	}
}
```

```
$ go test -bench . -benchmem
BenchmarkFunctionalOption-4     12982681                85.4 ns/op            64 B/op          4 allocs/op
BenchmarkBuilder-4              25243935                44.8 ns/op            32 B/op          2 allocs/op
```

上記のコードではBPの方がFOPよりも約2倍早く、メモリアロケーションの数も半分であることがわかりました。

アロケーションされてしまうタイミングを`GODEBUG=allocfreetrace=1`で見てみ(※1)ると、FOPでは`WithXXX`の関数実行の度にアロケーションが発生していました。`Option`型の値を返すためにアロケーションが必要なようです。
BPでは`WithXXX`の関数はインスタンス化された構造体の中身を更新するだけなのでアロケーションはなく、`New..()`と`Build()`の2回のみアロケーションがされていることを確認しました。

この検証でFOPの弱点を見つけることができました。

(※1) 以下のように実行するとトレース結果が出力されます

```
$ go build -o a.out; GODEBUG=allocfreetrace=1 ./a.out -test.run=none -test.benchtime=1ms 2>trace.log
```

## 結論としては

2つのデザインパターンで利用が想定される構造体の初期化は、プロセスとして始めの1度だけ実行されるようなユースケースという前提であれば使い勝手(メンテしやすさ)が優先されるという理由から、Functional Optionsパターンを使っていけば良さそうです。

# 参考
FOP

* https://qiita.com/weloan/items/56f1c7792088b5ede136
* https://blog.web-apps.tech/go-functional-option-pattern/

BP

* https://gist.github.com/vaskoz/10073335

Benchmark / Runtime debugging

* https://qiita.com/mattn/items/e613c1f8575580f98194
* https://methane.github.io/2015/02/reduce-allocation-in-go-code/