SuperAPI AI Directory

parse-dont-validate

General & other

View repository

Updated 10-16 06:19

About this skill

Parse, Don't Validate

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 singleerror "impossible", the modification cost is high
  • When compatibility with existing APIs is required
  • Performance-critical hot path

Review Perspectives

Checkpoints during code review:

  1. Return value checks: void/()Are there validation functions that return
  2. Type precision: Can it be expressed with a more precise type?
  3. Clarity of boundaries: Is input parsing completed at the system boundary?
  4. 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 constructors
  • when-to-wrap-primitives: Criteria for deciding whether to wrap primitive types
  • domain-building-blocks: Designing value objects (the application target of the parse pattern)