코딩 스탠다드가 53편이 됐다. 규칙은 빠르게 늘었는데, 글을 다시 읽다가 불편한 점이 하나 보였다. 규칙마다 「이게 낫다」고 적혀 있지만, 실제로 돌려보고 확인한 것이 없다.

예를 들면 이런 문장들이다.

// 표준 글에 적힌 모양
테이블에 인덱스를 걸면 조회가 빠르다.
안 C는 안 A보다 고칠 파일이 적다.
이 탐색 규칙이 에이전트에게 유리하다.

셋 다 그럴듯하다. 그런데 왜 그런지를 돌려서 보여준 곳이 없다. 이런 문장이 쌓이면 블로그 전체가 「그렇다고 적어둔 곳」이 되고, 6개월 뒤의 나는 규칙을 믿을 근거를 물려받지 못한다.

그래서 정해야 했던 것은 이거였다 — 표준의 규칙을 무엇으로, 어디에, 어떤 모양으로 증명하는가.


먼저: 근거가 없는 게 아니라 「세어본 것」만 있었다

정하기 전에 지금 상태를 쟀다. 표준 글 53편에서 수치를 적은 흔적을 키워드로 셌다. 키워드 검색이라 정밀한 값은 아니다.

근거의 종류 현황
관찰 — 실제 프로젝트에서 세어봤다 45편에 흔적이 있다
실험 — 조건을 맞춰 돌려서 비교했다 사실상 0. 「벤치마크」 1편, EXPLAIN 2편, 실험 코드를 둔 폴더는 없다
아무 수치도 없다 8편

문제는 근거가 없다는 것이 아니었다. 근거의 종류가 한쪽에 몰려 있었다. 「폴더를 세어보니 63곳」은 그 프로젝트의 사정을 보여줄 뿐, 규칙이 옳다는 증명은 되지 못한다. 다른 프로젝트에서는 숫자가 달라지고, 그때 규칙이 여전히 맞는지는 알 수 없다.

그래서 이 글이 채우는 자리는 「돌려서 증명한다」다.


원칙 1. 근거는 네 종류로 나누고 종류마다 증명 방법을 하나로 정한다

위의 세 예시를 보면 증명하는 방법이 서로 다르다. 인덱스는 돌려서 시간을 재면 되지만, 「고칠 파일이 적다」는 시간을 잴 대상이 아니다. 방법 하나로 묶으면 절반은 증명이 안 된다.

그래서 근거를 종류로 나누고, 종류마다 방법을 하나씩 정했다.

종류 증명 방법 남는 것
1. 기술 사실 인덱스를 걸면 빠르다 같은 데이터로 있을 때와 없을 때를 돌려 시간·실행계획을 비교 실험 코드 + 결과 수치
2. 안 비교 안 C가 안 A보다 낫다 같은 기능을 두 안으로 각각 구현해 파일 수·줄 수·「기능 하나 바꿀 때 고치는 파일 수」를 비교 두 구현 + 비교표
3. 에이전트 규칙 이 탐색 규칙이 유리하다 같은 과제를 규칙이 있을 때와 없을 때 여러 번 돌려 토큰·도구 호출·정답률을 비교 과제 목록 + 반복 결과
4. 명세 사실 412는 이런 뜻이다 공식 문서를 인용하고 최소 예제로 확인 출처 링크 + 재현 결과

넷 모두에 조건 하나를 건다. 6개월 뒤에 다시 돌려도 같은 결과가 나오게, 실험 코드와 결과를 저장소에 남긴다. 글에 수치만 적고 코드를 안 남기면, 그 수치 역시 「그렇다고 적어둔 것」으로 돌아간다.

종류 3의 「여러 번」이 몇 번인지는 정하지 못했다. 같은 과제도 돌릴 때마다 결과가 흔들리는데, 몇 번을 돌려야 차이를 믿을 수 있는지는 아직 기준이 없다. 이건 안건 이슈로 남겼다.


원칙 2. 모든 원칙을 증명하지 않는다 — 약속과 주장을 가른다

증명 순서를 정하려고 원칙 헤딩을 훑다가, 원칙에 성격이 두 가지 있다는 것을 알았다.

