構造化プログラミング
構造化プログラミング(こうぞうか-)は、コンピュータのプログラム上の手続きをいくつかの単位に分け、メインとなる処理では大まかな処理を記述し、サブルーチンによって細部を記述していく方法。1967年、エドガー・ダイクストラらによって提唱された。
全体的な機能から詳細機能へと順に開発するトップダウン式と小さな機能をまとめて大きな機能単位をつくるボトムアップ式の開発方法がある。
サブルーチン分割や構造化制御をサポートするプログラミング言語を構造化言語と呼ぶ場合があるが、現在の言語水準では特に明示されない場合でも、ほとんどの言語は構造化されているといえる。典型的な構造化言語はCやPascalといったいわゆるALGOL系の言語であろう。
オブジェクト指向でも、基本的な処理の流れは構造化から変わりない。
目次 |
[編集] 歴史
コンピュータが実用化され、その有用性が認められるようになるにつれ、その上で動作するプログラムは次第に大規模なものとなっていった。大規模なプログラムを矛盾なく正当に動作するように記述することは一般にとても困難である。
ダイクストラは、大規模化したプログラムを効率よく記述しプログラム設計上のミスが起こりづらいようにするための方法論を検討した。その結果、コラド・ベームとジュゼッペ・ヤコピーニによって1966年に証明されていた構造化定理をもとに、構造化プログラミングを提唱した。構造化定理とは、下に掲げるような定理である。
- 構造化定理
- 1つの入り口と1つの出口を持つようなプログラムは、「順次・反復・分岐」の3つの基本的な論理構造によって記述できる
[編集] 構造化プログラミングの構成要素
- 順次
- 順接、順構造とも言われる。プログラムに記された順に、逐次処理を行なっていく。プログラムの記述とコンピュータの動作経過が一致するプログラム構造。
- 反復
- 一定の条件が満たされている間処理を繰り返す。
- 分岐
- ある条件が成立するなら処理Aを、そうでなければ処理Bを行なう。
以上三つの論理構造を元にして、サブルーチンに機能を抽象化する。
「サブルーチン」とは不変契約をみたす関数や手続き、すなわち「同じ値を渡せば常に同じ結果が得られる」 ことを満たすような部分処理を指す。つまりデータと機能は直交するもので、可能な限りお互いの文脈に依存しないように分割しなければならない。こうすることでプログラムのモジュール性を高め、設計保守が容易となるというのが構造化プログラミングの主張である。
実際には、これだけでは多種多様なデータを扱うには適さず、大規模な開発ではむしろデータを中心に据えたオブジェクト指向に関心が移る事となった。しかし部分部分の処理分割では構造化が基本であり、オブジェクト指向と構造化プログラミングが対立しているわけではない。理想的な構造化プログラミングに、データの多態と継承などのオブジェクト関係性を扱う能力を持たせると、ほとんどオブジェクト指向と区別がつかないものとなり、パラダイムとしては案外に隣り合わせの関係にあることが分かる。
なお後述のダイクストラの論文などから「goto文を排除することが構造化プログラミングである」と誤解されるケースも多いが、goto文をなくしただけで処理の見通しが良くなるわけではない。たとえジャンプ命令しか持たないアセンブラであっても構造化に相当する処理分割は可能であり、一般にそのようなプログラムは見通しが良いといわれる。
[編集] 構造化プログラミングとオブジェクト指向
構造化とオブジェクト指向の繋がりを示す一例として疑似乱数生成を考える。
C言語の標準ライブラリでは、疑似乱数生成の関数として、乱数種を設定するvoid srand(unsigned)と乱数値を得るint rand(void)が定義されている。
randは呼び出しに際して引数を取らないが、返ってくる値は毎回異なる。つまり、rand関数は暗黙の内部状態をライブラリ側に持っており、しかもこれは別の関数srandの呼び出しによっても影響を受ける。これらの関数は二重の意味で構造化プログラミングに違反しているわけである。
もちろん、乱数は返ってくる値が予測できるべきものではないので、この設計自体が問題になる事は少ないのだが、例えばエミュレータのようなソフトウェアで決定論的な動作を行わせる場合、何らかの依存性を生じる危険があるのは明らかであろう。
疑似乱数系を正しく構造化したければ、乱数状態を表すデータオブジェクトを乱数種から生成するようにし、それを乱数生成時に毎回渡すようにすればよい。こうすることで、関数には毎回異なる値が渡る事になり、形式上構造化プログラミングの定義を満足する。
ところでこれは、オブジェクト指向のパラダイムとほとんど変わらない。一般的なオブジェクト指向言語での疑似乱数は、まず乱数ジェネレータを適当な乱数種から生成し、以降はそのジェネレータインスタンスから乱数を得る。通常同一の乱数種から生成されたジェネレータは、同じ生成シーケンスを与える限り同一の動作を行うように作られており、「同じ値が同じ結果を生む」という構造化プログラミングの原則に沿っているのが分かる。このように見ると、先述の構造化された疑似乱数系との違いは、データオブジェクトが関数(メソッド)と密結合されているかどうか、という点でしかない。
[編集] goto論争
[編集] ダイクストラの主張
構造化プログラミングを提唱するダイクストラは、理論の帰結から、goto文を使うべきでないとする。1968, 『Go to文は有害だと考えられる』("Go to statement considered harmful")
goto文は、プログラムの処理の流れを変え、プログラムリスト上の特定の場所へ移行させる。 従来の多くのプログラミング言語がgotoを組み込みの命令、あるいは予約語としてサポートしている。
goto文はプログラムを記述する上で非常に強力な道具であるが、goto文を駆使して複雑にジャンプを繰り返すプログラムは多くのプログラマにとってとても読みづらい。大規模プログラムになると「一体どこに移行しようとしているのか」が読めず、「この個所で何をしているのか」を読みとることが不可能となる。
これに対し反復・分岐の構造は、プログラムの流れがどのように変化するのかを読みとりやすい。また構造化定理は、基本構造を上手に使えば実はgoto文自体なくてもアルゴリズムを記述できることを示す。
[編集] goto派
しかし、goto文を使わずに3つの基本構造による代替を行うと、理論上は同値であっても実際にはプログラムの実行速度や記憶容量の点で性能が劣化する場合がある。また、特殊な場合にはgotoを使った方がプログラムを見通しやすくなると考える人もいる。例えばドナルド・クヌースも、著書「文芸的プログラミング」の中でそのような例をいくつかあげている。
こうした理由から、gotoを撲滅するのではなく上手に使い分けるべきだと考える人もいる。
goto文論争が不毛なのは、「構造化プログラミングの観点からgotoを使うのは望ましくない」という結論は真だが、「goto文を使わなければ構造化プログラミングになる」というわけではない点である。構造化プログラミングの本質は、状態遷移の適切な表現方法とタイミングを見極めることであり、これはプログラムの良し悪しを決める永遠の命題であるといっていい。
現在C言語を除く主流派の言語では、そのままのgoto文はほとんど見られなくなった。替わりにbreak文、continue文、もしくは例外処理のような特殊脱出(去勢されたgotoとも呼ばれる)をサポートし、単純な構造化制御だけでは弱いと考えられる部分を補っている。またクロージャやコードブロック、継続のような強力な制御機構を持つ言語ではそもそも抽象度の低いgotoを使う必要性はないといえるだろう。
一方で、例えば最新設計であるD言語では意図的にgoto文を引き継いでいる。これはgoto文支持の声の根強さを示す例の一つである。
[編集] その他の話題
それまでの職人芸的なプログラミングの時代から、構造化プログラミングというパラダイムの提唱によって、プログラミング技法の体系化を試みるプログラミング工学という学問が芽生えたと言っても過言では無いだろう。
なお、論文"Go to statement considered harmful"は、その半ば挑戦的な題名がプログラミング以外の様々な分野に及んで評判となり、それをもじった様々な "~ considered harmful"といった論説やジョークが生まれた。極めつきは、何かが有害であることだけが強調される題名の有害性を指摘した"'considered harmful' considered harmful"であろう。
ダイクストラは What led to "Notes on Structured Programming" (『「構造化プログラミングに関する覚え書き」へと導いたもの』)で、"A case against goto statement" (『Goto 文が不利な場合』)と題した論文を投稿したが、出版を早めるために編集者により "letter to the Editor" (『編集者への手紙』)と改題され、その過程でその編集者独自の考案で新たなタイトル("The goto statement considered harmful")を付けられた。その編集者とはニクラウス・ヴィルトであった、と述べている。