結論から言うと、構造化プログラミングには、大学教授ですら理解できないようなコードが存在する。しかも、それはたったの数十行である。
今回はそのコードを紹介するとともに、「ソフトウェアの専門家を育てること」の重要性を訴えていきたい。読者はこの記事の後半で紹介するコードがなぜ理解不能となるのか、ぜひとも、その理由を考えてもらいたい。
定義
まず、用語の定義を行う。
「クラス」と「オブジェクト」の2つの用語は「構造化プログラミング」*1の定義に従う。
特殊な実例(instance)が集まったクラス(class)なのである。いい換えると、動的システムに現れる現象を、現象のクラスとしてひとまとめにし、それぞれのクラスをプログラム部分として記述しようというのである。
存在し続けるブロックの実例のもとになる手続きをクラス(class)という。そして、その実例をそのクラスの対象(object)という。
G5 :- new Gauss(5); G7 :- new Gauss(7);……G5.integral(F, A, B) …… G7.integral(F, A, B) ……手続き integralが対象の外部から何度も使われることを想定している。
find: if x = val then this tree(中略)findの本体の中に現れる表現this treeは、そのときに扱っている節、すなわち所属物 find の、この特定の実例を所有している参照を値としようとするものである。たとえば、Xの手続き find が関数呼び出しX.find(x)で呼び出され、X.val = xであると、関数の値はX自身を指す参照値である。
(「構造化プログラミング」日本語版218ページより引用)
"truck", "bus", "car"は、クラスvehicleの部分クラス(subclass)と考えてよいであろう。
理解不能なコード
構造化プログラミングのSIMULA67に似た疑似コードを用いる。わかりやすくするためにコードの右端には行番号を付けてある。
あらかじめ、公正さのために言っておくと、
- 疑似コードはエラーを起こさない
- コード中のB() < Aはダイクストラ連接(クラスの継承)
- コメントは遠隔識別子の返り値を示す
1: class A() {
2: this.x = 0;
3: int procedure p() {
4: return x;
5: }
6: goto L;
7: }
8: class B() < A{
9: L: this.x = this.x + 1;
10: int procedure pp() {
11: return this.x;
12: }
13: }
14: objB :- new B();
15: objB.p(); // 1
16: objB.pp() // 1
17: objA :- new A();
18: objA.p(); // 1
19: objA.pp() // 1
ちょっとした解説
B < Aなのだから、ダイクストラ連接により、14行目におけるnew B()の計算の順序は、
A:{
2: this.x = 0;
3: int procedure p() {...}
}
B:{
9: this.x = this.x + 1;
10: int procedure pp() {...}
}
となる。
17行目におけるnew A()の計算の順序は、6行目のgoto文さえなければ、
A:{
2: this.x = 0;
3: int procedure p() {...}
}
と、ただ単にブロックAが実行されるのみである。
しかし、クラスAのインスタンスobjAから、遠隔識別子を呼び出そうとすると、goto文のジャンプにより、奇妙なことが起きる。
遠隔識別子ppは本来クラスAに属さない*3手続きのはずである。それなのに、objA.pp()と呼び出せるのである。
この疑似コードは、計算が問題なく実行できるものの、goto文のせいで、ダイクストラの連接(クラスの継承)が不適当なのである。
よって、ダイクストラが指摘した連接を用いる「抽象化(Abstraction)」も困難である。つまり、大学教授はおろか、すべての人間にとって理解不能なコードなのだ。
ソフトウェアの専門知識が必要
正直なところ、構造化プログラミングは、数学と計算論とプログラミングを学べば対処できるものではない。もっと膨大な基礎研究を踏まえた上での、より深い知見が必要だ。
そのために、ソフトウェアの専門家を育てることが重要なのである。