원칙 성격
라우트 원칙 9. 복수 단어 리소스명은 kebab-case로 쓴다 약속 — snake_case로 정했어도 통일만 되면 똑같이 동작한다
DTO 원칙 3. Jackson이 생성자를 쓰도록 명시한다 주장 — 명시하지 않으면 무언가가 깨진다는 뜻이다
라우트 원칙 3. 수정은 PUT 하나로 받고 PATCH를 열지 않는다 주장 — PATCH를 열면 코드 경로가 는다는 뜻이다

판별 질문은 하나로 정했다. 「반대로 정해도 통일만 되면 똑같이 동작하는가?」 그렇다면 약속이고, 아니면 주장이다.

안 A — 전부 증명한다

얻는 것은 빠지는 규칙이 없다는 것이다. 분류를 잘못해서 증명을 놓칠 일도 없다.

버린 이유는 약속 원칙에 실험을 붙이면 「kebab-case 요청 1만 건 vs snake_case 1만 건, 차이 없음」 같은 결과만 나오기 때문이다. 시간을 쓰고도 규칙에 대해 새로 알게 되는 게 없다. 약속은 원래 어느 쪽이든 동작하니까 약속이다.

골랐다 — 안 B. 주장만 증명하고, 약속은 약속이라고 밝힌다. 약속 원칙은 그 원칙 첫 줄에 > 약속: 반대로 정해도 동작은 같다. 통일이 목적이다.를 단다.

이게 신뢰성 문제의 절반을 푼다. 블로그를 못 믿게 만든 것은 근거가 없는 규칙이 아니라, 근거가 없는 이유가 보이지 않는 규칙이었다. 약속이라고 적혀 있으면 읽는 쪽은 「근거가 빠진 규칙」이 아니라 「근거가 필요 없는 규칙」으로 알아본다.

대신 주장을 약속으로 잘못 분류하면 증명에서 빠진다. 그래서 분류도 원칙 첫 줄에 드러나게 했다. 틀렸으면 읽다가 걸린다.


원칙 3. 실험 코드는 블로그 저장소 안 proofs/ 에 둔다

주장 하나마다 실험 코드가 생긴다. 금방 수십 개가 된다. 정해야 했던 건 이거였다 — 그 코드를 어디에 두는가.

안 A — 이 저장소 안 proofs/ 폴더

얻는 것은 글과 실험이 같은 커밋에 들어가고, 이름으로 짝을 검사할 수 있다는 것이다. coding-standards/open-source/도 이미 이 저장소 안에 있고 사이트 배포에서만 빠진다. 같은 방식이다.

안 B — 실험 전용 저장소 하나

얻는 것은 블로그 저장소가 가볍게 남고, 실험만 따로 CI로 돌리기 쉽다는 것이다.

버린 이유는 글과 실험이 두 저장소로 갈라지면 짝 검사가 닿지 않기 때문이다. 한쪽만 고쳐도 아무도 모른다. 저장소 밖에 걸어뒀다고 적힌 심링크가 실제로는 없었는데 아무도 몰랐던 일, 규칙만 배포되고 글이 빠졌던 일이 모두 이 모양이었다.

안 C — 실험마다 저장소 하나

얻는 것은 실험끼리 의존성이 섞이지 않는다는 것이다. 버린 이유는 안 B의 「어긋나도 모른다」가 실험 수만큼 늘어나기 때문이다.

안 D — 코드를 글 본문에만 적는다

얻는 것은 파일이 하나도 늘지 않는다는 것이다. 버린 이유는 다시 돌릴 수 없기 때문이다. DB 설정, 데이터 생성, 실행 명령이 글에 다 들어가지 않는다.

골랐다 — 안 A. 안 B와 안 C는 짝이 어긋나도 모르고, 안 D는 재현이 안 된다. 같은 커밋과 기계 검사를 둘 다 지키는 것은 안 A뿐이다.

대신 저장소가 이미 3.78GB라서 데이터를 커밋하지 않는다. 테스트 데이터는 시드를 고정한 스크립트로 매번 만든다.


원칙 4. 실험 폴더에는 claim.md · run.sh · result.md 셋을 고정한다

실험마다 모양이 다르면, 「반증 조건이 적혀 있나」, 「결과가 스크립트 출력인가」를 기계가 검사할 수 없다. 그래서 파일 셋을 고정했다.

proofs/<슬러그>/
  claim.md    돌리기 전에 쓴다 — 주장 한 문장, 근거 종류, 받치는 표준 글, 반증 조건
  run.sh      명령 하나로 처음부터 끝까지 재현한다
  result.md   run.sh 가 쓴다 — 판정·요약 두 줄, 날짜·환경·반복 횟수·원본 수치

셋 중 가장 중요한 것은 claim.md의 반증 조건이다. 「이 결과가 나오면 주장이 틀린 것이다」를 돌리기 전에 적는다. 결과를 보고 기준을 정하면 어떤 수치가 나와도 「그래도 빠르네」로 읽게 된다. 선을 먼저 그어야 실험이 규칙을 뒤집을 수 있게 되고, 뒤집을 수 있어야 증명이다.

run.sh를 bash로 정한 이유는 이 컴퓨터에 Git Bash가 이미 있고 나중에 리눅스 CI에서도 그대로 돌기 때문이다. Gradle 같은 도구는 Docker 이미지 안에서 돌린다. 내 PC에 무엇이 깔려 있는지에 결과가 좌우되지 않게 하려는 것이다.

result.md를 사람이 요약해 쓰는 안은 버렸다. 요약하다가 불리한 수치가 빠져도 아무도 모른다. 스크립트가 쓰면 빠질 수가 없다.


원칙 5. 실험 하나가 증명 글 하나이고 둘은 슬러그로 짝짓는다

처음에는 폴더를 proofs/<표준 글 슬러그>/<실험 이름>/으로 정했다. 표준 밑에 실험이 매달리는 모양이다. 그런데 증명 글과의 관계를 정하다가 이 모양이 틀렸다는 것을 알았다.

「인덱스 조회 실험」은 Repository 설계 표준과 목록 조회 계약 표준을 둘 다 받친다. 표준 밑에 두면 이 실험을 둘 곳이 없고, 두 곳에 두면 실험이 두 벌이 된다.

그래서 한 단계를 없앴다.

proofs/<증명 슬러그>/
_posts/proof/YYYY-MM-DD-<증명 슬러그>.md
  • 짝은 이름이다. 폴더와 증명 글 파일명의 슬러그가 같다. 규칙과 글을 슬러그로 짝짓는 검사와 같은 방식이다
  • 받치는 표준은 claim.md에 슬러그로 적는다. 여러 개여도 된다
  • 받치는 원칙은 번호로 적지 않는다. 원칙을 쪼개면 번호가 조용히 어긋난다. 아키텍처 소속 표준을 정할 때 이미 겪은 실패다. 원칙 위치는 표준 글 안의 인용 줄(원칙 6)이 맡는다

표준 하나에 증명 글 하나를 두고 실험 여러 개를 묶는 안도 검토했다. 버린 이유는 실험 하나를 다시 돌리면 묶인 글 전체를 다시 봐야 하기 때문이다. 증명 글이 표준 글처럼 길어지는 문제도 그대로 따라온다.


원칙 6. 과정은 증명 글에, 결과 한 줄은 표준 글의 원칙에 둔다

안 A — 표준 글의 원칙 안에 실험 과정을 다 넣는다

얻는 것은 규칙과 증명이 한 자리에 있다는 것이다.

버린 이유는 원칙 섹션이 1,000 토큰을 넘기 때문이다. 표준 글은 이미 평균 5,700 토큰이다. 실험 환경, 데이터 생성, 예상 밖 결과까지 넣으면 「이 규칙이 뭐냐」만 찾던 조회가 실험 과정을 통째로 끌고 온다. 섹션이 조회 단위라는 전제가 깨진다.

안 B — 증명 글만 따로 쓰고 표준 글은 두기

얻는 것은 표준 글이 그대로 유지된다는 것이다.

