抽象化について、もしくはニッポン構造化プログラミング未伝来説

http://d.hatena.ne.jp/shim0mura/20130117/1358436466

なんというか、こういうのを見ていると「ニッポンのプログラマには構造化プログラミングや構造化設計すら未だに伝来していないのじゃぁ」とか思ってしまうわけです。

何か1つ関数を読む場合、その関数のなかにさらに別の関数があれば、その関数の中でどんな処理を行なっているか見に行くことが多いと思います。

http://d.hatena.ne.jp/shim0mura/20130117/1358436466

うーん、この時点で「そのコード本当は抽象化されてないだろ」とつっこみたい。うまく抽象化されているなら、「その関数のなかにさらに別の関数が」あったとき、ひとまずその「別の関数」の中身を読まずにスルーしてもあまり問題ないはずだから。

上に上げた共通化すべきところをしないというのはかなり極端な例だし、コードを書く上で意識すべきは何も可読性のみではないのはもちろんなので、共通化は必須でしょう。ただそれを何のポリシーもなく、diffとったら重複行があったのでまとめましたみたいな機械的な共通化をしてるだけでは、可読性は上がるどころか下手すると下がってしまうのではないか?と考えたのです。

というか現に今も自分の書いたコードを毎日みながら、共通化してるとは言うけどその共通化部分が書いてある場所があっちこっちに散らばっていて非常に読みづらいし、このコード書いた奴を●してえ...と1日3回はツイッターで呟いている有様です。

http://d.hatena.ne.jp/shim0mura/20130117/1358436466

