プログラミング言語のパラダイムの変遷(2)

構造化プログラミングのおさらい

アセンブラ、高級言語をへて構造化プログラミングの手法が確立し、それまで の開発工程から画期的に生産性があがりました。高級言語では、 なりました。さらに、構造化プログラミングの手法で ました。ここで、サブルーチンの独立性について、もうすこし考えてみます。

変数の有効範囲について

サブルーチンの独立性のキーの1つは変数の有効範囲(scope)の制限 です。構造化ブログラミング以前でもサブルーチン、すなわち定形の一連の処 理をまとめて再利用する、ということは可能でした。しかし多くのプログラミ ング言語においては(アセンブリ言語がそうであったように)そのルーチンで 利用する変数、すなわちメモリ領域は別のルーチンからも参照するこ とができ、それを活用してパラメータを引き渡したり、処理の結果をうけとっ て別のルーチンで利用したり、ということが行われていました。しかし、この ような機構では、どの変数をどのサブルーチンが利用=変更するかがコー ド(プログラムのソース)を読んだだけではすぐに理解できず、しばしば バグの温床となってました。つまり、知らない間にある変数が変更さ れていて、その変更をしらずに元の値として計算を続けると、結果が異なる、 というわけです。

上の反省から、構造化プログラミングでは、サブルーチンにおいて、その中だ け有効なローカル変数というものをとりいれ、この変数は他のルーチ ンからは見られない(ので利用できない)ことから、データの不用意なルーチ ン間の依存関係を無くすようにしました。どういうことかというと (以下はCでの例です)、

#include <stdio.h>
int i;        // global var.
void func() {
  i = 1;      // set global var.
}
main() {
  int i;      // local var.
  i = 2;      // set local var.
  printf("%d\n",i);  // print the value of "i"(=2)
  func();     // change gloval but local 
  printf("%d\n",i);  // print i = 1 or 2?
}
上の場合、i は main の中と外のどちらにもありますが、func (Cではサブルー チンを関数の形で定義します)で代入している i は外のもの、main の中で代 入している i は main の中だけで有効な、外のものとは別の i になっている、 というのがポイントです。なので、上のプログラムを実行すると、2を二回出 力します。これでは見通しが非常に悪いので、グローバル変数は出来るだけ (使わなくていいなら)つかわない、という方針でプログラミングが出来るよ うに言語仕様が決まっています。

引数の値渡し

さらに、サブルーチンに別のルーチンから引数(プログラミングにお いては、別ルーチンへ渡すパラメータのことを引数と呼ぶことが慣例です)を 渡すときには、値渡しをするのが原則です。どういうことかというと;
#include <stdio.h>
int func(int i) {
  i = i*i;      // power of i
  return i;      // return the power-ed "i"
}
main() {
  int i,j;      // local var.
  i = 2;      // set local var.
  printf("%d\n",i);  // print the value of "i"(=2)
  j = func(i);       // CALL func
  printf("%d %d\n",i,j);  // print 2, 4 ("i" is still 2)
}
上を見てみると、func を呼びだしたときに、その時点での引数の値2が(コピー されて)上の関数 func に渡され、元の i は変更されない、という ことです。このことによっても、サブルーチン間の依存関係を極力へらしてい る、というわけです。
逆に見てみると、高級言語以前では、パラメータを渡すときにはメモリ (のアドレス)を渡す場合が多くありましたが、これは上でいうと i と いう変数を「箱」のまま渡すことになり、そうすると func 内での変 更はそのまま影響を受けるわけです。つまり、2回目の出力が "4 4" になって しまう、ということです。これではデータの流れの見通しがたちにくいわけで す。
以上のようなことがあるので、(建前として?)サブルーチン間のデータのや りとりは引数を渡して返り値をもらう、関数内ではローカル変数を利用するこ とで、サブルーチン同士の依存性をなくす、ということなのです。
ちなみに、このローカル変数は外部からはみえず、サブルーチンを抜けると無 くなってしまう、というのは、多くの場合スタックで実装しているということ から起因するわけなのですが、詳細はここでは略します(後に説明するかは未 定…)。

構造化言語の課題

しかし、まだ解決できていない課題があります。

グローバル変数の存在

