Menu
Coddy logo textTech

コード生成の概要

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 パターンでは、コード生成が安全性と利便性の最適なバランスを提供することがよくあります。

challenge icon

チャレンジ

簡単

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 を使用して完全なコードを生成して出力する
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Goオンラインコンパイラ