2013-03-26
プログラミング出来ないやつちょっと来い - 中級者編
前エントリの続きです
http://anond.hatelabo.jp/20130322031333
ブログで書け!という声が上から聞こえたので、今度はブログで書きます。
いくつか批判、というか誤解があるようなので、最初に説明しておく必要があるでしょう。
あくまで元記事は、初級者から中級者になるための方法論を述べているのであって、初級者から上級者への方法論を述べているのでありません。
この点に大きな誤解があるように思います。
例えば、初心者にエレガントなモジュール階層の方法論を説明したところで、その意味を汲み取ることができるのでしょうか?
printf デバッグが原始的で時代遅れで化石のようなデバッグ方法であるとご高説される方もいるようですが、printf デバッグ以上に単純で汎用的でどのプログラミング言語にも使えるデバッグ方法が存在するのでしょうか?
そのようにおっしゃる方は前回のエントリで、それぞれの言語のデバッガについて説明されることを期待されているのでしょうか?
結局いくら高機能なデバッガも "おかしな変数" を見つけるために "変数の中身を表示して確かめる" というプロセスを経るはずです。そのプロセスは printf デバッグでも同様です。そういう意味で、デバッグの "心" みたいなものは printf デバッグからでも学べるのではないでしょうか?
物事には順番というものがあります。タイピングが出来なければ、プログラミングが出来ないのと同様に、プログラミングが出来なければ、エレガントなプログラミングは到底出来ません。(前エントリでは "最低限のプログラミングが書ける" というレベルまでの説明です。)
そういう観点から、"プログラミングに必須" と思われる概念や手法のいくつかは "初心者のために" ばっさりと切り落としています。初心者にとっては、挫折しないためのより重要な学ぶべきことがあるからです。
誤解を恐れずに言うと、元記事の通りにプログラミングの練習に励んでも、1万行以上のコードは(おそらく)書けないし、グローバル変数だらけで、抽象化も一切なされていないプログラムになるだろう、ということです。
それ以上きちんとプログラミング書けるようになるかは、本人の努力次第だと思います。(また、その努力の方向性の一部については今回のエントリにて説明しています。)
この点については、ここで強調しておきます。
その他に元記事で表現が悪かったなぁ、と思った点が以下です。
> 多くの人は計算機科学を学び、効率のよいアルゴリズムとデータ構造、美しい階層化・モジュール化されたプログラム、などを作るためにプログラミングするのではない。目の前の問題を解決するためにプログラミングを行う。
まるで効率のよいアルゴリズムとデータ構造が必要ない、むしろ悪だ、と言わんばかりの書き方でした。この部分は "目の前の問題に対するプログラミング" に話題をフォーカルするばかりにいきすぎた表現になってしました。すいません。
あと、デバッグをデバックと書いていたのは、著者のリテラシー不足です。
最後に、もしこのエントリを見て何らかの意見を持つベテランの方がいらっしゃるのであれば、一字一句エントリのあらを探して批判するのも結構ですが、自分の考えた "プログラミング学習方法" について 建設的なエントリを書いていただければ他のプログラマーにとってもためになると思います。
今回の話は前回と比べだいぶ抽象的になります。
では本編です。
中級者レベルの人に向けて
初級者レベルの人に必要なのはプログラミング言語のモデルを自分の中に構築することだった。それでは、中級者レベルに必要なのはどんな能力だろうか?
答えを先に言うと、中級者レベルの人に必要なのは抽象化を意識してプログラミング出来るようになること。この一言に尽きると言ってよい。
上の一文を読んで "ああ、あれのことだな" と心当たりがないのであれば、あなたはプログラミングにおける抽象化能力を習得出来ていない可能性が高い。
このレベルから先に進めない人が多数いる、というのは紛れもない事実だ。
体感的に、プログラマーの多くがこの中級者レベルのままで成長が止まっている。
プログラミングの本当の難しさは、アルゴリズムでも文法でもなく、その複雑性にある。
ソフトウェアが扱う "現実" が非常に複雑なため、必然的にソフトウェア自身も複雑になってしまう。
そして、あまりに複雑なソフトウェアは大きくなるにつれてその複雑性の中に埋没し、やがて沈没船のように段々と沈んでいく。そのように葬り去れたソフトウェアは世の中にごまんとある。
ぐちゃぐちゃして分かりにくくなりプログラムを捨ててしまった経験を、おそらく誰もが経験しているはずだ。これこそまさに、ここで比喩している複雑に処理が絡み合ってどうしようもない状態のことである。
さて、中級者レベルの人が出来ないことをもう一度振り返ってみる。
以下の症状は、どれも "ソフトウェアの複雑性" という泥沼に腰まで浸かっていることに気付くはずだ。
・1万行以上のコードだとスパゲッティコードになり、保守不能になる
・重複するコードが多く存在する
・適切なサブルーチン化できない
・グローバル変数を多用してしまう
プログラマーは複雑性をその手中に収めなければならない。
何とか自分で思考できるレベルまで複雑性を下げ、その主導権をコンピューターからプログラマーに移す方法を探さなければならない。
その手法こそがまさに抽象化である。
複雑度を抑える手段として、抽象化が必要なことは分かった、では抽象化とは具体的に何だ?と疑問に思う人がいるかもしれない。
抽象化とは一段高い視点から物事を扱う手法のことだ。
"一段高い視点" から物事を俯瞰できるようになることで、今まで多くの詳細の中に隠れていた大域的な構造が発見できるようになる。それらの構造を用いてプログラムを作り上げることで、互いに疎結合で仕様と実装が分離された統一的なプログラムが作れるようになる。
実は過剰な抽象化も毒なのだが、それはさらにレベルアップした時に遭遇する課題なので、ここでは特に言及しない。
さて、プログラミングにおける抽象化には大きく分けて 3 種類ある。
・手続きの抽象化
・データの抽象化
・制御構造の抽象化
の3つだ。
抽象化を習得するためには、以下の項目を意識したプログラミングをすればよい。
[1] トップダウンのプログラミング
[2] プログラミングを行う前にデータ構造を考える
[3] 重複コードを書かない
[1] は手続きの抽象化について。
[2] はデータの抽象化について。
[3] はボトムアップの抽象化について。
の話である。
制御構造の抽象化について少しレベルが高いのでここで扱わない。あった方が便利だが、なくても一応プログラミングできるからだ。(もし制御構造の抽象化にも興味があるならば、高階関数やジェネレーター、継続、 Lisp のマクロについて調べるとよいだろう)
また、上記の中であえてオブジェクト指向はあえて入れていない。
なぜならば、データ抽象化の概念の先には自然とオブジェクト指向があるので、データ抽象化を習得すればオブジェクト指向も同様に習得できると考えたからである。
1 ~ 3 の中では 1 のトップダウンのプログラミングが抽象化を習得する上で非常に重要だと考えている。
以下ではそれぞれの項目について説明する。
1. トップダウンのプログラミング
最初に強調したいことは抽象化において最も基礎的な要素はクラスでもインターフェイスでもなく、"関数" であるということだ。
よって、まず一番基本的な要素である関数による抽象化を行えるようにするのが重要である。
関数による抽象化を学ぶことで、より大局的な抽象化技法(クラスやインターフェイス, モジュール化)も自然と使えるようになるはずである。
なぜなら、これらの抽象化技法は同じ目的を違う手段で表しているに過ぎないからだ。 "抽象化" という括りで考えれば、基本的にどれも同じ目的を共有しているので、一つを理解すれば他の要素を理解するのも容易いはずである。
さて、トップダウンのプログラミングとは、以下のことを指す。
関数を書いてから、関数の中身を書くこと
目的の関数をまず一通り書いてから、関数の中身を書く習慣を付けることだ。
普通プログラミングする際は関数の中身を書いてから、関数を書いているのではないだろうか。この方法をボトムアッププログラミングという。
実は、この方法には致命的な欠点がある。
プログラマーは常々膨大な詳細にまみれながらプログラミングを行わなければならない。
そのため、ボトムアッププログラミングだと、瑣末な詳細に個別対処しなければならず、全体の一貫性が崩れてしまう。そのようなプロセスを繰り返すうちに、自分すら全体を理解できないコードを量産してしまうことになる。
つまり、ボトムアッププログラミングの場合、全体として一貫性のあるコードが書くことが難しいのである。
それではトップダウンのプログラミングではどうか?
トップダウンのプログラミングの一番のメリットは、全体を俯瞰しながらプログラミングが行えることだ。
なぜならば、最初に全体を設計した後、個別のコードを書くからである。
瑣末な詳細に溺れる前に、一回全体を描いてから個別のコードを書くようにする。このような習慣を付けることで、自分の頭で考えられる範囲のコード片が出来上がることになる。
その結果、瑣末な詳細にとらわれる前の大局的な視点に立った判断が行える。
ただし最初から全体をトップダウンで設計するのは難しいので、小さなプログラムの塊で練習するのがよいだろう。
初めのうちはいつもと違う部分の頭を使う作業になるので、覚悟しておいた方がいいかもしれない。1 週間もトップダウンのプログラミングを行う癖を付ければ、見違える程プログラミングしやすくなっているはずだ。
ポイントは一段高い視点からプログラムを俯瞰できるようになるところ。
これが抽象化の真髄だ。
この作業を繰り返し続けているうちに、プログラムの本質的な側面・機能はどこか、という "一つ高い視点" で物事を考えられるになる。そうなればしめたもので、カプセル化やモジュール化などの切り出し方に対する "直感" みたいなものが働くようになっていると思う。
一つ注意点があるとすれば、全てをトップダウンでプログラミングする必要はない、ということ。
最下層の部分や汎用的なライブラリなどについては、ボトムアップでプログラミングするのがよいであろう。
2. データの抽象化
このレベルに属する人達はそもそもデータ構造を作るという発想を持っていないことが多い。
そのためプログラムの課題を与えると、何らデータが構造化されていないベタ書きのコードを書く傾向がある。
そこで最初に理解してもらいたいことは、全てのソフトウェは "現実のある側面をデータ構造として抽象化しなければならない" 、という点である。
データを構造を抽象化することのメリットは、中身の具体的なあれこれについて詳細を気にせずに "一段高い視点" から対象を操作することが出来るようになることだ。
基本的にデータ構造を作るアプローチは 2 段階に分かれる。
1. 対象が持っているデータを洗い出す
2. データを構造化させる
対象がどのようなデータを持っているのか?に着目するのが最初にステップだ。
その後、データを構造化する。この時、簡単なものであればほとんどを配列と連想配列で表すことができるので、最初のうちは配列と連想配列でどのように構造化すればよいかを考えればよい。
(Lisp であればリスト、C言語であれば構造体とリンクリスト、webアプリケーションであれば DB などデータ構造を表現する手段は様々だが、スクリプト言語の場合は配列と連想配列である。)
例えば、あなたは目覚ましアプリを作る予定だとしよう。
対象が持っているデータは何であろうか?
いろいろな解答があるが、例えば暫定的な答えは以下である。
・ 時間(毎週, 何時, 何分, アラーム音量, スヌーズ回数…etc)
それでは、上記の項目を "設定時間" として抽象化すればどうのように表せばよいだろうか?
配列を [], 連想配列を {} として表すとすれば、以下のように表せる。
[{ "hour": 23, "minute": 15, "wday": [0, 1, 2, 3], "snooze": { "times": 2, "minutes": 5, "volume": 2 } }, …]
hour, minute はアラーム時間, snooze はスヌーズ時間や音量, wday は毎週何曜日に音を鳴らすかどうかだ。
このように設定時間を抽象化することで、"設定時間" という一塊を最小単位として扱うことができるようになった。
データをまとめることで "設定時間" というものを具体的な表現まで落とし込み、"一つ高い視点" から扱うことができるようになった。
今回のような簡単な場合は別として、通常どのようなデータ構造が最適かは職人のプログラマでも判断することが難しい。一朝一夕ですぐに身につくようなスキルではない。
ただしスタート地点として、対象をどのようなデータ構造で表せるか、を意識することは非常に有意義である。
その練習を積み重ねることで、どのような場合にはどのようなデータ構造を使えばシンプルに対象を表現できるかどうかが分かるようになるはずである。
3. 重複コードを書かない(DRYの原則)
重複コードを共通化させることで、自然と機能ごとに要素が分割され、複雑度を抑えることができる。
これを DRY(Don't Repeat Yourself)の原則という。
コードの共通化は抽象化のボトムアップからのアプローチだ。
コードの共通化とは、部屋の整理に似ている。
同じようなものが、あちこちに散らばっていたら、探したいものも見つからないでしょう。
コードを共通化することにより、あるべきものはあるべきとこに置くようにする。
そうすると、必要な時に簡単に取り出せる上に、コードも短くなって修正も容易になる。
掃除をする場合、後でものを取り出しやすくするように、役割ごとにものを置く場所を分担するだろう。例えば、勉強関係の本はこの棚、冬ものの洋服はあのタンス、という感じに。
プログラミングのコードについても同様のことが言える。
コードの共通化を進めると、コード同士の構造や役割が明確になり、それらの構造を "一つ上の視点" から見えるようになる。そのため、"これらのコードは同じ役割だから一つにまとめてモジュール化できる"、というような気付きも増える。
掃除をしないとすぐに部屋が汚くなるのと同様に、意識しないと常にコードは汚くなる方向に進む。それらをどうやったら防げるのかを考えるのがプログラマーの腕の見せ所である。
この記事を書くためにいろいろ web を徘徊する中で、僕の言いたいことを簡潔に表現しているブログポストを発見した。
・考えることを減らせる様に書く
上記のエントリの考察は素晴らしいので、ぜひ一度読んでもらいたい。
抽象化とは一段高い視点から物事を扱えるようにする技法で、それはまさしく "考えることを減らせるように書く" ということなのだ。
これこそまさに僕がこのエントリで主張したかったことだ。
もし抽象化が課題であると気付いたのならば、上のような処方箋を意識することで自分の思い通りのコードを書けるようになってほしい。
抽象化を意識してぐちゃぐちゃにならないコードが書けるようになれれば、より早くより生産的で、より保守の容易いソフトウェアを書き上げられるはずである。そのための研鑽を怠らないようにしていただければありがたい。
最後に参考資料も挙げておきます。
この領域に関する最適な参考書はおそらく SICP -- 計算機プログラムの構造と解釈 -- だと思います。僕はこの本以外でソフトウェアの抽象化について体系的に説明している本を知りません。(最初のエントリと矛盾しますが。。)
なので、抽象化についてより深く知りたいのであれば、SICP (1章〜3章)を読むことを強く勧めます。