コード生成の概要
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 84/107。
コード生成は、実行時に型を検査するのではなく、コンパイル時にGoのソースコードを作成するリフレクションの代替手段です。このアプローチにより、繰り返しのボイラープレートコードを削減しながら、型安全性とパフォーマンスの向上を実現できます。
Goは、コード生成を自動化するためのgo generateコマンドを提供しています。ソースファイル内に特別なコメントディレクティブを配置し、go generateを実行することで、指定されたツールが実行されます:
//go:generate stringer -type=Status
type Status int
const (
Pending Status = iota
Active
Completed
)go generate ./... を実行すると、stringer ツールが呼び出され、Status 型のための String() メソッドを含む新しいファイルが作成されます。生成されたコードは通常の Go であり、プログラムと一緒にコンパイルされます。
Goのエコシステムにおける一般的なコード生成ツールには、列挙型(enum)の文字列メソッドのためのstringer、インターフェースからテスト用モックを作成するためのmockgen、およびProtocol Bufferの定義からコードを生成するためのprotocが含まれます。多くのORMやAPIフレームワークも、型安全なデータベースクエリやクライアントコードを作成するために生成機能を使用しています。
リフレクションに対する主な利点は、生成されたコードがコンパイラによってチェックされることです。エラーは実行時ではなくビルド時に検出されます。トレードオフは、追加のビルドステップと、リポジトリ内で管理する生成ファイルが必要になることです。多くの型にわたって反復的な実装を必要とするOOPパターンにおいて、コード生成は多くの場合、安全性と利便性の最良のバランスを提供します。
チャレンジ
簡単列挙型のような型に対して String() メソッドを作成するコードジェネレーターを構築しましょう!この環境では実際の go generate を実行することはできませんが、型定義に基づいてプログラム的に Go のソースコードを生成するという、コード生成ツールが行うことをシミュレートします。
コードは2つのファイルに分けて構成します:
generator.go: コード生成ロジックを作成します。Name(string) とValues(定数名を表す文字列のスライス) を持つ、列挙型のような型定義を表すEnumType構造体を構築します。String()メソッドの Go ソースコードを生成する関数GenerateStringMethod(e EnumType) stringを作成します。生成されるコードは以下のパターンに従う必要があります:func (e TypeName) String() string { switch e { case Value1: return "Value1" case Value2: return "Value2" default: return "Unknown" } }また、
iotaを使用して定数宣言を生成する関数GenerateConstants(e EnumType) stringも作成します:const ( Value1 TypeName = iota Value2 Value3 )最後に、これが生成されたコードであることを示すヘッダーコメントと共に、両方の出力を組み合わせる
GenerateFullCode(e EnumType) stringを作成します:// Code generated by enum generator. DO NOT EDIT. [constants] [string method]main.go: ジェネレーターを使用して、さまざまな列挙型のコードを生成します。列挙型の名前を読み取り、次に値の数、続いて各値の名前を読み取ります。この情報をジェネレーターに渡し、生成された完全なコードを出力します。
以下の入力が提供されます:
- 1行目:列挙型の名前
- 2行目:値の数
- 続く行:各値の名前
例えば、以下が与えられた場合:
Status
3
Pending
Active
Completed出力は以下のようになります:
// Code generated by enum generator. DO NOT EDIT.
const (
Pending Status = iota
Active
Completed
)
func (e Status) String() string {
switch e {
case Pending:
return "Pending"
case Active:
return "Active"
case Completed:
return "Completed"
default:
return "Unknown"
}
}また、以下が与えられた場合:
Priority
2
Low
High出力は以下のようになります:
// Code generated by enum generator. DO NOT EDIT.
const (
Low Priority = iota
High
)
func (e Priority) String() string {
switch e {
case Low:
return "Low"
case High:
return "High"
default:
return "Unknown"
}
}この演習は、stringer のようなツールの背後にある中心的な概念、つまり実行時にリフレクションを使用するのではなく、コンパイル時に反復的だが型安全なコードを生成することを実証しています。作成したジェネレーターは、ファイルに保存してプロジェクトの残りの部分と一緒にコンパイルできる有効な Go コードを生成します。
チートシート
コード生成は、実行時に型を検査するのではなく、コンパイル時にGoのソースコードを作成します。これにより、ボイラープレートを削減しながら、型安全性とパフォーマンスの向上を実現します。
go generateコマンドは、特別なコメントディレクティブを使用してコード生成を自動化します:
//go:generate stringer -type=Status
type Status int
const (
Pending Status = iota
Active
Completed
)go generate ./...を実行すると、指定されたツール(stringerなど)が呼び出され、生成されたコードを含む新しいファイルが作成されます。このファイルはプログラムと一緒にコンパイルされます。
一般的なコード生成ツールには以下が含まれます:
stringer- 列挙型(enum)のString()メソッドを生成しますmockgen- インターフェースからテスト用のモックを作成しますprotoc- Protocol Bufferの定義からコードを生成します
リフレクションに対する主な利点:
- 生成されたコードはコンパイラによってチェックされます
- エラーは実行時ではなくビルド時に検出されます
- パフォーマンスの向上
トレードオフとしては、追加のビルドステップが必要になることや、リポジトリ内で管理すべき生成ファイルが発生することが挙げられます。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 列挙型の名前を読み込む
typeName, _ := reader.ReadString('\n')
typeName = strings.TrimSpace(typeName)
// 値の数を読み込む
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// 各値の名前を読み込む
values := make([]string, count)
for i := 0; i < count; i++ {
value, _ := reader.ReadString('\n')
values[i] = strings.TrimSpace(value)
}
// TODO: 読み込んだデータを使用して EnumType を作成する
// TODO: GenerateFullCode を使用して完全なコードを生成し、出力する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー