Go言語における契約プログラミングと有効状態の保証

に公開

Go言語における契約プログラミングと有効状態の保証

ゼロ値とドメインモデル

Go言語は「ゼロ値」を持つことで、型の初期化をしないでもコンパイルしますが、それはときに無効なドメインオブジェクトを作ります。たとえば UserID("")Order{} はコンパイルするものの、ビジネスルール上は無効な状態です。

契約プログラミングの手段

1. Validate() error メソッド

ドメインオブジェクトには、メソッド Validate() error を実装させ、有効状態を保証します。これを通して、各型は自分の有効性を自分で検証できるようにします。

type Validator interface {
    Validate() error
}

2. コンストラクタ内での検証

型の初期化はコンストラクタの関数 (NewXxx)に限定し、その中で必要な前提条件を検証します:

func NewUserID(id string) (UserID, error) {
    if len(id) == 0 {
        return "", fmt.Errorf("UserID must not be empty")
    }
    return UserID(id), nil
}

後続部でも Validate() を呼び出せば、未知な入力やデータの加工に対応できます。

3. AssertValidatable への落とし込み

既定の合成ファイルでは AssertValidatable(v) のようなヘルパー関数を用意し、nil検査+検証をまとめて行います。

func AssertValidatable(v Validator) {
    if v == nil {
        panic("Validator is nil")
    }
    if err := v.Validate(); err != nil {
        panic(err)
    }
}

これは「プログラマーのミスは明確にパニックで示せ」という fail-fast 思想に基づいたものです。

4. DTO/外部インプットは error 戻し

ライブラリやサービス内では panic は推奨されないため、ユーザー入力などが原因の障害は error 戻しにします。

おわりに

Go言語では「型は作成された時点で有効であるべき」という思想が重要です。これを実現するために、Validateメソッドやコンストラクタでの検証を用いて、未検証でのインスタンス作成を防ぐことが推奨されます。

無効状態のオブジェクトをなるべく早期に検出し、平文時に修正を可能にするという思想は、Goのドメインデザインにおいて彼正しいプラクティスとされています。

GitHubで編集を提案

Discussion