なになれ

IT系のことを記録していきます

A Philosophy of Software Designの20章要約

本内容は「A Philosophy of Software Design」の20章を要約する記事です。

過去記事

20章の要約

  • 20章 パフォーマンスのためのデザイン
  • 概要
    • きれいな設計を犠牲にすることなく、高いパフォーマンスを達成する方法について説明する
    • シンプルであることはシステムの設計を向上させるだけでなく、システムをより高速にする
  • パフォーマンスをどう考えるか
    • 通常の開発プロセスにおいて、パフォーマンスをどの程度気にすべきか
      • すべてのステートメントを最適化して速度を上げようとすると、 開発のスピードが落ちるし、不必要に複雑になる
      • パフォーマンスの問題を完全に無視した場合、コード全体に多数の非効率な部分が存在することになり、その結果、システムは必要な速度の5倍から10倍も遅くなる可能性がある
    • 最適なアプローチは、この両極端の間にあるもので、パフォーマンスに関する基本的な知識を使って、「自然に効率的」かつ「クリーンでシンプル」な設計案を選択すること
    • どの操作が基本的にコストが高いか
      • ネットワーク通信は10~50μsから10〜100ms
      • ディスクI/Oは5〜10ms、SSDは10〜100μs、メモリは1μs
      • キャッシュミスはパフォーマンスに影響する
    • コストを知るにはマイクロベンチマークを実行する
    • 多くのケースで、より効率的なアプローチはより遅いアプローチと同じように単純である
      • 大きなオブジェクトのコレクションを保存し、それをキーとして検索する場合、ハッシュテーブルや順序付きマップを使用することができるが、ハッシュテーブルの方が高速である
    • 一般に、複雑なコードよりも単純なコードの方が高速に動作する傾向がある
      • 特殊なケースや例外を排除して定義すれば、それらのケースをチェックするためのコードが不要になり、システムの実行速度が上がる
      • 深いクラスは浅いクラスよりも効率的、各メソッド呼び出しに対してより多くの作業が行われるため
  • 修正する前に測定する
    • 直感に基づいて変更を始めると、実際にはパフォーマンスを改善しないことに時間を浪費することになる。そして、複雑にする
    • 変更を加える前に、システムの既存の挙動を測定する
      • パフォーマンスのチューニングが最も大きな影響を与える場所を特定する。システムの改善ポイントをごく少数、具体的に特定する
      • 基準値を設定する。これにより、変更後にパフォーマンスを再測定し、実際にパフォーマンスが向上したことを確認する
    • パフォーマンスが向上しなければ、複雑にしておく理由はない
  • クリティカルパス周辺の設計
    • 既存のコードをより速く実行できるように設計し直す方法について
    • 最も一般的なケースで実行しなければならない最小量のコードであるクリティカルパスのみを実装した新しいメソッドを書いていると想像する
    • 理想的なコードは、おそらく既存のクラス構造と衝突し、実用的でないかもしれないが、良い目標を提供する
    • これはコードがこれまでで最もシンプルで高速であることを表す
    • 次に、きれいな構造を維持しながら、理想にできるだけ近い新しい設計を探す
    • 理想的なコードを(ほとんど)そのまま維持するという制約のもと再設計する
    • このプロセスで最も重要なことの1つは、クリティカル・パスから特殊なケースを取り除くこと
    • コードが遅いのは、さまざまな状況を処理しなければならないためで、コードはすべての異なるケースの処理を単純化するように構成する
    • 各特殊なケースは、余分な条件文やメソッド呼び出しの形で、クリティカルパスに少しづつコードを追加し、コードを遅くする
    • パフォーマンスのために再設計する場合は、チェックしなければならない特別なケースの数を最小限にする
    • 理想的には、冒頭に1つのif文があり、1回のテストですべての特殊なケースを検出する
    • 特殊なケースでは性能はそれほど重要ではないので、性能よりも単純さを重視して特殊なケースのコードを構成することができる
  • 結論
    • クリーンな設計と高いパフォーマンスは両立する
    • パフォーマンスにとって最も重要なクリティカルパスを見つけ、それを可能な限りシンプルにする

感想

  • パフォーマンスはシンプルなコードで向上するのはなるほどな気付きだった。超絶技巧なコードを書くことがパフォーマンス向上には必要だと思っていた。
  • まとめると、重複を排除することや特殊ケースを分離することがパフォーマンスの向上に貢献するという感想を持った

A Philosophy of Software Designの19章要約

本内容は「A Philosophy of Software Design」の19章を要約する記事です。

過去記事

18章の紹介

