メインコンテンツまでスキップ

第4章:設計の判断軸は「変更理由」🧠✨

(分け方の基準は“好み”じゃなくて「それ、何が変わったら直す?」だよ〜😊🎀)


ねらい🎯

この章が終わると…👇✨

  • 「ここで分けるべき?」って迷った時に、“変更理由”で判断できる🧭
  • UI/業務ロジック/外部I/O を混ぜない“境界線”が引ける🚪📦
  • 小さな修正で壊れにくい設計の第一歩が踏み出せる😌💪

4-1. まず「変更理由」ってなに?🤔💡

めちゃくちゃシンプルに言うと👇

**「そのコードを直すことになる“理由”」**のことだよ📝✨

Visualizing Change Reasons

たとえば「注文合計を計算する処理」でも、変わる理由は色々あるよね👇

  • 税率が変わった💰📈(業務ルールが変わった)
  • 画面の見た目を変えたい🎨🖥️(UI都合で変わった)
  • DBのテーブル構造が変わった🗄️🔧(外部I/O都合で変わった)

この“理由”が違うのに同じ場所に混ざってると… **「UIちょっと変えただけなのに計算バグった😭」**みたいな事故が起きやすいの💥

ちなみにこの考え方、SRP(単一責任の原則)でもド真ん中の定義で、**「モジュールは1つの変更理由を持つべき」**って話につながるよ📚✨ (blog.cleancoder.com)


4-2. 分け方の超・合言葉🪄✨

迷ったらこれだけ唱えてOK👇😄

✅ 合言葉①:同じ理由で変わるものは、近くにまとめる🧲

たとえば「割引ルール」と「税計算ルール」が同時に同じ担当・同じタイミングで変わるなら、近くに置くのはアリ🙆‍♀️✨

✅ 合言葉②:違う理由で変わるものは、分ける✂️

  • 見た目(UI)が変わる理由
  • 業務ルールが変わる理由
  • DB/API/ファイル(外部I/O)が変わる理由

これらは基本、変わるタイミングも担当も違いがちだから分けると強いよ💪✨ (Microsoft Learn)


4-3. まずは「3つの箱」で境界線を引こう📦📦📦

超入門では、いったんこれでOK!👇✨

Visualizing Three Boxes

  1. **UI(画面・入力・表示)**🖥️🎛️
  2. **業務ロジック(ルール・判断・計算)**🧠📏
  3. **外部I/O(DB・API・ファイル・時刻・メール送信など)**🗄️🌐📁

「業務ロジックを中心にして、外側(UIやI/O)は差し替えやすくする」って考え方は、クリーンアーキテクチャ/レイヤードでも定番だよ🌸 特に “依存は内側に向ける” ってルールが有名✨ (blog.cleancoder.com)


4-4. 例:混ざるとどう怖い?😱➡️境界を引くとどう嬉しい?😄

題材は「クーポン付きの注文合計」🍔🎟️(ありがちなやつ!)

❌ 悪い例:UIイベントの中に全部混ぜる(変更理由がぐちゃぐちゃ🍲💥)

// 例:ボタン押下で注文確定(UIのはずなのに…)
private async void ConfirmButton_Click(object sender, EventArgs e)
{
// UI: 入力取得
var couponText = CouponTextBox.Text;

// I/O: DBへ接続して商品取得(仮)
var items = await _db.LoadCartItemsAsync(userId);

// 業務: 合計計算・割引・税(全部ここ)
decimal subtotal = items.Sum(x => x.Price * x.Quantity);
if (!string.IsNullOrWhiteSpace(couponText))
{
subtotal *= 0.9m; // 10% OFF(雑!)
}
decimal total = subtotal * 1.10m; // 税10%(ここも雑!)

// UI: 表示
TotalLabel.Text = total.ToString("N0");
}

これ、何が怖いかというと👇😵‍♀️

  • 税率変更(業務)でもこのUIイベントを触ることに…💥
  • DB変更(I/O)でもここが壊れる…💥
  • 画面変更(UI)でも計算に触って事故る…💥

つまり「変更理由が3種類」混ざってるのが問題だよ🍲🔥


✅ 良い例:まず“境界線”だけ引く(超入門版)🚪✨

この章では、DIとかinterfaceとかは次で丁寧にやるので、いまは 「箱分け」だけでOK🙆‍♀️🎀

// ① 業務ロジック(ルール)だけを担当🧠✨
public sealed class OrderPricingService
{
public decimal CalculateTotal(decimal subtotal, bool hasCoupon, decimal taxRate)
{
var discounted = hasCoupon ? subtotal * 0.9m : subtotal;
return discounted * (1 + taxRate);
}
}

