Convert from validation, which discards information, to parsing, which retains information.
Core Principles
Retain the check result in the type instead of discarding it.
| Approach | Return value | Information | Problem |
|---|---|---|---|
| Validate | () / void / bool |
Discard | Rechecking required, not guaranteed by the type |
| Parse | Typed value | Retain | A single check suffices, guaranteed by the type |
Decision Flow
チェック関数を書こうとしている
↓
戻り値は何か?
├─ () / void / bool → Validateパターン(問題あり)
└─ 新しい型 → Parseパターン(推奨)
Antipattern Detection
If you find the following patterns, consider converting:
❌ validate*() → ()
❌ check*() → bool
❌ assert*() → ()(表明目的以外)
❌ is*() → bool(分岐後に同じ値を使う場合)
❌ "should never happen" コメント
❌ case None/null の after 正常ケース
Conversion Patterns
1. NonEmpty conversion
// ❌ Validate: 情報を捨てる
function validateNonEmpty(list: string[]): void {
if (list.length === 0) throw new Error("list cannot be empty");
}
// ✅ Parse: 情報を保持する
type NonEmptyArray<T> = [T, ...T[]];
function parseNonEmpty<T>(list: T[]): NonEmptyArray<T> {
if (list.length === 0) throw new Error("list cannot be empty");
return list as NonEmptyArray<T>;
}
2. Duplicate key detection
// ❌ Validate: チェックして捨てる
function checkNoDuplicateKeys(pairs: [string, unknown][]): void {
const seen = new Set<string>();
for (const [key] of pairs) {
if (seen.has(key)) throw new Error(`duplicate key: ${key}`);
seen.add(key);
}
}
// ✅ Parse: Mapに変換して保持
function parseToMap(pairs: [string, unknown][]): Map<string, unknown> {
const result = new Map<string, unknown>();
for (const [key, value] of pairs) {
if (result.has(key)) throw new Error(`duplicate key: ${key}`);
result.set(key, value);
}
return result;
}
3. Smart Constructor
// ❌ 外部から直接構築可能
pub struct Email(String);
// ✅ Parse: Smart constructorで検証済みを保証
mod email {
pub struct Email(String); // private field
impl Email {
pub fn parse(s: &str) -> Result<Self, ParseError> {
if s.contains('@') && s.len() > 3 {
Ok(Email(s.to_string()))
} else {
Err(ParseError::InvalidEmail)
}
}
pub fn as_str(&self) -> &str { &self.0 }
}
}
Shotgun Parsing
Avoid: A pattern where input validation is scattered throughout the codebase.
❌ 処理開始 → 部分処理 → 検証失敗 → ロールバック困難
✅ 境界で完全Parse → 処理は型を信頼 → 安全
Application Guidelines
Recommended
- Input processing at system boundaries (JSON, CLI arguments, DB values)
- Domain models with complex invariants
Maybe/OptionPlaces where occurs frequently- Places with "should never happen" comments
Avoid Overapplication
- If it is only a single
error "impossible", the modification cost is high - When compatibility with existing APIs is required
- Performance-critical hot path
Review Perspectives
Checkpoints during code review:
- Return value checks:
void/()Are there validation functions that return - Type precision: Can it be expressed with a more precise type?
- Clarity of boundaries: Is input parsing completed at the system boundary?
- shotgun: Is validation logic scattered throughout the codebase?
Detailed Guidelines
For language-specific implementation patterns and details of type design, see references/patterns.md .
Related Skills (Recommended for Concurrent Reading)
When using this skill, also refer to the following skills:
domain-primitives-and-always-valid: Designing domain primitives with smart constructorswhen-to-wrap-primitives: Criteria for deciding whether to wrap primitive typesdomain-building-blocks: Designing value objects (the application target of the parse pattern)