# 검증이 아닌 Parse 기술 (2019)

> Clean Markdown view of GeekNews topic #15972. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=15972](https://news.hada.io/topic?id=15972)
- GeekNews Markdown: [https://news.hada.io/topic/15972.md](https://news.hada.io/topic/15972.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2024-07-23T09:52:19+09:00
- Updated: 2024-07-23T09:52:19+09:00
- Original source: [lexi-lambda.github.io](https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/)
- Points: 3
- Comments: 1

## Topic Body

### 파싱, 검증하지 말라

#### 타입 주도 설계의 본질

- 타입 주도 설계(type-driven design)를 설명하는 간단한 슬로건: **파싱, 검증하지 말라**
- 이 슬로건은 타입 시스템을 활용하여 코드의 안전성과 정확성을 높이는 방법을 의미함

##### 가능성의 영역

- 정적 타입 시스템은 특정 함수가 구현 가능한지 여부를 쉽게 판단할 수 있게 해줌
- 예시: `foo :: Integer -> Void`는 구현 불가능함 (`Void` 타입은 값을 가질 수 없음)
- 예시: `head :: [a] -> a` 함수는 리스트가 비어 있을 경우 정의되지 않음

##### 부분 함수를 전체 함수로 바꾸기

###### 기대 관리

- `head` 함수는 리스트가 비어 있을 때 값을 반환할 수 없으므로 `Maybe` 타입을 사용하여 `Nothing`을 반환할 수 있게 함
- 그러나 이는 사용 시 불편함을 초래할 수 있음

###### 기대를 전달하기

- `NonEmpty` 타입을 사용하여 비어 있지 않은 리스트를 표현함으로써 `head` 함수가 항상 값을 반환하도록 보장할 수 있음
- `NonEmpty` 타입을 사용하면 불필요한 체크를 제거하고, 타입 시스템을 통해 오류를 컴파일 타임에 잡을 수 있음

##### 파싱의 힘

- 파싱과 검증의 차이는 정보를 어떻게 보존하느냐에 있음
- `validateNonEmpty` 함수는 리스트가 비어 있지 않음을 검증하지만, 정보를 보존하지 않음
- `parseNonEmpty` 함수는 리스트가 비어 있지 않음을 검증하고, `NonEmpty` 타입으로 정보를 보존함

##### 검증의 위험성

- 검증 기반 접근 방식은 "shotgun parsing"이라는 문제를 초래할 수 있음
- 이는 프로그램이 입력의 일부를 처리한 후 나머지 입력이 유효하지 않음을 발견하는 상황을 초래할 수 있음
- 파싱은 프로그램을 두 단계로 나누어, 첫 번째 단계에서 입력의 유효성을 확인하고, 두 번째 단계에서 유효한 입력만을 처리하도록 함

#### 실전에서의 파싱

- 데이터 타입에 집중하여 함수의 타입 시그니처를 가능한 한 구체적으로 만듦
- 불법 상태를 표현할 수 없는 데이터 구조를 사용하고, 가능한 한 빨리 데이터를 구체적인 표현으로 변환함
- 데이터 타입이 코드를 안내하도록 하고, 코드가 데이터 타입을 제어하지 않도록 함
- `m ()`를 반환하는 함수는 신중하게 사용해야 함
- 여러 번에 걸쳐 데이터를 파싱하는 것을 두려워하지 말아야 함
- 데이터의 비정규화된 표현을 피하고, 필요한 경우 캡슐화를 통해 관리해야 함
- 검증기를 파서처럼 보이게 만드는 추상 데이터 타입을 사용해야 함

#### 요약, 반성, 관련 읽을거리

- Haskell의 타입 시스템을 최대한 활용하는 것은 어렵지 않으며, 최신 언어 확장을 사용할 필요도 없음
- 핵심 아이디어는 "전체 함수 작성"이며, 이는 간단하지만 실천하기 어려울 수 있음
- 관련 읽을거리로는 Matt Parson의 블로그 포스트 "Type Safety Back and Forth"와 Matt Noonan의 논문 "Ghosts of Departed Proofs"를 추천함

### GN⁺의 정리

- 이 글은 Haskell의 타입 시스템을 활용하여 코드의 안전성과 정확성을 높이는 방법을 설명함
- 파싱과 검증의 차이를 이해하고, 파싱을 통해 입력의 유효성을 확인하는 것이 중요함을 강조함
- 타입 시스템을 활용하여 불법 상태를 표현할 수 없는 데이터 구조를 사용하고, 가능한 한 빨리 데이터를 구체적인 표현으로 변환하는 것이 중요함
- 관련 읽을거리로는 Matt Parson의 블로그 포스트와 Matt Noonan의 논문을 추천함

## Comments



### Comment 27470

- Author: neo
- Created: 2024-07-23T09:52:19+09:00
- Points: 1

###### [Hacker News 의견](https://news.ycombinator.com/item?id=41031585) 
- 이 조언과 기사는 매우 유익함
- 정적으로 타입이 지정된 함수형 언어를 사용하지 않는 사람들에게도 유용함
- 이 아이디어는 패러다임을 초월함
- 80~90년대 객체지향 문헌에서도 유사한 개념을 찾을 수 있음, 예를 들어 Design by Contract
- TypeScript는 런타임에 타입을 세분화하는 방식으로 작성되는 경우가 많음
- Design by Contract는 Clojure의 spec에 영향을 미쳤을 것임 (Clojure는 동적 언어임)
- 기본적으로 이는 가정과 보장에 관한 것임 (요구와 제공)
- 가정이 확인되고 보장이 이루어지면 프로그램의 다른 부분에서 중복된 가정을 다시 확인할 필요가 없음
- 코드에서 이미 보장된 속성이 다시 확인되는 것을 보면 혼란스러울 수 있음, 이는 코드 이해와 개선을 어렵게 만듦

- 이 패턴은 현대 C#에서도 잘 작동하며 공간 절약 효과도 있음
  - 예시 코드:
    ```csharp
    if(!Whatever.TryParse&lt;Thingy&gt;(input, out var output)) output = some-sane-default;
    ```
  - 예시 코드:
    ```csharp
    if(!Whatever.TryParse&lt;Thingy&gt;(input, out var output)) throw new ApplicationException($"Not a valid Thingy: {input}");
    ```
  - 커널 모드 드라이버에서는 후자를 사용하지 말 것을 권장함

- 강력한 타입 시스템을 활용하여 오류 사례를 표현할 수 없게 만드는 것이 좋음, 이는 소프트웨어 버그를 줄이는 데 도움이 됨
- 문제를 생각하고 디자인을 따르는 데 시간이 더 걸리지만, 많은 경우 그 시간이 가치가 있음

- "Parse, don’t validate"라는 슬로건이 타입 기반 설계를 잘 요약함
- 개인적으로는 "항상 단일 생성자에서만 유효성 검사를 수행"하는 것이 좋음, 이렇게 하면 유효하지 않은 객체가 전혀 존재하지 않게 됨
- 객체를 수정하려면 동일한 생성자를 다시 호출하여 새 상태를 구성하는 방식으로 구현해야 함

- qmail의 섹션 5가 떠오름, 여기에는 "파싱하지 말라"와 "좋은 인터페이스와 사용자 인터페이스가 있다"는 내용이 포함됨
- 중간 규모의 프로그래밍 수업을 가르친다면 학생들에게 이 제안을 비교하고 대조하는 에세이를 작성하게 할 것임, 각 제안은 배울 점이 있으며 처음에는 모순처럼 보일 수 있음

- 관련 자료: Richard Feldman의 "Making Impossible States Impossible"
  - [YouTube 링크](https://www.youtube.com/watch?v=IcgmSRJHu_8)

- 이전 논의:
  - [링크 1](https://news.ycombinator.com/item?id=35053118)
  - [링크 2](https://news.ycombinator.com/item?id=21476261)

- Crowdstrike에 전달됨

- 2000년대 중반 XML 열풍 때 누군가의 댓글이 떠오름, 많은 조직이 XML을 선택한 이유는 XML이 파서를 제공하기 때문임
- 파서를 작성하는 것이 어렵지 않고 재미있음에도 불구하고, 사람들이 파서를 작성하지 않으려는 이유를 이해할 수 없음

- Protocol Buffers의 "required" 키워드가 큰 실수였다는 의견과 반대되는 것인지 궁금함
  - [Cap'n Proto FAQ 링크](https://capnproto.org/faq.html#how-do-i-make-a-field-required-like-in-protocol-buffers)
- 유연하고 검증되지 않은 파싱과 검증된 파싱 기능을 모두 갖추는 것이 최선일 것임
