ソフトウェア設計のトレードオフと誤り

―プログラミングの際により良い選択をするには

[cover photo]
TOPICS
Programming
発行年月日
PRINT LENGTH
480
ISBN
978-4-8144-0031-7
原書
Software Mistakes and Tradeoffs
FORMAT
Print PDF EPUB
Ebook
4,180円
Ebookを購入する
Print
4,180円

「プログラムを設計するときに行った技術的な判断や選択が、後日大きな制約となる」これはプログラマなら誰しも経験したことのあることでしょう。本書は、そんなプログラミングにおける各種の設計上の選択について、トレードオフの内容やそれがどのような誤りを招きうるのかという点を踏まえて紹介する書籍です。
コードの重複、エラーや例外処理、柔軟性と複雑性のバランスのようなコードレベルの選択から、APIの設計、時刻の扱い、データローカリティのようなシステム寄りの話題、またライブラリの選択、分散システムの一貫性と原子性、バージョニングのようなより抽象度の高い内容まで、さまざまなシチュエーションにおけるトレードオフの実態と、その失敗例をとり上げます。
本書は日々のプログラミングにおける解決策のヒントを得るだけでなく、より幅広い設計上の知見を広める上でも役に立つでしょう。

正誤表

ここで紹介する正誤表には、書籍発行後に気づいた誤植や更新された情報を掲載しています。以下のリストに記載の年月は、正誤表を作成し、増刷書籍を印刷した月です。お手持ちの書籍では、すでに修正が施されている場合がありますので、書籍最終ページの奥付でお手持ちの書籍の刷版、刷り年月日をご確認の上、ご利用ください。

第1刷の正誤表

71ページ リスト3-23の説明❶

誤:
ハンドラーが実行されると true をセットする
正:
ハンドラーが実行されると true がセットされる変数を宣言する
補足:
実際にハンドラーが実行されるのは❸の箇所で、ここでは値がセットする変数を宣言しています

第2刷の正誤表(第3刷で修正予定)

132ページ 図5-6の下3段落目の冒頭

誤:
第2章の公式を使って、2つのエンドポイントが性能に占める影響を計算しましょう。
正:
5.2節の公式を使って、2つのエンドポイントが性能に占める影響を計算しましょう。

補足:
参照先が間違っていました

目次

献辞
はじめに

1章 イントロダクション
    1.1 すべての決断とパターンの結果
        1.1.1 単体テストの決定
        1.1.2 単体テストと統合テストの割合
    1.2 デザインパターンと、それらが万能でない理由
        1.2.1 コードの測定
    1.3 アーキテクチャデザインパターンと、それらが万能でない理由
        1.3.1 スケーラビリティ
        1.3.2 開発スピード
        1.3.3 マイクロサービスの複雑さ
    1.4 まとめ

2章 コードの重複は必ずしも悪ではない:コードの重複 vs コードの柔軟性
    2.1 コードベース間の共通のコードと重複
        2.1.1 コードの重複を必要とする新しいビジネス要件の追加
        2.1.2 新しいビジネス要求の実装
        2.1.3 結果の評価
    2.2 ライブラリとコードベース間のコードの共有
        2.2.1 共有ライブラリのトレードオフとデメリットの評価
        2.2.2 共有ライブラリの作成
    2.3 異なるマイクロサービスへのコード抽出
        2.3.1 分離したサービスのトレードオフとデメリット
        2.3.2 サービス分離に関する結論
    2.4 コードの重複による疎結合の改善
    2.5 重複を減らすための継承を用いたAPI設計
        2.5.1 リクエストハンドラーの共通部分の抽出
        2.5.2 継承と密結合について深掘りする
        2.5.3 継承とコンポジションのトレードオフ
        2.5.4 本質的な重複と偶然の重複に着目する
    2.6 まとめ

3章 例外 vs 他のエラーハンドリングパターン
    3.1 例外の階層
        3.1.1 すべてのエラーを捕捉する vs きめ細やかに処理する
    3.2 コードで例外を処理するベストプラクティス
        3.2.1 公開APIでの検査例外の処理
        3.2.2 公開APIでの非検査例外の処理
    3.3 例外処理のアンチパターン
        3.3.1 エラー発生時のリソースのクローズ
        3.3.2 アプリケーションフローを制御するために例外を使うアンチパターン
    3.4 サードパーティーライブラリの例外
    3.5 マルチスレッド環境における例外
        3.5.1 Promise APIを使った非同期処理の例外
    3.6 Tryを使った関数型アプローチにおける例外処理
        3.6.1 本番環境でのTryの使い方
        3.6.2 Tryと例外を投げるコードの混合
    3.7 例外処理の実装におけるパフォーマンスの比較
    3.8 まとめ

