第6章:ニオイ図鑑② 混ぜるな危険(UI・業務・DB)🍲💥
この章のゴール🎯
この章が終わると…👇
- 「ここ混ざってる!危険!😱」を見抜けるようになる👀✨
- UI変更・DB変更が来ても、壊れにくい境界(バリア)🛡️を作れるようになる
- まずは “3つの箱📦📦📦” で仕分けできるようになる
※2026年1月12日時点だと、.NET は **.NET 10 が LTS(長期サポート)**で現役だよ〜📌(サポート期間などの公式情報) (Microsoft Learn) (この章の中身は “.NETのバージョンが変わっても効く設計の話” だから安心してね😊)
1) 「混ぜる」と何が起きるの?😱(地獄のはじまり)
たとえば「ボタン押したら注文確定」みたいな処理で…

- UI(画面)📱が変わっただけなのに、業務ルール(割引計算)💰が壊れる
- DBのテーブル名を変えただけなのに、画面が動かなくなる🧨
- ちょっと直したら、別の場所が連鎖で壊れる💥(変更が波及する)
これ、理由はシンプルで👇 UI・業務・DBが同じ場所に入ってると、どれを変えても全部に触れる必要が出るから🍲💦
Microsoftのアーキテクチャ資料でも、ざっくり言うと
- UIはUIの責任
- 業務ロジックはコア側
- データアクセスはインフラ側 …に分けるのが基本だよ、って考え方が紹介されてるよ📚 (Microsoft Learn)
2) まず覚える「3つの箱」📦📦📦(超・実戦ルール)
この章では、難しい言葉より 箱分けでいくよ〜😊🎀
箱① UI(見た目・入力・表示)🖥️✨
- 画面表示、入力フォーム、ボタンイベント、表示用の整形
- 「入力が空ならメッセージ出す」みたいな見た目寄りのチェックはOK👌
入れちゃダメな代表例🚫
- 割引率の決定、在庫の引当、締切判定みたいな業務ルール
- SQL、DB接続、HTTP直叩きみたいな外部I/O
箱② 業務(ルール・判断・手続き)🧠⚙️
- 価格計算、割引、状態遷移、バリデーション(業務上の)
- 「注文できる?できない?」の判断
- UIにもDBにも寄らない、意思決定の中心💕
入れちゃダメな代表例🚫
- TextBox / Button などUI部品の参照
- SQL文字列、DbContext直参照、ファイルパス直参照などI/Oの詳細
箱③ I/O(DB・ファイル・外部API)💾🌐
- DB保存・取得、ファイル読み書き、外部API呼び出し
- “外の世界”との接続担当🔌
入れちゃダメな代表例🚫
- 「会員なら10%引き」みたいな業務判断(それは業務の箱!)
この分け方、Microsoft Learnでも「UI→BLL→DAL」みたいな層の考え方として説明されてて、UIが直接DB層に触らないのが基本だよ〜って書かれてるよ📘 (Microsoft Learn)
3) 典型的な“混ぜ混ぜコード”を見てみよう🍲💥
(わざと悪い例ね!学習用だから安心して😆)
using System.Data.SqlClient;
public class OrderFormLike
{
// UIっぽい入力(本当はTextBoxとか想像してね)
public string CustomerTypeText { get; set; } = "";
public string AmountText { get; set; } = "";
public void OnClickSubmit()
{
// ✅ UI: 形式チェックっぽい
if (string.IsNullOrWhiteSpace(AmountText))
{
Console.WriteLine("金額を入力してね!");
return;
}
// 🍲 ここから混ざり始める…
var amount = decimal.Parse(AmountText);
// ❌ 業務: 割引ルール(UIの中にある)
var isMember = CustomerTypeText == "Member";
var discountRate = isMember ? 0.10m : 0.00m;
var finalAmount = amount * (1 - discountRate);
// ❌ I/O: DB保存(UIの中にある)
using var conn = new SqlConnection("Server=...;Database=...;Trusted_Connection=True;");
conn.Open();
using var cmd = conn.CreateCommand();
cmd.CommandText = "INSERT INTO Orders(Amount) VALUES(@Amount)";
cmd.Parameters.AddWithValue("@Amount", finalAmount);
cmd.ExecuteNonQuery();
// ✅ UI: 表示
Console.WriteLine($"注文OK!最終金額は {finalAmount} 円だよ〜✨");
}
}
どこがヤバい?😱(すぐ起きる事故)
- 割引ルール変更💰 → 画面コードを触る羽目 → UI担当も巻き込まれる🌀
- DBが変わる💾 → 画面コード修正 → 画面改修と同じリリースに混ざる🧨
- テストしたい🧪 → DBが必要になって面倒(速く回せない)
4) ハンズオン🛠️:3つの箱に仕分けしよう📦📦📦
上のコードを見て、各行に ラベルを付けてみてね🏷️✨
- [UI]
- [BIZ](業務)
- [IO](DB/外部)
例(こんな感じ👇)
Console.WriteLine("金額を入力してね!");→ [UI]var discountRate = ...;→ [BIZ]new SqlConnection(...)→ [IO]
🌟コツ:迷ったら「これって何が変わったら直す?」で考える🧠
- 画面文言が変わったら?→UI
- ルールが変わったら?→業務
- DBやAPIが変わったら?→I/O
5) じゃあ、どう直すの?😌✨(境界を作る🛡️)
いきなり大改造じゃなくてOK🙆♀️ まずは 置き場所を分けるだけで一気に楽になるよ〜🎀

