Parse, don’t validate

적용 배경

적용 배경

신경망 학습용 데이터 생성 스크립트나 모델 구조 정의 등과 관련된 설정들은 주로 YAML 파일(config)로 관리하고 있는데, 설정값이 많아지면서 관리할 방안이 필요했습니다. 최근에 Pydantic을 사용해보고 있고, 각 설정을 담는 클래스를 BaseModel을 상속받도록 구현한 뒤, 값의 범위 등을 확인하는 함수를 적용하고 있습니다.

하지만 설정 파일 내의 속성이 많다 보니, 코드 곳곳에서 각 변수마다 검증 여부를 일일이 기억하고 추적하기가 까다로웠습니다. 또한 유효성 검증을 수행할 체계적인 방법이 없기 때문에 여러 번 중복 수행할 가능성이 있는데, 이 경우에는 함수 호출이 반복되어 프로그램 성능이 저하될 우려도 있었습니다.

어떤 방법을 적용할지를 찾아보다가 **‘Parse, don’t validate(검증하지 말고 파싱하라)’**라는 디자인 패턴을 알게 되었는습니다. 패턴의 핵심은 데이터를 처음 로딩하는 시점(시스템 경계)에서 검증을 단 한 번만 수행하되, 단순히 통과/실패 여부만 확인하는 것을 넘어 **검증을 통과한 데이터를 구체적인 타입의 객체로 변환(파싱)**하는 것입니다.

이 패턴을 적용하면 해당 타입의 객체가 존재한다는 사실 자체가 유효성 검증을 통과했다는 증거가 되므로, 이후의 코드에서는 재검증할 필요가 없다고 합니다. 단순한 상태 확인에 그치는 검증(validate)과 달리, 파싱(parse)*은 객체의 ‘타입’ 자체를 유효성 확인 결과로 활용하는 것 같습니다.

* 파서(Parser): “덜 구조화된 입력을 받아 더 구조화된 출력을 만드는 함수”로 정의.

예시

# 클래스 정의 예시
from pydantic import BaseModel, Field, ValidationError
class SolutionConfig(BaseModel):
model_config = {"frozen": True} # 생성 후 변경 불가 (불변화)
solution_ppm: float = Field(gt=0) # 0보다 커야 함
temperature_k: float = Field(ge=273.15, le=623.15) # 범위 제약 예시
label: str = Field(min_length=1) # 빈 문자열 금지 예시
import yaml
raw = yaml.safe_load(open("config.yaml")) # 1단계: 텍스트 -> dict
cfg = SolutionConfig(**raw) # 2단계: dict -> 검증된 객체 (파싱)
cfg.solution_ppm # 이 시점 이후 어디서든 ppm > 0 이 보장됨

참고