ローカル変数のスコープ
ローカル変数とは、メソッド内などで宣言された一時的な変数のことです。JavaやRubyなどのローカル変数は、宣言された場所からスコープが始まり、宣言されたブロックが終わるとスコープが終了します。リスト1の例でいうと、変数totalはスコープが大きく、変数nはスコープが小さいです。ある変数への依存を最小化し、プログラムの変更を行いやすくするために、「ローカル変数のスコープは可能な限り小さくする」というのが大原則になります。
もう少し具体的な例を見てみましょう。リスト2はスコープが長いローカル変数の例です。変数open/closeのスコープは①から始まり、③で終わります。しかし、実際にopen/closeが使用されている個所は②のforループブロックの部分のみです。無駄にスコープが長くなっているので、適切な長さにしてみましょう。
スコープを短く書き換えた例がリスト3です。ローカル変数open/closeの宣言を、実際に使用する直前の①で行っています。スコープはforループブロックの②までになり、かなり小さくなりました。
ローカル変数のスコープが小さくなると、ローカル変数を参照できる範囲は小さくなります。つまり、そのローカル変数の影響範囲が小さくなるということです。たとえば、open/closeの名前を、openDate/closeDateに変更したり、型を変更したとしても、スコープが小さければ変更の影響は局所的で済みます。
また、処理を別メソッドへ抽出するリファクタリングを行う場合も、ローカル変数ごと別メソッドに移動することができます。スコープを小さくすることで、より小さな範囲に注目すればよくなるため、リファクタリングが行いやすい状態を作り出すことができるのです。
ローカル変数のスコープを小さくするガイドライン
次のガイドラインにより、ローカル変数のスコープを最小化できます。
変数は使用する直前で宣言する
リスト3のように、変数宣言はその変数を使用する直前で行うようにしましょう。JavaやC#、Rubyなどの言語では好きな場所でローカル変数を宣言できますので、本当に必要になってから宣言することでスコープを最小化できます。
なお、古いC言語ではブロックの先頭でしか変数宣言ができないという言語上の制約がありますので、このテクニックが使えない場合があります。
メソッドに抽出する
ある程度の固まりの処理を別メソッドに抽出するリファクタリングを行うと、新しいメソッドの範囲で変数のスコープができあがります。ローカル変数ごとうまく別メソッドに移動できれば、元の処理は変数自体を知る必要がなくなります。これにより、メンテナンス性や再利用性、可読性を高めることができます。
イテレータの一時変数のスコープをループ内に閉じ込める
一時変数とは一時的な入れ物として利用する変数です。リスト4)のwhileループの例ではイテレータを保持する変数iterが一時変数にあたります。これはJavaでかつ限定された場面でのテクニックになりますが、イテレータを利用したwhileループは、リスト5のようにforループに書き換えることで、一時変数iterをループブロック内に閉じ込めることができます。
代入されない変数にはfinalを付ける
Javaでは変数宣言時にfinalを付けると、変数に対する代入ができなくなります。スコープの長さは変わらないのですが、うっかり代入ミスをコンパイルエラーにより防止することができます。特に長いスコープの変数やメソッドの引数で使用すると効果的です。
なおC#では、readonlyキーワードでフィールド変数に対してfinal同様の効果を与えることができます。
フィールド変数のスコープ
フィールド変数とはインスタンス変数のことです。フィールド変数のスコープは、フィールド変数に指定されたprivate/publicなどの可視性によって決まります。Javaの場合、フィールド変数に対する可視性はスコープが小さい順に次の4つがあります。
- private
- package private
- protected
- public
スコープが一番小さいprivateは、オブジェクト内からのみフィールド変数にアクセスできます。publicにするとオブジェクトの外からアクセスできるようになります。protectedは同一パッケージと下位クラスからのみアクセスでき、package privateは同一パッケージからのみアクセスできます。
「フィールド変数はすべてprivate」が基本戦略です。フィールド変数に外部や下位クラスからアクセスしたい場合は、セッタ/ゲッタなどアクセス用のメソッドを用意して公開することになります。
ただし、基本戦略が望ましいということを理解したうえで、実装効率やアクセサメソッドの冗長性を排除するために、protectedフィールドやpublicフィールドを使用する場合もあります。