mcpField
G
go-defensive

go-defensive

github.com/cxuu開発1861.2k 回インストール19 週間前に更新
リポジトリ

go-defensive について

go-defensiveは、GoのAPI境界における防御的プログラミングの実践をまとめたガイドです。データの意図しない共有、リソースリーク、エラー処理のリスクを抑えたい開発者向けに、コピー、後処理、列挙値、乱数、panicの扱いを説明します。

主な機能

  1. 01API境界でスライスとマップをコピー
  2. 02deferによるリソース解放
  3. 03可変なグローバル状態の回避
  4. 04列挙値を非ゼロから開始
  5. 05暗号学的乱数と通常の乱数を区別
  6. 06panicを制限しrecoverを適切に使用

活用シーン

  1. 01API境界におけるデータ安全性のレビュー
  2. 02ファイル、ロック、キャンセル処理の後始末確認
  3. 03列挙値、グローバル状態、panicの使い方の評価

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/cxuu/golang-skills --skill go-defensive

インストール後、Claude Code で/go-defensiveを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
BOUNDARY-COPYING.md
GLOBAL-STATE.md
MUST-FUNCTIONS.md
PANIC-RECOVER.md
TIME-ENUMS-TAGS.md

詳細

Go の防御的プログラミングパターン

互換性:暗号関連の例では crypto/rand.Text を使用する場合があります。この機能には Go 1.24 以降が必要です。

リソースのルーティング

  • references/BOUNDARY-COPYING.md - API 境界を越えてスライス/マップをコピーする場合に読みます。
  • references/GLOBAL-STATE.md - パッケージグローバル変数を導入または削除する場合に読みます。
  • references/MUST-FUNCTIONS.md - エラー時に panic するヘルパーが許容されるか判断する場合に読みます。
  • references/PANIC-RECOVER.md - panic、recover、またはクラッシュの封じ込めを評価する場合に読みます。
  • references/TIME-ENUMS-TAGS.md - 時刻型、列挙型のゼロ値、または構造体タグを扱う場合に読みます。

防御的プログラミングのチェックリストの優先順位

API 境界でコードを堅牢化する場合は、次の順に確認します。

Reviewing an API boundary?
├─ 1. Error handling     → Return errors; don't panic (see go-error-handling)
├─ 2. Input validation   → Copy slices/maps received from callers
├─ 3. Output safety      → Copy slices/maps before returning to callers
├─ 4. Resource cleanup   → Use defer for Close/Unlock/Cancel
├─ 5. Interface checks   → Route compile-time assertions to go-interfaces
├─ 6. Time correctness   → Use time.Time and time.Duration, not int/float
├─ 7. Enum safety        → Start iota at 1 so zero-value is invalid
└─ 8. Crypto safety      → crypto/rand for keys, never math/rand

クイックリファレンス

パターンルール詳細
境界でのコピー受け取り時と返却時にスライス/マップをコピーするBOUNDARY-COPYING.md
defer によるクリーンアップos.Open の直後に defer f.Close() を記述する以下を参照
インターフェースの確認コンパイル時の実装確認アサーションgo-interfaces を参照
時刻型time.Time / time.Duration を使い、生の int は使わないTIME-ENUMS-TAGS.md
列挙型の開始値iota + 1 を使い、ゼロを無効値にする以下を参照
暗号用乱数キーには crypto/rand を使い、math/rand は使わない以下を参照
Must 関数初期化時のみ使用し、失敗時は panic するMUST-FUNCTIONS.md
panic/recoverパッケージ境界を越えて panic を公開しないPANIC-RECOVER.md
可変グローバル変数依存性注入に置き換える以下を参照

インターフェースへの準拠を確認する

コンパイル時のインターフェースアサーションについては、go-interfaces に任せてください。 このスキルは、API 境界における堅牢性のリスクに気づくためだけに使用します。アサーションが適切な場面と、具体的なアサーションの形式はインターフェーススキルが扱います。

