導入
前回のように新しい仕様を素朴なifの羅列で実装すると、動きはするものの読みにくいコードになりがちです。ここでは先に動く(Green)が読みにくい実装を見せ、同じテストを1つも変えずに、読みやすい実装へリファクタリングする練習をします。
説明
flowchart LR
T["既存テスト(変更しない)"] -->|通れば安全| BEFORE["Before: foreachとifの羅列"]
T -->|同じテストが通れば安全| AFTER["After: LINQのGroupBy"]
BEFORE -.振る舞いは同じ.-> AFTER
style T fill:#e1f5fe
- リファクタリングの定義: 外から見た振る舞いを変えずに、内部の実装だけを整理すること
- 「テストが1つも変わらず、全部緑のまま」であることが、振る舞いが変わっていない証拠になる
- 優先度ごとの件数を数える
CountByPriorityをBefore/Afterで比べます
// Before: 動くが、優先度の種類が増えるたびに if を1つ増やす必要がある
public static Dictionary<string, int> CountByPriority(IReadOnlyList<TaskDto> tasks)
{
var result = new Dictionary<string, int> { ["Low"] = 0, ["Medium"] = 0, ["High"] = 0 };
foreach (var task in tasks)
{
if (task.Priority == "Low") result["Low"]++;
else if (task.Priority == "Medium") result["Medium"]++;
else if (task.Priority == "High") result["High"]++;
}
return result;
}
- 動作は正しいが、優先度の選択肢が1つ増えるたびに
ifを書き足す必要があり、書き忘れるとバグになる IReadOnlyList<TaskDto>をGroupByでグループ化すれば、優先度の種類を1つも書かずに集計できる(Afterは演習で実装します)
やってみよう
Beforeの実装を読み、「優先度がもう1種類増えたら、どこを直し忘れそうか」を考えてから演習に進みましょう。
演習
var tasks = new List<TaskDto>
{
new(Guid.NewGuid(), "牛乳を買う", "Low", false),
new(Guid.NewGuid(), "レポート提出", "High", false),
new(Guid.NewGuid(), "掃除する", "Low", false),
new(Guid.NewGuid(), "会議資料作成", "High", false),
new(Guid.NewGuid(), "本を読む", "Medium", false),
};
var counts = TaskStats.CountByPriority(tasks);
Assert(counts["Low"] == 2, "Lowは2件");
Assert(counts["Medium"] == 1, "Mediumは1件");
Assert(counts["High"] == 2, "Highは2件");
Console.WriteLine("全テスト成功");
public record TaskDto(Guid Id, string Title, string Priority, bool IsCompleted);
// TODO: 上のBeforeと同じ結果を、ifの羅列を使わずに
// GroupBy を使ったリファクタリング版で実装してください
public static class TaskStats
{
___
}
void Assert(bool c, string n) { if (!c) throw new Exception($"FAIL: {n}"); }
- 期待される出力:
全テスト成功
ヒント1を見る
tasks.GroupBy(t => t.Priority)で優先度ごとのグループが作れます
ヒント2を見る
public static Dictionary<string, int> CountByPriority(IReadOnlyList<TaskDto> tasks) => tasks.GroupBy(t => t.Priority).ToDictionary(g => g.Key, g => g.Count());
まとめ
- リファクタリングは「テストを変えずに実装だけを整理する」こと
- Before/Afterで
Assertが1つも変わっていない=外から見た振る舞いは変わっていない証拠 GroupByのようなLINQを使うと、選択肢が増えても書き直す箇所を減らせる
次回: FakeではなくReal同士を組み合わせて検証する「統合テスト」です。