4章 柔軟性と複雑性のバランス
    4.1 ロバストではあるが拡張性のないAPI
        4.1.1 新しいコンポーネントを設計する
        4.1.2 もっとも簡単なコードから始める
    4.2 好きなメトリクスフレームワークを利用できるようにする
    4.3 フック経由でAPIに拡張性を与える
        4.3.1 フックAPIの想定外な利用への対処
        4.3.2 フックAPIの性能への影響
    4.4 リスナー経由でAPIに拡張性を与える
        4.4.1 リスナーの利用 vs フックの利用
        4.4.2 イミュータブルな設計
    4.5 APIの柔軟性分析 vs メンテナンスコスト
    4.6 まとめ

5章 早すぎる最適化 vs ホットパスの最適化:コードの性能に影響する決断
    5.1 早すぎる最適化が悪である場合
        5.1.1 アカウントを処理するパイプラインの作成
        5.1.2 誤った仮定を元にした最適化処理
        5.1.3 性能の最適化をベンチマークする
    5.2 コードの中のホットパス
        5.2.1 ソフトウェアシステムにおけるパレートの法則を理解する
        5.2.2 SLAに対応する並列ユーザー(スレッド)数を設定する
    5.3 ホットパスになりうる部分がある単語サービス
        5.3.1 「今日の単語」を取得する
        5.3.2 単語が存在するか検証する
        5.3.3 HTTPサービスを用いてWordsServiceを公開する
    5.4 コードの中のホットパスを検出する
        5.4.1 Gatlingを利用した APIの性能テストの作成
        5.4.2 MetricRegistryを用いてコードパスを測定する
    5.5 ホットパスの性能を改善する
        5.5.1 既存の解決策に対してJMHマイクロベンチマークを作成する
        5.5.2 キャッシュを用いて単語の存在検証機能を最適化する
        5.5.3 性能テストを修正し、入力単語数を増やす
    5.6 まとめ

6章 API のわかりやすさ vs メンテナンスコスト
    6.1 他のツールから利用されるベースのライブラリ
        6.1.1 クラウドサービスクライアントを作る
        6.1.2 認証の実現方法を探る
        6.1.3 設定のメカニズムを理解する
    6.2 依存するライブラリの設定をそのまま公開する
        6.2.1 バッチツールの設定
    6.3 依存するライブラリの設定を隠蔽するツール
        6.3.1 ストリーミングツールの設定
    6.4 クラウドクライアントのライブラリに新しい設定を追加する
        6.4.1 バッチツールへの新しい設定の追加
        6.4.2 ストリーミングツールの設定の追加
        6.4.3 UX の使いやすさとメンテナンスのしやすさについての2つの解決策の比較
    6.5 クラウドクライアントライブラリの設定を非推奨にする/ 削除する
        6.5.1 バッチツールからの設定の削除
        6.5.2 ストリーミングツールからの設定の削除
        6.5.3 UXの良さとメンテナンスのしやすさにおける2つの解決策の比較
    6.6 まとめ

7章 日付と時間のデータを効率よく扱う
    7.1 日付と時間情報の概念
        7.1.1 機械時間: 時点、エポック、期間
        7.1.2 常用時 : 暦法、日付、時間、時間量
        7.1.3 タイムゾーン、UTC、UTCからのオフセット
        7.1.4 私を悩ませる日時の概念
    7.2 日時情報を仕事で扱うための準備
        7.2.1 スコープを制限する
        7.2.2 日付の要求の明確化
        7.2.3 適切なライブラリ、パッケージを利用する
    7.3 日時のコードの実装
        7.3.1 概念を適用し続ける
        7.3.2 テスト容易性の改善
        7.3.3 日時をテキストとして表現する
        7.3.4 コメント付きのコード解説
    7.4 認識してテストすべき境界条件
        7.4.1 カレンダー計算
        7.4.2 夜中のタイムゾーン切り替え
        7.4.3 曖昧な時間やスキップされた時間を取り扱う
        7.4.4 更新されるタイムゾーンデータの取り扱い
    7.5 まとめ

8章 データローカリティとメモリーの活用
    8.1 データローカリティとは何か?
        8.1.1 計算処理をデータ側に移動する
        8.1.2 データローカリティを使った処理のスケーリング
    8.2 データのパーティション化とデータ分割
        8.2.1 オフラインでのビッグデータのパーティション化
        8.2.2 パーティション化 vs シャーディング
        8.2.3 パーティション化のアルゴリズム
    8.3 複数のパーティションのビッグデータのセットを結合する
        8.3.1 同じ物理マシン上にあるデータの結合
        8.3.2 データの移動が必要な結合
        8.3.3 ブロードキャストを活用した結合の最適化
    8.4 データ処理:メモリー vs ディスク
        8.4.1 ディスクベース処理を利用
        8.4.2 なぜMapReduceが必要なのか?
        8.4.3 アクセス時間の計算
        8.4.4 RAMベースの処理
    8.5 Apache Sparkを使った結合処理の実装
        8.5.1 ブロードキャストなしの結合の実装
        8.5.2 ブロードキャストを伴う結合の実装
    8.6 まとめ