境界でスライスとマップをコピーする

スライスとマップには、基になるデータへのポインターが含まれています。意図しない変更を防ぐため、API 境界でコピーします。

// Receiving: copy incoming slice
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)

// Returning: copy map before returning
result := make(map[string]int, len(s.counters))
for k, v := range s.counters { result[k] = v }

defer でクリーンアップする

defer を使って、ファイルやロックなどのリソースをクリーンアップします。複数の return 経路でクリーンアップ漏れが発生するのを防げます。

p.Lock()
defer p.Unlock()

if p.count < 10 {
  return p.count
}
p.count++
return p.count

defer のオーバーヘッドは無視できる程度です。分かりやすさのため、os.Open の直後に defer f.Close() を記述します。defer された関数の引数は、その関数の実行時ではなく、 defer の実行時に評価されます。複数の defer は LIFO 順に実行されます。

構造体フィールドのタグ

推奨事項:マーシャルまたはアンマーシャルされる構造体には、必ず明示的なフィールドタグを追加してください。

type User struct {
    Name  string `json:"name"  yaml:"name"`
    Email string `json:"email" yaml:"email"`
}

フィールドタグはシリアライズの契約です。タグを更新せずに構造体フィールドの名前を変更すると、 ワイヤー互換性が気づかれないまま損なわれます。シリアライズ境界を越える型では、タグを公開 API の一部として扱ってください。

列挙型は 1 から始める

未初期化の値と有効な値を区別するため、列挙型はゼロ以外の値から始めます。

const (
  Add Operation = iota + 1  // Add=1, zero value = uninitialized
  Subtract
  Multiply
)

例外:ゼロが妥当なデフォルト値である場合(例:LogToStdout = iota)。

時刻、構造体タグ、埋め込み

可変グローバル変数を避ける

パッケージレベルの変数を変更するのではなく、依存関係を注入します。これにより、グローバル変数の保存と復元をせずにコードをテストできます。

type signer struct {
  now func() time.Time  // injected; tests replace with fixed time
}

func newSigner() *signer {
  return &signer{now: time.Now}
}

暗号用乱数

キーの生成に math/rand または math/rand/v2 を使用しないでください。これはセキュリティ上の懸念です。時刻をシードにするジェネレーターは、出力を予測できます。

import "crypto/rand"

func Key() string { return rand.Text() }

テキスト出力には crypto/rand.Text を直接使うか、ランダムなバイト列を encoding/hex または encoding/base64 でエンコードします。


Panic と Recover

本当に回復不能な状況でのみ panic を使用してください。ライブラリ関数では panic を避けるべきです。

func safelyDo(work *Work) {
    defer func() {
        if err := recover(); err != nil {
            log.Println("work failed:", err)
        }
    }()
    do(work)
}

主なルール:

  • パッケージ境界を越えて panic を公開しないでください。必ずエラーに変換してください。
  • ライブラリが自身をセットアップできない場合は、init() で panic してもかまいません。
  • サーバーのゴルーチンハンドラーで panic を隔離するには recover を使用してください。

Must 関数

Must 関数はエラー時に panic します。失敗するとプログラムを実行できない場合の、プログラム初期化時に限って使用してください。

var validID = regexp.MustCompile(`^[a-z][a-z0-9-]{0,62}$`)
var tmpl = template.Must(template.ParseFiles("index.html"))

関連するスキル

  • エラー処理:エラーを返すかパニックを起こすかの選択、または境界でエラーをラップする場合は、go-error-handlingを参照してください
  • 並行処理の安全性:ミューテックス、アトミック操作、チャネルで共有状態を保護する場合は、go-concurrencyを参照してください
  • インターフェースのチェック:コンパイル時のインターフェース充足チェックを追加する場合は、go-interfacesを参照してください
  • データ構造のコピー:スライス/マップの内部構造やポインターのエイリアスを扱う場合は、go-data-structuresを参照してください

コメント