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() メソッドを含む新しいファイルが作成されます。生成されたコードは通常の Go であり、プログラムと一緒にコンパイルされます。

Goのエコシステムにおける一般的なコード生成ツールには、列挙型(enum)の文字列メソッドのためのstringer、インターフェースからテスト用モックを作成するためのmockgen、およびProtocol Bufferの定義からコードを生成するためのprotocが含まれます。多くのORMやAPIフレームワークも、型安全なデータベースクエリやクライアントコードを作成するために生成機能を使用しています。

リフレクションに対する主な利点は、生成されたコードがコンパイラによってチェックされることです。エラーは実行時ではなくビルド時に検出されます。トレードオフは、追加のビルドステップと、リポジトリ内で管理する生成ファイルが必要になることです。多くの型にわたって反復的な実装を必要とするOOPパターンにおいて、コード生成は多くの場合、安全性と利便性の最良のバランスを提供します。

challenge icon

チャレンジ

簡単

列挙型のような型に対して 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 を使用して完全なコードを生成し、出力する
}
quiz icon腕試し

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

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