버린 이유는 규칙을 읽는 사람은 증명이 있다는 것조차 모르기 때문이다. 신뢰성 문제를 풀려고 증명했는데, 규칙을 읽는 자리에서는 여전히 근거가 안 보인다.

골랐다 — 안 C. 과정은 증명 글에, 표준 글의 원칙에는 한 줄만.

> 실측(supported): 100만 행 단건 조회 인덱스 0.4ms vs 없음 380ms — [증명](/post/proof/<슬러그>/)

안 A는 조회 단위를 깨고, 안 B는 규칙 옆에서 근거를 감춘다. 안 C만 규칙 옆에 근거를 보이면서 원칙 섹션을 짧게 둔다.

대신 수치가 두 곳에 산다. 실험을 다시 돌리면 증명 글과 표준 글이 함께 바뀌어야 한다. 이 대가를 원칙 7이 막는다.


원칙 7. 표준 글이 인용한 요약은 result.md 와 글자 그대로 같아야 한다

안 A — 사람이 기억해서 맞춘다

만들 게 없다. 버린 이유는 result.md만 새로 뽑히고 표준 글은 옛 수치로 남기 때문이다. 같은 규칙이 파일마다 14/13/12줄로 갈라졌던 실패와 같은 모양이다.

안 B — 표준 글에는 판정과 링크만 둔다

낡을 수치가 없으니 문제가 사라진다. 버린 이유는 규칙 옆에서 「얼마나」가 안 보이기 때문이다. 「증명됐다」는 말만 남으면 신뢰성 문제를 반만 푼다.

안 C — 생성기가 표준 글의 인용 줄을 써넣는다

절대 낡지 않는다. 버린 이유는 표준 글 수십 곳에 마커를 박고, 생성기가 글 본문을 고치게 되기 때문이다.

골랐다 — 안 D. run.sh가 요약 한 줄을 만들고, 검사가 「그 줄을 그대로 인용했나」를 본다.

result.md 첫머리    판정: supported
                    요약: 100만 행 단건 조회 인덱스 0.4ms vs 없음 380ms
표준 글             > 실측(supported): 100만 행 단건 조회 인덱스 0.4ms vs 없음 380ms — [증명](…)

검사는 표준 글의 > 실측(...) 줄마다 링크된 증명의 result.md를 열어 판정과 요약이 글자 그대로 같은지 본다. 안 B와 달리 수치가 보이고, 안 A와 달리 낡으면 걸리고, 안 C와 달리 글에 마커가 없다. 저장소 안 파일끼리 비교하므로 check-standards.rb에 넣어 배포를 막을 수 있다.

대신 표준 글은 요약 한 줄만 인용할 수 있고, 증명 글 본문의 표 수치는 이 검사 밖이다.


원칙 8. 반증되면 지우지 않고 발행한 뒤 모순 이슈로 규칙을 다시 연다

결과는 claim.md의 반증 조건으로만 판정한다. supported(주장대로), refuted(반증 조건에 걸림), inconclusive(둘 다 아님) 셋이다.

정해야 했던 건 이거였다 — refuted가 나오면 무엇을 하는가. 이게 없으면 불리한 결과가 조용히 묻히고, 증명 체계 전체가 신뢰를 잃는다.

안 A — 실험을 고쳐 다시 돌린다

얻는 것은 데이터가 너무 작았다든지 하는 설계 결함을 바로잡을 수 있다는 것이다. 버린 이유는 맞는 결과가 나올 때까지 조건을 바꾸게 되기 때문이다. 반증 조건을 먼저 적은 의미가 사라진다.

안 B — 표준 글의 규칙을 바로 고친다

얻는 것은 틀린 규칙이 가장 빨리 사라진다는 것이다. 버린 이유는 실험 하나가 틀렸을 수 있기 때문이다. 확인 없이 뒤집으면 잘못된 실험이 멀쩡한 규칙을 지운다.

골랐다 — 안 C.

  1. result.md와 판정을 지우지 않고 커밋한다
  2. 증명 글을 반증된 채로 발행한다. 틀렸다는 것도 증명이다
  3. 표준 글의 그 원칙에 > 반증: <요약> — [증명](링크)을 단다
  4. coding-standard 이슈를 kind: 모순으로 열고, 실험의 결함인지 규칙의 결함인지 따진 뒤 규칙을 고친다

