構造化プログラミングから“なぜ”“どのようにして”「オブジェクト指向プログラミング」が誕生したのか? 今回は、その歴史を振り返る
ソフトウェアの品質、複雑性、生産性を計測するのが「ソフトウェア・メトリクス」です。ソフトウェア・メトリクスの歴史を「混乱期」「胎動期」「活動期」「反抗期」「成熟・定着期」に分割し、“何を”“何のために”“どのように”測定するのかというソフトウェア・メトリクスの本質に迫ります。
前回「構造化プログラミングの苦難の歴史」では、ソフトウェア・メトリクスとソフトウェア開発の混乱期である40〜50年前に勃発した「構造化プログラミング大論争」について述べました。今回は、構造化プログラミングの延長である「オブジェクト指向」について解説します。
構造化プログラミングから、“なぜ”“どのように”オブジェクト指向が誕生したのかを十分に理解しないと、オブジェクト指向の良さを生かした設計はできません。この点を中心に説明します。
10年以上にわたる構造化プログラミング大論争が決着したのは、いまから35年前のことです。大論争の末、構造化プログラミングの定義は以下のようになりました。
要するに、「部屋への出入りはドアを使い、手近な窓やベランダが開いているからといって使用しないこと。ただし、火事や地震のような非常事態の場合は、そばの窓から避難してもよい」ということです。以上が、前回の概要です。
40年前のプログラミングは、伝統工芸品や芸術品を作るのと同様、個人の特殊技能、いわゆる「匠の技」が非常に重要でした。「名人」が作ったプログラムは、ダイナミック・ステップ数が短くて高速処理が可能でしたが、他人には(自分でも)理解しにくく、保守性がよいものではありませんでした。そんな「芸術品」から脱却して「工業製品」へ遷移すること、あるいは、「処理性能重視」から「保守性重視」へ向かったのが構造化プログラミングです。
別の見方をすれば、構造化プログラミングは、処理効率を犠牲にして、作りやすさや理解容易性を求めたともいえますが、構造化プログラミングを採用したからといって性能が落ちることはなく、逆に、プログラム構造がすっきりするので処理効率が上がると考えるのが一般的です。
では、構造化プログラミングを採用すれば、「書きやすく理解しやすいプログラミング」が可能でしょうか?
答えは、50%がYESで、残りの50%はNOです。
プログラムとは、データを処理する手順を定義することであり、「制御+データ=プログラミング」です。先ほど「50%がYESで、50%はNO」と書いたのは、構造化プログラミングは「制御の流れ」を構造化しただけであり、「データ構造」には何の制限や規則も設けていないためです。
構造化プログラミングは、「書きやすく理解しやすい制御構造」を定めたものであり、データ構造に関しては何の規定もなく、一種の“無法地帯”といえます。書きやすく理解しやすいプログラミングのためには、コントロールの構造化だけでは不十分で、データの構造化も必要です。
そこで、「理解しやすいデータ構造」を目指し、新しい潮流として誕生したのが「オブジェクト指向プログラミング」です。
ソフトウェアの生産性について、このコラムでは一貫して「1人1カ月1000ステップという生産性は30年前から変わっていないし、これからも改善されることはない」と書いてきました。非常に悲観的な予想ですが、もし、生産性向上の切り札があるとすれば、「既存ソフトウェアの再利用」と筆者は考えています。
善しあしは別として、まったく勉強せずに試験で良い点を取りたい場合、最も効率がいいのはクラスで一番優秀な友人の隣に座り、答案を丸写しすることです。ただし、ソフトウェアの開発では、カンニングのような100%の「コピペ」、すなわち、別のOSで同じアプリケーション・プログラムを稼働させることは、再利用ではなく流用と呼び、再利用とは一線を画します。いわゆる“ソフトウェアの再利用”は、例えば、「猫でも書ける英文の手紙」という本のサンプルを参照して、英文でお礼状を書くようなものです。
再利用には、ライブラリ関数や100ステップ程度の小さいサブルーチンをコピーする「小規模再利用」と、ソフトウェア全体をコピペして必要個所を変更する「大規模再利用」があります。関数ライブラリや個人のサブルーチンレベルの小規模再利用は大昔から定着しており、すでに大成功を収めています。ソフトウェア業界が求めているのは大規模再利用です(狭義の「再利用」は、大規模再利用を意味します)。そして、構造化プログラミングの欠点として、指摘されているのが「大規模再利用が困難」という問題です。
再利用が成功する最大のキーは、「元のプログラムが理解しやすいかどうか」にあります。構造化プログラミングをベースにしたソフトウェアは、大昔の職人芸的なプログラミングに比べると分かりやすいのですが、自分が作ったプログラムであっても、3カ月たてば他人のプログラムと同じぐらい理解するのが大変です。これが、構造化プログラミングの大きな欠点といえます。
再利用の成否のカギは、元のソフトウェアを簡単に理解できるかどうかにあると書きましたが、理想は「理解していなくても再利用できる」です。例えば、C言語のライブラリ関数を使う場合、関数内のロジックやデータ構造をまったく知らなくても、インターフェイスと機能さえ分かっていれば、誰でも簡単に使えます。この利点が、一般のプログラムにも拡張できれば、ソフトウェアの生産性は大きく上がると期待できます。オブジェクト指向によるプログラミングは、ある意味、「中身を理解していなくても再利用できるソフトウェア」を開発する技法といえます。
ではどうすれば、中身を理解せずに再利用できるソフトウェアを作れるのでしょうか? なぜ、構造化プログラミングではそれができないのでしょうか?
答えは、「ローカル変数」と「グローバル変数」にあります。
1つのモジュールの内部でしか使用しないデータがローカル変数であり、プログラムのどのモジュールからでもアクセスできるデータがグローバル変数です。
ローカル変数の例として、C言語のライブラリ関数の内部で使うデータは、関数内限定でアクセスします。外部モジュールには読み書きできませんし、読み書きする必要もありません。もちろん、データ構造を理解する作業も不要です。
一方、グローバル変数は、プログラムのあらゆるモジュールからアクセスできる共通データ領域であり、一見、便利に思えますが、便利さ故に問題は少なくありません。グローバル変数は、例えば、従業員が5人のタコ焼き屋で、現金出し入れ用に、誰でもアクセスできる金庫を店の奥に設置するようなものです。Aさんがタコ焼き5人前の売上金として1500円を金庫に入れ、Bさんが小麦粉代3000円を金庫から出して支払い……と従業員の誰でもが簡単に入出金できるようにしておくと便利に思えます。しかし、どこで誰がいくら入出金したのかを正確に把握するのは非常に困難です。入出金の記録を取るため出納帳を置いても、記入する金額を間違えたり、未記入のままお金を持ち去る不正もあり得ます。金額不一致が起きた場合、原因を突き止めるのは容易ではありません。
プログラミングの場合、グローバル変数関連のバグが発生した場合、どのモジュールがどのように不正処理をしたのかを追跡するのは容易ではありません。
このように、モジュールの内部でしか使用しないローカル変数は、バグの原因にはなりにくいのですが、プログラム全体でアクセスできるグローバル変数の弊害は大きいと思われます。
構造化プログラミングの基本は、「goto文を使わない」でしたが、オブジェクト指向プログラミングの基本は、「グローバル変数を使わない」です。
例えば、前出のタコ焼き屋の場合、入出金だけを専門に扱う出納係を1人置き、その人にしか金庫を触らせないようにするのがオブジェクト指向的な方法です。Aさんが「3人分の売り上げです」といって出納係に1500円を渡し、Bさんが「小麦粉代をください」と申告して出納係から3000円もらう……。このような方式を取れば、金額不一致が発生した場合、原因は出納係の「バグ」しかあり得ませんので、分析する範囲は極めて狭くなります。また、お金を入れておく金庫(データ)は、本格的な耐火金庫でも、紙の箱でも、出納係のポケットでも構いません。すなわち、ほかの従業員は、金庫の形状、大きさ、設置場所、個数を知る必要がないのです。
出納係の権限や仕事の内容を決め、従業員と出納係の“インターフェイス”を決定すれば(例えば、入出金伝票に金額を記入して、現金とともに出納係に渡す)、従業員は、入出金という作業(制御方式)や金庫の詳細(データ構造)をブラックボックスと見なすことが可能です。「金庫」というデータが、出納係の作業(制御)の後ろに隠れている、というより金庫の詳細を見せないようにしてあるのです。これが、オブジェクト指向における「情報隠蔽(ぺい)」という基本的な考え方です。
このように、処理内容とデータ構造をローカル化し、ブラックボックスにして、出納係にしか処理をさせないようにしておくと、この「入出金方式」は、例えば、このタコ焼き屋だけでなく、ハンバーガー・ショップや寿司屋でも、手を加えることなくそのまま使えます。オブジェクト指向が狙っているのは、このような再利用性の向上です。
制御構造に制限を設けて、コントロールの流れをきれいにしたのが構造化プログラミングであり、さらに、データ構造に制限を設けて、構造化プログラミングの欠点であった「グローバル変数の弊害」を是正して、再利用性を向上させたのがオブジェクト指向プログラミングといえます。
こう書くと簡単に聞こえますが、このようなプログラミング方式の進化過程を理解せず、制御指向プログラミングでしか設計してこなかった旧世代のソフトウェア開発者にとって、設計パラダイムが大きく異なるオブジェクト指向を理解することは、簡単ではありません。構造化プログラミングにはない「クラス」「継承」「ポリモーフィズム」を言葉では分かっていても、なぜ、それが必要なのかを理解するのは困難ですし、オブジェクト指向の長所を生かした設計はなかなか難しいといえるでしょう。
以上、プログラミング方式の一般的な進化過程です。プログラミング方式の進化は、「処理効率・性能」重視から「保守性・理解容易性」重視へと向かう歴史といえます。
一方、「“処理効率・性能”と“保守性・理解容易性”の戦い」となる組み込み系ソフトウェア開発では、処理性能を上げ、メモリ占有量を最小にすることを優先するため、プログラム構造をきれいにしたり、理解しやすいロジックを作ったりすることが犠牲になる場合があります。また、高級プログラミング言語を使うより、理解は困難ですが性能を出せるアセンブリ言語でコーディングすることも少なくありません。
組み込み系のプログラムの場合、携帯電話や複写機のソフトウェアのように、10MLOCを超える大規模なものも少なくありません。組み込み系の製品では、CPUパワーが貧弱で、メモリも不十分なのに、複雑怪奇な超大規模プログラムが走るのです。
組み込み系システムは、汎用システムに比べ、ハードウェアから受ける制限が圧倒的に多く・強いため、販売台数が多い組み込みシステムほど、コスト削減のため、ハードウェアから受ける制限が厳しくなります。ハードウェアからの理不尽な制限、特に、CPUとメモリから受ける“縛り”により、処理方式だけでなく、プログラムの構造、設計技法まで大きな悪影響を受けるのです。
処理性能を出すため、保守性や理解容易性が犠牲になることが少なくない組み込み系ソフトウェア開発において、過去40年間で進化したプログラミング技法や設計方法論をどのように取り入れるかは、非常に大きな課題といえます。(次回に続く)
Copyright © ITmedia, Inc. All Rights Reserved.