導入
構造化プログラミングの最終回として、スコープについて話します。
かつて、スコープという概念が一般的でなかった頃、変数の取り扱いは大変面倒でした。ある場所で作った変数と同じ名前の変数が別のところで使われると、意図しない場合に値が変更されるのです。ソースコードが大きくなるにつれ、ソフトウエア開発に参加する人数が増すにつれ、変数の管理問題は指数関数的に難易度が高くなります。変数の管理のために帳簿やデータベース、管理の担当部署が必要になるほどでした。
この問題を上手に解決するためにプログラミング言語側に設けられた仕組みがスコープです。Processingなどのオブジェクト指向言語では、クラスやメソッドもスコープを適用できます。スコープを意識し、上手に区別してコードを作成するようにしましょう。
展開
スコープとは
スコープとは、変数やメソッド、クラスなどが使える範囲のことです(「見える範囲」とも言います)。コードはできる限りシンプルな方が良いため、ある箇所だけで必要なものは他所から使えないようにするべきなのです。スコープの仕組みの無い言語では、プログラマが神経を使って注意深くコードを書く必要があります。スコープの仕組みは、プログラマをストレスから解放してくれるのです。
ここでは変数のスコープに話を絞ります。メソッドやクラスについても同様に考え、極力狭いスコープに収まるようにしましょう。
スコープを限定する方法
スコープを限定する最も基本的な方法は、ブレース(波括弧)で囲むことです。
コメントアウトされているprintln
文をアンコメントアウトすると、スコープ外で変数y
を使用するのでエラーになります。
次のようにメッセージエリアへ「y
が見えません」と表示されます。
[作業] sketch BasicScope.pde
を自分の手で入力して実行しましょう。コメントアウトしている行をアンコメントアウトして、エラーを起こしてみましょう。変数xもブレースの中に移動してエラーを発生させましょう。
ブレースで囲まれたコードブロック内で宣言された変数は、そのコードブロックの中でしか参照できません。スコープの仕組みはこのように変数の有効範囲を限定できます。
コードブロックを切り分けていれば、同じ名前の変数を別の型や値で使用することができます。スコープを切り分けなければ、同じ名前の変数名を使わずに、無理に説明的な名前で区別をすることになります。不必要に長い名前付けは避けるべきです。コードを読みにくくします。
明確にコードブロックを切り分けていれば、また、双方のブロックがネストしていない状況ならば、それぞれのコードブロック内で同名の変数を宣言すれば良いでしょう。
次のようにネストしたコードブロックでは、同名の変数を宣言できません。
スコープを限定して変数を使っている例の代表は、for
ループのカウンタ変数です。次のサンプルコード(GSWPのExample 04-10)をご覧ください。
2重for
ループを使ってディスプレイウインドウを円で埋め尽くします。x
とy
は円の中心座標のためだけの変数ですので他所で必要ありません。このコードのようにx
とy
を宣言すると、外側のfor
のコードブロック内ではy
のみ有効となり、内側のfor
のコードブロックの中ではx
とy
両方が有効となります。
つまり、次のように変数を参照するコードを追加するとエラーになります。
これをwhile
文で書き換えると状況が少し変わります。
Example 04-10と同じ動作をさせるためには、変数x
のスコープを外側のwhile
文のコードブロック内にしなければなりません。同様に、変数y
のスコープはこのスケッチ全体となります。for
文の利点は、ループの中だけで必要なカウンタ変数のスコープを閉じ込めることができることでしょう。
[作業] EX04_10_WithWhile.pde
を変更して、変数x
のスコープも変数y
と同じにしましょう。それでsketchの動きに変化があるでしょうか。エラーや問題が発生するでしょうか。エラーや問題が起きないとしたら、この変更の問題点や利点を検討してみましょう。
スコープを限定しない方法
ある範囲のコードブロック全体で参照できる変数を、そのスコープの範囲のグローバル変数といいます。
sketch CalcHookesLaw.pde
のコードの中で、ばね定数を表すdouble型の変数k
は、このソースコードのすべての場所から参照できるグローバル変数です。
関数に引数として渡さずに使えますから、関数の呼出しがシンプルになります。ただし、注意が必要です。
・注意その1
物理定数など変更の可能性も必要性もない値で、なおかつ変数名が他とダブりようがないのであればグローバル変数として許容できます。しかし、final
で宣言されていませんから、ソースコードのどこででも値の変更が可能なのです。すると、思いも寄らぬところで値を変更してしまい、それに気付かず誤った値で計算をしてしまうという危険性があるのです。
グローバル変数はなるべく使わない、という勧めがされるのはこのためです。どうしても必要ならば、final宣言し値が変更できないようにすべきです。
・注意その2
また、例えば、先ほどのsketchのばね定数は輪ゴムを使った場合のばね定数だったとします。しかし、ゴムを強力な結束ゴムバンドに変更したとします。すると、先ほどのコードでは困ってしまいます。輪ゴムの場合と結束ゴムバンドの場合と別のバネ定数を用意した上で、結束ゴムバンドのための別のメソッドを作らなくてはなりません。
こんなコードは悪夢です。
こうなることは容易に推測がつきますから、最初からメソッドhookesLaw
の引数はばね定数とばねの伸びを2つとも持つべきだったのです。
こうしてばね定数を引数で渡す仕組みにするならば、もはやばね定数をグローバル変数にする必要がなくなりました。次のように書き換えるべきでしょう。
グローバル変数はなるべく使わずに済ませたいものです。
スコープを意識せず参照したい場合
変数は基本的に使用するクラスのオブジェクト内部で閉じたスコープにすべきです。これをインスタンス・スコープと呼びます。例外的に、物理定数など一般的に良く知られており不変な定数値を持つ変数は、クラス外に公開してあると便利です。
Processingでは、次のようにMathクラスを省略して書けます。
Java言語ではMathクラスのスタティックフィールドPIをクラス名から指定して使用するのが一般的ですが、Processingではその手間を省いています。この定数フィールドPIがグローバルなスコープを持つ変数(あるいはフィールド)の代表例です。
自分で作成したコードにおいても、考えうる限り変更の可能性の無い値を持つフィールドを、このようにスコープを限定せず公開する手があります。ただし、よほどの理由が無い限りやめましょう。万が一、フィールドの値が変更になった場合、そのフィールドを利用するすべてのコードに影響を与えるからです。
私はソフトの名称やバージョン番号などを定数クラスのstaticフィールドに持たせておき使っています。こうすると、同一パッケージ内ではグローバル定数として参照できますから、マジックナンバーをコード内にばらまかずに済みますし、変更は定数クラスの一カ所だけで済みます。コンパイルすればすべて完了、便利です。現在のコードのバージョンナンバーは、現在使用しているすべてのコードで統一しておきたい、つまり影響を及ぼしてほしい場合だからです。
演習
演習1(難易度:middle)
GSWPのサンプルコードEx_05_09.pde
を読んで、変数x
, y
, px
, py
をdraw
メソッドのローカル変数にしなかった理由を説明してください。
演習2(難易度:middle)
sketch Ex_08_08.pde
は地上での体重を火星での体重に換算するものです。地球上での体重を火星での体重に換算する定数0.38は、火星の重力加速度(3.71[m/s2])を地球の重力加速度(9.8[m/s2])で割ったものです。この定数は後で意味が分からなくて困ってしまう可能性のあるマジックナンバーですので、定数クラスを作成して定数として参照できるようにすると良いでしょう。この定数クラス(GravitationalAccelerationValues
)を作成して、定数クラスのフィールドを参照して計算するコードに変更してください。ファイル名はEx_08_08_WithConstClass.pde
としてください。
まとめ
学習の確認
それぞれの項目で、Aを選択できなければ、本文や演習にもう一度取り組みましょう。
- スコープの意味が理解できましたか?
- 理解できた。自分のコーディングに活かす決心をした。
- 理解できた。しかし、自分のコーディングに活かす必要を感じない。
- 理解できない。
- グローバル変数を避けるべき理由を理解できましたか?
- 理解できた。
- 理解はできるが必要を感じない。
- 理解できない。
参考文献
- 『Javaルールブック ~読みやすく効率的なコードの原則』(電通国際情報サービス 監修、大谷晋平、米林正明、片山暁雄、横田健彦 著、技術評論社)
- Javaのコードを書く人は必携。ほぼ見開きでコンパクトながら意を尽くした解説がある。
- 『イラストで読むプログラミング入門』(ダニエル・アップルマン著, インプレス社)
- プログラミングという技術をイラストレーションで分かりやすく解説している。例をふんだんに用いておりなるほどと思わされる。
演習解答
x
, y
, px
, py
をdraw
メソッドのローカル変数にすると、マウスポインタの位置で各変数の値を更新するのですが、その回のdraw
メソッドの呼出しが完了すると変数の値は破棄されます。前回draw
メソッドを実行した結果を保持するためには、変数のスコープをdraw
メソッドの外にする必要があるのです。
高速で繰り返し実行されるdraw
メソッド内でたくさんの変数を宣言すると、その「変数を宣言するための仕事にかかる時間」がコンピュータの負担になることが考えられます。実行速度をわずかでも向上したい場合には、ローカル変数を外に出すことも検討しましょう。しかし、そのようなチューニングはコードの読みやすさが低下しますから、なるべく選択するべきではありません。値の保持と言う問題が無ければ、変数の数はわずか4つ5つですから、すべてローカル変数にしても良いでしょう。
Ex_08_08_WithConstClass.pde