A Philosophy of Software Design ch19 Software Trends
Software Trends
- ここ数十年で人気となったトレンドやパターンについて
- 本書で挙げた設計原則との関連について述べる
- 本書で挙げた設計原則に照らして評価する
- ソフトウェアの複雑性に立ち向かうための力を提供してくれるかどうか
Object-oriented programming and inheritance
- OOPはここ30-40年で最も重要なアイデアの一つ
- 導入された概念
- クラス
- 継承
- プライベートメソッド
- インスタンス変数
- 導入された概念
- 注意深く利用すれば良い設計の助けになる
- 例: privateメソッド/変数
- 知識の隠蔽に有用
- 外からアクセスできないので外部との依存がない
- 知識の隠蔽に有用
- 例: privateメソッド/変数
- OOPの鍵は継承
- 2つの形がある
- インタフェースの継承
- 実装の継承
- 2つの形がある
- インタフェースの継承
- 実装の継承
- 親がデフォルトの実装も定義する
- サブクラスは親の実装を継承するか、上書きするか選べる
- 同じ実装が複数のサブクラスに重複するのを避ける
- 複雑性の3本の柱のひとつ、「変更の増幅」を避ける
- 実装の継承の利用は注意深く
- 親クラスとサブクラスとの間に依存が生じる
- 知識の漏出
- 親クラスの変数は子クラスからアクセスされうる
- 親クラスの実装者は、子クラスを壊さないように全子クラスを調べる必要があるかもしれない
- 子クラスの実装者は、メソッドをオーバライドする場合、親クラスの実装を知る必要があるかもしれない
- 最悪のケースでは、あるクラスに変更を加えるために、クラスツリー中の全クラス調べないといけないかもしれない
- 実装の継承は広範囲にわたり複雑になりがち
- 知識の漏出
- 継承を選ぶ前に、集約と委譲を検討せよ
- 継承以外に可能な選択肢がない場合は、親が管理する状態と子が管理する状態とを分離せよ
- 例: あるインスタンス変数は親のメソッドでのみ管理
- 子から利用するときは…
- 読み取りのみ行う
- 必ず親クラスのメソッドを通す
- 子から利用するときは…
- 例: あるインスタンス変数は親のメソッドでのみ管理
- 親クラスとサブクラスとの間に依存が生じる
- オブジェクト指向は、きれいな設計の助けにはなれど、それ自体が良い設計を保証するものではない
Agile development
- 1990代後半に生じたソフトウェア開発アプローチ
- 開発を軽量に、柔軟に、インクリメンタルにするためのアイデア群
- 2001年に、実践者らにより形式化
- アジャイル開発のアイデアの多くは、設計ではなく開発プロセスに関するもの
- チーム構成
- スケジュール管理
- 単体テストの役割
- 顧客と関わる
- etc.
- 一部は設計にも関連する
- 「開発はインクリメンタルで反復的であるべき」
- プロジェクトの最初から複雑なシステム像を描いて最適な設計を決定することは困難
- インクリメンタルアプローチで、各インクリメントごとに数個の抽象を追加し、リファクタリングするのが最良
- 「開発はインクリメンタルで反復的であるべき」
- アジャイル開発のリスク: 戦術的プログラミングに陥る
Unit test
- 開発者がテストを書かなかったのも今は昔
- 書くにしても、独立したQAチームが書いていた
- アジャイルの教義のひとつ: テストと開発とは統合され、プログラマは自分の書いたコードのテストも書くべき
- 今や広く普及している
- テストは典型的に2種に分けられる
単体テスト | システムテスト | |
---|---|---|
誰が書く | 開発者自身 | QAチーム テストチーム |
スコープ | 狭い。 1メソッドの1セクション等 |
アプリケーション全体 |
環境 | 隔離 | 商用 |
- 単体テストはカバレッジツールと組み合わせて実行されること多し
- 開発者の責任で、適正なカバレッジを保つようにテストを更新する
- 新しくコードを書いたとき
- 既存コードに変更を加えたとき
- 開発者の責任で、適正なカバレッジを保つようにテストを更新する
- 特に単体テストは、リファクタリングを容易にすることから、ソフトウェア設計において重要な役割をになう
- 著者自身、Tclスクリプトのエンジンを根本的に作り変えた際、素晴らしい単体テストスイートのおかげで、エンバグは1つで済んだ
Test-driven development
- 著者は単体テストの支持者だが、テスト駆動開発のファンではない
- テスト駆動開発の問題は、「良い設計」ではなく、「特定の機能が動くようにすること」にフォーカスしていること
- 戦術的プログラミングの欠点そのもの
- テスト駆動開発はインクリメンタルすぎる
- 次の機能がテストをパスするようにコードを改造することの繰り返しに陥りがち
- 設計のための時間を明確に割くことがない
- 先述のとおり、開発の単位は機能ではなく抽象であるべき
- 抽象化の必要性が判明したら、長時間かけてバラバラに作るのではなく、一度に作れ
- そのほうがクリーンな設計になりやすい
- 各抽象のピースがうまくフィット
- 不具合修正の場合はテストファーストが良い
- その不具合が原因で落ちるテストをまず書く
- 不具合を修正し、テストが通ることを確認する
- テストを書く前に不具合を直してしまうと、追加されたテストが本当にその不具合を再現するのかわからない
- 不具合が本当に直ったのか分からない
Design patterns
- ある種の問題を解決するために共通して利用されるパターン
- デザインパターンは設計の選択肢
- 1から設計するより、有名なデザインパターンを適用せよ
- それで大抵うまくいく
- 共通の問題の綺麗な解決法として、広く受けられてきたものだから
- うまくいく場合は、それより良い別の方法を思いつくのは難しいだろう
- デザインパターン最大のリスクは、適用しすぎること
Getters and setters
- Java界隈で普及しているデザインパターン
- 厳密には不要なもの
- publicメンバと同じ
- 支持者の言い分
- 値の読み出し、変更時に別の処理を挟める
- 例
- 変更通知
- 値の制約条件を課す
- 例
- 最初は無くても、将来、呼び出し側に変更を加えることなく追加できる
- 値の読み出し、変更時に別の処理を挟める
- 内部表現の暴露なのでやめたほうがいい
- getter/setterは浅いメソッド
- 典型的には1行程度
- 大した機能を提供しないわりにクラスのインタフェースを散らかしてしまう
- getter/setterは浅いメソッド
- デザインパターンを確立することのリスクは、それを「良いものだ」と思った開発者が、なるべく多く適用しようと努めてしまうこと
- Javaのgetter/setterはこの類
Conclusion
- 新しいソフトウェア開発のパラダイムに出くわしたら、複雑性の観点から疑ってみよ
- 大きなソフトウェアシステムの複雑性を最小化する助けになるだろうか?
- うわべは良さそうに見えるもの
- が、深く見てみると、複雑性を悪化させるものだとわかることも
英語
- steer clear of
- 避ける、かかわらない
- viable
- 実行可能な