『Code Complete 第2版』を読んだ

新型コロナウイルスも落ち着きつつあって、よさそう。そろそろ物理出社が怖くなってきた。というわけでぶっとい本を土日にガッと読んだ。

www.nikkeibp.co.jp

めっちゃ有名。「おすすめ技術書」みたいなウェッブ記事でだいたい挙がってくる、バイブル的なやつ。著者はSteve McConnell氏というかたで、Microsoft社やBoeing社に勤務された経歴あり、1993年に本書の初版を出版している。2003年にオブジェクト指向をとりいれた第2版が出版された。紙の本だと上下で1000ページ超えという鈍器のようなもの。

Amazonの履歴をみると3年前に購入していた。上下巻で出版されているが、自分はそれらがあわさった電子合本版をKindle半額セールで買った。そのときにも読んでいる(はずだ)が、当時から3年もたっているので、読みとる情報も違ってきそう。と思って再読した。何より、記憶がまったくない。

全体の感想としては、さすがバイブルに紹介されるだけあって、説明がわかりやすく、サンプルコードも豊富で読みやすい。ソフトウェアエンジニアが経験的に取得していく知識や考え方が綺麗にまとめられている印象を受けた。なんとなく考えていたことが明文化されているかんじ。自分の力だけで整理するには規模が大きすぎるため、本書はその助けになりそう。また、こういった本を読むと、力の入れどころがわかる、みたいなのがある。「見積もりってだれがやってもしんどいんだなあ」とか、「設計レビューでフルボッコにされたけど、だれも答えはわかってないんか」とか。経験がないとついネガティブな感情になりがちなところを、知識でフォローできる。

ただ、本書に限ったことではないが、やはり実感が伴わないと理解にはつながらないとおもう。3年前の自分もたぶんそうだったし、3年後の自分が再読すると、また同じことを感じるかもしれない。特定技術ではなくこういった網羅系の本は、自分にマッピングしながら読みすすめる、マッピングできない部分は未来の自分に託して読み飛ばす、くらいの読み方がいいのかもしれない。

参考文献が豊富に紹介されていて、ここからさらに理解を深めたい領域の本をたどっていくのも良さそう。Kindleで読みながらマーカをつけていたので、以降ではその中から引用して、感想をまとめている。引用末尾の数字は、Kindle上の位置。何年後かの自分に読まれたい。

第1部 基礎を固める

ソフトウェアプロジェクトをいくつかの工程にわけたとき、一般にプログラミングや開発と呼ばれる工程あるいは詳細設計などを、「コンストラクション」と読んでいる。これは建築のメタファである。そして、コンストラクションの前段階がプロジェクトの成否を決めると説明している。

ソフトウェア開発プロジェクトのリスクで最も目立つのは、準備不足とプロジェクト計画不足である。(1140)

ソフトウェア開発の工程は基本的に不確実性を減らしていく作業と理解している。不確実性はソフトウェアの完成が近づくにつれて減少していく。それをいかに早い段階で最小化できるか、という問題を解くのがプロジェクトマネジメントの能力だと思っている。コードを書く(というメタファの功罪であるが)作業は、不確実性が最小化された状態で行うべきであり、そのためにプロジェクトの前半に投資が必要となる。

この段階での手戻りは、後段のプログラミングの手戻りと比べると、比較にならないくらい被害が大きい。

コンストラクションを始める前に完了しなければならない最初の準備は、システムが解決するはずの課題を明記することである。(1331)

プロジェクトのスタートは「要件定義」ではなく、本質的には「課題定義」としている。何を解決するプロジェクトなのかを確実に言語化し、ステークホルダー間で合意しておくことが、まずスタートラインになる。これをやるのは開発担当者ではないケースは往々にしてあるが、少なくとも「課題は何か」と、次段階にあたる要件を満たせば、その課題が解決されることは、理解しておく必要がある。そもそも課題を解決する最適な手段が、ソフトウェア開発ではないかもしれない。

www.eijipress.co.jp

準備が不十分になる一般的な原因は、上流の作業を担当する開発者が、与えられた仕事をこなすための専門知識を持っていないことである。(1148)

不確実性、つまり「やってみないとわからない」をなるべく減らす上で、知識は比較的潰しやすい領域である。ただ、すべてを把握することは難しいので、かけられる時間を制約として、プロジェクトを計画する上で最低限必要な知識を得るための最適な行動を、戦略的に考える必要がある。自分の経験としては、知識以前に、検討を詰め切れていないというケースもやらかす。これは知識不足よりもさらにもったいない。

コンストラクションの準備を最終的に左右するのは、まずそのプロジェクトに適したコンストラクションの準備とは何かを判断することだ。(1323)

さらにメタな視点で、コンストラクションの準備、ひいてはプロジェクト全体の成否を決める上で、「何を決めるか」を決める、というのが最も重要になる。象徴的な例としては、「プロジェクトがどうなったら成功で、どうなったら失敗なのか」を定義しておく。極端にいうと、顧客の課題は解決できたけど予算は大幅にオーバーしました、というのは、とても成功とはいえない。個人的には「制約」という考え方が重要であり、明確な言語化がサボられがちだと感じる。課題と制約を明らかにした上で、それらをみたす要件を定義し、設計、計画を構築する。