他担当の方が要約した18章の内容を引用します。

  • 18章 コードは明白であるべき
  • 不明瞭性は、システムに関する重要な情報が新しい開発者にとって明白でない場合に発生する
    • 不明瞭さの問題の解決策は、コードが明白になるように書くこと
  • この章では、コードを明白にする、または明白でなくする要因のいくつかを説明する
  • "自明 "とは読む人の心の中にあるものです。自分のコードの問題点に気づくより、他人のコードが自明でないことに気づく方が簡単です。したがって、コードの自明性を判断する最良の方法は、コードレビューを通じて行うこと
    • もし、あなたのコードを読んだ誰かが自明でないと言ったら、あなたにはどんなに明確に見えても、それは自明ではない
  • コードを明白にするための最も重要な2つのテクニックとして
    • 1つ目は、良い名前を選ぶことです(第14章)
      • 正確で意味のある名前は、コードの振る舞いを明確にし、ドキュメントの必要性を低減する
    • 2つ目のテクニックは「一貫性」(第17章)
      • 同じようなことがいつも同じような方法で行われていれば、読者は以前見たことのあるパターンを認識し、コードを詳しく分析しなくてもすぐに(安全な)結論を導き出すことができる
  • その他のテクニック
    • 空白をうまく利用する
      • コードの書式は、コードの理解しやすさに影響を与えることがある
      • この方法は、各空白行の後の最初の行が、次のコードブロックを説明するコメントである場合に特に効果的:空白行は、コメントをより見やすくする
  • コードを非自明化するものはたくさんある
    • イベントドリブンプログラミング
      • イベントドリブンプログラミングでは、制御の流れを追うことが難しくなります。イベントハンドラ関数は直接呼び出されることはなく、イベントモジュールによって間接的に呼び出されます
      • 不明瞭さを補うために、各ハンドラ関数のインターフェイスコメントを使用して、それがいつ呼び出されるかを示すようにする
  • Redflag: Nonobvious Code
    • コードの意味や動作が、ぱっと読んだだけでは理解できない場合は、赤信号です。多くの場合、これは、コードを読んだ人がすぐに理解できない重要な情報があることを意味します
    • 多くの言語では、JavaのPairやCのstd::pairのように、2つ以上の項目を1つのオブジェクトにまとめるための汎用クラスが提供されている
    • 汎用コンテナは、グループ化した要素の意味が不明瞭な汎用名を持っているので、明白ではないコードになってしまう
      • コンテナが必要なら、特定の用途に特化した新しいクラスや構造体を定義してください。そうすれば、要素に意味のある名前を使うことができますし、汎用コンテナでは不可能な追加ドキュメントを宣言で提供することができます
        • 一般的なコンテナは、コードを書く人にとっては便利ですが、後に続くすべての読者にとって混乱を引き起す。コードを書く人にとっては、数分余計に時間をかけて特定のコンテナ構造を定義した方が、出来上がったコードがより明白になる
  • 自明性について考えるもう一つの方法は、情報という観点
  • コードを明白にするためには、読者がコードを理解するために必要な情報を常に持っているようにしなければならない
    • 一番良い方法は、抽象化や特殊なケースの排除などの設計テクニックを使って、必要な情報の量を減らすこと
    • 第二に、読者が他の文脈で既に習得している情報(例えば、慣習に従う、期待に沿うなど)を利用し、読者があなたのコードのために新しい情報を習得する必要がないようにすること
    • 第三に、良い名前や戦略的なコメントなどのテクニックを使って、コードの中で重要な情報を読者に提示すること

19章の要約

  • 19章 ソフトウェアの動向
  • 概要
    • 過去数十年の間にソフトウェア開発のトレンドと本書の原則との関係を説明する
    • ソフトウェアの複雑性に対するレバレッジを提供するかどうか原則を使用して評価する
  • オブジェクト指向プログラミングと継承
    • インタフェース継承
      • 同じインタフェースを複数の目的で再利用することで複雑性に対するレバレッジを提供する
    • 実装継承
      • 使用には注意が必要
      • 親クラスの下にあるクラス階層全体を把握しなければ変更できない可能性がある
      • 継承ではなく、委譲が使えないかを検討する
    • オブジェクト指向プログラミングはクリーンな設計の実装に役立つが、良い設計を保証するわけではない
  • アジャイル開発
    • 1章で述べたインクリメンタルアプローチに似ている
    • 戦術的なプログラミングにつながる可能性がある
    • アジャイル開発はできるだけ早く実用的なソフトウェアを作るために設計の決定を先延ばしにすることを奨励する
  • ユニットテスト
  • テスト駆動開発
    • テスト駆動開発の問題点は、最適な設計を見つけることよりも、特定の機能を動作させることに注意を集中させること
    • これは戦術的なプログラミングである
    • 設計を行うための明確なタイミングがないため、混乱した状態で終わる
    • 開発の単位は機能ではなく、抽象化であるべき
    • テストを最初に書くことが理にかなっているのは、バグを修正するとき
  • デザインパターン
  • getterとsetter
    • インスタンス変数の公開は、クラスの実装の一部が外部に見えてしまうことを意味し、 情報隠蔽の考え方に反し、クラスのインターフェイスを複雑化させる
    • getterやsetter(あるいは実装データの公開)は、できるだけ避ける
  • 結論
    • 新しいソフトウェア開発パラダイムに出会ったときはいつでも複雑性の観点から評価する
    • それらのいくつかは複雑さを改善するのではなく、悪化させることもある

感想

A Philosophy of Software Designの17章要約

本内容は「A Philosophy of Software Design」の17章を要約する記事です。

過去記事

