良いコ-ドへの道―普通のプログラマのためのステップアップガイド

第3回スコープを意識したプログラミング―その2 変数のスコープ

ローカル変数のスコープ

ローカル変数とは、メソッド内などで宣言された一時的な変数のことです。JavaやRubyなどのローカル変数は、宣言された場所からスコープが始まり、宣言されたブロックが終わるとスコープが終了します。リスト1の例でいうと、変数totalはスコープが大きく、変数nはスコープが小さいです。ある変数への依存を最小化し、プログラムの変更を行いやすくするために、ローカル変数のスコープは可能な限り小さくするというのが大原則になります。

リスト1 ローカル変数のスコープ
private void execute() {
    ...
    int total = 1;                  ┓変数totalのスコープ開始
    ...                             |
    ...                             |
    for (int i = 1; i <= 10; i++) { |
     int n = i * i;                 |┓変数nのスコープ開始
        total += n;                 |┛変数nのスコープ終了
    }                               |
    ...                             |
    System.out.println(total);      ┛変数トータルのスコープ終了
}

もう少し具体的な例を見てみましょう。リスト2はスコープが長いローカル変数の例です。変数open/closeのスコープはから始まり、で終わります。しかし、実際にopen/closeが使用されている個所はのforループブロックの部分のみです。無駄にスコープが長くなっているので、適切な長さにしてみましょう。

リスト2 スコープが長すぎるローカル変数
public boolean isClosing(Date now) {
    Date open = null;    ┓
    Date close = null;   ┛①
    ... 
    for(Timetable table : tables){ 
        open = getDate(table.openDateString());     ┓
        close = getDate(table.openCloseString());   |
        if(contains(now, open, close)){             ┛②
            return false; 
        } 
    } 
    ... 
    return true;      ―③
}

スコープを短く書き換えた例がリスト3です。ローカル変数open/closeの宣言を、実際に使用する直前ので行っています。スコープはforループブロックのまでになり、かなり小さくなりました。

ローカル変数のスコープが小さくなると、ローカル変数を参照できる範囲は小さくなります。つまり、そのローカル変数の影響範囲が小さくなるということです。たとえば、open/closeの名前を、openDate/closeDateに変更したり、型を変更したとしても、スコープが小さければ変更の影響は局所的で済みます。

また、処理を別メソッドへ抽出するリファクタリングを行う場合も、ローカル変数ごと別メソッドに移動することができます。スコープを小さくすることで、より小さな範囲に注目すればよくなるため、リファクタリングが行いやすい状態を作り出すことができるのです。

リスト3 スコープが短くなったローカル変数
public boolean isClosing(Date now) {
    ...
    for(Timetable table : tables){
        Date open = getDate(table.openDateString());    ┓
        Date close = getDate(table.openCloseString());  ┛①
        if(contains(now, open, close)){
            return false;
        }     ―②
    }
    ...
    return true;
}

ローカル変数のスコープを小さくするガイドライン

次のガイドラインにより、ローカル変数のスコープを最小化できます。

変数は使用する直前で宣言する

リスト3のように、変数宣言はその変数を使用する直前で行うようにしましょう。JavaやC#、Rubyなどの言語では好きな場所でローカル変数を宣言できますので、本当に必要になってから宣言することでスコープを最小化できます。

なお、古いC言語ではブロックの先頭でしか変数宣言ができないという言語上の制約がありますので、このテクニックが使えない場合があります。

メソッドに抽出する

ある程度の固まりの処理を別メソッドに抽出するリファクタリングを行うと、新しいメソッドの範囲で変数のスコープができあがります。ローカル変数ごとうまく別メソッドに移動できれば、元の処理は変数自体を知る必要がなくなります。これにより、メンテナンス性や再利用性、可読性を高めることができます。

イテレータの一時変数のスコープをループ内に閉じ込める

一時変数とは一時的な入れ物として利用する変数です。リスト4のwhileループの例ではイテレータを保持する変数iterが一時変数にあたります。これはJavaでかつ限定された場面でのテクニックになりますが、イテレータを利用したwhileループは、リスト5のようにforループに書き換えることで、一時変数iterをループブロック内に閉じ込めることができます。

リスト4 whileを使ったイテレータのループ
Iterator iter = iterator();
while (iter.hasNext()) {
    Item item = (Item)iter.next();
    ...|変数iterのスコープ
}
// ここでも変数iterが参照できてしまう
...
リスト5 forを使ったイテレータのループ
for (Iterator iter = iterator(); iter.hasNext(); ) {  ┓
    Item item = (Item)iter.next();                    |変数iterのスコープ
    ...                                               |
}                                                     ┛
// ここでは変数iterは参照できない
...

代入されない変数にはfinalを付ける

Javaでは変数宣言時にfinalを付けると、変数に対する代入ができなくなります。スコープの長さは変わらないのですが、うっかり代入ミスをコンパイルエラーにより防止することができます。特に長いスコープの変数やメソッドの引数で使用すると効果的です。

final int count = getCount();
...
... ※長い処理
...
count = getTotalCount(); // コンパイルエラーが発生

なおC#では、readonlyキーワードでフィールド変数に対してfinal同様の効果を与えることができます。

フィールド変数のスコープ

フィールド変数とはインスタンス変数のことです。フィールド変数のスコープは、フィールド変数に指定されたprivate/publicなどの可視性によって決まります。Javaの場合、フィールド変数に対する可視性はスコープが小さい順に次の4つがあります。

  • private
  • package private
  • protected
  • public

スコープが一番小さいprivateは、オブジェクト内からのみフィールド変数にアクセスできます。publicにするとオブジェクトの外からアクセスできるようになります。protectedは同一パッケージと下位クラスからのみアクセスでき、package privateは同一パッケージからのみアクセスできます。

フィールド変数はすべてprivateが基本戦略です。フィールド変数に外部や下位クラスからアクセスしたい場合は、セッタ/ゲッタなどアクセス用のメソッドを用意して公開することになります。

// フィールド変数
private int count;

// セッタ
public void setCount(int count) {
    this.count = count;
}

// ゲッタ
public int getCount() {
    return this.count;
}

ただし、基本戦略が望ましいということを理解したうえで、実装効率やアクセサメソッドの冗長性を排除するために、protectedフィールドやpublicフィールドを使用する場合もあります。

おすすめ記事

記事・ニュース一覧