실험 설계를 바꿔 다시 돌린다면 새 실험으로 만들고 옛 실험은 남긴다.

안 A는 불리한 결과를 지우고, 안 B는 실험 하나에 규칙을 맡긴다. 안 C만 결과를 남기면서 판단은 따로 거친다. 대신 그 이슈가 닫힐 때까지 그 규칙은 표준 글에 「흔들리는 규칙」으로 드러난 채 남는다.


원칙 9. 시범 1건을 끝까지 돌린 뒤 참조가 많은 표준부터 넓힌다

시범은 DTO 생성자 표준 원칙 3 「Jackson이 생성자를 쓰도록 명시한다」로 정했다.

본문에 돌려볼 수 있는 주장이 둘 있다.

  1. @NoArgsConstructor@Setter가 붙어 있으면 Jackson이 에러 없이 setter 경로로 객체를 만든다
  2. 필드를 final로 두면 @Setter, @NoArgsConstructor, @Data컴파일되지 않는다

2번은 반증될 가능성이 있다. 내가 아는 Lombok 동작으로는 final 필드에 @Setter@Data를 붙여도 컴파일 에러 없이 setter만 생기지 않는다. 아직 확인하지 않았다. 맞다면 첫 실험에서 반증 처리 절차(원칙 8)까지 한 번에 거친다.

시범에서 보는 것은 수치가 아니라 구조가 굴러가는지다. claim.mdrun.shresult.md → 증명 글 → 표준 글 인용 줄 → 짝 검사와 인용 일치 검사를 한 번씩 모두 거친다. DB 없이 Docker 안의 Java만 있으면 몇 분에 끝난다.

그다음은 다른 글에서 참조가 많은 표준의 주장 원칙 순서다. 패키지 구조(25), Repository 설계(15), 예외 분류(15), 예외 처리(14). 참조가 많은 규칙은 틀렸을 때 그 위에 선 글들이 같이 흔들린다.

바로 참조 순서로 시작하는 안은 버렸다. 패키지 구조는 종류 2(두 안을 구현해 비교)라 무겁고, 구조에 구멍이 있으면 무거운 실험 위에서 발견하게 된다. 싼 것부터 전부 하는 안도 버렸다. 참조가 적은 규칙부터 증명하게 되어 신뢰성이 가장 늦게 오른다.


판단 기준 정리

질문 결론
반대로 정해도 통일만 되면 똑같이 동작하는가 그렇다 약속. 증명하지 않고 > 약속:을 단다
근거가 기술 사실·안 비교·에이전트 규칙·명세 중 무엇인가 종류마다 그 종류의 증명 방법 하나로 증명한다
실험 코드를 어디에 두나 이 저장소 proofs/<슬러그>/ — 데이터는 커밋하지 않는다
실험 하나가 표준 여럿을 받치나 그럴 수 있다 폴더를 표준 밑에 두지 않는다. 증명 글과 슬러그로 짝짓는다
반증 조건은 언제 적나 돌리기 전 claim.md에 먼저 적는다
결과는 누가 쓰나 스크립트 run.shresult.md를 쓴다. 사람이 요약하지 않는다
표준 글에 무엇을 적나 한 줄 > 실측(판정): <요약 그대로> — [증명]
다시 돌려 요약이 바뀌었나 그렇다 인용 일치 검사가 배포를 막는다
refuted가 나왔나 그렇다 지우지 않고 발행하고, 모순 이슈를 연다

이 표준을 정하기까지

시작은 신뢰성이었다. 코딩 스탠다드 규칙은 계속 정해지는데 그걸 뒷받침하는 실험이 없어서 글을 믿기 어렵다는 문제 제기였다. 인덱스가 빠른 이유를 돌려보거나, 한 규칙이 다른 규칙보다 나은 이유를 파일 수로 비교하거나, 에이전트 탐색 규칙이 유리한 이유를 재보는 글이 없었다.