9章 サードパーティーライブラリ:あなたが使うライブラリはあなたのコードとなる
    9.1 ライブラリをインポートしてその設定に対して完全な責任を負う:デフォルト値に注意する
    9.2 並列実行モデルとスケーラビリティ
        9.2.1 非同期、同期 APIを使う
        9.2.2 分散時のスケーラビリティ
    9.3 テスト容易性
        9.3.1 テストライブラリ
        9.3.2 偽の値とモック(テストダブル)を使ったテスト
        9.3.3 統合テストツールキット
    9.4 サードパーティーライブラリの依存関係
        9.4.1 バージョン衝突の防止
        9.4.2 多過ぎる依存関係
    9.5 サードパーティー依存関係の選択と維持
        9.5.1 第一印象
        9.5.2 コードを再利用する別の方法
        9.5.3 ベンダーロックイン
        9.5.4 ライセンス
        9.5.5 ライブラリとフレームワークのどちらにするか
        9.5.6 セキュリティとアップデート
        9.5.7 意思決定のためのチェックリスト
    9.6 まとめ

10章 分散システムにおける一貫性と原子性
    10.1 データソースのat-least-once 配信
        10.1.1 単一ノード間のトラフィック
        10.1.2 アプリケーションからの呼び出しのリトライ
        10.1.3 データ生成とべき等性
        10.1.4 コマンドクエリ責務分離(CQRS)を理解する
    10.2 重複排除ライブラリの素朴な実装
    10.3 分散システムで重複排除を実装する際のよくある誤り
        10.3.1 ノードが1つの場合
        10.3.2 複数ノードのコンテキスト
    10.4 レースコンディションを防ぐためにロジックをアトミックにする
    10.5 まとめ

11章 分散システムのデータ配信
    11.1 イベント駆動アプリケーションのアーキテクチャ
    11.2 Apache Kafkaに基づくプロデューサー・コンシューマーアプリケーション
        11.2.1 Kafkaをコンシューマー側から見る
        11.2.2 Kafkaブローカーの設定を理解する
    11.3 プロデューサーの処理内容
        11.3.1 プロデューサーの一貫性と可用性の選択
    11.4 コンシューマーの実装とデータ配信セマンティクス
        11.4.1 コンシューマーの手動コミット
        11.4.2 最初または最新のオフセットからの処理再開
        11.4.3 (実質)厳密に1回のデータ配信
    11.5 データ配信保証の仕組みにより耐障害性を向上させる
    11.6 まとめ

12章 バージョンと互換性の管理
    12.1 バージョン管理概論
        12.1.1 バージョンのプロパティ
        12.1.2 後方互換性と前方互換性
        12.1.3 セマンティックバージョニング
        12.1.4 マーケティングバージョン
    12.2 ライブラリのためのバージョン管理
        12.2.1 ソース、バイナリ、セマンティック互換性
        12.2.2 依存グラフと菱形依存
        12.2.3 破壊的変更の取り扱い方
        12.2.4 内部だけで使用されるライブラリの管理
    12.3 ネットワークAPIのバージョン管理
        12.3.1 ネットワークAPI呼び出しのコンテキスト
        12.3.2 カスタマーフレンドリーなわかりやすさ
        12.3.3 一般的なバージョン管理戦略
        12.3.4 バージョン管理に関するさらなる考慮事項
    12.4 データストレージのバージョン管理
        12.4.1 Protocol Buffersの簡単なイントロダクション
        12.4.2 破壊的変更とは何か?
        12.4.3 ストレージシステム内のデータマイグレーション
        12.4.4 想定外を想定する
        12.4.5 APIの分割とストレージ表現
        12.4.6 ストレージフォーマットの評価
    12.5 まとめ

13章 流行を追いかけ続けること vs コードのメンテナンスコスト
    13.1 依存性注入のフレームワークの使いどころ
        13.1.1 依存性注入の自作
        13.1.2 フレームワークを利用した依存性注入
    13.2 リアクティブプログラミングの使いどころ
        13.2.1 シングルスレッドでの処理の作成・ブロッキング処理
        13.2.2 CompletableFutureの利用
        13.2.3 リアクティブによる実装
    13.3 関数型プログラミングの使いどころ
        13.3.1 非関数型言語での関数型コードの作成
        13.3.2 末尾再帰最適化
        13.3.3 不変性の活用
    13.4 遅延評価と先行評価の使い分け
    13.5 まとめ

付録A データライフサイクルとトレードオフ
    A.1 データの表現方式
        A.1.1 即値(リテラル)
        A.1.2 定数
        A.1.3 コマンドライン引数
        A.1.4 環境変数
        A.1.5 設定ファイル
        A.1.6 ダウンロードコンテンツ方式
        A.1.7 オンラインデータベース
    A.2 トレードオフを考慮する視点
        A.2.1 デプロイまでの手順
        A.2.2 複数のデータセットのハンドリング
        A.2.3 起動時間
        A.2.4 セキュリティ
    A.3 ハイブリッド方式
        A.3.1 複数のデータソースの透過利用
        A.3.2 ソースコードの自動生成
        A.3.3 設定DSL
        A.3.4 設定ファイルをバンドル
        A.3.5 ライブリロード
        A.3.6 .envファイル
    A.4 まとめ

訳者あとがき
索引