// ② I/O担当(今はダミーでもOK)🗄️✨
public sealed class CartRepository
{
public Task<List<CartItem>> LoadCartItemsAsync(string userId)
=> Task.FromResult(new List<CartItem>
{
new("Coffee", 500m, 2),
new("Sandwich", 700m, 1),
});
}

public sealed record CartItem(string Name, decimal Price, int Quantity);
// ③ UIは “集める&渡す&表示” に寄せる🖥️✨
private async void ConfirmButton_Click(object sender, EventArgs e)
{
var couponText = CouponTextBox.Text; // UI
var items = await _repo.LoadCartItemsAsync(userId); // I/O

var subtotal = items.Sum(x => x.Price * x.Quantity); // ここは集計(薄い処理)
var hasCoupon = !string.IsNullOrWhiteSpace(couponText);

var total = _pricing.CalculateTotal(subtotal, hasCoupon, taxRate: 0.10m); // 業務へ委譲🧠
TotalLabel.Text = total.ToString("N0"); // UI
}

これだけでもメリットが出るよ〜!🎉

  • 税率や割引ルールが変わったら OrderPricingServiceだけ見ればいい🧠✨
  • 画面の変更はUI側だけで済みやすい🖥️🎨
  • DBの都合はRepository側に閉じ込めやすい🗄️🔒

この「変更理由で箱分け」こそが、高凝集・低結合の入口だよ😊💗


4-5. 変更理由から境界を引く「3ステップ」🗺️✨

Step1️⃣:変更理由を5個くらい“妄想”する(予言🔮)

ポイント:正解じゃなくてOK! 「変わりそう」を出すだけで、境界が見えてくるよ〜😄

例(注文機能)🍔

  • 税率が変わる
  • 割引ルールが増える(学割、雨の日割など☔)
  • 画面の入力項目が増える
  • DBがSQLite→SQL Serverに変わる
  • 外部の決済APIが変わる💳🌐

Step2️⃣:UI/業務/I/O に分類する📦

  • UI:画面・入力・表示
  • 業務:ルール・計算・判断
  • I/O:DB・API・ファイル・時刻など

Step3️⃣:分類ごとに「置き場」を決める📍

最初はクラス3つでもOK🙆‍♀️ (UI側・業務側・I/O側)に“寄せる”だけでだいぶ事故が減るよ✨


4-6. ハンズオン🛠️:要件から「変わりそう」を3つ書いて境界線を引く🗺️💕

お題:学食の注文システム🍛✨

要件(シンプル版)👇

  • メニューを表示して注文できる
  • クーポンがあれば割引
  • 合計金額を表示

やること①:変更理由を3つ書く📝

例(このまま真似してOK)

  • UI:注文画面のレイアウト変更
  • 業務:割引ルールが「10%」→「固定100円引き」に変更
  • I/O:メニュー取得が「DB」→「Web API」になる

やること②:境界線を引く(図でOK)✍️

こんな感じでOK👇

  • UI:画面イベント、入力取得、表示
  • 業務:CalculateTotal()
  • I/O:LoadMenu() LoadCartItems()

やること③:コードにする(最低限の骨組み)🦴✨

  • OrderPricingService(業務)
  • MenuRepository(I/O)
  • 画面(UI)は“渡して表示するだけ”に寄せる

4-7. よくある落とし穴😵‍♀️💦(ここ大事!)

  • 「とりあえずクラス分割」して満足しちゃう🥲 → 分割の根拠が“変更理由”じゃないと、また混ざる🍲
  • UIに計算ロジックが残る🖥️💥 → 画面改修で壊れやすい原因No.1!
  • I/Oの都合(DBの列名とか)が業務に漏れる🗄️➡️🧠😱 → 次章以降で「依存」を弱める練習をするよ🔗✨

4-8. AIプロンプト🤖(この章は1つだけ🎀)

(要件を貼って、変更理由を出してもらうよ!)

この機能の「変更理由」を5つ挙げてください。
それぞれを UI / 業務ロジック / 外部I/O(DB・API・ファイル等) に分類して、
最後に「境界線(分ける場所)」の提案を1つください。

使い方のコツ🍀

  • AIの案は“候補”💡
  • 採用する前に **「それ同じ理由で変わる?」**を自分で確認🧠✅

4-9. まとめ🎉

  • 設計の判断軸は **「変更理由」**🧠✨(好みで分けない!)
  • まずは UI/業務/I/O の3箱で境界線を引く📦📦📦
  • “同じ理由で変わるものをまとめ、違う理由は分ける”が基本🎯✨ (blog.cleancoder.com)

必要なら次に、あなたの教材用に 第4章の「クイズ問題セット🧩」(正解・解説つき)も作れるよ😊💖