Upgrade to Pro — share decks privately, control downloads, hide ads and more …

変更を楽に安全にするための設計の考え方とやり方

8f84b7d8869ef6005d89b378e8661f7c?s=47 増田 亨
August 03, 2021

 変更を楽に安全にするための設計の考え方とやり方

シンプレクス株式会社さん( https://www.simplex.inc/ )の社内勉強会で使った資料です。

・変更を楽に安全にする設計の基本原則は「関心の分離」
・どういう枠組みと視点で関心を分離するか
・事業活動の仕組みと決め事に焦点を合わせたクラス設計の考え方とやり方
・事実の記録に焦点を合わせたテーブル設計の考え方とやり方

8f84b7d8869ef6005d89b378e8661f7c?s=128

増田 亨

August 03, 2021
Tweet

Transcript

  1. 変更を楽で安全にする設計 2021年7月30日(金) 増田 亨

  2. 自己紹介 有限会社システム設計 増田 亨 C言語 UNIXネットワークプログラミング ↓ PL/SQL, Pro*c OracleのSI部門

    ↓ Java/Spring Eコマース, 業務系Webアプリケーション 著書
  3. 書籍のもとになった体験 ⚫大炎上中のプロダクトの火消し役に呼ばれた ⚫マルチテナントの人材採用業務のSaaSでリリース済 • 重大な不具合だけで100件以上 • 毎日のように重大な不具合が追加される • 次年度の新卒採用業務に導入したため使うしかない(ちゃんと動いている機能の評 価は悪くはない)

    ⚫不具合の例 • 他のテナントの応募者情報が見える • 集計表が一時間待っても出力されない(408 リクエストタイムアウト) • 選考プロセスを進めると登録した応募データが消える、消えたと思ったら別の検索 条件で復活する
  4. 書籍のもとになった体験 コード(クラス設計)の品質 • 共通機能クラスのコードを修正するとほぼ間違いなく壊れる • 個別機能クラスはほとんどが複製して編集したコード(そうやって作 れという指示がでていた) テーブル設計とSQLの品質 • 制約が見当たらない100カラムを超えるテーブルの群れ

    • JOINがうまくできないので、DISTINCTやUNIONが横行 • カラムの意味(特に区分)が複数の意味・用途で使われている
  5. 設計の体験学習の日々 リリース事故の不安よりも重大バグの修正に賭ける毎日 正午(お昼休み)と18:00、毎日2回停止して修正版リリース マルチテナントのSaaSでも、そうするしかなかった(許された) 設計アンチパターンの巣窟 プログラムの初歩的なリファクタリングとテーブル設計の基本的 な改善活動の結果を、即座に大きな効果として実感できる日々

  6. やったこと(効果があったこと) クラス設計とパッケージ設計のリファクタリング ✓名前の変更 ✓メソッドの抽出 ✓クラスの抽出 ✓カプセル化(getterの撲滅) ✓イミュータブルなオブジェクト(setterの撲滅)

  7. やったこと(効果があったこと) テーブル設計とSQLのリファクタリング ✓制約がほぼ皆無→制約追加の試み→ほとんどエラー→制約可能に ✓さまざまな関心事・用途の混在したテーブルを分割 ✓SQLの単純化 ✓SQL⇔オブジェクトのマッピングを透明に(MyBatis)

  8. 影響の大きかった本 『リファクタリング』 マーチンファウラー 『実装パターン』 ケントベック 『ドメイン駆動設計』 エリックエヴァンス 『理論から学ぶデータベース実践入門』 奥野幹夫 『オブジェクト指向入門』バートランドメイヤー

    これらを参考にしつつ、現場での実験を通してうまくいったやり方とその背景に ある考え方(なぜそうするのか)を言葉にしてみたのが 『現場で役立つシステム設計の原則』 ~変更を楽に安全にするオブジェクト指向の実装技法
  9. 学んだこと ① 良い設計は変更を楽で安全にする(設計に投資する効果) ② 関心を分離する効果(クラス設計、テーブル設計) ③ 要点に時間とエネルギーを重点配分する(全体を平坦に扱わなない) ④ 費用対効果を考える(設計ドキュメント、テスト、ログ…)

  10. 学んだこと・効果があったこと 変更を楽で安全にする設計 小さなクラス イミュータブルな クラス getter/setter 撲滅 型による モジュール化 名前へのこだわり

    パッケージ構造の 価値 一意制約 NOT NULL制約 外部キー制約 イミュータブル データモデル 画面の分割 用途別の画面 値オブジェクト 区分オブジェクト ドメインモデル ビジネスアクショ ンクラス アクティビティク ラス 範囲オブジェクト コレクション オブジェクト 関心の分離 自己文書化
  11. 関心の分離の枠組み ビジネスアクション 通知と記録 計算判断の実行 ビジネスルール 事業活動の決め事 計算判断ロジック データベース 通信 変数、計算式

    if 文、for 文 戻り値
  12. 関心の分離の枠組み ビジネスアクション 通知と記録 計算判断の実行 ビジネスルール 事業活動の決め事 計算判断ロジック データベース 通信 変数、計算式

    if 文、for 文 戻り値 ドメインオブジェクト カプセル化 データソース層 アプリケーション層
  13. 変更を楽に安全にするために 効果の大きかった関心の分離 ① ビジネスルール(計算判断ロジック)の記述を独立させる • 計算判断ロジックの複雑さ=アプリケーションの複雑さ • 計算判断ロジックを分離できれば他の部分は単純化・定型化できる ② ビジネスアクション(通知・記録・計算判断の実行)の関心事と

    ビジネスルール(計算判断ロジック)の記述を分離する ③ ビジネスの関心事の記述にデータベース操作を持ち込まない ④ 画面のごちゃごちゃ感(関心の混在)をクラス設計に持ち込まない
  14. 計算判断ロジック(ビジネスルール) を扱うクラスの設計 実践的で役に立つオブジェクト指向プログラミング ① 型(ビジネスで扱う値の種類)によるモジュール化 ② カプセル化(インスタンス変数とメソッドの強結合) ③ 宣言的に記述(イミュータブル) ④

    業務の関心事で抽象化(パッケージ名・クラス名・メソッド名・変数名)
  15. 計算判断ロジックを扱うクラスの設計 型とカプセル化 型 ⇒ビジネスで扱う値の種類の分類(クラス名=関心のある値の名前) ⇒適切な値の範囲を限定(BigDecimalやLocalDateの値の範囲の異様さ) ⇒適切な操作(計算・判断)に限定 カプセル化 ⇒計算判断に使うデータ(インスタンス変数)と計算判断ロジック(メソッ ド)を一つのクラスに集める ⇒インスタンス変数と計算判断ロジック(メソッド)を強く結合する

    ⇒getterはなくなる(他のクラスのインスタンス変数を使わなくなる)
  16. 計算判断ロジックを扱うクラスの設計 宣言的な記述(イミュータブルな設計) クラスをイミュータブル(不変)に設計する ⇒一度作ったオブジェクトの内部状態は変えない(setterがない世界) ⇒あるオブジェクトのメソッドは常に同じ結果を返す ⇒イミュータブルに設計すると実行時の挙動が安定する ビジネスルールの記述 ⇒ビジネスルールに基づく計算判断は、同じ事実に対して常に同じ結果 を返す必要がある ⇒ビジネスルールを記述するクラスはイミュータブルがMUST

  17. 計算判断ロジックを扱うクラスの設計 業務の関心事で抽象化する 業務の関心事 ⇒事業活動で使われている言葉 ⇒事業活動の説明に使われる言葉(業務では使われていないこともある) 抽象化 ⇒細かいことをまとめて、一つの名前で表す (細かいことを気にしなくてよいようにする) ⇒パッケージ名・クラス名・メソッド名・メソッドの返す型名を事業活動の仕組みや 決め事の名前に抽象化する

    ⇒実装の詳細や計算判断の詳細は気にしなくてよいようにする ⇒どこに何が書いてあるか業務の関心事からの検索性があがる ⇒変更の影響範囲が事業活動の構造や関連性と連動する ソースコードがビジネスルール記述書になる
  18. ドメインオブジェクト ビジネスルールを記述する基本部品 値オブジェクト 基本データを使う計算・判断ロジック をカプセル化 金額、数量、率 日付、日数、時刻、時間 区分オブジェクト 区分の列挙(enum) 区分ごとのデータとロジックや区分の

    判定ロジックのカプセル化 顧客種別、配送区分、 在庫区分、… 範囲オブジェクト 値の範囲内・範囲外の判定や範囲どう しの演算をカプセル化 金額範囲、数量範囲 期間、時間帯 コレクション オブジェクト 値・区分・範囲のコレクションの リスト処理・集合演算・写像操作のカ プセル化 受注明細 スキルセット 価格表
  19. 複合オブジェクト ✓ドメインオブジェクトを組み合わせたオブジェクト ✓複雑な計算判断を集約する ✓一つのアプリケーションで複雑な複合オブジェクトは数個 例 • 値決め(価格体系、割引ルール、…) • 与信(取引のリスク判定) •

    サービス提供ルール(提供可否の判定、提供レベルの設定) • キャンセルポリシー
  20. ビジネスアクションのクラス設計 シナリオクラス 主要な活動の流れを表現(数は少ない) 詳細はアクティビティクラスで記述 予約受付シナリオ アクティビティクラス 商流・物流・金流などで、事業活動をグ ルーピングしたクラス群 要素サービスクラスを組み合わせて記述 受注アクティビティ

    出荷アクティビティ 請求支払アクティビティ 要素サービスクラス リソースごとのアクションを表現 原則として一つのリポジトリを持つ 注文の記録と参照 出荷時の通知 関心事が異なる:視点・粒度
  21. テーブル設計の考え方 テーブル設計が悪いとプログラムが複雑になる ⇒ 変更がやっかいで危険になる ⇒ データの品質が劣化→プログラムが複雑になる 良いテーブル設計 ⇒ 関心事を徹底的に分離する(第6正規形まで分解可能であることを意識) ⇒

    事実の記録と状態の参照を分離する ⇒ 発生タイミングが異なる事実、参照単位が異なる事実は別テーブルに分ける ⇒ 事実の記録テーブルは更新と削除不可(イミュータブル) ⇒ 状態参照用のテーブルはインデックスやキャッシュと同じ位置づけ
  22. テーブル設計のやり方 事実を記録するテーブル(主) ① 事実の発生のタイミングごとのテーブル ② NOT NULL 制約(NULLという事実はない) ③ 更新・削除不可

    ④ 外部キー制約(事実と事実の関係の記録) 状態を参照するためのテーブル(補助:インデックスやキャッシュと同じ扱い) ① 状態が事実の記録から動的に導出可能であれば補助テーブルは作らない ② 削除あるいは有効性は、事実の記録とは別のテーブルで表現して結合する ③ 最新を表現するテーブル(ポインタ式 or 完全な実体) 共通 ① スキーマ名・テーブル名・カラム名を日本語(文書化、スキーマで名前空間の整理) ② 更新日時カラムは作らない(すべてINSERT:作成日時のみになる)
  23. ソースコードの自己文書化 ⚫大量のドキュメント作成と更新は費用対効果が悪い ✓ 費用がかかる ✓ 効果は? ⚫ソースコードの自己文書化で多くのドキュメントの作成・更新が不要になる ✓ 常に最新かつ設計と実装が完全に同期している ✓

    より現実的で具体的な進捗管理と品質管理が可能 ⚫段階的な詳細化の活動は、ソースコードの段階的な改善活動になる ✓ ソースコードを設計文書として書き始めレビューと改善を繰り返す ✓ ソフトウェアの変更活動が開発の初期から常に回っている状態
  24. 自己文書化の実際 仕様記述と 詳細設計 プログラミング言語で記述する(正確、IDEで高度な編集環境) ツールで可視化(依存関係や全体構成の俯瞰、構造化された用語集) ドキュメントの作成と更新=コードの作成とリファクタリング(設計改善) テーブル定義 DDL文で記述→実データベース構築 実データベースのデータカタログからツールを使って設計を可視化 スキーマ名・テーブル名・カラム名は日本語(文書化)

    品質管理 コードレビュー=設計レビュー(関心の分離のレビューに重点) コードレビュー=要件や仕様の理解度のレビューを兼ねる コンパイラやコードインスペクションで記述の正確性・妥当性を常に検査 進捗管理 ソースコードリポジトリの変化を可視化 issueでタスク分解、commitでタスク完了(分析設計の進捗もソースコード) 早い段階からCI/CD : 動くプロダクトで進捗を確認