コンテンツへスキップ

QualiArtsengineer blog

Go Conference 2025に登壇しました

Go Conference 2025に登壇しました

4 min read

2025年9月27日から9月28日にかけて開催されたカンファレンス「Go Conference 2025」において、弊社のエンジニアが登壇いたしました。

Go Conferenceとは

プログラミング言語「Go」を扱うユーザーのためのカンファレンスです。 Go言語の技術や活用事例、コミュニティの知見を共有する場として、多くのGopher(Go言語開発者)が参加しています。

Go Conference 2025

コード生成なしでモック処理を実現!ovechkin-dm/mockioで学ぶメタプログラミング

本セッションでは、コード生成を必要とせずランタイムでモック処理を実現するovechkin-dm/mockio[1]というライブラリを取り上げ、Go言語におけるメタプログラミングの手法について解説します。

モックライブラリは単体テストにおける強力なツールです。 依存モジュールの振る舞いを一時的に置き換え、対象ロジックのテストを容易にします。 uber-go/mock[2]やmatryer/moq[3]といった既存のGoモックライブラリの多くは、事前のコード生成を前提とした設計になっています。 一方、本セッションで取り扱うmockioは、code1に示す通り、コード生成を一切必要とせずに型安全なモック処理を実現する革新的な手法を採用しています。

mockioの根幹を支えるのがovechkin-dm/go-dyno[4]です。 go-dynoは、インターフェース型を渡すとそれを満たす構造体を動的に生成するDynamic関数を提供します。 さらに、生成された構造体のメソッド呼び出し時に任意の処理を発火させるプロキシ関数(code2のCalculatorHandler参照)を渡すことが可能で、これがモック処理の肝になります。

本セッションでは、go-dynoがどのようにしてインターフェースの型情報から、それを満たす構造体を動的に生成しているのか、メソッドの中身を自由に定義することができるのかを深く掘り下げます。 コード生成なしで型安全なメタプログラミングを実現するテクニックについて、generics, reflect, unsafe, assemblyを活用した内部実装を詳細に解き明かします。

また、mockioのモックライブラリとしての価値にも言及します。 コード生成を行わないモックアプローチの機能的な・パフォーマンス的なメリットと課題について、既存モックライブラリと比較・評価します。

Goのランタイムの理解を深めたい方やモックライブラリ開発に興味がある方にとって、このセッションが有益な情報となれば幸いです。

code1: mockioを活用した単体テスト

// main.go
package main

type Calculator interface {
	Add(a, b int) int
}

func Sum(calculator Calculator, nums ...int) int {
	var result int
	for _, num := range nums {
		result = calculator.Add(result, num)
	}
	return result
}


// main_test.go
package main

import (
	"testing"
	"github.com/stretchr/testify/assert"
	"github.com/ovechkin-dm/mockio/v2/mock"
)

func Test_Sum(t *testing.T) {
	ctrl := mock.NewMockController(t)

	// NOTE: コード生成を行わずにモックを実現
	mockCalculator := mock.Mock[Calculator](ctrl)
	mock.When(mockCalculator.Add(0, 1)).ThenReturn(1)
	mock.When(mockCalculator.Add(1, 2)).ThenReturn(3)

	result := Sum(mockCalculator, 1, 2)
	assert.Equal(t, 3, result)

	mock.Verify(mockCalculator, mock.Times(1)).Add(0, 1)
	mock.Verify(mockCalculator, mock.Times(1)).Add(1, 2)
}

code2: go-dynoによるメタプログラミング

package main

import (
	"fmt"
	"reflect"

	"github.com/ovechkin-dm/go-dyno/pkg/dyno"
)

type Calculator interface {
	Add(a, b int) int
	Sub(a, b int) int
}

// NOTE: ランタイムで生成される構造体が持つメソッドの振る舞いをプロキシとして定義
func CalculatorHandler(m reflect.Method, values []reflect.Value) []reflect.Value {
	fmt.Println("Method called:", m.Name)
	switch m.Name {
	case "Add":
		return []reflect.Value{reflect.ValueOf(int(values[0].Int() + values[1].Int()))}
	case "Sub":
		return []reflect.Value{reflect.ValueOf(int(values[0].Int() - values[1].Int()))}
	}
	return nil
}

func main() {
	// NOTE: Calculator型を満たす構造体をランタイムで生成
	dynamicCalculator, _ := dyno.Dynamic[Calculator](CalculatorHandler)

	addResult := dynamicCalculator.Add(2, 1) // Output: Method called: Add
	fmt.Println(addResult) // Output: 3

	subResult := dynamicCalculator.Sub(2, 1) // Output: Method called: Sub
	fmt.Println(subResult) // Output: 1
}

[1]: https://github.com/ovechkin-dm/mockio
[2]: https://github.com/uber-go/mock
[3]: https://github.com/matryer/moq
[4]: https://github.com/ovechkin-dm/go-dyno

なぜGoのジェネリクスはこの形なのか? - Featherweight Goが明かす設計の核心

Go 1.18でジェネリクスが導入されました。本セッションでは、その仕様、特に「なぜ型制約にinterfaceを使うのか」「なぜコンパイルにモノモーフィゼーションという手法が選ばれたのか」という設計上の選択について、その背景を解説します。 この設計の背景には、Goジェネリクス設計の根幹をなす理論的支柱、Featherweight Goという論文があります。この論文は、Goの父であるRob Pike氏や、Haskellなどで知られる型理論の権威Philip Wadler氏らが参加し、Goのジェネリクスに「理論的な正しさ」を与えるために行われました。この論文で示された理論的な裏付けが、初期案であったcontractsから現在のinterfaceベースの仕様へと繋がりました。 このセッションでは、Featherweight Go論文を基に、Goのジェネリクスの仕組みを深掘りし、以下の点をお話しします。

  • interfaceが型制約として採用された経緯(contracts案との比較)
  • モノモーフィゼーションの仕組みと、それがもたらす性能上のメリット
  • 現在の仕様では実現できない「共変レシーバ」と、それが解決する「式問題」の解説

このセッションを聞くことで、Goのジェネリクスがなぜ現在の仕様になったのかを深く理解し、そのトレードオフを意識した上で、より自信を持ってコードを書けるようになります。