基本はグローバル変数を使わない、ということなのですが、なかなか この通りにはいかないことがあります。ローカル変数は、その定義してあるサ ブルーチン内でのみ有効なわけで、それはすなわちサブルーチンを使っている 間だけ(メモリ上に)存在する、すなわちサブルーチンから抜けた時点で存在 できない、ということです。大規模なプログラムでは(プログラムのおかれて いる状況をあらわすフラグ等)サブルーチンをまたがって永続的にデー タを管理したい場合があるのですが、その場合はグローバル変数を使うほかあ りません。

しかし、これはそのまま問題につながります。上でも述べている通り、ざっと プログラムを見ただけでは、そのグローバル変数がどこで変更されているかが すぐ分かりにくいからです。これはバグに直結する場合があります。

再利用はほんとうに簡単?

構造化言語環境では、よく使われるサブルーチンはまとめて「ライブラリ」と いう形でパッケージングされ汎用的に利用されていました。具体的にはデータ の変換 (atoi,...) 、数値計算 (sin,cos,...)、入出力等です。しかし、それ らは実際にプログラミングされるコード全体からすると微々たるもので、より 大規模なソフトウェア開発での再利用の必要性が認識されていました。

これらの要求から、オブジェクト指向へとつながっていきます。

抽象データ型からオブジェクト指向へ

プログラムを構造化すると、その処理対象となるデータについても、たんなる 変数というだけではなく、一連の処理の対象になるデータはまとめて構造 をもたせて意味をもたせる、ということでさらにプログラムが書きやすく 見やすくなることが期待出来ます。VBでは構造体 (structure) や、 それにより実装されるリスト、二分木等のデータ型がそれに対応しま す。
狭義の抽象データ型とは、上でいうリストなどの、構造体などの機構を利用し てつくられる、現実の対象をイメージとして捉えやすくした=抽象化した型の ことをさします。
さらにこれを一歩すすめると、プログラムを仮想的に「もの=オブジェク ト」のあつまりとして、そのオブジェクトの中身は一切関知せ ず、あくまでもその外部仕様のみをお互いに知っており、それらオブジェ クトの間の情報のやりとりで処理をすすめていく、という考え方がでてきまし た。この場合、それぞれのオブジェクトがしっかりと設計されていれば、相互 に干渉することはないので、プログラムが機能的にきちんと分離されます。さ らに、機能がはっきりしているのですからそれを部品化して再利用するのも容 易になるでしょう。

Xerox PARC の Smalltalk はこの思想(?)を忠実に実現しようとし たもので、とくに GUI (Graphical User Interface) を開発する上で、 部品化という意味でのとの相性(?)がよかったことで受け入れられることと なり、その流れが C++, Objective-C, Java などにつながっています。

オブジェクト指向の特徴

つまり(逆説的になりますが)オブジェクト指向プログラミングとは が備わっていることになります。プログラムを整理する仕組みといえなくも ないです。

その具体的な仕組みなのですが、

とよく言われます。これらについて簡単に述べます。

クラスとは?

クラスとはオブジェクトの性質などといわれますが、プログラミング手法とし て見ると、関連性の強い(グローバル変数におかれるべき)データと、そのデー タにたいする操作(サブルーチン)をカプセル化、すなわち1つにまとめて粒 の大きいプログラミングの部品を作る仕組みです。この仕組みを使うことで、 それまでばらばらにあったサブルーチンとそれに関連する変数をまとめて整理 することができます。

継承と多態性

継承と多態性は、動物の分類・進化などにたとえられることがよくあります。 つまり、人も馬も哺乳類の性質を持つので哺乳類の派生とすればよい、という のが継承で、犬もセミも鳴くのだからとりあえず生き物は鳴くものとしておい て、鳴き方をそれぞれの実装によって区別するのが多態性というものです。

しかし、これもまたプログラミング手法としてみると、構造化言語ではうまく まとめて記述することが出来なかった似て非なるコードを一本化してまと める ための仕組みです。この仕組みを利用することで、似て非なるコー ドの「非なる」差分だけをコーディングすることが出来るようになり、コード の無駄をはぶき汎用性の高い部品とすることができ、再利用がしやす くなる、というわけです。


講義用スタイル
印刷用スタイル