Go/defer
表示
< Go
Goのdeferキーワードについて
[編集]Goプログラミング言語におけるdeferキーワードは、関数の実行を遅延させるために使用される重要な機能です。defer文で指定された関数呼び出しは、それを含む関数(外側の関数)がreturnする直前に実行されます。
基本的な使い方
[編集]defer 関数呼び出し
主な特徴
[編集]- 後入れ先出し(LIFO: Last In, First Out): 複数の
defer文がある場合、最後に定義されたものから順に実行される - 引数の即時評価:
defer文の引数は、defer文が実行された時点で評価される - パニックハンドリング: パニックが発生した場合でも、
defer文は実行される - スコープ:
defer文は、それを含む関数内でのみ有効
他のキーワードとの組み合わせとユースケース
[編集]1. リソース管理との組み合わせ
[編集]func readFile(filename string) ([]byte, error) { file, err := os.Open(filename) if err != nil { return nil, err } defer file.Close() // 関数終了時に必ずファイルを閉じる return ioutil.ReadAll(file) }
ユースケース:
- ファイルハンドルのクローズ
- ネットワーク接続の終了
- ロックの解放
- データベース接続のクローズ
func safeOperation() (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("パニックが発生しました: %v", r) } }() // パニックが発生する可能性のある処理 performRiskyOperation() return nil }
ユースケース:
- パニックのキャッチと処理
- グレースフルなエラー回復
- パニックからのエラー変換
3. 関数引数の即時評価の活用
[編集]func main() { a := 1 defer fmt.Println("a:", a) // この時点での値(1)が使われる a = 2 fmt.Println("関数内のa:", a) // 2を表示 } // 出力: // 関数内のa: 2 // a: 1
ユースケース:
- 関数実行時の状態を記録
- 変数の元の値の保存
- デバッグや実行追跡
4. 無名関数との組み合わせ
[編集]func processData() { resource := acquireResource() defer func() { releaseResource(resource) fmt.Println("リソースを解放しました") }() // リソースを使用した処理 }
ユースケース:
- 複雑なクリーンアップ処理
- ローカル変数へのアクセス
- 条件付きの後処理
5. エラー処理との組み合わせ
[編集]func processWithErrorHandling() (err error) { // 最終的なエラー状態を処理 defer func() { if err != nil { log.Printf("エラーが発生しました: %v", err) } }() if err = step1(); err != nil { return err } if err = step2(); err != nil { return err } return nil }
ユースケース:
- エラーロギング
- エラー状態の変換や拡張
- 命名された戻り値の操作
6. トレース/プロファイリングとの組み合わせ
[編集]func timeTrack(start time.Time, name string) { elapsed := time.Since(start) log.Printf("%s の実行時間: %s", name, elapsed) } func someFunction() { defer timeTrack(time.Now(), "someFunction") // 実行時間を測定したい処理 time.Sleep(1 * time.Second) }
ユースケース:
- 関数の実行時間測定
- 処理のトレース
- パフォーマンスプロファイリング
7. ミューテックス/ロックとの組み合わせ
[編集]func updateSharedResource() { mu.Lock() defer mu.Unlock() // 関数終了時に確実にロックを解放 // 共有リソースの更新処理 }
ユースケース:
- 排他制御の簡素化
- デッドロック防止
- クリティカルセクションの管理
8. トランザクション管理との組み合わせ
[編集]func performDatabaseOperation(db *sql.DB) error { tx, err := db.Begin() if err != nil { return err } defer tx.Rollback() // エラー時は自動的にロールバック // トランザクション内の操作 if _, err := tx.Exec("INSERT INTO users (name) VALUES (?)", "John"); err != nil { return err } return tx.Commit() // 成功時はコミット }
ユースケース:
- データベーストランザクションの管理
- 失敗時の自動ロールバック
- ACID特性の確保
9. HTTPリクエスト処理との組み合わせ
[編集]func handleRequest(w http.ResponseWriter, r *http.Request) { // リクエストボディを必ず閉じる defer r.Body.Close() // リクエスト処理 body, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, "リクエストの読み取りに失敗しました", http.StatusBadRequest) return } // 処理結果をレスポンスとして返す w.Write([]byte("処理完了")) }
ユースケース:
- HTTPリクエストの後処理
- レスポンスヘッダーの設定
- エラーレスポンスの統一的な処理
10. コンテキスト(context)との組み合わせ
[編集]func processWithContext(ctx context.Context) error { ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() // 関数終了時にキャンセル関数を呼び出す return doSomethingWithContext(ctx) }
ユースケース:
- コンテキストのキャンセル保証
- リソースリークの防止
- タイムアウト処理の適切な終了
11. 複数のdeferの順序制御
[編集]func multipleDefers() { fmt.Println("開始") defer fmt.Println("defer 1") // 最後から2番目に実行 defer fmt.Println("defer 2") // 最後に実行 defer fmt.Println("defer 3") // 最初に実行 fmt.Println("処理中") } // 出力: // 開始 // 処理中 // defer 3 // defer 2 // defer 1
ユースケース:
- 複数のリソースの順序付き解放
- 入れ子の処理の管理
- クリーンアップの優先順位付け
12. 値の変更とdeferの組み合わせ
[編集]func modifyReturnValue() (result int) { result = 1 defer func() { result *= 2 }() // 戻り値を変更 return 1 // 実際には2が返される }
ユースケース:
- 戻り値の後処理
- 統計情報の集計
- 最終値の調整
使用上の注意点
[編集]- パフォーマンス:
deferは若干のオーバーヘッドを伴うため、頻繁に呼び出される小さな関数内での過剰使用は避ける - 引数評価:
defer文の引数は遅延実行ではなく即時評価されることを理解する - 順序: 複数の
deferは逆順に実行されることを考慮する - ループ内での使用: ループ内で
deferを使用すると、関数が終了するまで解放されないため注意が必要 - パニック後の実行: パニックが発生しても
defer文は実行されるが、os.Exit()の呼び出し後は実行されない
deferキーワードは、Goにおけるリソース管理とエラー処理のための強力なツールであり、適切に使用することでコードの信頼性と可読性を大きく向上させることができます。