第24章:巨大クラスのサイン(責務が混ざってる)🏢➡️🏠🏠
この章でできるようになること🎯✨
- 「あ、これ巨大クラスかも…!」を具体的なサインで見抜ける👀💡
- クラスの中身を「入力」「計算」「保存」「通知」みたいに責務ラベルで仕分けできる🏷️🗂️
- 壊さずに、**小さく安全に Extract Class(クラス分割)**を進められる🛡️✂️
- AI(Copilot / OpenAI系)に頼る時も、事故らない指示が出せる🤖✅
1. 巨大クラスってなに?🏢💦
巨大クラス(Large Class)は、ざっくり言うと **「なんでも屋さんになってしまったクラス」**です😵💫
本来は1つの目的のためにあるはずなのに、いつの間にか…
- 入力チェックもして🧾
- 計算もして🧮
- DB保存もして💾
- メール通知もして📧
- ログも握って🧾
- 例外も揉み消して😇
みたいに、複数の役割(責務)が混ざってしまう状態です。 この “混ざり” が増えると、変更のたびに壊れやすくなります🥲 (「Large Class は Extract Class などで分割する」のが典型的な対処として整理されています📚) (refactoring.guru)
2. 巨大クラスの「よくあるサイン」チェックリスト👃🔎
次のサインが複数当てはまったら、巨大クラス疑い強めです⚠️
サインA:変更理由が多い🌀
- 「仕様変更」が来るたびに、いつもこのクラスが巻き込まれる😇
- “割引の変更”でも“画面表示の変更”でも同じクラスを触る…
👉 1つのクラスに “変更理由” が複数あるのが危険信号です🚨
サインB:フィールド(状態)が多すぎる🧳💦
- フィールドがズラッと並ぶ
- しかも「どれがどの処理で必要なの?」ってなる
「インスタンス変数が多すぎるのは、やりすぎの兆候」っていう古典的な見立てもあります📖 (industriallogic.com)
サインC:メソッドが散らかってる🧨
- 似た名前のメソッドが大量(
ProcessAProcessB…) - 呼び出し関係が迷路🌀
サインD:クラス名がフワッとしてる☁️
Manager/Service/Helper/Utilが付いてる (全部が悪いわけじゃないけど、巨大化しやすい名前です😇)
サインE:#region が多い(あるある)📦
#region Validation#region Database#region Emailみたいに “区画整理” してるなら、区画ごとに家(クラス)を分けられる可能性が高いです🏠✂️ (指標として語られることがあります📌) (anthonysciamanna.com)
3. でも「大きい=悪」ではない🙅♀️⭕
サイズが大きくても、必ずしも悪ではありません🙂✨ たとえば…
- DTO / record みたいに「データの箱」だけの型📦
- **Facade(窓口)**として「薄く呼び出すだけ」の型🚪
- 生成コードやフレームワーク都合の型🧩
こういうのは “大きく見える” けど、責務が混ざってないことも多いです👌 大事なのは 行数より “責務の混ざり” です🏷️⚖️
4. まずは「責務ラベル貼り」から🏷️🗂️(最強に効く)

巨大クラスを分割する時、いきなり切ると事故りやすいです🧨 だから最初に、クラス内のコードを ラベルで仕分けします✨
やり方(超シンプル)📝
-
クラスを上から読む👀
-
メソッド(または処理の塊)ごとにラベルを付ける🏷️
- 例:
入力/検証/計算/永続化/通知/ログ/変換など
- 例:
-
同じラベルが固まってる場所を見つける🔎
-
「まとまり」を1つ選んで、そこだけ切り出す✂️🏠
この章のミニ演習も、この方法でいきます🎀
5. 分割の“良い切り方”と“微妙な切り方”🍰⚔️
良い切り方(おすすめ)👍✨
- “計算” を
Calculatorにする🧮 - “検証” を
Validatorにする✅ - “永続化” を
Repositoryにする💾 - “通知” を
Notifierにする📧
✅ つまり 「役割(責務)で切る」 です🏷️
微妙な切り方(初心者がやりがち)😇
- 「private メソッドを適当に別クラスに移しただけ」
- 「データだけ移して、処理が元クラスに残る」
こうなると、分割したのに呼び出しが増えて余計に読みにくい…ってなりがちです🥲
6. 実例で体験:注文処理クラスが巨大化した例🛒💦
Before:なんでも屋 OrderProcessor 🏢
public sealed class OrderProcessor
{
private readonly IOrderRepository _repo;
private readonly IEmailSender _email;
private readonly ILogger _log;
public OrderProcessor(IOrderRepository repo, IEmailSender email, ILogger log)
{
_repo = repo;
_email = email;
_log = log;
}
public async Task<OrderResult> PlaceOrderAsync(OrderRequest request, CancellationToken ct)
{
// ①入力チェック(検証)
if (request is null) return OrderResult.Fail("request is null");
if (request.Items.Count == 0) return OrderResult.Fail("no items");
// ②価格計算(計算)
var subtotal = request.Items.Sum(x => x.UnitPrice * x.Quantity);
var discount = request.CouponCode == "NEW10" ? subtotal * 0.10m : 0m;
var total = subtotal - discount;
// ③永続化(保存)
var orderId = await _repo.InsertAsync(request.CustomerId, request.Items, total, ct);
// ④通知(メール)
await _email.SendAsync(request.CustomerEmail, $"Thanks! OrderId={orderId}", ct);
// ⑤ログ
_log.Info($"Order placed. id={orderId} total={total}");
return OrderResult.Success(orderId);
}
}
この時点でラベル貼りすると…
- ①検証✅
- ②計算🧮
- ③保存💾
- ④通知📧
- ⑤ログ🧾
ほら、もう責務が5つありますね😵💫
7. 安全に切る手順(小さく!)🛡️✂️
巨大クラスは「一気に分割」すると壊れやすいので、小さな勝ちを積み上げます🏆✨ (リファクタはステップを小さく&テストで確認が基本、という整理もあります📚) (refactoring.guru)
手順0:挙動を固定する✅📸
- 既にテストがあれば最高🧪✨
- なければ「最小の Characterization Test(挙動写し)」でOK📸
例:正常系だけでも1本あると安心度が爆上がりです🛡️
手順1:切り出す責務を “1つだけ” 選ぶ🏷️➡️🏠
初心者におすすめは 「計算」か「検証」 です🙂 外部I/O(DB/メール)より安全に切れます✅
手順2:新しいクラスを作る🧱
OrderPricing/OrderValidatorみたいに、役割がわかる名前に🏷️✨
手順3:フィールドではなく「メソッドの塊」から移す✂️
- まず
CalculateTotal(...)を新クラスに移動🧮 - 元クラスはそれを呼ぶだけにする📞
手順4:コンパイル✅→テスト✅→コミット🌿
- 1ステップごとに区切ると、戻せて安心です🧸✨
8. After:計算だけ外に出してスッキリ🧮✨
public sealed class OrderPricing
{
public Money CalculateTotal(OrderRequest request)
{
var subtotal = request.Items.Sum(x => x.UnitPrice * x.Quantity);
var discount = request.CouponCode == "NEW10" ? subtotal * 0.10m : 0m;
return new Money(subtotal - discount);
}
}
public readonly record struct Money(decimal Value);
public sealed class OrderProcessor
{
private readonly IOrderRepository _repo;
private readonly IEmailSender _email;
private readonly ILogger _log;
private readonly OrderPricing _pricing = new();
public OrderProcessor(IOrderRepository repo, IEmailSender email, ILogger log)
{
_repo = repo;
_email = email;
_log = log;
}
public async Task<OrderResult> PlaceOrderAsync(OrderRequest request, CancellationToken ct)
{
if (request is null) return OrderResult.Fail("request is null");
if (request.Items.Count == 0) return OrderResult.Fail("no items");
var total = _pricing.CalculateTotal(request);
var orderId = await _repo.InsertAsync(request.CustomerId, request.Items, total.Value, ct);
await _email.SendAsync(request.CustomerEmail, $"Thanks! OrderId={orderId}", ct);
_log.Info($"Order placed. id={orderId} total={total.Value}");
return OrderResult.Success(orderId);
}
}
✅ これだけでも、元クラスの頭の中が一気に軽くなります🧠✨ ここから次の責務(検証・通知・保存)も同じやり方で、1個ずつ切れます✂️🏠🏠
9. Visual Studioでの探索テク(巨大クラスほど効く)🔍💻
- Find All References:そのフィールド/メソッドがどこで使われてるか一撃👀
- Call Hierarchy:呼び出しの流れを迷子にならずに追う🧭
- Go To Definition:依存先をすぐ開ける📌
- 差分表示(Diff):AI提案も、必ず差分で確認✅
Visual Studio 2026 は AI 統合やパフォーマンス改善を前面に出しています🤖⚡ (Microsoft Learn)
10. AIに頼むときの“安全プロンプト”集🤖🛡️
AIはめっちゃ便利だけど、「一気に全部直して」は事故りやすいです😇 1コミット分の小ささで頼むのがコツです🌿✨
① 責務ラベルを付けてもらう🏷️
このC#クラスを読んで、責務を3〜6個のラベルに分類して。
各メソッドを「どのラベルか」対応付けて、理由も短く。
② 切り出し候補を “小さく” 出してもらう✂️
このクラスから Extract Class するなら、まず一番安全に切れる責務を1つ選んで。
最小ステップ(コンパイルが通る単位)で手順を書いて。
③ テストの当たりを作ってもらう🧪
このメソッドの挙動を固定するために、最小のテスト(正常系1本+境界1本)案を出して。
期待値は推測じゃなく「入力→出力」を明記して。
④ 差分レビュー補助📌
この変更差分で、挙動が変わってしまいそうな点を列挙して。
特に null、例外、境界条件、I/O、非同期を重点的に。
11. ミニ演習📝🎀:責務ラベル → 1つだけ分割!
演習1:ラベル貼り🏷️
- 自分のプロジェクト or サンプルで「大きめのクラス」を1つ選ぶ🏢
- メソッドごとにラベルを付ける(最低4つ)🏷️🏷️🏷️🏷️
おすすめラベル例👇
- 検証✅ / 計算🧮 / 変換🔁 / 保存💾 / 通知📧 / ログ🧾 / 例外🚧
演習2:一番安全そうなラベルを1つだけ切る✂️
- “計算” か “変換” が初心者向き🙂✨
- 新クラス名は 役割が伝わる名詞で🏷️(例:
Pricing,Validator)
演習3:コミットを3回に分ける🌿
- コミット1:新クラス追加(まだ未使用でもOK)
- コミット2:メソッド移動(元クラスから呼ぶだけに)
- コミット3:名前や引数を整理(リネームなど)
12. 仕上げチェック✅✨(分割が成功してるサイン)
- 元クラスのメソッドが「やってること説明」になった(短く読める)📖
- 新クラス名を見て役割が一発でわかる🏷️
- 変更が来た時「触るクラス」が減った🧯
- テストが書きやすくなった🧪✨
まとめ🌈✨
巨大クラスの正体は「サイズ」じゃなくて 責務の混ざりです🏷️🌀 まずは 責務ラベル貼りで見える化して、1責務ずつ安全に切り出すのが最短ルート✂️🛡️ Large Class は Extract Class などで分割する、という整理が定番としてまとめられています📚 (refactoring.guru)
次章では、巨大クラスとセットで出やすい「引数が多い(Data Clumps)」を扱います🧳💦