コード生成の概要
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() methodを含む新しいファイルが作成されます。generated codeは、プログラムとともにコンパイルされる通常のGoコードです。
Go エコシステムで一般的なコード生成ツールには、enum の文字列メソッド用の stringer、インターフェースからテスト用モックを作成する mockgen、Protocol Buffer の定義からコードを生成する protoc などがあります。多くの ORM や API フレームワークでも、型安全なデータベースクエリやクライアントコードを作成するために生成が利用されています。
リフレクションに対する主な利点は、生成されたコードがコンパイラーによってチェックされることです。エラーは実行時ではなく、ビルド時に検出されます。その代わりに、追加のビルド手順と、リポジトリで管理する生成ファイルが必要になります。多くの型にわたって反復的な実装を必要とする OOP パターンでは、コード生成が安全性と利便性の最適なバランスを提供することがよくあります。
チャレンジ
簡単enum のような型用の String() メソッドを作成するコードジェネレーターを構築しましょう!この環境では実際の go generate を実行できませんが、型定義に基づいてプログラムで Go ソースコードを生成するという、コード生成ツールの動作をシミュレートします。
コードを2つのファイルに分けて整理します。
generator.go: コード生成ロジックを作成します。Name(string)と、定数名を表す文字列のスライスであるValuesを持ち、enum のような型定義を表す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: ジェネレーターを使用して、さまざまな enum 型用のコードを生成します。enum の型名を読み取り、続けて値の個数と各値の名前を読み取ります。この情報をジェネレーターに渡し、生成された完全なコードを出力します。
次の入力が提供されます。
- 1行目: Enum の型名
- 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 コードであり、ファイルに保存してプロジェクトの他の部分とともにコンパイルできます。
自分で試してみよう
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復習 - ファイルパーサー自分で練習してみよう: Goオンラインコンパイラ