16章の紹介

他担当の方が要約した16章の内容を引用します。

  • 16章 既存コードの修正
  • この章ではシステムが進化する過程で複雑性が忍び込まないようにする方法を説明
  • 開発者がバグフィックスを行うなど実装の変更を行う際、通常、戦略的な思考はしない
    • "私が必要とすることを実現するためにできる最小限の変更は何か?"を考え実行する
    • 大きな変更は新たなバグを引き起こすリスクが高くなることを懸念することが原因である
    • これでは戦術的なプログラミングになり、最小限の変更を行うたびに、いくつかの特殊なケースや依存関係その他の複雑な形態が生まれる
  • 理想的には、各変更を終えたときに、最初からその変更を念頭に置いて設計した場合の構造を持ったシステムになっていること
  • 既存のコードを変更した場合、その変更によって既存のコメントの一部が無効になることがある。それをふせぐいくつかの方法がある
  • 1. そのコメントが記述されているコードの近くに配置することで、開発者がコードを変更したときにそれを見ることができます
    • ドキュメントはコードを書く開発者にとって最も便利な場所に配置されるべき
      • 各コメントは、そのコメントによって参照されるすべてのコードを含む最も狭い範囲に押し込められるべき
      • たとえば、あるメソッドが3つの主要なフェーズを持つ場合、メソッドの先頭にすべてのフェーズの詳細を説明する1つのコメントを書くのは良くない
      • 各フェーズに個別のコメントを書き、そのコメントをそのフェーズのコードの最初の行のすぐ上に配置します
    • コードを修正するときによくある間違いは、ソースコードリポジトリのコミットメッセージに変更に関する詳細な情報を記載し、その後、コードにそれを文書化しないこと
      • コミットメッセージを書く際には、開発者が将来その情報を使う必要があるかどうかを自問すべき
    • 必要な場合は、その情報をコードで文書化すべき
    • 情報はコードの中に入れることです。これは、開発者が最も目にする可能性の高い場所にドキュメントを配置するという原則を表しています
    1. 重複を避ける
    2. ドキュメントが重複していると、開発者が関連するコピーをすべて見つけて更新するのが難しくなる
    3. 設計上の決定を一度だけ文書化すべき
    4. 負数の箇所が影響を受ける場合は、最もわかりやすい場所を1カ所だけ選んで、その場所に文書を配置
    5. 特定のドキュメントを開発者が見つけやすい場所に置くための「明白な」単一の場所がない場合、セクション 13.7 で説明するように designNotes ファイルを作成すべき
    6. さらに、他の場所には、中心となる場所を参照する短いコメントを追加
    7. 読者があなたのコードを理解するために必要なすべてのドキュメントを簡単に見つけられるようにすることは重要ですが、それはあなたがそのドキュメントをすべて書かなければならないということではない
  • ドキュメントを最新の状態に保つための良い方法の一つは、リビジョン管理システムに変更をコミットする前に数分かけて、そのコミットに対するすべての変更点をスキャンし、各変更点がドキュメントに適切に反映されていることを確認すること
  • ドキュメントを維持するための最後の考えとして、コメントはコードよりも高レベルで抽象的である方が維持しやすくなり、変更の頻度も低い

17章の要約

  • 17章 一貫性
  • 概要
    • 一貫性は複雑さを軽減し、動作を明確にする
    • システムが一貫していれば、見慣れた状況に基づいて行った推測は正しい
    • 一貫性があることで、開発者はより早く、より少ないミスで作業を行える
  • 一貫性の例
    • 名前
    • コーディングスタイル
    • インターフェイス
    • デザインパターン
    • Invariant(不変)
      • 構造体や変数の不変的な特性のこと
      • 例えば、テキストの行を格納するデータ構造では、各行の終端が改行文字であること
  • 一貫性の確保
    • 一貫性を維持することは、特に多くの人が長期間にわたってプロジェクトに携わる場合、困難
    • 一貫性を維持するためのヒント
      • ドキュメント
        • 目立つ場所にコーディングスタイルガイドを用意する
        • いくつかのスタイルガイドがWeb上で公開されているので、これらのうちの一つから始める
        • 局所的なルールは適切な場所にドキュメント化する
      • 実行する
      • 既存の慣習を変えない
        • より良いアイデアがあるからといって、矛盾を引き起こす理由にはならない
        • 慣習を変える場合にYESであるべき質問
          • 古い慣習を変えるべき重要な新情報があるか?
          • それは古い慣習を全て変える価値があるほど優れているか?
  • 行き過ぎた一貫性
    • 一貫性は同じようなことは同じような方法で行うだけでなく、似て非なることは異なる方法で行うべきということ
    • 異質なものを無理やり同じ手法にしようとすると複雑さを招くだけ
  • 結論
    • 一貫性を確保するには少し余分な作業が必要になるが、その投資に対するリターンはコードがより明白になること

感想

  • なんかイケテナイから書き方変えようとかあまり考えずにやってそうなので既存の慣習を変えないというのは意識すると良さそう
  • 一貫性を保つには仕組みだけではダメで、教育も必要だと改めて思った