Code Complete
基礎
ソフトウェアコンストラクションとは
- コンストラクションとはプログラミングのこと
- コーディング+デバッグ+ α(詳細設計やユニットテストなど)からなる
- 「コーディング」という言葉単体は、単なる機械的な作業を連想させるため、コンストラクションの説明としては適切ではない
- コンストラクション以外の作業は?
- プロジェクトマネジメント
- 要求開発(要件定義)
- 概要設計(アーキテクチャ)
- UI 設計
- システムテスト
- 保守
なぜコンストラクションが重要か
- 開発時間の大部分を占めるため
- 開発の中心であるため
- コンストラクションの改善は驚くべき効果をもたらすため
- コードが唯一のドキュメントになってしまうことがよくあるため
- どれだけ急いでいても絶対に省略できない工程であるため
メタファにより開発への理解を深める
メタファの重要性
- メタファを使った理解を「モデリング」という
- よく理解できないものを、既に理解しているものと照らし合わせることで、理解が深まること
- 概念全体を把握しやすくなる
メタファの使用方法
メタファはヒューリスティクスの意味合いが強い。どうすれば物事がうまくいくか、考えるために使うものである。
- アルゴリズム
- 厳密に定義された一連の命令
- ヒューリスティクス(発見的)
- 答えを見つけるために役立つテクニック
システム構築のメタファ
- BAD) コードを書く
- 手紙を書くイメージ
- 書いて終わりじゃねーのよ
- BAD) システムを育てる
- 作物を育てるイメージ
- コードはすくすく育たないし収穫もできないのよ
- SOSO) システムをインクリメンタルに開発する
- 真珠養殖のイメージ
- GOOD) システムを構築する
- 建築のイメージ
- 現代のシステム開発における用語の多くは建築由来。e.g. アーキテクチャ、ビルド、etc
話は逸れるけど、知識を常に収集してストックしておく「知的道具箱」というメタファもいいね
上流工程
準備の重要性
- 品質向上したいタイミングが
- プロジェクトの終わりの場合
- システムテストを強化する
- ただし、どれだけテストをしたところで、もとがクソなら意味がない
- プロジェクトの途中の場合
- コンストラクションのプラクティスを強化する
- プロジェクトの最初の場合
- 高品質な計画、要求、設計を行う
- 安い車として設計したものは、どれだけ後段で努力してもロールスロイスにはならないからね
- プロジェクトの終わりの場合
準備の最大の目標
リスクを減らすこと。
準備不足の原因
- 上流担当開発者が仕事をこなすだけの知識を持っていないから
- すぐにコードを書きたい衝動を押さえられない開発者がいるから
- 準備にかける時間を上司がよく思わないから。対処法:
- はっきり断る
- コーディングしているふりをする
- 準備を怠る危険を上司に叩き込む
- 転職する
準備に文句を言わせないためには
- 論理で訴える
- ユーザが求めていないものを作ると目も当てられないよ
- 変な構築をすると必要のないものに膨大な時間と資金が消えていくよ
- 例えで訴える
- プランクトンがぴょう期ならいずれクジラも死ぬ、みたいな食物連鎖の例とか
- データで訴える
- 問題の修正時期があとになるほど、コストは指数的に増えるよ
- これはウォーターフォール・アジャイルを問わない
ソフトウェアの種類
- ゆるいシステム
- アジャイル開発
- 反復型 の開発手法が適している(以下を交互に繰返し行う)
- 計画、要件定義、アーキテクチャの策定
- コンストラクション、システムテスト、品質保証
- ざっくりとした要求仕様の策定
- 設計とコーディングを同時にやってしまう
- 別チームでのテストや QA はなし
- かたいシステム
- ウォーターフォール開発
- 逐次的 な開発手法が適している
- ちゃんとした要求仕様の策定
- アーキテクチャの設計とレビュー
- 詳細設計とレビュー
- 別チームでのテスト
- QA
反復型手法
コストは以下の順に安くなる
- 準備なしの逐次型(高い)
- 準備なしの反復型
- 準備ありの逐次型
- 準備ありの反復型(安い)
どんなプロジェクトでも「最も重要な要求とアーキテクチャの要素を早期に洗い出すこと」が大事。目安として:
- 逐次型なら事前に 80%の要求を明らかにしておく
- 反復型なら事前に 20%の要求を明らかにしておく
反復型 or 逐次型
- 逐次型に向いているもの
- 要求が安定している
- 設計が単純で理解しやすい
- メンバーが分野に精通している
- プロジェクトのリスクが低い
- 長期的な予測が重要である
- 下流での変更が高く付く
- 反復型に向いているもの
- 要求が不透明
- 設計が複雑で理解しにくい、手間がかかる
- メンバーが分野に明るくない
- プロジェクトのリスクが高い
- 長期的な予測が重要でない
- 下流での変更は安価である
以下、プロジェクトに適したコンストラクションの準備とは何かを考えてみる
準備:課題定義
Product vision statement / Mission statement / 課題定義
- システムが解決する課題が何であるかを定義するもの
- ソリューションについては一切言及しない
- ユーザの言葉で書く。コンピュータ用語は使わない。
- 以下を防ぐためのもの
- 誤った課題を解決しようとして時間を無駄にすること
- 本来の課題が解決されないこと
準備:要求
要求 / 要求開発 / 要求分析 / 要求定義 / 仕様 / 機能仕様 / スペック とは:
- システムが何をすべきかを定義するも の
要求が必要な理由
- システムの機能をユーザ主導(not プログラマ)で決定するのに役立つ
- プログラマ同士の議論を減らせる
- 手戻りを抑制できる
要求は変わる
- 顧客は、開発の過程を通じて自分自身のニーズを理解していくものだから
- 平均して当初の仕様の 25% は変更される
要求変更への対処方法
- 要求がきちんと定義されているかチェックリストで確認する
- コストを関係者全員に認識させる
- 変更の管理手順を予め定めておく
- 変更に対応できる開発手法をとる
- プロジェクトを中止する
- その変更が本当にビジネスに価値をもたらすのか考えさせる
アーキテクチャ
- 最上位レベルの設計、概略設 計のこと
- 最終的なシステムの品質を決定する
- 後で変更すると膨大なコストがかかる
アーキテクチャの構成要素
- 概要
- 全体構成をざっと説明するもの
- どんなパーツがあるのか列挙
- 各パーツの役割は1つに絞る
- パーツ間のやり取り規則は明確にする
- 主要なクラス
- システムの8割を司る2割のクラスを明記する感じ
- データ設計
- DB の大まかな構造と内容
- 業務ルール
- 例えば顧客情報は 30 日より古くなってはならない、などあれば
- UI 設計
- リソース管理
- セキュリティ
- パフォーマンス
- スケーラビリティ
- i18n, l10n
- エラー処理
- メッセージ規約など
- フォールトトレランス
- オーバーエンジニアリング
- どれくらい堅牢にすべきか
よいアーキテクチャとは
- 採用した理由、採用しなかった理由が明記されている
- 途中変更する場合は全体と調和するようにする
- 目的が明確であ る(パフォーマンス重視、柔軟な変更可能性重視、など)
- マシンや言語に依存していない
- 過不足がなく丁度いい
- リスクが明記されている
- 複数の角度からの見解が盛り込まれている
- 不安要素がない
上流工程にかける時間
スケジュール全体の2割から3割
コンストラクションの重要な決断
言語
- 使い慣れた言語だと生産性が高まる
- 高級言語の生産性は低級言語の 5 から 15 倍
- 考える能力は言葉(≒ プログラミング言語)を知っているかどうかで決まる
設計
設計の難しさ
設計の難しさは、設計が:
- wicked problem であるため
- wicked problem = やっかいな問題
- やってみて初めて気がつく問題が内包されていること
- ルーズなプロセスであるため
- 良い方法と悪い方法の違いがわずかであり、間違えやすい
- どれだけやれば十分かわからない、よくあるのは「時間がなくなったら」
- 妥協と優先順位付けの産物であるため
- 制限がつきものであるため
- 非決定論的であるため
- 無限にやり方がある
- ヒューリスティックなプロセスであるため
- 発見的、試行錯誤的
- 試してみないとわからない
- 創発的であるため
- 常に動き続け、レビュー、話し合い、経験を通じて改善されていくもの
重要な設計概念
鉄則:複雑さへの対応
- essential(本質的)問題と accidental(偶発的・付随的)問題
- なぜ複雑さへの対応が必要か?
- 人間の頭はプログラム全体を理解することはできないから
- 複雑な問題を 単純な問題に分割する ことが必要
- 方針
- 一度に対処しなければならない本質的な複雑さを最小限に抑える
- 偶発的な複雑さを必要以上に増やさない
高品質な設計とは
- 最小限の複雑さ 凝った設計にするな
- 保守性
- 疎結合
- 拡張性
- 再利用性
- 高いファンイン
- あるクラスが、他のたくさんのクラスで使われている
- 低いファンアウト
- 1 つのクラスが使用する他のクラスが少ない
- 移植性
- 無駄がない 足すものがなく削るものもない
- 階層化
- 汚いコードを覆うインターフェース層を追加する、など
- 標準化
- 独自のフレームワークをつくるより、一般的なデザインパターンを採用する、など
設計のレベル
- Lv1 ソフトウェア
- Lv2 サブシステム・パッケージ
- Lv3 クラス
- Lv4 ルーチン
- Lv5 ルーチン内部
Lv1 ソフトウェア
システム全体のこと。
Lv2 サブシステム・パッケージに分割
- 大きな単位でシステムを分割する よくあるサブシステムの分け方:
- 業務ルール 源泉徴収額の計算、など
- ユーザーインターフェース 画面描写
- DB アクセス
- システムへの依存部分 windows 用コード、Mac 用コードなど
- サブシステム間のやり取りを可能な限り少なくする => 複雑さへの対処
- ベスト サブシステムが別のサブシステムのルーチンを呼ぶ
- まあまあ サブシステムが別のサブシステムのクラスを含む
- だめ サブシステムが別のサブシステムのクラスを継承する
Lv3 クラスに分割
- パッケージ内で、機能をクラスに分割し、クラスを設計する
- クラスのインターフェース(パブリックなルーチン)も検討する
Lv4 ルーチンに分割
- 各クラスをルーチン(プライベートなルー チンを含む)に分割し、ルーチンを設計する
- このレベルで検討した結果、Lv3 に戻ってインターフェースを変更するのもアリ
- 担当エンジニアの頭の中で行われることが多い
Lv5 ルーチンの内部設計
- ルーチンの設計を行う
- 担当エンジニアの頭の中で行われることが多い
構成要素の設計:ヒューリスティクス
設計には、必ず正しい答えがあるわけではなく、常に発見的・試行錯誤的である。 このため「完璧ではないものの、概ね良い答えが出るであろう方法(≒ 経験則)」で設計は行われる。 この方法のことをヒューリスティクスと呼ぶ。
以下、いくつかのヒューリスティクスを説明する。
現実の世界をオブジェクトにする
- 属性を決める
- メソッドを決める
- 包含や継 承の関係を決める
- 属性・メソッドのパブリック・プライベートの別を決める
一貫した抽象化を行う
- 抽象化とは、詳細にこだわらず概念だけを表すこと
- 複雑な部分を無視して簡単にすることで、頭の中で整理できるようにする
- 人間は、集合を扱う場合は必ず抽象化を行う
- 「ガラス・木・釘の組み合わせ」=「家」など
- 抽象化のレベルを揃えること
- ルーチンのインターフェース = ドアノブ
- クラスのインターフェース = ドア
- パッケージのインターフェース = 家
- 極端に細かすぎる or 大きすぎる単位で抽象化しない。頭の中で一度に理解できないから。
- 木の繊維、鉄の分子レベル
実装の詳細をカプセル化する
- カプセル化=複雑な詳細を、隠す
- 抽象化=複雑な詳細を、単純に見せる
継承する
可能かつ最 適な場合は、継承を使うと良い。 派遣社員と正社員は、どちらも「社員」の属性と振る舞いを継承できる。 (派遣社員 'is-a' 社員、正社員 'is-a' 社員)
秘密を隠蔽する
隠蔽は、価値が明白に実証され、長きに渡ってその価値を失わない、数少ない理論的手法の一つ。 以下のような情報は隠蔽し、外部にはインターフェースのみを提供すること。 何を隠蔽すべきかを常に意識し、プロジェクト全体に情報をばらまかないこと。
- 変更される可能性の高い領域のコード
- マシン固有のコード
- 型の実装の詳細(int ではなくユーザ定義型を使う、など)
そうすることで以下のメリットが有る
- 変更時に 1 箇所変えればいい=コストを抑えられる
- 頭の中で 1 度に整理すべき事項が少なくなることで、「複雑さへの対処」を行うことができる
変更の可能性が高い領域を特定する
変更されそうな部分は、独立したクラスにしておく。一般的に変更されやすい領域は以下の通り。
- 業務ルール
- これはテーブル駆動型にしておくとなおよい
- ハードウェアに依存する部分
- 入出力インターフェイス(ファイルのフォーマットなど)
- 設計や実装の難度が高い部分
- プログラムの状態変数
- ブール型ではなく列挙型にする
- アクセスルーチンを介して利用する
- データサイズの制約
疎結合にする
疎結合の判断基準
- 数
- パブリックメソッドの数は少なく
- 引数の数は少なく
- 可視性
- データは引数として明示的に渡す
- こそこそグローバルデータを見に行ったりするのは ×
- 柔軟性
- どんな場面でも使いやすいこと
結合の種類
- 単純データパラメータ結合
- モジュール間を、プリミティブな引数のみでやりとり
- オブジェクトパラメータ結合
- モジュール間を、オブジェクト(というよりクラスインスタンスなど)を引数としてやりとり
- 1 よりは密結合
- 単純オブジェクト結合
- モジュールと、その中でインスタンス化されたオブジェクトの関係のこと
- セマンティック結合
- 相手モジュールの内部の仕組みを暗に推察して、何かを行うこと
- 極めて危険で、やっかいな問題を起こすので、使うな
デザインパターンを使う
pattern | desc |
---|---|
Abstract Factory | ? |
Adapter | クラスのインターフェースを別のインターフェースに変換する |
Bridge | インターフェースと実装を別個に変更できるようにする |
Composite | オブジェクトにオブジェクトを包含させる |
Decorator | 動的に機能を追加する。機能ごとにサブクラスを作らない。 |
Facade | 一貫したインターフェースを提供する |
Factory Method | サブクラスでインスタンスを作る |
Iterator | 要素に順次アクセスする方法を提供する |
Observer | 複数のオブジェクトに変更を知らせる |
Singleton | インスタンスを 1 つしか持たない |
Strategy | アルゴリズムをいくつか用意する |
Template Method | ? |
カスタムなやり方をせず、可能な限りデザインパターンをつかうこと。
- デザインパターンを使えば簡単に意図を他者と共有できる
- より上位レベルの話に専念できる
- 車輪の再発明を防げる
- 問題には、問題を複数回解決してみないとわからない部分がある
- 既存のソリューション・ライブらいはそれらを経験し、克服している
注意点
- パターンは絶対ではない。標準パターンに 100%無理に合わせなくてもいい。
- あくまで手段であ る。パターンを試してみたいという理由だけで使うな。
その他のヒューリスティクス
- 凝集度を強くする
- 階層化する
- クラス規約(事前条件・事後条件など)を決めておく
- 責任を割り当てる
- テストしやすい設計にする
- バインディングタイムを意識的に選択する
- 制御を一元化する(可能な限り 1 箇所に集める)
- 総当たり法をバカにするな、場合によっては最適である
- 図を書いてみる
- ルーチンやクラスを「ブラックボックス」に仕立て、設計をモジュール化する、簡単にする
設計のプラクティス
設計で心がけたほうが良い手順
反復
- 何度も設計してみる。ほぼ必ず、2 回目以降のほうがうまくいく。
- 上流・下流を行ったり来たりすることで、大きな改善が得られる(頭を切り替えるのが辛いけど頑張れ)