Steve McConnell『Code Complete 第2版』、読んだSteve McConnellの『Code Complete』(第2版、2004年)は、ソフトウェア開発の名著とされる。たとえば、The 25 most recommended programming books of all-time.では第3位に位置している。わたしも昔読んだが、そのときは「よくまとまっているんだろうけれど、特に面白みがない本」という印象だった。ただし、そのときのわたしは遊びのプログラミングはしていたが、堅苦しいオフィスでのソフトウェア開発はしていなかった。プラクティス本と呼べばいいんだろうか、プログラミング言語を限定せずにプログラミングの汎用的なグッドプラクティスを書いた類の本がある。『プログラミング作法』、『達人プログラマー』、『Clean Code』、『リーダブルコード』、『Code Complete』。近年ではプラクティス本はあまり流行らなくなった印象だが、言語ごとの強力な lint ツールが自動で助けてくれるからだろう。「『Code Complete』を読むこと。それに従って書くこと。レビュー時にそれに従っていないコードを指摘すること」そんな手間より「この lint ツールに従うこと。CI が勝手にエラーにするから」とする方が楽だ。プラクティス本として見ると、『Code Complete』は一般的なグッドプラクティスを言っているだけなので、初読時と同じ感想で「特に面白みがない」。ただ、『Code Complete』のポイントは、類書と比べると少しアカデミックな堅苦しい書き方と、コーディングだけでなくソフトウェア開発全体にフォーカスしていることにある。2022年に読み直してみたところ、まずすごい労作だと思う。特に第1版が出版された昔や、第2版が出版された当時は、名著とされるに足る。ただ、現在となっては、本書の中心的な内容であるコーディングプラクティス部分はすすめづらい。言語中立の書き方で、しかも Visual Basic がサンプル言語の1つとして選ばれているため、利用している言語向けのプラクティス記事を読む方がよい。『Code Complete』で書かれていることは、適切で大切なことだ。しかし、この本を読んで学べる人はすでにその内容を実践できている実力の人だし、逆にそうでない人はこの本を読んでも説明の具体性が足りなくて実践できない。1/5くらいの分量にして「詳細は参考文献を参照」を連発するか、C++ か C# にでも対象言語を絞って具体的な1つのプロジェクト開発の例を一貫して出して記述するか。どちらかにした方がよそうだ。面白かったのは、参考文献。ここに Steve McConnell の個性が感じられた。Tom Gilb “Principles of Software Engineering Management” (1988)Alain Abran et al. “SWEBOK: Guide to the Software Engineering Body of Knowledge” (2001)Karl Wiegers “Software Requirements” (2003)Len Bass, Paul Clements, Rick Kazman “Software Architecture in Practice” (2003)Steve Maguire “Writing Solid Code” (1993)Jon Bentley “Programming Pearls” (2000)Andrew Hunt, David Thomas “Prgamatic Programmer” (2000)KentBeck “Extreme Programming Explained: Embrance Change” (2000)David L. Parnas “On the Criteria to Be Used in Decomposing Systems into Modules” (1972)Erich Gamma et al. “Design Patterns: Elements of Reusable Objet-Oriented Software” (1995)Arthur J. Riel “Object-Oriented Design Heuristics” (1996)Bertrand Meyer “Object-Oriented Software Construction” (1997)Martin Fowler “Refactoring” (1999)Brooks “The Mythical Man-Month” (1995)Gerald M. Weinberg “The Psychology of Computer Programming” (1998)DeMarco, Lister “Peopleware” (1999)上はあげられている参考文献の一部にすぎないが、Tom Gilb “Principles of Software Engineering Management” と Arthur J. Riel “Object-Oriented Design Heuristics” への高評価は印象的だった。McConnell の Construx 社では The Professional Development Ladder を出していて、そこではレベルに応じた書籍を紹介している。たとえば、ソフトウェアアーキテクトのキャリアパス。読書メモ第1章 ソフトウェアコンストラクションへようこそこの章では、この本のテーマであるソフトウェアコンストラクションについて説明している。ソフトウェア開発は次のプロセスからなるとして:課題定義要求開発コンストラクション計画ソフトウェアアーキテクチャ(または概略設計)詳細設計コーディングとデバッグ単体テスト統合テスト(または結合テスト)統合システムテスト保守そのうち、ソフトウェアコンストラクションは上のほとんどのプロセスを含んではいるものの、重きを置いているのは「作る」プロセスつまり「コーディングとデバッグ」だという。要するに、ソフトウェアコンストラクションという用語は、ソフトウェア開発の言い換えにすぎないんだろうけれど、よりコーディングにフォーカスしているニュアンスが入っている。ただし、重きを置いていないだけで、コーディング以外もソフトウェアコンストラクションに含まれるから、『Code Complete』ではすべて扱ってはいる。このすべてを扱っているということが偉い。根本的な疑問として、ソフトウェア開発のプロセスを上のように定義することがどこから来たか、正しいかは分からない。この章については参考資料がない。第2章 ソフトウェア開発への理解を含めるメタファこの章ではメタファの説明をしている。それ自体はプログラミングと関係ない「メタファ」というテーマだけで1章費やしているのは、本のバランスを崩していると思った。読者を「なんだこの本…?」と困惑させるだけじゃないか? そもそも人間の思考活動にとってメタファは大事なので、ソフトウェア開発に限った話じゃない。第3章 2回測って、1度で切る:上流工程の必要性この章では上流工程の説明をしている。ソフトウェア開発の上流工程というのは、課題定義からソフトウェアアーキテクチャまでを指している。上流工程という準備が不足していると、手戻りのコストは最も高くなる。課題定義「課題定義」は、プロジェクトビジョン、プロダクト定義などとも呼ばれる。ユーザの言葉で書かれた、システムが解決すべき課題。要求「要求」は、要求開発、要求分析、ソフトウェア要求、仕様などとも呼ばれる。要求仕様をどう書くかは具体例も何もないが、要求のチェックリストは用意されている。自分なりに簡潔化すると:機能要求システムへの入力を、入力元・精度・範囲・頻度を含めて明記する。システムからの出力を、出力先・精度・範囲・頻度・出力形式を含めて明記する。外部のハードウェアインタフェース、ソフトウェアインタフェース、通信インタフェースを明記する。ユーザが実行したいと考えている各タスク、それ使われるデータを明記する。非機能(品質)要求ユーザが期待する応答時間を、操作ごとに期待する。処理時間、データ転送の速度、システムのスループットなど、時間に関する検討事項を明記する。セキュリティレベルを明記する。ソフトウェアの障害の影響、障害から保護すべきデータ、エラーの検出と回復の手順を含め、信頼性を明記する。最低限必要なメモリ容量と空きディスク容量を明記する。特定機能の変更など、システムの保守性を明記する。アーキテクチャ「アーキテクチャ」は、システムアーキテクチャ、概略設計とも呼ばれる。アーキテクチャのチェックリストは用意されている:アーキテクチャの概要や理由付けを明記する。プログラムのそれぞれの構成単位が受け持つ領域や、他の構成単位とのインタフェースを定義する。主要なクラスの説明を明記する。データ設計の説明、データベースの構造を明記する。業務ルールを明記する。ユーザインタフェースの設計方針を明記する。入出力の処理方針を明記する。リソースに対する使用量の見積もりやリソース管理の方針を明記する。セキュリティ用件を明記する。パフォーマンス目標の推定値と、機能のスペースと速度を見積もる。スケーラビリティの実現方法を明記する。相互運用性に対処する。国際化と地域化の方針を明記する。エラー処理方針を明記する。フォールトトレランスの手法を定義する。システムは技術的に実現可能か考慮する。オーバーエンジニアリングへの取り組み方を明記する。購入か構築かの決断を含める。再利用するコードを説明する。予想される変更に適応できる設計にする。アーキテクチャの全体的な品質を確認する。参考資料最後に参考資料が紹介され、詳細はそちらに任せられている。この本だけでは上流工程の説明が圧倒的に足りないが、それはもう別の本で学べということなんだろうね。要求・仕様については Karl E. Wiegers の『ソフトウェア要求』を読むべきなんだろうけど、紙版は絶版で、電子版も高い。『ユースケース実践ガイド』も読むべきなんだろうけれど、電子版はなく、紙版は絶版。高かったけど、中古で買った。あとは『SWEBOK』も紹介されている。アーキテクチャの本はいろいろあるが、今だと『ソフトウェアアーキテクチャの基礎』なんだろうか。買ってないけれど。ソフトウェア開発手法全般の本は、今はアジャイル系以外絶版しかない。第4章 コンストラクションの重要な決断この章では、ソフトウェア開発をする上で選定すべきいくつかのものを説明している。プログラミング言語、プログラミング規約、ツールやフレームワークの選択について。第5章 コンストラクションにおける設計設計に望ましい特性:最小限の複雑さ保守性:「保守プログラマを顧客と見なし、見ればすぐわかるようなシステムを設計しよう」疎結合拡張性再利用性高いファンイン:そのクラスを使用するクラスがたくさんあること。高いファンアウト:1つのクラスが使用するほかのクラス数を少なくすること。移植性無駄のなさ階層化:分解のレベルを階層化して、他のレベルを見なくても、1つのレベルを見ればわかるように設計する。標準化手法設計のレベル:ソフトウェアシステムサブシステムまたはパッケージへの分割業務ルールユーザインタフェースデータベースアクセスシステムへの依存部分クラスへの分割ルーチンへの分割ルーチンの内部設計構成要素の設計:現実世界のオブジェクトを見つけて、その属性・インタフェースを定義する一貫した抽象化を行う実装詳細をカプセル化する(設計が単純になる場合)継承する情報を隠蔽する変更可能性が高い箇所を特定する(業務ルール、ハードウェアへの依存部分、入出力など)疎結合を維持するデザインパターンを探す参考資料では、情報隠蔽については David L. Parnas の3つの論文を「現時点で最も優れた資料である」として紹介している。“On the Criteria to Be Used in Decomposing Systems into Modules” (1972)、“Designing Software for Ease of Extension” (1979)、“The Modular Structure of Complex Systems”(1985)。第6章 クラスの作成ADT、よいクラスの抽象化、よいクラスのカプセル化、継承の問題、クラスの利点について。『Effective C++』や『Effective Java』のようないわゆる Effective 本やプラクティス本で書かれているようなことが、言語中立的に詰め込まれている。参考資料には Bertrand Mayer の “Object-Oriented Software Construction” が紹介されている。訳書が高い。第7章 高品質なルーチンルーチンの利点、ルーチンの凝集度、ルーチン名、ルーチンの長さ、ルーチンの引数、マクロとインラインルーチンについて。これも Effective 本やプラクティス本のような内容が詰め込まれている。第8章 防御的プログラミング無効な入力外部ソースからのデータの値をすべて確認するルーチンのすべての入力引数の値を確認する不正な入力を処理する方法を決定するアサーション発生してはならい状況にアサーションを使う事前条件と事後条件の文書と検証にアサーションを使うエラー処理当たり障りのない値を返す次に有効なデータで代用する全開と同じ答えを返す有効な値のうち、最も近いもので代用するファイルに警告メッセージを記録するエラーコードを返すエラー処理ルーチン・オブジェクトを呼び出すエラーが発生した場所でエラーメッセージを表示するローカルで最もうまくいく方法でエラーを処理する処理を中止する例外無視すべきでないエラーに例外を使う例外的な状況でのみ例外を使うライブラリコードが投げる例外を知るバリケードによるエラーの被害の囲い込み入力データは入力されたときに正しい型に変換する第9章 擬似コードによるプログラミングルーチンの詳細設計として擬似コードを書く、擬似コードプログラミングプロセス (PPP) について。PPP 以外の方法として、テスト駆動開発、リファクタリング、契約による設計を出している。第10章 変数の使用変数は宣言時に初期化する。変数のスコープは最小限に抑える。変数の値は適切なタイミングでバインドする。変数の目的は1つだけにする。第11章 変数名の力変数や関数の名前の付け方について。第12章 基本的なデータ型数値0 による除算はエラーになる異なる型の値の演算は、暗黙の型変換が行われる整数整数の除算は、端数が切り捨てられる整数には最大値があるため、演算で桁あふれすることがある浮動小数点数浮動小数点数には有効桁数があるため、大きさが極端に異なる数同士の演算は情報落ちすることがある浮動小数点数の演算では誤差が出るため、等価にならないことがある第13章 特殊なデータ型構造体、ポインタ、グローバル変数について。第14章 ストレートなコードの構成ストレートなコードは、文の実行順序の依存性を明白にする。第15章 条件文の使用if/else, case 文について。第16章 ループの制御while, for 文について。第17章 特殊な制御構造return, goto 文と再帰について。第18章 テーブル駆動方式テーブル駆動方式について。テーブル駆動方式について書かれた本はほかにもありそうだが、なんだろう。『プログラミング作法』にあったような気がしたんだけれど(Rob Pike はその後 Go で Table-Driven Testing を推進している)、手元に本がないので分からない。第19章 制御構造の問題論理式は、肯定的な単純な式にして、かっこを使って明確化する数値を含んでいる式は、数直線の順番に並べる深いネストは減らす構造化プログラミングは、制御構造を連続・選択・反復の3つとする第20章 ソフトウェアの品質品質保証について。第21章 コラボレーティブコンストラクションペアプログラミング、ソフトウェアインスペクション(ピアレビュー)について。第22章 デベロッパーテストルーチンの直線パスを 1 として、if/while 文 や case ごとや and/or 式があるたびに 1 を加える。その結果が最低限必要なテストケースの数となる。defined -> used データフローのすべてのパスをテストする。過去に発生したよくあるエラーのテストケースを足す。境界条件をテストする。代表的な値をテストする。第23章 デバッグデバッグについて。第24章 リファクタリングMartin Fowler の “Refactoring” をもとに、“Code Complete” では以下のリファクタリングを概略している:データレベルのリファクタリングマジックナンバーを名前付きの定数に置き換える変数をより明白なものに変える式をインラインにする式をルーチンに置き換える中間変数を導入する多目的変数を複数の単一目的変数に変換するローカルの目的には引数ではなくローカル変数を使用するデータプリミティブをクラスに変換する一連の型コードをクラスまたは列挙に変換する一連の型コードをスーパークラスとサブクラスに変換する配列をオブジェクトに変換するコレクションをカプセル化する従来のレコードをデータクラスに置き換えるステートメントレベルのリファクタリング論理式を分解する複雑な論理式を分かりやすい名前の付いた論理関数にする条件文に分散している重複するコードを1つにまとめるループ制御変数ではなく break や return を使用するネストした if-then-else ブロクで答えが分かったら、戻り値を代入せずに、すぐに return する条件文、特に case 文の繰り返しをポリモーフィズムで書き換えるnukll 値を評価するのではなく null オブジェクトを生成して使用するルーチンレベルのリファクタリングルーチンやメソッドを抽出するルーチンのコードをインラインにする長いルーチンをクラスに変換する複雑なアルゴリズムを単純な雨後リズムで代用する引数を追加する引数を削除する問い合わせと更新を分離する同じようなルーチンは引数を介在させてまとめる入力引数によってふるまいに異なるルーチンを分離する特定のフィールドではなくオブジェクト全体を渡すオブジェクト全体ではなく特定のフィールドを渡すダウンキャストをカプセル化するクラス実装のリファクタリング値オブジェクトを参照オブジェクトに変更する参照オブジェクトを値オブジェクトに変更するvirtual メソッドをデータの初期化に置き換えるメソッドまたはメンバデータの配置を変更する特殊なコードをサブクラスとして抽出する同じようなコードをスーパークラスにまとめるクラスインタフェースのリファクタリングメソッドを別のクラスに移動する1つのクラスを2つに分けるクラスを削除する委譲を隠蔽する中間オブジェクトを削除する継承を委譲に置き換える委譲を継承に置き換える外部ルーチンを導入する拡張クラスを導入する公開されているメンバ変数をカプセル化する変更できないフィールドのセッタ・メソッドを削除するクラスの外側で使用されないメソッドを隠蔽する使用されないメソッドをカプセル化するスーパークラスとサブクラスの自走が非常によく似ている場合は1つにまとめるシステムレベルのリファクタリング制御できないデータについては、最も信頼のおけるデータソースを作成する一方向のクラス結合を双方向のクラス結合に変更する双方向のクラス結合を一方向のクラス結合に変更する単純な雨後コンストラクタではなくファクトリメソッドを提供するエラーコードを例外に置き換える、または例外をエラーコードに置き換える第25章 コードチューニング戦略コードチューニング戦略について。第26章 コードチューニングテクニックコードチューニングテクニックについて。第27章 プログラムサイズが及ぼす影響プロジェクトが大きいほど、コミュニケーションコストが高くなり、生産性は下がる。プログラムサイズがフェうrほどエラー数は増える。第28章 コンストラクションの管理コーディングプラクティス、変更管理、工数の見積もり、ピープルウェアについて。第29章 統合インクリメンタルなインテグレーションと、デイリービルドとスモークテストの自動化について。第30章 プログラミングツールCASE ツール、IDE、一括検索・置換、diff、フォーマッタ、スニペット、linter、リファクタリングツール、バージョン管理ツール、ビルドツール、デバッガ、テストルールなど。第31章 レイアウトとスタイルコードのインデントのレイアウトについて。第32章 読めばわかるコードコードコメントについて。第33章 個人の資質必要な個人の資質は、謙虚さ、好奇心、知的な誠実さ、創造性と規律、啓発的な怠惰。第34章 ソフトウェア職人気質とは第35章 さらに情報を得るには様々な参考文献。