이 글에서 다루는 것
코드 생성 도구에게 프론트엔드 작업을 맡기는 흐름을 정리하면서, 요구사항이 구현 도중 슬쩍 바뀌는 문제와 "테스트 통과했습니다"가 실제로는 가짜 초록불인 문제를 어떤 구조로 막을 수 있는지 살펴봤다. 특정 도구나 제품이 아니라, 어디서나 적용할 수 있는 판정 기준 설계 원칙만 남긴다.
같은 날 예전에 조사해 둔 텍스트 앵커 복원 로직도 함께 정리했는데, 두 주제가 뜻밖에 같은 원칙으로 수렴했다.
먼저 알아둘 개념
오라클(oracle) 은 실행 결과가 맞는지 틀린지를 판정해 주는 기준이다. 수학 시험의 정답지에 해당하며, 답안(코드)만 노려봐서는 채점이 되지 않는다. 정책 출처(policy source) 는 "무엇이 정답인지" 정할 자격이 있는 곳을 뜻한다. VALID_RED 는 새로 쓴 테스트가 우리가 의도한 그 이유로 실패하는 상태를 가리킨다. 오타나 설정 문제로 빨간 것을 진짜 실패로 착각하면 엉뚱한 것을 고치게 된다.
판정 기준을 정할 권한을 제한한다
가장 인상적인 설계는 정답을 정할 수 있는 곳을 사용자의 명시적 답변과 승인된 명세 문서 두 가지로만 제한한 것이다. 제품 코드, 기존 테스트, 브라우저에서 관찰한 동작, 리뷰어의 의견은 전부 조사 자료로 강등된다.
이 구분이 왜 중요한지는 반례를 보면 분명하다. 도구는 기존 코드를 읽을 수 있으므로 "지금 이렇게 동작하니 이게 요구사항이겠지"라고 추론하기 쉽고, 그 순간 버그가 사양으로 승격된다. 구현과 요구사항이 어긋나면 구현에 맞추지 말고 멈춰서 확인을 요청하는 규칙 하나가 이 실패 모드의 대부분을 막는다.
기준은 "이런 상황에서 이렇게 하면 이런 결과가 나와야 하고, 이건 절대 일어나면 안 된다"를 한 줄씩 적은 표로 만든다. 출처를 붙이지 못하는 행은 관례로 채우지 않고 미결로 둔다.
합의는 텍스트로 잠그고 해시로 검증한다
대화로만 정한 요구사항은 긴 작업에서 반드시 흐려진다. 기준 표를 파일로 저장하고 내용 해시를 기록해 두면, 흐려졌다는 사실 자체를 사람이 아니라 도구가 먼저 감지한다.
여기서 중요한 규칙이 하나 붙는다. 해시가 안 맞는다고 다시 잠가서 통과시키지 않는다. 그때까지 모은 증거를 버리고 사람의 결정을 기다리는 상태로 되돌아간다. 검증을 통과시키려고 검증 기준을 바꾸면 검증이 아니게 된다.
가짜 초록불에 이름을 붙여 금지한다
"통과했습니다"를 만드는 편법은 몇 가지로 정해져 있다. assertion을 느슨하게 바꾸기, 실패하는 테스트를 건너뛰기로 전환하기, 임의의 대기 시간을 넣어 타이밍 문제를 덮기. 이 세 가지를 명시적 금지 목록으로 적어 두는 편이 "대충 하지 마세요"보다 훨씬 잘 지켜졌다.
더 근본적인 장치는 완료의 정의를 옮기는 것이다. 테스트 통과가 완료라면 통과시키는 것이 목표가 되고, 그때부터 assertion이 약해진다. 브라우저에서 실제로 조작한 증거와 독립적인 리뷰까지 마쳐야 완료라고 정의하면 느슨하게 만들 이득이 사라진다.
테스트가 진짜로 결함을 잡는지 기계적으로 확인하고 싶다면 코드를 일부러 조금 망가뜨려 보는 방법이 있다.
확신이 부족하면 멈춘다
같은 날 예전 조사 기록을 정리하다가 흥미로운 대칭을 발견했다. 웹 문서에서 하이라이트를 복원할 때, 후보 구간들의 점수를 매긴 뒤 1등과 2등의 점수 차가 작으면 후보가 있어도 붙이지 않는 규칙이 있었다. 임계값을 넘었는지와 별개로 경쟁 상황 자체를 보는 지표다.
이것은 앞의 "정책이 미결이면 진행하지 않고 멈춘다"와 정확히 같은 판단이다. 한쪽은 사람에게 질문을 돌리고 다른 쪽은 하이라이트를 포기하지만, 둘 다 조용히 틀린 결과를 만들지 않으려는 목적을 가진다. 엉뚱한 곳에 붙는 것이 안 붙는 것보다 나쁘다는 원칙이 숫자로 옮겨진 셈이다.
종료 코드 0을 믿지 않기
부수적으로 하나 더 배웠다. 노트를 만드는 명령이 종료 코드 0에 출력도 없이 끝났는데 정작 파일이 생기지 않았다. 종료 코드 0은 "성공했다"가 아니라 "에러를 보고하지 않았다" 는 뜻이다. 파일 생성처럼 부작용이 목적인 명령은 부작용 자체를 확인해야 한다.