第2部 高品質なコードの作成

第1部のテーマだった「コンストラクション」に対し、ここではコードの作成を区別し、それらの設計からその方法論を述べている。

SapirとWhorfの仮説によると、物事を考える能力は、その考えを表現できる言葉を知っているかどうかにかかっている。言葉を知らなければ考えを表現することはできないし、系統立てて説明することはなおさらできないだろう(Whorf 1956)。(2002)

知らないことを考えることはできない。知っていることから組み上げて、思考を導出するべき。たまに知らない領域で、「考えがでてこない」みたいな感覚に陥ることがあるが、たいていは前提となる知識が足りていない。何が足りないのか、どうしたらうまるのかを考えるところから素地を作っていく必要がある。

設計とは、要求をコーディングやデバッグと結び付けるアクティビティである。(2248)

要求から即コーディングに入ると、だいたいうまくいかない。その間には、想像以上の距離がある。経験豊かな人は手を動かしながら考えるけど、それは脳内で設計ができているだけ。規模が大きくなれば立ち行かなくなる。要求を実現する手段として何が最適か、戦略を立てて意思決定をする、という段階を「設計フェーズ」として確実に進めるほうがいい。

プロジェクトの失敗の原因が主に技術的なものであるとしたら、その多くは、複雑さを野放しにしたことが原因である。(2358)

プロジェクトが失敗する(予算が上振れする、計画が遅れるなどを含む)原因は、控えめに言ってほとんどが要求や計画の不備である。これは十二分に体感している。というか、論理の世界において技術的な問題で無理でした、というケースが想像つかない。

例えば「プログラミングの知識不足で想定より時間がかかりました」というのは、計画のミスでしかないし、「このフレームワークではこの設計は実現できませんでした」というのは、設計(技術選定を含む)のミスでしかない。本文にある複雑さの例にしても、その複雑さを想定していない計画のズレでしかない、と思う。そうなってくると、技術力とはなにか?みたいな話になってきて難しい。

設計は反復的なプロセスである。(3150)

これ本書(少なくとも上巻)で繰り返し述べられている、象徴的なテーマと言ってもいい。設計は非決定論的であり、ヒューリスティックである。したがってある時点で答えが出ることはなく、繰り返し改善をされているべきプロセスになる。設計のする、ということではなく、設計という反復作業をいかに効率よくイテレートするかを戦略的に考えることが、プロジェクト成否を分けそう。

私たちは、プロジェクトの最後に十分な時間が残るように、設計プロセスをさっさと片付けて、問題を解決しようとする。しかし、その残った時間は、設計プロセスを急いだために生じたエラーを発見するために使われる。   ─ Glenford Myers (3284)

はい。

プログラムには必ず問題があり、プログラムは変更されるものであり、賢いプログラマはそれを踏まえてコードを開発する(5017)

プログラミングは本質的に製造のプロセスではなく(便宜上「開発」と呼ばれるが)設計のプロセスであって、前述の設計原則は変わらず適用される。反復的に行われるべき。コードレビューよりもセルフレビューのほうが圧倒的にコストが低いため、その段階で一定レベルまで改善できる力が、すなわちプログラミングというレイヤにおいて知識以外の技術力のひとつなのかもしれない。

そこにおいて、本書にある「擬似コード」というプロセスをもう少し明示的にやってみようと思った。日本語のコメントで処理内容を説明するやりかた。要求とプログラミングをつなぐために設計があり、さらに絵設計とプログラミングをつなぐために、擬似コードは有効な手段だと感じる。レビューも擬似コードとその実装をわけるとレビュアーの負荷は軽減されそう。

「あともう1回コンパイルすれば、きっと決着がつくだろう」という「あともう1回だけコンパイル」症候群は、エラーの原因となる軽率な変更を促し、長い目で見れば余計に時間がかかる。(5942)

「あともう1回だけコンパイル」症候群の重篤患者であるぼくとしては身につまされる一文だった。完全に理解していないコードをコンパイラに渡してはいけない。たまたま動いてしまったときの被害が、コストに見合わない。簡単にコンパイル・実行できる環境や、スケジュールに追われる状況において、いかに思考を続けるかが品質を決めるし、脳の可塑性を信じてトレーニングに精進したい。

第3部 変数

第2部と第4部もそうだったが、このあたりの具体的    なプログラミングのプロセスは、さすがに知らないということはほぼなかった。「読みやすいコード」「シンプルなコード」などを志向していると、自ずと考えられる。

犬とその名前は別々のものだが、変数とその名前は本質的に一体である。(9640)

ここだけ最初、よくわからなかった。「太郎」というのは一般に人間の名前だが、それを犬につけてもいい。ただし、ある家庭において、太郎という犬と、太郎という人間が同時に所属している場合、それは望ましい命名ではない。これは、変数名のスコープと等価のはず。

変数名は、そのポインタが指すメモリ上のアドレスに存在するバイト列に対して名前をつけているのであって、それが変更されることはない。犬の例でいうと、犬が生成された瞬間(=生まれた瞬間)に割り振られ、以降は変わることはない。それが「本質的に一体」という意味だと理解した。

