問題の歴史:
シャドーイングとは、内部スコープで同じ名前の変数が宣言される現象で、外部スコープの変数を覆い隠す(シャドーする)ことを指します。Swift の初期バージョンではコンパイラはシャドーイングに対して厳格ではありませんでしたが、言語モデルが複雑になるにつれ、バグを避けるためにこのようなケースを明示的に識別することが必須となりました。
問題:
シャドーイングはコードの保守を困難にし、目に見えないエラーを引き起こす可能性があります。同じ名前の変数が誤って宣言されると、開発者が期待する変数にアクセスできなくなります。特に、参照による変数の渡しやループ、クロージャ内では、エラーが実行時にのみ発生することがあります。
解決策:
Swift はシャドーイングを許可しますが、重要な変数やプロパティのシャドーイングを避けることが推奨されます。異なる名前を使用するか、明示的な self/this リファレンスを使用するべきです。特に struct/class のイニシャライザでは、self を用いてメンバーとパラメータを区別することが推奨されます:
コード例:
struct User { let name: String init(name: String) { self.name = name // selfは明示的な代入を保証します } }
主な特徴:
ループやクロージャ内でシャドーイングが起きるとどうなりますか?
クロージャ内に外部コンテキストと同じ名前の変数を宣言すると、シャドーイングにより外部変数が隠れます。これにより、特にマルチスレッド環境で予期しない動作が発生することがあります。
let value = 10 let closure = { let value = 20; print(value) } closure() // 20 print(value) // 10
同じスコープ内で同じ名前の定数と変数を宣言することはできますか?
いいえ。Swift は同じスコープ内で var と let を同一の名前に持つことを許可しません:
let x = 5 var x = 10 // エラー: 'x' の無効な再宣言
継承時にクラスのプロパティのシャドーイングによる混乱を避けるにはどうすればよいですか?
もしサブクラスが親クラスの名前を持つプロパティを宣言した場合、シャドーイングが発生します。しかし、super や self を通じてアクセスすると、対応するプロパティが選択されます。必要のない限り、同じ名前を使用しない方が良いです。
Account クラスで、開発者の一人が意図せず balance 変数を関数のパラメータ名に使用し、関数内で再度 let balance = ... を使用しました。シャドーイングにより、正しい値で計算されず、関数は誤った結果を返しました。これはテストの後期になって初めて明らかになりました。
利点:
欠点:
チーム全体がプレフィックス(例えば、inputBalance)を使用することや、プロパティへのリファレンスには常に self を用いることに合意しました。その結果、シャドーイングによるエラーがほぼ消滅し、コードの保守が簡単になりました。
利点:
欠点: