第24章:リファクタの安全運転(テストがある時の整理)🛡️
この章は「テストがあるからこそ、怖がらずにコードを整える」回だよ〜!😊 TDDの Red→Green→Refactor のうち、今日は Refactor(整理整頓) を“安全運転”でやるコツを身につけます🚗💨
1) この章のゴール🎯✨

読み終わる頃に、こうなってたら勝ちです💪💕
- ✅ Green(テスト全部成功)を保ったまま、コードを少しずつ綺麗にできる
- ✅ 「リファクタ」と「仕様変更(挙動変更)」の違いを説明できる
- ✅ 小さく変更→すぐテストのループが手癖になる
- ✅ AI(Copilot/Codex)を使っても、“最小の一歩”だけ採用できる 🤖✅
2) まず超大事:リファクタって何?🧹✨
リファクタはひとことで言うと、
外から見える動き(ふるまい)を変えずに、中身のコードを読みやすく・直しやすくすること🧼✨
✅ ふるまいは同じ ✅ でも中の作りを良くする(名前、重複、構造、見通し)
そして、TDDだと最強なのがこれ👇
- **テストが「安全ベルト」**になってくれる🛡️
- だから 安心して直せる😊✨
3) 安全運転の「5つのルール」🚦🛡️
ここから先、ずっとこのルールでいこう〜!🌸
- Greenから始める✅(赤いまま整理しない)
- 1回の変更は“1種類だけ”✂️(リネームだけ/抽出だけ…)
- 変更は小さく🐣(差分が小さいほど安全)
- こまめにテスト🧪(できれば数十秒おき)
- こまめにコミット📌(戻れる場所=安心)
4) 「リファクタしていいタイミング」いつ?⏱️✨
✅ やっていいタイミング
- テストが全部Green✅
- 実装が“とりあえず通った”けど、見た目がぐちゃぐちゃ😵💫
- 重複や命名が気持ち悪い👃🚨(におい!)
🚫 やっちゃダメ(事故りやすい)
- テストがRedのまま
- 仕様がまだ固まってないのに、設計を作り込み始める
- まとめて大改造(いきなりフォルダ構成変更とか)😇
5) 実戦:カフェ会計の「割引計算」を安全に整える☕️🧾🛠️
ここからハンズオン! 「動くけど読みにくい」典型コードを “小さく直す” 練習するよ😊✨
5-1. まずはテスト(安全ベルト)を用意🧪🛡️
✅ 仕様(例)
- 合計金額
subtotalがある - 会員なら 10% 引き(ただし最大 500 円まで)
- クーポン
"200OFF"は 200 円引き(ただし 0円未満にならない) - 最終金額は 0円以上
xUnit テスト例
using Xunit;
public class PricingTests
{
[Fact]
public void MemberDiscount_is_10_percent_capped_at_500()
{
var p = new Pricing();
var result = p.CalcFinalPrice(subtotal: 10_000, isMember: true, couponCode: null);
// 10% = 1000 だけど上限500
Assert.Equal(9_500, result);
}
[Fact]
public void Coupon_200OFF_is_applied_after_member_discount()
{
var p = new Pricing();
var result = p.CalcFinalPrice(subtotal: 3_000, isMember: true, couponCode: "200OFF");
// 会員10%で 2700、そこから200引きで 2500
Assert.Equal(2_500, result);
}
[Fact]
public void Final_price_never_goes_below_zero()
{
var p = new Pricing();
var result = p.CalcFinalPrice(subtotal: 100, isMember: false, couponCode: "200OFF");
Assert.Equal(0, result);
}
}
5-2. “ぐちゃぐちゃ実装”(リファクタ前)😵💫
public class Pricing
{
public int CalcFinalPrice(int subtotal, bool isMember, string? couponCode)
{
var price = subtotal;
if (isMember)
{
var discount = (int)(price * 0.1);
if (discount > 500)
{
discount = 500;
}
price = price - discount;
}
if (couponCode == "200OFF")
{
price = price - 200;
if (price < 0)
{
price = 0;
}
}
if (price < 0)
{
price = 0;
}
return price;
}
}
動く。けど…
price < 0チェックが重複してる🙈- マジックナンバーが裸で出てくる(0.1 / 500 / 200)🧙♀️
- “何の意図でこうしてる?” が読み取りづらい😵
ここの順番を テストで固定 していくよ〜!🧪✨
6) リファクタ手順(小さく → テスト)🪜✅🧪
STEP 1:マジックナンバーに名前をつける🏷️✨(挙動は変えない)
「数字に意味があるなら、名前をつける」だけで一気に読みやすくなるよ😊
public class Pricing
{
private const decimal MemberDiscountRate = 0.10m;
private const int MemberDiscountCap = 500;
private const int Coupon200OffAmount = 200;
public int CalcFinalPrice(int subtotal, bool isMember, string? couponCode)
{
var price = subtotal;
if (isMember)
{
var discount = (int)(price * MemberDiscountRate);
if (discount > MemberDiscountCap)
{
discount = MemberDiscountCap;
}
price = price - discount;
}
if (couponCode == "200OFF")
{
price = price - Coupon200OffAmount;
if (price < 0)
{
price = 0;
}
}
if (price < 0)
{
price = 0;
}
return price;
}
}
✅ ここでテスト実行!🧪
(Visual Studioなら Test Explorer で “Run All”、CLIなら dotnet test)
STEP 2:重複を“1か所”に寄せる(Clampを抽出)✂️🧼
price < 0 が2回出てるよね?
「0未満禁止」は ルール だから 1か所に寄せたい✨
public class Pricing
{
private const decimal MemberDiscountRate = 0.10m;
private const int MemberDiscountCap = 500;
private const int Coupon200OffAmount = 200;
public int CalcFinalPrice(int subtotal, bool isMember, string? couponCode)
{
var price = subtotal;
if (isMember)
{
var discount = (int)(price * MemberDiscountRate);
if (discount > MemberDiscountCap)
{
discount = MemberDiscountCap;
}
price -= discount;
}
if (couponCode == "200OFF")
{
price -= Coupon200OffAmount;
}
return ClampToZero(price);
}
private static int ClampToZero(int price) => price < 0 ? 0 : price;
}
✅ 変更は「抽出して寄せた」だけ! ✅ ここでまたテスト実行!🧪✨
STEP 3:意図が読めるように“処理の塊”を抽出する🧩✨
「会員割引」「クーポン適用」って、意味のある処理の塊だよね? メソッドにすると読む側が幸せ🥰
public class Pricing
{
private const decimal MemberDiscountRate = 0.10m;
private const int MemberDiscountCap = 500;
private const int Coupon200OffAmount = 200;
public int CalcFinalPrice(int subtotal, bool isMember, string? couponCode)
{
var price = subtotal;
price = ApplyMemberDiscount(price, isMember);
price = ApplyCoupon(price, couponCode);
return ClampToZero(price);
}
private static int ApplyMemberDiscount(int price, bool isMember)
{
if (!isMember) return price;
var discount = (int)(price * MemberDiscountRate);
discount = Math.Min(discount, MemberDiscountCap);
return price - discount;
}
private static int ApplyCoupon(int price, string? couponCode)
{
if (couponCode != "200OFF") return price;
return price - Coupon200OffAmount;
}
private static int ClampToZero(int price) => price < 0 ? 0 : price;
}
✅ ここでまたテスト!🧪✨ (この章の目的は “毎回テストするクセ” を作ることだよ〜!)
7) Visual Studio / VS Code の「安全な武器」🧰✨
Visual Studio(超よく使うやつ)
- リネーム(Rename)📝:変数・メソッド名の変更が安全
- Extract Method(メソッド抽出)✂️
- “Find All References”🔍:影響範囲の確認
- Test Explorerで 変更のたびに実行🧪
VS Code(補足)
- Rename Symbol(だいたい
F2)📝 - “Go to References”🔍
- ターミナルで
dotnet test🧪
8) AI(Copilot/Codex)を使う時の「事故らない頼み方」🤖🛡️
AIは便利だけど、一気に大改造提案してきがち😇 だから、こう頼むのが安全!
✅ 安全プロンプト例
- 「挙動を変えない前提で、最小のリファクタ1手だけ提案して」
- 「このコードの重複を減らしたい。差分が小さい順に3案出して」
- 「“命名だけ”改善したい。変数名・メソッド名の候補を5つ出して」
- 「この変更で壊れそうな場所を指摘して。確認方法もセットで」
✅* AIの提案を採用する判断も速い🏎️💨
✅ AI提案を採用する条件(超重要)✅
- ✅ 差分が小さい
- ✅ テストが全部Green
- ✅ 自分が説明できる(“なんとなく”採用しない)🙅♀️
9) ありがち事故🚑💥(と対策)
-
❌ まとめて直してテスト落ちる → どこで壊れたか分からない ✅ 対策:1手ごとにテスト🧪
-
❌ リファクタのつもりで仕様変えてた ✅ 対策:仕様変えるなら先にテスト追加📌
-
❌ テストが弱くて、壊しても気づけない ✅ 対策:境界値(0/上限/下限)を増やす🔁✨
10) ミニ問題(理解チェック)📝💕
Q1. リファクタとは「何を変えない」作業?
Q2. Refactorに入っていいタイミングは?
Q3. 「変更が大きくなった」と感じたら最初にすることは?
Q4. AIの提案をそのまま採用していい条件は?
Q5. price < 0 の重複がある時、まずやると安全な改善は?
(答えは自分の言葉でOKだよ〜😊✨)
11) 提出課題(コミット単位つき)📌🧪
課題:さっきの Pricing を、次の順番でリファクタしてコミットしよう💪✨
- 🏷️ マジックナンバーに名前をつける(コミット①)
- 🧼
ClampToZeroを作って重複を消す(コミット②) - ✂️
ApplyMemberDiscount/ApplyCouponを抽出(コミット③) - 🧪 各コミットでテストがGreenのスクショ or ログを残す(提出物)
12) ちょい最新メモ(今日の“事実確認”)🗓️✨
この章の内容は特定バージョンに依存しないけど、教材全体の前提が最新かは確認済みだよ😊✅
- .NET 10 の最新パッチは 10.0.2(2026-01-13)。(Microsoft)
- xUnit v3 のリリースは 3.2.2 まで出てるよ。(xunit.net)
- C# 14 は .NET 10 でサポートされる “最新” として案内されてるよ。(Microsoft Learn)
必要なら、この章の続きとして👇も一気に作れるよ😊✨
- 「安全なリファクタ手順」チェックリスト(印刷用)🧾🛡️
- カフェ会計②(割引・クーポン)に向けて、“におい→改善手”対応表👃➡️🛠️
- Visual Studioでの操作手順を、スクショ前提みたいに“手取り足取り”文章化🪟📘