第4部 ステートメント

要求、要件、基本設計、クラス設計、変数・ステートメント、と具体化されてきて、第3部と第4部が本書の最下層。特に本部では、ループの使いわけ、みたいな、そうとう具体的なプログラミングの方針を説明されている。

有能なプログラマは、頭の中でシミュレーションし、電卓で計算する。(8976)

第2部でもあったが、コンパイラに渡すのは製造工程であって、開発工程では明確に品質を保証すべき。ステートメントが常に正しい振る舞いをすること、なぜそうなるのかを言葉で説明できることを、プログラマは責務として負っている。本質的にめちゃくちゃ脳に負荷がかかる。がんばりたい。

有能なプログラマは自分の脳みそがほんのちょっとしかないことを十分承知している。だから、とても謙虚な姿勢でプログラミングにのぞむ」(Dijkstra 1972)。(10256)

そういうこと。

第5部 コードの改良

「品質とはなにか」という話から始まり、テスト、ペアプロ、リファクタリング、チューニングについて。

プログラマは指定された目標に取り組むが、それには、目標が何かを知らされていなければならない。(12166)

品質には様々な特性があるが、どれに取り組むかは明示するべき。例えば、正当性と堅牢性はときに相反することがある。それぞれを目的にした場合、エンジニアはその目的に沿った開発をする。ひとくちに「品質を高く」とはいっても、何をどれだけ高くするか、そのために使えるコストはどれくらいなのかが曖昧だと、望むアウトプットは得られない。

コードに問題があっても当事者には見えないことがある。このような盲点は人によって異なるので、だれかに自分のコードを見てもらうことは開発者にとって有益である(12379)

文法上の問題はコンパイラ、仕様上の問題はテストで検出できたりするが、それ以外の問題や、テストの不備は、人間にしか検出できない。しかも、それを書いた当人にとっては見えにくい。そのため、コードレビューによる問題発見は効果的であるとしている。また、人に見られるということ自体が、コードの品質を向上させる。さらに、コードレビューは技術知識を組織に浸透させるような効果もある。本書にはないがいまはGitHubというツールがあり、コードレビューを通じたコラボレーティブな開発が容易にできている。

テストの量を増やしてソフトウェアの品質を改善しようとするのは、痩せたくて体重計に乗る回数を増やすようなものだ。(12936)

あくまでテストは問題発見のアクティビティでしかないということをいっている。品質を向上するのは開発のプロセスであり、問題を解消するのはデバッグのプロセスである。それらは明確に区別されるべきと思った。テストは「問題がない」ことを確認する(証明はできない)ために使うべきで、その段階で品質は保証されてしかるべき。「テストでみつかったらなおそう」というのはその役割を誤解している。

他の何よりもコンピューティングにおいて罪深き行為は、(それを達成する必要もないのに)効率という名のもとに行われている。   ── W. A. Wulf (15009)

可読性を落とすチューニングは、果たして本当に必要なのか?という話。そのチューニングの結果を求めている人は本当に存在するのか。そもそもアーキテクチャや設計のレベルで解決すべき(それはたいてい可読性を「上げる」)では。

第6部 システムの考察

プロジェクトの規模が大きくなると、通常は要求や設計のミスによるエラーの割合が高くなる (16152)

ほとんどの方法論のポイントは、コミュニケーションの問題を軽減することである。(16340)

コンストラクションの目的が複雑性への対処である、としているのと相似的に、方法論の目的はコミュニケーションへの対処になる。その過程で、ドキュメントや開発プロセスの整備、コーディング規約の策定などが行われる。「生産性が下がる」といわれる原因のほとんどはコミュニケーションにありそう。

ソフトウェア開発に反復が無駄になる領域はほとんどない。見積もりは反復が役立つ領域の1つである。(16653)

見積もりについても言及されている。エンジニアリング自体が複雑性への対処であって、そのプロセスは常に非決定論的である、ということが本書のいたるところから読み取れる。その結果、反復的なプロセスが役立つ。スケジュールの半分の期日で最低限のアウトプットを出し、残りの期間で改善する、というやり方で、だいたいギリギリうまくいく。体感としても。

第7部 ソフトウェア職人気質とは

プログラマは人間である (16819)

忘れがち。このあたりは、自分自身の生産性を最大化する意味でも課題になる。推薦書籍として以下がたびたび挙げられている。

shop.nikkeibp.co.jp

プログラミングを排除させない原動力となったのは、たとえ十分なツールのサポートがあったとしても、プログラミングが本質的に 難しい ことである。

10年前の本書でも、いまでも、プログラミングはいつかなくなるといわれているけど、いっこうにその気配がない。飯の種にはしばらく困らなさそう。本質的に複雑性への対処である。

コードを書くときには、あなたのプログラムを保守するだれかが、あなたの居場所を知っている凶暴な変質者であると心得よ。   ── 作者不詳 (10716)

ここまでではなくとも、将来の若者にdisられないように、怯えながらコードを書きましょう。そうでなくとも、人への優しさ、慈しみ、そういった心が大切です。