재보니 근거가 없는 게 아니라 한쪽으로 몰려 있었다. 45편에 「세어봤다」가 있었고, 「돌려봤다」는 사실상 0이었다. 문제의 이름이 「근거 부족」에서 「실험 부족」으로 좁혀졌다.

세 예시가 서로 다른 방법을 요구한다는 데서 원칙 1이 나왔다. 인덱스는 시간을 재면 되지만 「고칠 파일이 적다」는 잴 시간이 없다. 명세 사실까지 더해 넷으로 나눴다.

그러자 코드가 엄청 많아질 거라는 우려가 나왔다. 관리 방법부터 정하고 가자는 요청이었다. 전용 저장소가 가장 깔끔해 보였지만, 짝이 어긋나도 모르는 구조라는 점에서 이 저장소가 이미 두 번 당한 모양과 같았다. 그래서 proofs/를 저장소 안에 두기로 했다.

파일 셋을 고정하면서 반증 조건이 중심에 섰다. 결과를 보고 기준을 정하면 실험은 규칙을 뒤집을 수 없다. 뒤집을 수 없는 실험은 증명이 아니라 확인 절차일 뿐이다.

증명 글과의 관계를 정하다가 앞의 합의가 틀렸다는 것을 발견했다. 폴더를 표준 밑에 두기로 했었는데, 실험 하나가 표준 둘을 받치는 경우를 떠올리자 둘 곳이 없었다. 폴더 한 단계를 없애고, 원칙 위치는 번호 대신 표준 글의 인용 줄이 맡게 했다. 이 글에서 방향이 꺾인 유일한 지점이다.

반증 절차는 불리한 결과가 묻히는 것을 막으려고 정했다. 다시 돌리는 안과 바로 고치는 안이 각각 결과를 지우거나 실험 하나에 규칙을 맡긴다는 것을 확인하고, 기록과 판단을 분리했다.

수치가 두 곳에 사는 대가는 검사로 막았다. 생성기가 글을 고치는 안은 글 곳곳에 마커를 퍼뜨린다. 요약 한 줄을 글자 그대로 인용하게 하면 검사는 문자열 비교로 끝난다.

증명 순서를 정하려고 원칙을 훑다가, 증명할 수 없는 원칙을 발견했다. kebab-case냐 snake_case냐는 돌려서 나올 결과가 없다. 이 발견이 「약속」이라는 분류를 만들었고, 신뢰성을 떨어뜨린 것이 근거가 없는 규칙이 아니라 근거가 없는 이유가 안 보이는 규칙이었다는 것도 이때 드러났다.

마지막으로 시범을 하나 고르면서 첫 반증 후보가 나왔다. DTO 원칙 3의 「final이면 @Setter가 컴파일되지 않는다」는 내가 아는 Lombok 동작과 어긋난다. 구조를 검증하는 시범이 규칙까지 흔들 수 있다는 것이 이 선택의 이유다.


정리

  • 세어본 것은 증명이 아니다. 그 프로젝트의 사정일 뿐, 규칙이 옳다는 증거는 조건을 맞춰 돌려본 실험에서 나온다
  • 약속은 증명하지 않고 약속이라고 밝힌다. 신뢰를 깎는 것은 근거가 없는 규칙이 아니라 근거가 없는 이유가 안 보이는 규칙이다
  • 근거 종류마다 증명 방법이 하나다. 기술 사실은 돌리고, 안 비교는 구현해서 세고, 에이전트 규칙은 반복하고, 명세는 인용해 확인한다
  • 실험은 proofs/<슬러그>/에 두고 증명 글과 이름으로 짝짓는다. 저장소가 갈라지면 어긋나도 모른다
  • 반증 조건은 돌리기 전에 적고, 결과는 스크립트가 쓴다. 둘 중 하나라도 사람 손에 맡기면 실험이 규칙을 뒤집을 수 없다
  • 표준 글에는 요약 한 줄을 글자 그대로 인용한다. 다시 돌려 바뀌면 검사가 배포를 막는다
  • 반증은 지우지 않고 발행한다. 규칙은 모순 이슈에서 따로 따진 뒤에 고친다

자신만의 철학을 만들어가는 중입니다.
최상단으로 이동했습니다!
확대 이미지

댓글남기기