Programmingバックエンド開発者

Goにおけるdeferred function parametersの動作の特徴を説明してください: deferが発動する瞬間にパラメータがどのように計算され、これはdeferが発動した時点での関数呼び出しと何が違うのか。

Hintsage AIアシスタントで面接を突破

答え。

Deferは、現在の関数が終了した後に関数を実行できるGoのユニークなメカニズムで、panicが発生してもそうです。歴史的には、on-exit構造の類似物ですが、Goではdeferred呼び出しのスタックとして実装されています。重要なポイントは、deferred関数のパラメータはdeferの宣言時に直ちに計算され、実際の呼び出し時には計算されないということです!

問題 — 明白ではない振る舞い: deferが発動する時にパラメータが渡されると期待するかもしれませんが、実際にはそうではありません。これは、可変な変数や外部変数に対処する際にバグを引き起こすことがよくあります。

解決策 — パラメータは直ちに計算され、deferの効果がその後発生することを常に考慮してください。

コードの例:

func f() { x := 5 defer fmt.Println(x) x = 10 } // 出力: 5, ではなく10

主な特徴:

  • deferred関数のパラメータの値はdeferの宣言時に計算されます。
  • deferred関数はpanicが発生しても必ず実行されます(panicが事前にキャッチされていない限り)。
  • deferredで無名関数を宣言すると、変数の現在の値にアクセスできます。

ひねりのある質問。

次のコードは何を出力しますか?なぜ?

func main() { i := 0 defer fmt.Println(i) i = 1 }

答え: 0が出力されます。fmt.Printlnの引数はdeferの宣言時に保存されます。

deferの宣言後に変数の変更は、その値が関数に渡されることに影響しますか?

いいえ、影響しません — 計算はdeferの宣言時に行われます:

defer fmt.Println(x) // 値xは今保存され、後ではありません

deferを使って変数の最終的な状態を出力できますか?

はい、無名関数(クロージャ)を使えばできます:

defer func() { fmt.Println(x) }() // deferredの呼び出し時にxの現在の値をキャッチします

よくある間違いとアンチパターン

  • パラメータがdeferの呼び出し時に現行のものであることを期待すること。
  • 可変変数と一緒にパラメータを使用したdeferの利用。
  • 明示的な依存関係の記述がない混乱したdeferスタック。

実生活の例

ネガティブケース

コードが次のように呼び出されます:

var f *os.File // ... defer f.Close()

しかし、fは後で割り当てられるので、nilポインタによるpanicが発生します!

利点:

  • 変数がすでに初期化されている場合、短い記述。

欠点:

  • もし f == nil であれば — panicが発生します。

ポジティブケース

無名のdeferred関数を使用して清掃処理をラップし、確認を行います:

defer func() { if f != nil { f.Close() } }()

利点:

  • 安全性とpanicの回避。

欠点:

  • より長く、「ノイズ」の多い記述。