第03章:部:事後条件(Postconditions)で“保証”を設計する🎁✨
3.1 「public境界」ってなに?(DbCの“入口”)🗝️✨
ポイントはこれ👇 「自分のコードが“信じられない入力”を受け取る最初の場所」=境界だよ〜!🚪✨
3.2 境界(public)で守るのが効率的🛡️🚪

DbCでいちばん大切なのは、「どこで契約をチェックするか?」なんだ💡 外から呼ばれる入口=境界**です🚪💡 ここでいう「public境界」は、C#のキーワード「public」だけの話じゃないよ〜!😊
public境界に含めて考える場所🧭
- publicメソッド / publicプロパティ(クラスの外から触れる入口)🔓
- **publicな型(DTO・Request/Response・公開してるモデル)**📦
- API/GUI/CLIなどの“外部入力”を受け取る場所(コントローラ、ハンドラなど)🌐🖱️⌨️
- 別プロジェクトや別レイヤーから呼ばれる入口(internalでも、実質“外部”なら境界扱い)🏢➡️🏠
「自分のコードが“信じられない入力”を受け取る最初の場所」=境界だよ〜!🚪✨
3.2 なぜ“境界”が最重要?(入口でチェック、内部は信頼)🧘♀️🌸
DbCの気持ちいい流れはこれです👇
- 入口(public境界)でしっかりチェック✅
- 中(private/internalの処理)は“もう安全”としてシンプルに書く🧼✨
これをやると何が嬉しいの?🎁
- チェックが散らからないから、修正が1か所で済む🧹✨
- 「同じチェックがあちこちにある」事故(片方だけ直し忘れ)を防げる🧨🚫
3.3 “悪い例”と“良い例”(チェックが散る vs 入口に集める)😵💫➡️😌✨
😵💫 悪い例:チェックが内部に散って、何が正しいか分からなくなる
- privateメソッドの奥深くで例外が出る
- 同じチェックが複数に重複する
- 「このメソッドって null OK だっけ?」が迷子になる🌀
public sealed class OrderService
{
public decimal CalculateTotal(string customerId, int itemCount, decimal unitPrice)
{
// ここでは何もチェックしない(不安…)
return CalculateCore(customerId, itemCount, unitPrice);
}
private decimal CalculateCore(string customerId, int itemCount, decimal unitPrice)
{
if (customerId == null) throw new ArgumentNullException(nameof(customerId));
if (customerId.Trim().Length == 0) throw new ArgumentException("empty", nameof(customerId));
if (itemCount <= 0) throw new ArgumentOutOfRangeException(nameof(itemCount));
if (unitPrice < 0) throw new ArgumentOutOfRangeException(nameof(unitPrice));
return itemCount * unitPrice;
}
}
これだと、呼ぶ側から見ると「何を守ればいいの?」が見えにくいよね…🥺💭 しかも内部が増えるほど、チェックがあちこちに増殖しがち…🌱🌱🌱
😌 良い例:public境界で契約を完結させ、内部はスッキリ✨
入口で「契約(Pre)」を満たすことを強制して、内部は信頼して進めます🚪✅
public sealed class OrderService
{
public decimal CalculateTotal(string customerId, int itemCount, decimal unitPrice)
{
// ✅ Pre(入口で契約チェック)
ArgumentException.ThrowIfNullOrWhiteSpace(customerId);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(itemCount);
ArgumentOutOfRangeException.ThrowIfNegative(unitPrice);
// ここから下は「安全な値しか来ない」前提で書ける✨
return CalculateCore(customerId, itemCount, unitPrice);
}
private static decimal CalculateCore(string customerId, int itemCount, decimal unitPrice)
{
// ✅ 追加の引数チェックが不要になってスッキリ!
return itemCount * unitPrice;
}
}
この「ThrowIf〜」系は、近年の .NET に用意されている“ガード節の定番”だよ〜🛡️✨ (文字列なら ThrowIfNullOrWhiteSpace、nullなら ThrowIfNull など)📌 (Microsoft Learn)
3.4 “public境界”に書く契約のコツ(短く・強く・迷わない)📝🎀
① まずは自然言語で「契約文」を書く🗣️✨
例(さっきのメソッドなら)👇
- customerId は null/空白じゃないこと
- itemCount は 1以上
- unitPrice は 0以上
ここまで書けたら勝ち!🏆 あとはこれを 入口のガード節に落とすだけ💪😊
② ガード節は“テンプレ化”すると読みやすさが安定する📐✨
よく使う“入口チェック”セット🧰
- null:ArgumentNullException.ThrowIfNull 🧷 (Microsoft Learn)
- 文字列:ArgumentException.ThrowIfNullOrWhiteSpace(空白も弾ける)🧼 (Microsoft Learn)
- 範囲:ArgumentOutOfRangeException.ThrowIfNegative / ThrowIfNegativeOrZero 📏 (GitHub)
「if で例外 new する」より、こういうヘルパーの方がスッキリ&推奨されやすいよ〜🧁✨ (Microsoft Learn)
③ “public境界”の中でも、特に重要なのは「公開API」🔑🌟
- 他の人(未来の自分含む)が呼ぶ場所ほど、契約がドキュメントになる📘✨
- 境界で落ちると、原因が入力に近い位置になる(デバッグがラク)🔍💕
3.5 ここは注意!「境界でやりすぎ」もあるよ⚠️🍙
境界で全部やればいい…とはいえ、境界で“複雑な業務判断”までやり始めると逆に読みにくくなることも😵💫
境界が向いてる✅
- null / 空 / 範囲 / 形式 / 引数関係(開始<=終了など)🚪🛡️
境界に詰め込みすぎ注意⚠️
- 複雑な業務ルール(割引条件が10個ある等)🧩🧠 → これは「ドメイン側のメソッド」や「専用の型」に寄せた方がキレイになりやすいよ✨
3.6 ちょい重要な豆知識:「昔のCode Contracts」とDbCは別ものだよ🧓➡️🆕✨
.NET Framework時代には “Code Contracts” という仕組みがあったけど、.NET 5+ ではサポート対象外と案内されているよ📌 (Microsoft Learn)
だから今のDbCは、こういう現代寄りの組み合わせで実戦投入する感じになるよ〜🛠️✨
- 入口ガード(ThrowIf〜、例外、Resultなど)🛡️
- Nullable参照型で「そもそも null を減らす」🧷
- テストで「契約が守られる」ことを固定🧪
(C#の言語バージョンも進んでいて、C# 13 は .NET 9、C# 14 は .NET 10 以降が対応、みたいに“言語とランタイムのセット”も意識されてるよ〜)🧠✨ (Microsoft Learn) (.NET の現行配布も .NET 8/9 が並行で更新されてるよ📦) (Microsoft)
3.7 ミニ演習:入口の1箇所に検証を集める🧹✅
お題:このコード、チェックが散ってるので入口に集めよう💪🌸
public static class UserService
{
public static string NormalizeUserName(string name)
{
return NormalizeCore(name);
}
private static string NormalizeCore(string name)
{
if (name == null) throw new ArgumentNullException(nameof(name));
if (name.Trim().Length == 0) throw new ArgumentException("empty", nameof(name));
return name.Trim().ToUpperInvariant();
}
}
ゴール🎯
- publicメソッドでガード節をする
- privateは「安全が保証された前提」でスッキリさせる✨
模範解答(例)🧁
public static class UserService
{
public static string NormalizeUserName(string name)
{
ArgumentException.ThrowIfNullOrWhiteSpace(name);
return NormalizeCore(name);
}
private static string NormalizeCore(string name)
{
return name.Trim().ToUpperInvariant();
}
}
3.8 AI活用ちょいワザ:契約文→ガード節をサクッと作る🤖⚡️
たとえば、こういうお願いをすると速いよ〜💨✨
- 「このメソッドの Pre 条件を日本語で3つ書いて」📝
- 「Pre を満たすためのガード節(ThrowIf 系)を C# で提案して」🛡️
- 「ガード節が散らばってるから public 入口に集めてリファクタして」🧹
出てきたコードは、**例外の種類(ArgumentNull/ArgumentException/ArgumentOutOfRange)**が意図どおりかだけ最後に目視チェックすると安心だよ👀✅
3.9 まとめ:第3章の“合言葉”🎀✨
- **契約を書く最優先は public境界(入口)**🚪🌟
- 入口でチェック、内部は信頼🧘♀️✨
- ガード節は ThrowIf〜 でテンプレ化🛡️🧰
- チェックが散ったら、まず入口に集める🧹✅