Step A:業務を「注文サービス」に追い出す💨
public class OrderService
{
public decimal CalculateFinalAmount(decimal amount, bool isMember)
{
var discountRate = isMember ? 0.10m : 0.00m;
return amount * (1 - discountRate);
}
}
Step B:DB保存を「リポジトリ」に閉じ込める💾
(ここは“保存するだけ”に集中!ルールは入れない!🚫)
using System.Data.SqlClient;
public class OrderRepository
{
private readonly string _connectionString;
public OrderRepository(string connectionString)
{
_connectionString = connectionString;
}
public void Save(decimal finalAmount)
{
using var conn = new SqlConnection(_connectionString);
conn.Open();
using var cmd = conn.CreateCommand();
cmd.CommandText = "INSERT INTO Orders(Amount) VALUES(@Amount)";
cmd.Parameters.AddWithValue("@Amount", finalAmount);
cmd.ExecuteNonQuery();
}
}
Step C:UIは「集めて渡して表示する」だけにする🖥️✨
public class OrderFormLike
{
public string CustomerTypeText { get; set; } = "";
public string AmountText { get; set; } = "";
private readonly OrderService _service;
private readonly OrderRepository _repo;
public OrderFormLike(OrderService service, OrderRepository repo)
{
_service = service;
_repo = repo;
}
public void OnClickSubmit()
{
// UI: 入力の最低限チェック
if (!decimal.TryParse(AmountText, out var amount))
{
Console.WriteLine("金額は数字でお願い🙏");
return;
}
// UI→業務へ「情報を渡す」
var isMember = CustomerTypeText == "Member";
var finalAmount = _service.CalculateFinalAmount(amount, isMember);
// UI→I/Oへ「保存を頼む」
_repo.Save(finalAmount);
// UI: 表示
Console.WriteLine($"注文OK!最終金額は {finalAmount} 円だよ〜✨");
}
}
この時点でもう強い💪✨
- ルール変更 →
OrderServiceだけ見ればいい - DB変更 →
OrderRepositoryだけ見ればいい - UI変更 → 画面だけ直せる
(この「責任を分ける」考え方は、UI/業務/I/Oを分離してテストもしやすくなるよ〜って説明とも相性バツグン📘) (Microsoft Learn)
6) “混在”を見抜くチェックリスト✅👀
UIにこれがあったら黄色信号🚦
SELECT/INSERT/UPDATEみたいなSQL文字列🧨DbContext/SqlConnection/HttpClient直出し🔌- 「会員なら…」「在庫が…」みたいな判断ロジック🧠
業務にこれがあったら黄色信号🚦
TextBox/Console.WriteLine/HttpContext参照🖥️- DBテーブル名や外部APIのURLが出てくる💾🌐
I/Oにこれがあったら黄色信号🚦
- 割引・状態遷移・締切判定などのルール💰⏳ (保存係が勝手に判断し始めるとカオス…😵)
7) よくある質問コーナー🙋♀️💡
Q1. 入力チェックはUIでやっちゃダメ?🥺
A. “見た目/形式”はUIでOK👌、でも“業務上の正しさ”は業務へ🧠✨
- UI:空欄、数値かどうか、桁数など
- 業務:上限金額、会員条件、締切、在庫など
Q2. 分けたらファイル増えてダルくない?😵
A. 最初はちょい増えるけど、変更が来た瞬間に勝つ😌✨ “直す場所が1つに決まる”のが超大きいよ〜🧭
8) ミニクイズ🧩✨(理解チェック)
次の変更が来た!どの箱を触る?📦
- 「会員の割引率を10%→12%に変更」💰
- 「注文テーブル名が Orders→Purchases に変更」💾
- 「画面の文言を ‘注文OK’→‘購入完了’ に変更」🖥️
(答え:1)業務 2)I/O 3)UI 🎉)
AIプロンプト(この章は1個だけ🤖🎀)
このコードを UI / 業務 / I/O に分類して、混ざってる箇所を指摘して。
さらに「最小の移動」で3つの箱に分ける提案を、クラス名つきで出して。
(過剰なレイヤー追加はしないで、まずは3箱だけ)
AIの答えを採用する前の最終チェック🧠✅
- UIがDBに触ってない?💾🚫
- 業務がUI部品名を知らない?🖥️🚫
- I/Oが割引判定してない?💰🚫
- “分けた結果、直す場所が1つに絞れた?”🧭✨