世の中には「モジュール強度」なる指標があって(ry

共通部分でなくとも抽象化

http://d.hatena.ne.jp/shim0mura/20130117/1358436466

構造化プログラミングのトップダウン手法では「仮想の命令と仮想のデータ型で問題を解く -> 『仮想』なので後でブレイクダウン」ということをやっていて、「仮想の命令」の中には1度しか使用しないものもあるわけで(ry

我ながらオッサンくさい。まあ年齢的にはおっさんだけど、プログラミング歴的にはたかだか7〜8年の若造だ。

ちなみに「(彼は)不勉強だ」となじっているのではなく、こう、なんというか、少なくとも(私を含めて)21世紀になってからプログラミングを学んだ人たちは過去から学んでいないというか、うまい具合に学べるパスがなかなか無いというか、そういう方面の不安や不満がある。

構造化プログラミングなんて、「構造化プログラミング論」を基準とすれば40年前のものだ。なのに知らない、理解していない――ぼくたちは1960年代のプログラマなのさ。

構造化と抽象化

id:eel3:20121225:1356443485 でも書いたけど、元々構造化プログラミングには、

  1. 小さなプログラムでは、そのプログラムが正しいことを数学的手法や論理的手法で検証できるが、大きなプログラムではその方法が使えない。
  2. ソフトウェア・テストはバグの存在を証明するが、バグの不在を証明することはできない。
  3. (1)〜(2) より、プログラムが正しいことを検証するために、人間がソースコードを読んで理解する必要がある。
  4. (3) を達成するためには、最初から正しいプログラムを記述しなくてはならない。つまり、読むことで正しさを検証できるようなプログラムの記述・設計が必要だ。

――こんな背景というか思想がある*1

構造化プログラミングで出てくるgoto云々は、上記を達成するためにまずはソースコード(プログラムの静的構造)と実行時のフロー(動的構造)を一致させよう、という話だ。

「構造化プログラミング論」ではgoto(というか制御構造)の話以外にもサブルーチンによる分割統治やトップダウン手法の話がでてくる。こちらは「人間の頭には限界があるのだから、理解できる分量に分割しよう」という話だ。

『インタフェースデザインの心理学』を読めばわかるように、人間が一度に扱うことが可能な情報量の上限はたったの4つな訳で、ソースコードを「読むことで正しさを検証できるよう」に記述するには、情報をうまい具合にチャンク化することを含めて、読み手がたった4つのセルでうまい具合にやりくりできる大きさにしなくてはならない。

単純なコード行数でみても、『Code Complete 第2版』で指摘されているように、サブルーチン/関数/メソッドの適切な大きさについての数多くの調査の結果、少なくとも(空白行やコメントを除いて)200行を超える関数でバグが減少したという結果は得られていないのだ。

ではどうやって分割するか? 構造化プログラミングの頃には既にサブルーチンなるものが言語機能に含まれていた訳で、これを使ってソースコード中の論理構造の一部を切り出して、閉じ込めてしまおうと考えられた。

構造化プログラミングでは、基本的に順次・選択・反復の3つの制御構造を入れ子状に組み合わせて、入り口と出口を1つに制限するスタイルがとられた*2。こうするとプログラムは3つの制御構造が多層に積み重なった状態となる。ここで、ある階層以下を切り出してみると、切り出した部分は「1つの入り口と1つの出口を持つ、順次・選択・反復の3つの論理構造の組み合わせ」となる。

この「切り出した部分」は容易にサブルーチンに閉じ込めることができる。サブルーチンに適切な名前を付けてしまえば、サブルーチンを呼び出す側のソースコードの文脈では、サブルーチンの中身を読まなくてもソースの内容を読んで理解することができるはずだ。

これが抽象化の第一歩だ。構造化プログラミングの時点で既に、抽象化がプログラマの武器であることが指摘されている。

問題は、どの部分/範囲を切り出すか、だろう。

構造化プログラミングでは、トップダウン手法の説明での「仮想の命令と仮想のデータ型で問題を解く」という発想が、その答えの一端を示しているように思う。解きたい問題と、使用する言語の標準機能やライブラリ機能には隔たりがある。ならば、まずは問題を解くのに都合のよい命令やデータ型が存在すると仮定して、その命令やデータ型を用いて問題を解こう――それらの命令やデータ型は、後で中身を書けばよい(それも、また別の仮想の命令とデータ型を使って……)。*3

仮想の命令は、後で中身を書くときにインライン展開する必要はない。サブルーチンに置き換えてしまえばよい。仮想のデータ型は、今日では抽象データ型もClassもObjectも言語機能に含まれているのだから、それらに置き換えてしまえばよい。

「仮想の命令と仮想のデータ型」をより現実的に言い換えるなら、「問題を解くためのAPIセットを考え、そのインタフェースに基づいてコードを書く」ということになる。

『ソフトウェア作法』P.159を引用するなら:

これらの下位の補助ルーチンは、言語の拡張であり、ありふれた操作を簡潔に、解くべき問題の真の内容から目をそらすことなく表現するための手段なのだ、という風に考えることをおぼえてほしい。

問題を解きやすくするために、言語の標準機能を使って合法的にその言語を拡張するのだ。拡張によって追加された命令には、1度しか呼ばれないものもあれば、何度も呼ばれるものもあるだろう。

この段階では抽象化についての明確な指針は現れていない。しかし「問題を解くためのAPIセットを考え、そのインタフェースに基づいてコードを書く」ということだから、ある種のカプセル化がなされた(中身を気にしなくても使える)サブルーチンや抽象データ型であるべきだろう。

構造化設計になると、サブルーチン/関数/メソッドの設計についてもう少し明確な指針が出てくる。基本情報技術者試験でおなじみのモジュール強度・結合度がそれだ。

乱暴にいえば、基本的にはサブルーチンは単機能(単一の機能のみ提供する)ないし特定のデータ構造を扱うものが望ましいし、データは必要なもののみ受け渡しするか、又は必要なデータを含むデータ構造一式を受け渡しするスタイルが望ましい。

ただそれを何のポリシーもなく、diffとったら重複行があったのでまとめましたみたいな機械的な共通化をしてるだけでは、可読性は上がるどころか下手すると下がってしまうのではないか?と考えたのです。

http://d.hatena.ne.jp/shim0mura/20130117/1358436466

――というやり方では、大抵は時間的強度/手順的強度/連絡的強度になってしまうだろうし、下手にフラグで制御できるようにすると論理的強度になってしまうだろう。

21世紀の構造化プログラミング

構造化プログラミングは小規模なプログラミング向けの技法なので、手続き型の系譜の言語を使うのなら、プログラミングのキャリアの比較的初期の段階に学ぶべきだと思う。

図書館から『構造化プログラミング』を借りてきて、その中の「構造化プログラミング論」(著者はもちろんE.W.ダイクストラ)を眺めてみると、p.2にこんな記述がある。

いま私が関心をもっているのは、巨大なプログラムを作成することです。その大きさは、たとえばこの小論全体ほどのものです。

「構造化プログラミング論」の原版にあったたことは無いのだけど、日本語版は32行×95ページだ。ちなみに1行は全角35文字(半角70文字?)。単純に考えると3000行強のソフトウェアになる*4。原版も同程度の分量だと仮定しよう。

重要なのは2点。

1点目は、たった3000行程度のソフトウェアを実装する場合でも何らかの手法が必要だということだ。言語にもよるけど、実際に仕事などで実装するソフトウェアは、システム全体では3000行よりも遥かに大きいことが多い。

2点目は、3000行というボリュームは、今日では小規模な部類に入るということだ。まあこのあたりは言語に左右されやすいのだけど、組込みでCプログラミングな場合は「小規模」のカテゴリになる。

元々、構造化プログラミングは大規模プログラミング向けに考案されたのだけど、今日では小規模プログラミング向けだ。

ちなみに、例えば1000行未満のボリュームなら構造化プログラミングは不要かと問われれば、答えは否だ。『ソフトウェア作法』では数百行のソースコードでも適切な構造化がなされている。

ニッポンに構造化プログラミングは本当に伝来したのか?

ここまで書いてきた内容は30〜40年ほど前のものを下敷きとしている。参考にしている本も割合と古い。

コの業界は技術の移り変わりが激しいのだけど、例外的にこれらの内容は現在でもそれなりに有効なものだと思う。

しかしニッポンでプログラマとしてキャリアをスタートした人がこういった内容を理解した上で現実のプログラミングにて適用しているか? 残念ながら、座学でおざなりに学ぶ機会はあっても、実際のプログラミングに適用するようなレッスンはまずないだろう。

そもそも世の中に出回っている構造化プログラミングについての説明自体がここ数年でようやくマシになってきた訳で、それよりも前にキャリアをスタートした人は「構造化プログラミングとは何か?」という問いに正しく答えられるのか怪しいところだ。

ぼくらは過去を学ばず、今日も先達と同じところでつまづき、車輪に劣る車輪もどきの再発明を繰り返している。

*1:この考え自体は、ソフトウェア工学の基本理念みたいなものだと私は解釈している。

*2:ただし現実にはこのスタイルがそぐわない状況もある訳で、逃げ口としてbreak、continueや例外機構などが言語に用意されていたりする。

*3:このあたり、何となくダイクストラ本人の経歴が反映されている気がする。というのも、「『構造化プログラミングに関する覚え書き』へと導いたもの」によると、未完成のハードウェアに対して仕様書を元にソフトウェアを書いたことがあるらしいのだ。この経験が「仮想の命令」などの発想につながっている気がしてならない。

*4:コメントや空行等を含むかどうかは不明。