go-defensive
APIの境界でGoコードを堅牢化する際に使用します。スライス・マップのコピー、インターフェース準拠の検証、`defer`によるリソース解放、`time.Time`/`time.Duration`の適切な利用、ミュータブルなグローバル変数の回避などが対象です。ユーザーが「防御的プログラミング」と明示しない場合でも、クリーンアップ漏れや安全でない暗号処理などの堅牢性に関するレビューにも適用されます。なお、エラーハンドリング戦略は対象外です(`go-error-handling`を参照)。
description の原文を見る
Use when hardening Go code at API boundaries — copying slices/maps, verifying interface compliance, using defer for cleanup, time.Time/time.Duration, or avoiding mutable globals. Also use when reviewing for robustness concerns like missing cleanup or unsafe crypto usage, even if the user doesn't mention "defensive programming." Does not cover error handling strategy (see go-error-handling).
SKILL.md 本文
Go 防御的プログラミングパターン
防御的チェックリスト優先順位
API 境界でコードを堅牢化する場合、この順序でチェックします:
API 境界をレビューしていますか?
├─ 1. エラーハンドリング → エラーを返す; panic しない (go-error-handling 参照)
├─ 2. 入力値検証 → 呼び出し元から受け取るスライス/マップをコピー
├─ 3. 出力安全性 → 呼び出し元に返す前にスライス/マップをコピー
├─ 4. リソースクリーンアップ → Close/Unlock/Cancel に defer を使用
├─ 5. インターフェースチェック → var _ Interface = (*Type)(nil) でコンパイル時検証
├─ 6. 時間正確性 → int/float ではなく time.Time と time.Duration を使用
├─ 7. Enum 安全性 → ゼロ値が無効になるよう iota を 1 から開始
└─ 8. 暗号安全性 → キー生成に crypto/rand を使用、math/rand は絶対禁止
クイックリファレンス
| パターン | ルール | 詳細 |
|---|---|---|
| 境界でのコピー | 受け取りと返却時にスライス/マップをコピー | BOUNDARY-COPYING.md |
| Defer クリーンアップ | os.Open 直後に defer f.Close() | 下記参照 |
| インターフェースチェック | var _ I = (*T)(nil) | go-interfaces 参照 |
| 時間型 | time.Time / time.Duration, 生の int は不可 | TIME-ENUMS-TAGS.md |
| Enum 開始 | iota + 1 でゼロ値が無効 | 下記参照 |
| 暗号 rand | キーは crypto/rand, math/rand は絶対禁止 | 下記参照 |
| Must 関数 | 初期化時のみ使用; 失敗時は panic | MUST-FUNCTIONS.md |
| Panic/Recover | パッケージ外に panic を漏らさない | PANIC-RECOVER.md |
| 可変グローバル | 依存注入で置き換え | 下記参照 |
インターフェース実装の確認
コンパイル時チェックを使用してインターフェース実装を確認します。詳細は go-interfaces: インターフェース満足度チェック を参照してください。
var _ http.Handler = (*Handler)(nil)
境界でスライスとマップをコピー
スライスとマップは基礎データへのポインタを含んでいます。意図しない変更を防ぐために API 境界でコピーします。
// 受け取り: 受け取ったスライスをコピー
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)
// 返却: 返す前にマップをコピー
result := make(map[string]int, len(s.counters))
for k, v := range s.counters { result[k] = v }
API 境界でスライスやマップをコピーする場合、または防御的コピーが必要か不要かを判断する場合は
references/BOUNDARY-COPYING.mdを読んでください。
Defer でクリーンアップ
リソース (ファイル、ロック) をクリーンアップするには defer を使用します。複数の return パスでクリーンアップを漏らすことを防ぎます。
p.Lock()
defer p.Unlock()
if p.count < 10 {
return p.count
}
p.count++
return p.count
Defer のオーバーヘッドは無視できるレベルです。明確性のために defer f.Close() を os.Open の直後に配置してください。遅延関数の引数は defer が実行される時点で評価され、関数が実行される時点ではありません。複数の defer は LIFO 順で実行されます。
構造体フィールドタグ
推奨: マーシャル/アンマーシャルされる構造体には常に明示的なフィールドタグを追加してください。
type User struct {
Name string `json:"name" yaml:"name"`
Email string `json:"email" yaml:"email"`
}
フィールドタグは シリアライゼーション契約 です — タグを更新せずに構造体フィールドの名前を変更すると、ワイヤ互換性が無声で破壊されます。シリアライゼーション境界を超えるあらゆる型について、タグを公開 API の一部として扱ってください。
Enum を 1 から開始
初期化されていない値と有効な値を区別するために、enum をゼロ以外から開始します。
const (
Add Operation = iota + 1 // Add=1, ゼロ値 = 初期化されていない
Subtract
Multiply
)
例外: ゼロが適切なデフォルト値である場合 (例: LogToStdout = iota)。
Time、構造体タグ、埋め込み
生の int ではなく
time.Time/time.Durationを使用する場合、マーシャルされた構造体にフィールドタグを追加する場合、または公開構造体に型を埋め込むかどうかを判断する場合はreferences/TIME-ENUMS-TAGS.mdを読んでください。
可変グローバルを避ける
パッケージレベル変数を変更する代わりに、依存関係を注入します。これにより、グローバルな保存/復元なしでコードをテストできるようになります。
type signer struct {
now func() time.Time // 注入済み; テストで固定時刻に置き換え
}
func newSigner() *signer {
return &signer{now: time.Now}
}
グローバル変数が適切かどうかを判断する場合、New() + Default() パッケージ状態パターンを設計する場合、または可変グローバルを依存注入で置き換える場合は
references/GLOBAL-STATE.mdを読んでください。
暗号 Rand
キー生成に 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 することは許容される - サーバー goroutine ハンドラで panic を分離するために recover を使用
HTTP サーバーで panic リカバリを書く場合、パーサーで panic を内部制御フロー機構として使用する場合、または log.Fatal と panic の間で選択する場合は
references/PANIC-RECOVER.mdを読んでください。
Must 関数
Must 関数はエラー時に panic します — プログラム初期化中に のみ 使用します。失敗はプログラムが実行不可能であることを意味します。
var validID = regexp.MustCompile(`^[a-z][a-z0-9-]{0,62}$`)
var tmpl = template.Must(template.ParseFiles("index.html"))
カスタム Must 関数を書く場合、与えられた呼び出しサイトで Must が適切かどうかを判断する場合、またはフォールバック初期化を panic ヘルパーで包装する場合は
references/MUST-FUNCTIONS.mdを読んでください。
関連スキル
- エラーハンドリング: エラーを返すか panic するかの選択、または境界でのエラーのラッピングについては
go-error-handlingを参照してください - 並行安全性: ミューテックス、アトミック、またはチャネルで共有状態を保護する場合は
go-concurrencyを参照してください - インターフェースチェック: コンパイル時インターフェース満足度チェック (
var _ I = (*T)(nil)) を追加する場合はgo-interfacesを参照してください - データ構造のコピー: スライス/マップの内部やポインタエイリアシングを扱う場合は
go-data-structuresを参照してください
ライセンス: Apache-2.0(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- cxuu
- リポジトリ
- cxuu/golang-skills
- ライセンス
- Apache-2.0
- 最終更新
- 不明
Source: https://github.com/cxuu/golang-skills / ライセンス: Apache-2.0
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。