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

プロダクトのコードから見るGoによるデザインパターンの実践 #go_night_talk

プロダクトのコードから見るGoによるデザインパターンの実践 #go_night_talk

2025年10月14日(火)開催、「Go Night Talks – After Conference」で登壇した、弁護士ドットコム株式会社 クラウドサイン エンジニアの 神達 小楠 が登壇した際の資料です。

Go Night Talks – After Conference(11社合同開催!Goエンジニア交流LTイベント)
https://mercari.connpass.com/event/367075/

セッションタイトル: AI時代に活躍できるエンジニアとは
https://bengo4.connpass.com/event/365999/

■ 弁護士ドットコム株式会社プロダクト組織について
https://speakerdeck.com/bengo4com/introduction-for-creators

■ 採用情報はこちら
https://hrmos.co/pages/bengo4/jobs

■ テックブログ:弁護士ドットコム Creators’ blog
https://creators.bengo4.com/

■ X(Twitter):弁護士ドットコム CREATOR'S
https://x.com/bengo4_creators

Avatar for 弁護士ドットコム

弁護士ドットコム

October 14, 2025
Tweet

More Decks by 弁護士ドットコム

Other Decks in Technology

Transcript

  1. Bengo4.com,Inc. VISION まだないやり方で、世界を前へ。 Drive a paradigm shift for the better

    world. MISSION 「プロフェッショナル・テック 」で、次の常識をつくる。 Be the Professional-Tech Company. プロフェッショナルだからできること。専門知とテクノロジーで、社会に貢献する。
  2. Bengo4.com, Inc. クラウドサイン は、弁護士ドットコム が運営するサービスです OUR SERVICE 4 税理士に無料で相談・検索できる日本最大級の 税務相談ポータルサイト

    最新の法改正や実務について分かりやすく解説する 日本最大級の企業法務ポータルサイト 日本最大級の無料法律相談ポータルサイト 時事問題の弁護士解説を中心としたメディア 弁護士事務所、企業法務職向け人材紹介事業 AI基盤技術「 LegalBrain 1.0」を組み込んだ リーガル特化型 AIエージェント 契約の締結から管理までデジタルで完結させる 契約マネジメントプラットフォーム
  3. © 2025 Bengo4.com, inc. 自己紹介 神達 小楠 弁護士ドットコム株式会社 開発本部 クラウドサインProduct

    Engineering部 スクラムチームに所属してクラウドサインのバックエンド開発 を担当しています。 新規機能の開発や既存機能の改善に取り組んでいます。 基本的にGoを使って開発を行っています。 趣味では個人開発をしたり、ダーツやカメラ、ガジェットが好き です。 5
  4. © 2025 Bengo4.com, inc. 今日の結論 • デザインパターンは適切に使えば有用 • 具体的な事例 から学ぶと理解しやすい

    • Goで実践する際の特徴がある 6 私が学んだ過程からGoでデザインパターン を使う きっかけやヒントになれば嬉しいです🥺
  5. © 2025 Bengo4.com, inc. 今日話すこと • 実装上の悩みとデザインパターンの関係 • 実際のプロダクトのコードからデザインパターンを知る •

    言語によるポリモーフィズムの実現方法の違い • Goの言語仕様で十分なこと • デザインパターンへの向き合い方 7
  6. © 2025 Bengo4.com, inc. 実装上の悩み こんな悩みありませんか? 9 • 同じ仕組みのコードを複数箇所で実装 しているので保守性が低い

    • 実装間の依存度が高く、 拡張やリファクタリングが難しい • 実装のたびにコードの構成について設計するのが大変
  7. © 2025 Bengo4.com, inc. デザインパターンとは デザインパターン (ソフトウェア ) ソフトウェア開発におけるデザインパターンまたは 設計パターン(英:

    design pattern)とは、過去のソ フトウェア設計者が発見し編み出した設計ノウハウ を蓄積し、名前をつけ、再利用しやすいように特定 の規約に従ってカタログ化したものである。パター ン(pattern)とは、型紙(かたがみ)やひな形を意味 する。 引用元:Wikipedia 14
  8. © 2025 Bengo4.com, inc. デザインパターンとは 具体的なパターンとしてはGoFのデザインパターン が有名です。 ※書籍「オブジェクト指向における再利用のためのデザインパターン」  原題「Design Patterns:

    Elements of Reusable Object-Oriented Software」 16 • Iterator • Adapter • Template • Factory • Singleton • Prototype • Builder • Abstract • Bridge • Strategy • Composite • Decorator • Visitor • Chain • Facade • Mediator • Memento • State • Flyweight • Proxy • Command • Interpreter
  9. © 2025 Bengo4.com, inc. デザインパターンとは 色んなパターンに共通していそうな点をざっくり言うと… • 実装を抽象化して使い勝手を良くしたい • 不都合な依存を無くして保守性を高めたい

    • 再利用性を高めたい • よくある設計上の問題をパターンとして解決したい • パターンに名前をつけることで他の人に伝えやすくしたい これらを必要としているなら知る価値があるかもしれない 18
  10. © 2025 Bengo4.com, inc. 使われているデザインパターンの調査 調査対象のプロダクト • 電子契約サービスの「クラウドサイン 」 •

    リリースから約10年経過(2015年10月19日に提供開始) • 多数のサービスやパッケージ、リポジトリが存在している • バックエンドはほとんどGoで書かれている 人力だとコード量が多すぎて調査が厳しい … 23
  11. © 2025 Bengo4.com, inc. 使われているデザインパターンの調査 󰱢 < 〇〇パターンについて条件を含め具体的に解説して。    構成要素、使用する・しない例を挙げて説明して。

    (具体的な定義と説明を出力)> 🤖 󰱢 < その内容に当てはまるコードを特定して (悪くない精度で特定したコードを出力) > 🤖 うまく行った! 26
  12. © 2025 Bengo4.com, inc. Singleton(シングルトン)パターンの説明 30 type Hoge struct {

    Name string } var hoge *Hoge func GetHogeInstance() *Hoge { if hoge == nil { hoge = &Hoge{Name: "singleton"} } return hoge } func main() { h1 := GetHogeInstance() h2 := GetHogeInstance() fmt.Println(h1 == h2) // true } 特徴 インスタンスがプログラム全体で絶対に1つしか生 成されない ことを保証する。 インスタンスへのグローバルなアクセス を提供す る。 メリット 生成コストの高い インスタンスを共有できる。 再利用したい データや共有したい 設定を効率的に 扱える。 遅延初期化すればアプリケーションの起動コストを 下げれる。 サンプル
  13. © 2025 Bengo4.com, inc. Singleton(シングルトン)パターンの実例 31 func NewClient() Client {

    return onceNewClient() } var onceNewClient = sync.OnceValue( func() *hoge.Client { return hoge.NewClientFromEnv() }, ) 概要 外部サービス連携用のクライアントをシングルトンで 管理している。 ポイント グローバルにアクセス出来るようにしつつ、syncパッ ケージのOnceValue関数で確実に一度だけ生成さ れるようにしている。 この関数は使用直前に値を初期化する、遅延初期 化という制御も行ってくれる。
  14. © 2025 Bengo4.com, inc. Builder(ビルダー)パターンの説明 33 type User struct {

    Name string Age int } type UserBuilder struct { user *User } func NewUserBuilder() *UserBuilder { return &UserBuilder{user: &User{}} } func (b *UserBuilder) SetName(name string) *UserBuilder { b.user.Name = name return b } func (b *UserBuilder) SetAge(age int) *UserBuilder { b.user.Age = age return b } func (b *UserBuilder) Build() *User { return b.user } func main() { user1 := NewUserBuilder().SetName("山田").SetAge(30).Build() fmt.Println(user1) // 山田 30 user2 := NewUserBuilder().SetName("佐藤").Build() fmt.Println(user2) // 佐藤 0 } 特徴 オブジェクトの「生成プロセス」と「表現(完成形)」を 分離する。段階的かつ任意の設定にてオブジェクト を生成出来る。 メリット コンストラクタの引数が多くなりすぎる問題を回避 し て、コードの可読性と保守性を向上させる。 また、パラメータが増えても既存コードに影響を与 えずに拡張出来る。 サンプル
  15. © 2025 Bengo4.com, inc. Builder(ビルダー)パターンの実例 34 users := testm.UserListBuilder.Edit( func(i

    int, user *models.User) { user.Name = fmt.Sprintf("山田 %d郎", i) user.Email = fmt.Sprintf("%[email protected]", i) user.Team = team user.Status = models.USER_VALID } ).BuildN(3) 概要 テストコード用のドメインモデルのオブジェクトを簡単 に生成するためのビルダー。 ビルダーはどのモデルでも共通のインターフェイス になっている。 ポイント 最低限のデータのセットがデフォルトで行われてお り、必要に応じて追加でデータをセットするだけで済 む。リストとして複数要素を生成する仕組みもあり、 同じ値・少しずつ違う値・全く異なる値など柔軟に指 定可能になっている。 var UserBuilder = datam.NewBuilder[models.User]().Edit( func(m *models.User) { m.ID = ids.NewUserID() m.Email = fmt.Sprintf("%[email protected]", m.ID) m.Name = fmt.Sprintf("user-name-%s", m.ID) m.Organization = fmt.Sprintf("org-%s", m.ID) } ) var UserListBuilder = datam.NewListBuilder(UserBuilder)
  16. © 2025 Bengo4.com, inc. Builder(ビルダー)パターンの実例 35 type Editor[T any] func(x

    *T) type Builder[T any] struct { child *Builder[T] editor Editor[T] subBuilders map[string]any } func NewBuilder[T any]() *Builder[T] { return &Builder[T]{} } func (b *Builder[T]) Edit(f Editor[T]) *Builder[T] { return &Builder[T]{ child: b, editor: f, } } type ListBuilder[T any] struct { generatorBuilder GeneratorBuilder[T] defaultLen int } func NewListBuilder[T any](b *Builder[T]) *ListBuilder[T] { return &ListBuilder[T]{ generatorBuilder: NewGeneratorBuilder(b), defaultLen: 1, } } ポイント ジェネリクスを使って異なるモデル構造体であっても 同じように扱えるようにしている。 実際にテストコードから呼ばれるのは生成されたモ デルごとのビルダーになるので、利用側はinterface で定義されたメソッドの使い方だけ把握していれば 困らない。
  17. © 2025 Bengo4.com, inc. Simple Factory(シンプルファクトリ)パターンの説明 37 type Vehicle interface

    { GetType() string } type Car struct{} func (c *Car) GetType() string { return "Car 🚗" } type Bike struct{} func (b *Bike) GetType() string { return "Bike 🚲" } func NewVehicle(vehicleType string) (Vehicle, error) { switch vehicleType { case "car": return &Car{}, nil case "bike": return &Bike{}, nil default: return nil, fmt.Errorf("invalid vehicle type") } } func main() { car, _ := NewVehicle("car") fmt.Println(car.GetType()) // Car 🚗 bike, _ := NewVehicle("bike") fmt.Println(bike.GetType()) // Bike 🚲 } 特徴 オブジェクト(インスタンス)を生成するためのパター ン。 共通のインターフェイスで扱えるオブジェクトの生成 をファクトリ関数に集約 する。 メリット オブジェクトの生成方法を利用側が知る必要が無 く、具体的な実装への依存も弱くなる。 新しく返す種類を増やす場合もファクトリ関数の修 正だけで済み 、オープン・クローズドの原則をある程 度守ることが出来る。 サンプル
  18. © 2025 Bengo4.com, inc. Simple Factory(シンプルファクトリ)パターンの実例 38 type MailBuilder interface

    { … } type SESMailBuilder struct{ … } func NewSESMailBuilder() MailBuilder { return &SESMailBuilder{ … } } type MailpitMailBuilder struct{ … } func NewMailpitMailBuilder() MailBuilder { return &MailpitMailBuilder{ … } } type MockMailBuilder struct{ … } func NewMockMailBuilder() MailBuilder { return &MockMailBuilder{ … } } func NewMailBuilder(env string) MailBuilder { switch env { case "development": return NewMailpitMailBuilder() case "test": return NewMockMailBuilder() default: return NewSESMailBuilder() } } 概要 指定された環境に応じた メールビルダーを共通の interfaceで生成する。 ※記載の問題でビルダーのメソッドを省略している ポイント 環境ごとに使用する外部サービスが変わる というよ くあるケースを抽象化している。 利用側のコードは環境ごとの差異 について考慮す る必要が無くなる。 DIするほどではないケースや、DIする元の分岐とし て利用されているように感じた。
  19. © 2025 Bengo4.com, inc. Simple Factory(シンプルファクトリ)パターンの実例 type MailBuilder interface {

    SetFrom(v string) MailBuilder From() string SetFromName(v string) MailBuilder FromName() string SetTo(v string) MailBuilder To() string SetMultiTo(v []string) MailBuilder AppendMultiTo(v string) MailBuilder SetReplyTo(v string) MailBuilder ReplyTo() string SetCC(v []string) MailBuilder AppendCC(v string) MailBuilder SetBCC(v []string) MailBuilder AppendBCC(v string) MailBuilder SetSubject(v string) MailBuilder SetBody(v string) MailBuilder SetAlternative(v string) MailBuilder AppendAttachments(name string, data []byte) MailBuilder TotalAttachmentSize() int64 Send(runMode string) (string, error) } 参考 先程のメールビルダーのinterfaceで 定義されているメソッド。 メールを動的に組み立てて送信する ための仕組み。 処理の具体は環境ごとの実装で異 なる。
  20. © 2025 Bengo4.com, inc. JavaとはFactory Methodパターンの実現方法が異なる理由 abstract class Restaurant {

    // どんな料理を作るかはサブクラスが決める abstract protected Dish createDish(); } // ピザ屋は Restaurantを継承 class PizzaShop extends Restaurant { @Override protected Dish createDish() { // 複雑な生成過程をこのメソッド内に隠蔽する return new Pizza("M"); } } // パスタ屋は Restaurantを継承 class PastaShop extends Restaurant { @Override protected Dish createDish() { // 複雑な生成過程をこのメソッド内に隠蔽する return new Pasta("クリーム "); } } 44 interface Dish { void serve(); } class Pizza implements Dish { private final String size; public Pizza(String size) { this.size = size; } @Override public void serve() { System.out.println("pizza:" + this.size); } } class Pasta implements Dish { private final String sauce; public Pasta(String sauce) { this.sauce = sauce; } @Override public void serve() { System.out.println("pasta:" + this.sauce); } }
  21. © 2025 Bengo4.com, inc. JavaとはFactory Methodパターンの実現方法が異なる理由 45 Javaだと継承によってオーバーラ イドしたメソッドに具体的な実装を 持たせる。

    継承しているためクラスの階層が 出来るので依存性と複雑性は増 す。 class OrderService { private final Restaurant factory; public OrderService(Restaurant factory) { this.factory = factory; } public void order() { Dish dish = this.factory.createDish(); dish.serve(); } } public class Main { public static void main(String[] args) { Restaurant pizzaShop = new PizzaShop(); OrderService pizzaOS = new OrderService(pizzaShop); pizzaOS.order(); Restaurant pastaShop = new PastaShop(); OrderService pastaOS = new OrderService(pastaShop); pastaOS.order(); } }
  22. © 2025 Bengo4.com, inc. func CreatePizza() Dish { return NewPizza("M")

    } func CreatePasta() Dish { return NewPasta("クリーム ") } type OrderService struct { create func() Dish } func NewOrderService(creator func() Dish) *OrderService { return &OrderService{create: creator} } func (r *OrderService) Order() { dish := r.create() // DIされた生成関数を実行 dish.Serve() } func main() { pizzaOrderService := NewOrderService(CreatePizza) pizzaOrderService.Order() pastaOrderService := NewOrderService(CreatePasta) pastaOrderService.Order() } JavaとはFactory Methodパターンの実現方法が異なる理由 47 type Dish interface { Serve() } type Pizza struct { Size string } func NewPizza(size string) Dish { return &Pizza{Size: size} } func (p *Pizza) Serve() { fmt.Printf("pizza: %s\n", p.Size) } type Pasta struct { Sauce string } func NewPasta(sauce string) Dish { return &Pasta{Sauce: sauce} } func (p *Pasta) Serve() { fmt.Printf("pasta: %s\n", p.Sauce) }
  23. © 2025 Bengo4.com, inc. func CreatePizza() Dish { return NewPizza("M")

    } func CreatePasta() Dish { return NewPasta("クリーム ") } type OrderService struct { create func() Dish } func NewOrderService(creator func() Dish) *OrderService { return &OrderService{create: creator} } func (r *OrderService) Order() { dish := r.create() // DIされた生成関数を実行 dish.Serve() } func main() { pizzaOrderService := NewOrderService(CreatePizza) pizzaOrderService.Order() pastaOrderService := NewOrderService(CreatePasta) pastaOrderService.Order() } JavaとはFactory Methodパターンの実現方法が異なる理由 48 Javaのコードではファクトリとし て class PizzaShop を定義 していますが、 Goのこの例では 代わりに func CreatePizza を使っ ています。 type PizzaShop struct の形で定義することもできます。
  24. © 2025 Bengo4.com, inc. Goの言語仕様で十分なケース 多くの場合、GoでIterator(イテレータ) パターンを自前で実装する必要はなさそ う。 for…rangeでは配列・スライス・文字列・ マップ・チャネルに対応しており、range

    over func使うとより柔軟かつシンプルな ループ処理を実現できる。 iterパッケージを組み合わせて使うとよく あるケースをrange over funcで簡単に実 現できる。 50 type Iterator interface { HasNext() bool Next() int } type HogeIterator struct { … } func NewHogeIterator(data []int) *HogeIterator { … } func (it *HogeIterator) HasNext() bool { … } func (it *HogeIterator) Next() int { … } func main() { it := NewHogeIterator([]int{1, 2, 3}) for it.HasNext() { fmt.Println(it.Next()) } } func reverse(s string) func(yield func(rune) bool) { return func(yield func(rune) bool) { for i := len([]rune(s)) - 1; i >= 0; i-- { yield([]rune(s)[i]) } } } func main() { for char := range reverse("あいうえお ") { fmt.Printf("%c", char) } // 出力: おえういあ }
  25. © 2025 Bengo4.com, inc. Goの言語仕様で十分なケース マルチスレッドデザインパターン にはGoの goroutine・channelでシンプルに実現出来 るものがある。 自前でキューや同期を管理するのは壊れ

    やすく難易度が高い。 サンプルはProducer-Consumer パターン を簡易的に表現したもの。 51 func producer(ch chan<- int) { defer close(ch) for i := 0; i < 5; i++ { fmt.Println("Produced:", i) ch <- i } } func consumer(ch <-chan int, wg *sync.WaitGroup) { defer wg.Done() for data := range ch { fmt.Println("Consumed:", data) } fmt.Println("Consumer finished.") } func main() { var wg sync.WaitGroup ch := make(chan int) wg.Add(1) go producer(ch) go consumer(ch, &wg) wg.Wait() fmt.Println("All processes finished.") }
  26. © 2025 Bengo4.com, inc. デザインパターンへの向き合い方 学んでみて感じた向き合い方 • 設計の選択肢として用途や実現方法を知っておく価値はある • 言語によって実現方法に差が出る

    ので、それを踏まえて使うべき • 読んだコードがどんなパターン にあてはまるのか分かるだけでも価値がある • 「ハンマーを持つ人にはすべてが釘に見える」現象に気をつけたい 53
  27. © 2025 Bengo4.com, inc. デザインパターンへの向き合い方 デザインパターンを採用する基準になりそうだと思った要素 • パターンの用途やメリットに目的が合致 しているか •

    複雑化とのトレードオフ に見合うのか • 所属している組織にて保守していける 内容なのか • 言語仕様やライブラリでシンプルに解決 出来ない問題なのか 54
  28. © 2025 Bengo4.com, inc. 学んでみた感想 • そのまま適用出来なくても考え方が設計のヒント になった • 普段開発しているプロダクトのコードから学ぶと頭に入りやすい

    • 言語ごとの思想の違い を感じられた(ポリモーフィズムの実現方法など) • Goのシンプルさ と強力な言語仕様・標準パッケージ のおかげでデザインパターンを あまり意識しなくても困らない場面が多いことに気づいた 56