JJUG CCC 2023 Spring 発表資料(ステップアップセッション)。 私がクラス設計をするときに重視している考え方とやり方を紹介。 主な内容 ・クラス設計のスキル 3段階 ・クラス設計の技能を習得するシナリオ ・7つの基礎知識 ① 入出力と計算判断 ② プログラムの中核と周辺 ③ モジュラー性 ④ データ抽象 ⑤ カプセル化 ⑥ 契約プログラミング ⑦ 不変(イミュータブル)
これだけは知っておきたいクラス設計の基礎知識有限会社システム 増田 亨JJUG CCC 2023 Spring Step Up Session2023年6月4日
View Slide
アプリケーション開発者業務系アプリケーションドメイン駆動設計/リファクタリングJava/Spring/IntelliJ IDEA/JIG著書『現場で役立つシステム設計の原則』~変更を楽で安全にするオブジェクト指向の実践技法設計コミュニティ「現場から学ぶモデル駆動の設計」主催自己紹介2023/6/4 2
ソフトウェアの設計2023/6/4 3プログラム永続化通信⇒ クラス設計⇒ テーブル設計⇒ プロトコル設計
ステップアップする2023/6/4 4
クラス設計のスキル 3段階初級 クラス構文を使って読み書きができる中級 既存のクラス設計を参考になんとなく真似ができる上級なぜそういう設計をするのかなぜそういう設計を避けるか複数の判断軸を持っている2023/6/4 5
クラス設計 技能習得のシナリオ2023/6/4 6情報 ⇒ 知識 ⇒ 技能クラス設計の参考情報クラス設計の知識クラス設計の技能外部 自分今日の内容書籍ネットワーク外部から得た情報を自分の体験や知識と関連づける手を動かして習熟する繰り返して体で覚える理屈ではなく感覚
プログラムの設計分けてつなぐ2023/6/4 7
プログラムの設計 「分けて」整理するコードを整理するどうやって?クラス コード整理の基本単位メソッド 文(ステートメント)を集めて整理するパッケージ クラスを集めて整理する2023/6/4 8
プログラムの設計 「つないで」動かすプログラムを動かすどうやって?メソッドを定義して公開する つないで動かす唯一の仕組みオブジェクトを生成してメソッドを呼び出す• メソッドを呼び出す側 利用者• メソッドを提供する側 提供者2023/6/4 9
分けてつなぐための7つの基礎知識① 入出力と計算判断② プログラムの中核と周辺③ モジュラー性④ データ抽象⑤ カプセル化⑥ 契約プログラミング⑦ 不変(イミュータブル)2023/6/4 10この発表から入手した情報をまず自分の経験・知識と関連づけるそのあと、手を動かして体で覚える
分けて整理する2023/6/4 11
① 入出力と計算判断➢入出力と計算判断を分ける。これがクラス設計の鉄則。• 同じクラスに入出力と計算判断を混ぜない• 同じパッケージに入出力と計算判断をまぜない➢入出力を特定範囲(クラス/パッケージ)に閉じ込める➢入出力クラスは計算判断クラスを使ってよい➢計算判断クラスは入出力クラスを使ってはいけない2023/6/4 12
① 入出力と計算判断 (続き)• 計算判断クラスの例• LocalDate• BigDecimal• String• ArrayList• 入出力クラスの例• PrintStream (System.out、System.err)• java.io.*, java.nio.*, java.net.*, java.sql.*, …• @Controller, @Repository, @Entity, …2023/6/4 13計算判断のための専用クラス(入出力から隔離する)入出力は特定のクラス/パッケージに分離して閉じ込める
② プログラムの中核と周辺2023/6/4 14業務視点のクラス群中核通信アダプターHTTP@Controller@RestControllerRestTemplate通信アダプターJMS@JmsListnerJmsTemplate永続化アダプターJDBC@Repository@Entity周辺周辺 スケジュールアダプター@Scheduled周辺周辺関連する考え方:『ドメイン駆動設計』「ドメインを隔離する」; ヘキサゴナル(Ports & Adapters)アーキテクチャアプリケーションの全体像
② プログラムの中核と周辺(続き)2023/6/4 15中核と周辺の分離シナリオいろいろな関心事いろいろな関心事業務視点の計算判断クラス(業務ロジック)いろいろな関心事業務視点の計算判断クラス(業務ロジック)業務視点の入出力クラス記録/参照、通知/転送業務視点の計算判断クラス(業務ロジック)通信アダプター永続化アダプター設計 設計 設計業務視点の入出力クラス記録/参照、通知/転送中核 中核 中核中核 中核周辺 周辺 周辺ライブラリやフレームワークをアダプターの設計と実装に活用する
② プログラムの中核と周辺(続き)➢書籍『ドメイン駆動設計』ドメインを隔離する• ソフトウェアの複雑さは対象となる業務活動に起因する• その複雑さをうまく扱う工夫の経験談➢書籍『リファクタリング』• サンプルコードは(意図的に)業務視点のクラスになっている• 業務視点のクラスの不吉な臭いと改善方法の経験則➢ヘキサゴナル(Ports & Adapters)アーキテクチャ• 業務視点のクラスをフレームワーク・UI・データベースから独立させる2023/6/4 16業務視点でクラスを設計する 関連する情報
分けてつなぐ2023/6/4 17
③ モジュラー性➢いろいろな部品• パーツ 断片なども含む(部分)• コンポーネント 組み立て(一回の結合)を意図した部品• モジュール 「組み換え」を意図した部品➢モジュラー性の特徴• 用途がわかりやすい(単体で独立した機能)• モジュールとモジュールをつなぎやすい• モジュールの修正や組み換えが楽で安全➢モジュラー性を向上することがよいクラス設計の基本2023/6/4 18
③ モジュラー性(続き)2023/6/4 19モジュラー性の着眼点業務視点の計算判断クラス(業務ロジック)中核 ① 入出力と計算判断② プログラムの中核と周辺この二つの分離原則が重なる場所ここのクラス設計をしっかりやると、プログラム全体をわかりやすく分けて整理できる業務視点の計算判断ロジックが断片化したり重複したりするとプログラムの見通しが悪くなり、変更がやっかいで危険になるここのモジュラー性が、事業活動の変化への適応能力に直結する
④ データ抽象➢データの型を操作(メソッド)の集合で定義する例• int型 ==, !=, <, >, +,- , *, /, %• BigDecimal型 add(), devide(), multiply(), subtract(), …• LocalDate型 isAfter(), isBefore(), plus(), minus(), with(), …• DayOfWeek型 from(), valueOf(), plus(), minus(), …• String型 isEmpty(), length(), substring(), replace(), …2023/6/4 20計算判断クラスを設計する基本知識この考え方とやり方を体で覚える
④ データ抽象 (続き)➢業務視点でデータ型を発見する(業務知識から設計する)• 金額• 数量• 比率• 日付、日数• 時刻、時間• 範囲 (金額範囲、数量範囲、日付範囲、時刻範囲、… )• 区分 (分類区分、状態区分、適用区分、…)• 多値 (金額リスト、数量リスト、範囲セット、区分セット、… )2023/6/4 21計算判断クラスを設計する基本知識
④ データ抽象 (続き)➢単一値(金額、数量、日付、時刻など)のメソッド候補• 同値の判定 isSame()• 比較 isGreaterThan()/isLessThan(), isAfter(), isBefore()• 加算・減算 add(), plus(), subtract(), minus()• 掛算・乗算 multiply() (整数倍と単位付き数量倍の違い)• 割算・除算 devide() (整数除算と単位付き割算の違い)• 最大・最小 MAX, MIN (有効な範囲)• 基本型の変換 toString()/parse(), intValue()/of()2023/6/4 22計算判断クラスの設計パターンLocalDate, BigDecimalなど既存のクラスを参考にする業務の関心事を特定し、それらしい名前をつける(日本語推奨)手を動かして習熟する
④ データ抽象 (続き)➢範囲のメソッド候補• 範囲の判定 範囲の重なり・連続、前後、包含、…• 範囲の合成 二つの範囲を一つにまとめる• 部分範囲 一つの範囲の一部を取り出す➢区分のメソッド候補• 区分への変換 from(), of(), parse(), …• 区分の判定 for()• 移動 next(), previous()2023/6/4 23計算判断クラスの設計パターン自分の知識・経験と関連づける手を動かして体で覚える
④ データ抽象 (続き)➢多値(配列や順序)のメソッド候補• 絞り込み filter()• 変換 map() 多値から別の多値への写像• 集約 reduce()➢値(集合)のメソッド候補• 和(足し算)• 差(引き算)• 共通部分• 要素を含む2023/6/4 24計算判断クラスの設計パターン自分の知識・経験と関連づける手を動かして体で覚える
⑤ カプセル化➢データ抽象(データ型を操作の集合で定義)をクラスとして実装する➢基本アイデア• 値を表現するデータ構造(フィールド一式)を公開メソッドで囲む• 実装(ソースコード)は公開する(隠蔽しない)• 実装に依存したプログラムを書けないメソッドだけを提供する➢カプセル化の成功例• Java 9 でStringクラスの実装は全面的に書き換えられた• Stringクラスを使うプログラムに影響はなかった2023/6/4 25
つないで動かす2023/6/4 26
⑥ 契約プログラミング➢基本アイデア• モジュールをつなぐ唯一の手段はメソッドの公開• メソッドの仕様を明確にするとモジュラー性が改善する➢メソッドの仕様を明確にする方法• メソッドの利用者と提供者の約束事を明示する• 事前条件(利用者の義務) メソッドの引数の型と数• 事後条件(提供者の義務) メソッドの返す型• 不変条件( 同上 ) インスタンスの状態の整合性の保障2023/6/4 27モジュラー性を改善する考え方とやり方メソッドを契約プログラミングの視点で設計する既存のクラス(LocalDate, BigDecimal, Stringなど)のメソッドをこの視点から評価してみる
⑥ 契約プログラミング(続き)➢金額#割る(数量) ⇒ 単価• 端数の処理は型からは判断できない(単価型で有効桁数は定義可能)➢金額#割る(整数) ⇒ 金額• ゼロ除算が起きる可能性がある• 負の金額になる可能性がある• 端数の処理は型からは判断できない➢金額#割る(自然数) ⇒ 金額• 端数の処理は型からは判断できない2023/6/4 28事前条件(引数の型)と事後条件(返す型)の例:金額の割算引数の型(事前条件)と返す型(事後条件)で計算判断ロジックの仕様を表現するおまけ:金額#割る(金額) ⇒ 比率
⑦ 不変(イミュータブル)➢実行中にモジュールの状態が変わると、使う側の負担が大きい• 挙動が不安定になる• 防御的なコードが不必要に増殖する➢基本アイデア• 一度作ったインスタンスは状態を変えない• 状態の変更の代わりに同じ型の別のインスタンスを生成する2023/6/4 29モジュールのつなぎ方を安定させる不変方式のクラスの使い勝手を評価する(String, BigDecimal, LocalDate)可変方式のクラスの使い勝手を評価する(StringBuilder, ArrayList, HashSet)
本日のまとめ2023/6/4 30
クラス設計 技能習得のシナリオ2023/6/4 31情報 ⇒ 知識 ⇒ 技能クラス設計の参考情報クラス設計の知識クラス設計の技能外部 自分今日の内容書籍ネットワーク外部から得た情報を自分の体験や知識と関連づける手を動かして習熟する繰り返して体で覚える理屈ではなく感覚
クラス設計 7つの基礎知識① 入出力と計算判断② プログラムの中核と周辺③ モジュラー性④ データ抽象⑤ カプセル化⑥ 契約プログラミング⑦ 不変(イミュータブル)2023/6/4 32この発表から入手した情報をまず自分の経験・知識と関連づけるそのあと、手を動かして体で覚えるAny questions?