가끔 버그 하나를 고치다 보면 이상한 일이 벌어진다.

처음에는 분명히 작은 문제였다.

"어? 이 예외 케이스 하나만 처리하면 되겠는데?"

그런데 막상 들여다보면 줄줄이 따라 나온다.

테스트 이름이 애매하다.
파일 위치가 이상하다.
리뷰어가 보기엔 왜 이 테스트가 여기에 있는지 모르겠다.
패치는 맞는 것 같은데, 실제 제품 경로가 보호되는지도 애매하다.
커밋 제목은 또 뭘로 해야 하지?

작은 버그가 갑자기 작은 프로젝트가 된다.

이럴 때 필요한 건 천재적인 직감이 아니라 레시피다.

버그 수정은 요리가 아니다, 하지만 레시피는 필요하다

요리할 때도 "맛있게 만들기"는 목표가 되지만 절차는 아니다.

좋은 레시피는 이렇게 말해준다.

  1. 재료를 먼저 꺼낸다.
  2. 불을 켜기 전에 손질한다.
  3. 센 불은 여기까지만 쓴다.
  4. 마지막에 간을 본다.

버그 수정도 비슷하다.

"고친다"는 목표다.
하지만 실제로는 이런 순서가 필요하다.

증상 확인
  ↓
원인 후보 분리
  ↓
재현 또는 증거 확보
  ↓
최소 패치
  ↓
테스트 추가
  ↓
코드 리뷰 기준 점검
  ↓
커밋 / PR
  ↓
다음 사람을 위한 지식화

이 순서가 없으면, 우리는 자꾸 같은 실수를 반복한다.

고치긴 고쳤는데 테스트가 애매하고,
테스트는 있는데 실제 경로를 못 지키고,
PR은 올라갔는데 제목만 봐서는 무슨 일이 있었는지 모르고,
다음 달에 비슷한 버그가 나오면 또 처음부터 기억을 더듬는다.

테스트 이름은 티켓 번호가 아니라 지도여야 한다

예를 들어 어떤 버그를 고치면서 이런 테스트 파일이 생겼다고 해보자.

Issues/QCP_1234.cs

당장은 편하다.

"아, 이 티켓 때문에 만든 테스트구나."

하지만 시간이 지나면 문제가 생긴다.

이 테스트가 무엇을 검증하는지 이름만 봐서는 알 수 없다.
어느 모듈과 관련 있는지도 모른다.
비슷한 기능을 고칠 때 어디를 봐야 하는지도 애매하다.

조금 더 나은 이름은 이런 식이다.

Modules/MySql/Protocol/Payloads/MySqlInitialHandshakeErrPacketTests.cs

길지만 친절하다.

이름만 봐도 알 수 있다.

  • MySQL 관련 테스트구나.
  • Protocol payload 쪽이구나.
  • initial handshake 시점의 ERR packet을 다루는구나.

테스트 이름은 티켓 번호표가 아니라 지도여야 한다.

티켓 번호는 주석이나 PR 본문에 남기면 된다.
파일명과 클래스명은 "이 코드가 무엇을 지키는가"를 말해야 한다.

테스트도 층이 있다

버그를 고치면 테스트를 추가한다.

여기까지는 쉽다.

어려운 건 "어떤 테스트를 추가해야 충분한가"다.

예를 들어 네트워크 프로토콜 관련 버그가 있다고 하자.

가장 낮은 층에서는 byte 배열이 제대로 해석되는지 볼 수 있다.

Parser test
- 이 byte가 에러 패킷인가?
- 에러 코드가 보존되는가?
- 잘못된 payload로 파싱하면 실패하는가?

하지만 실제 제품 버그는 parser에서 끝나지 않을 수도 있다.

제품 경로에서는 이런 일이 더 중요할 수 있다.

Behavior test
- 서버가 에러 패킷을 먼저 보내면?
- 프록시가 클라이언트로 그대로 전달하는가?
- 세션은 올바르게 중단되는가?

둘 다 필요할 때가 있다.

Parser test는 현미경이다.
작은 구조가 맞는지 본다.

Behavior test는 CCTV다.
실제 사람이 겪는 흐름을 본다.

현미경만으로는 사람이 문에 부딪히는지 알 수 없고,
CCTV만으로는 나사가 왜 빠졌는지 알 수 없다.

좋은 패치는 둘의 역할을 헷갈리지 않는다.

변수명은 작은 문서다

코드를 읽다가 이런 이름을 만나면 잠깐 멈춘다.

payload
packet
stream1
stream2

틀린 이름은 아니다.

하지만 리뷰어 입장에서는 머릿속으로 해석해야 한다.

"이 payload는 정상 응답인가? 에러인가?"
"이 packet은 어떤 프로토콜 packet이지?"
"stream1은 클라이언트 쪽인가, 프록시 쪽인가?"

조금만 더 구체적으로 쓰면 코드가 설명을 시작한다.

TooManyConnectionsErrPayload
CreateMySqlPacket
localProxyStream
localClientStream
MySqlPacketHeaderLength

이름이 길어졌지만 독해 비용은 줄었다.

좋은 변수명은 주석을 줄인다.
좋은 상수명은 magic number를 설명한다.
좋은 helper 이름은 테스트의 의도를 드러낸다.

결국 변수명은 가장 작은 문서다.

주석은 보험처럼 써야 한다

주석은 많다고 좋은 게 아니다.

// local로 보낸다
SendToLocal()

이런 주석은 별 도움이 안 된다.

코드가 이미 말하고 있기 때문이다.

하지만 이런 상황은 다르다.

SendToLocal(errorPacket)
FlushWrite()
return false

여기서 FlushWrite()는 얼핏 보면 없어도 될 것처럼 보일 수 있다.

"이미 보냈는데 왜 또 flush 하지?"

바로 이런 줄에는 주석이 필요하다.

// 이 경로는 바로 return하므로, 연결이 닫히기 전에
// 클라이언트가 에러 패킷을 실제로 관측할 수 있게 flush한다.

좋은 주석은 코드가 무엇을 하는지가 아니라, 왜 이 줄이 사라지면 안 되는지를 말한다.

주석은 장식이 아니라 보험이다.

커밋 제목은 영수증이 아니라 표지판이다

가끔 커밋이나 PR 제목을 이렇게 쓰고 싶어진다.

[QPD-1234] 에러 처리 개선
fix(QPD-1234): bug fix

추적은 된다.

하지만 제목만 봐서는 무엇이 바뀌었는지 알기 어렵다.

더 나은 제목은 이런 모양이다.

fix(apps/arisa): Handle MySQL initial ERR packet
fix(apps/api): Refresh allowed-zone cache on policy update
fix(libs/qsi): Preserve CTEs for PostgreSQL UPDATE

여기서 중요한 건 두 가지다.

첫째, 어디가 바뀌었는가.
둘째, 어떤 동작이 바뀌었는가.

티켓 번호는 PR 본문, Jira 링크, 테스트 주석에 남기면 된다.

커밋 제목은 리뷰어와 미래의 나를 위한 표지판이어야 한다.

AI 에이전트에게도 체크리스트가 필요하다

AI 에이전트와 함께 코딩하면 속도가 빨라진다.

하지만 속도가 빨라질수록 기준선이 더 중요해진다.

에이전트가 매번 즉흥적으로 판단하면 결과가 흔들린다.

오늘은 테스트 위치를 잘 잡고,
내일은 티켓 번호 파일명으로 만들고,
모레는 PR 제목에 scope를 빼먹을 수 있다.

그래서 좋은 방식은 이렇다.

Skill = 언제 무엇을 볼지 알려주는 라우터
Document = 예시와 판단 기준을 담은 기준선
Test = 실제 회귀를 막는 실행 가능한 기억
PR = 사람에게 설명하는 변경 기록

스킬에 모든 내용을 욱여넣으면 스킬이 비대해진다.

반대로 문서만 있고 스킬이 모르면 매번 사람이 찾아줘야 한다.

가장 좋은 구조는 스킬이 문서를 가리키는 것이다.

"이런 유형의 작업이면 이 플레이북을 먼저 봐."
"이 언어면 이 코딩 기준을 같이 봐."
"PR 전에는 이 체크리스트를 확인해."

이렇게 하면 AI 에이전트는 단순히 빠른 도구가 아니라, 점점 더 나은 작업 방식을 기억하는 동료가 된다.

결국 좋은 패치는 작지만 친절하다

좋은 패치는 보통 크지 않다.

하지만 친절하다.

  • 왜 고쳤는지 보인다.
  • 어디를 고쳤는지 보인다.
  • 어떤 테스트가 지키는지 보인다.
  • 리뷰어가 다시 물어볼 부분이 줄어든다.
  • 다음 사람이 비슷한 버그를 만났을 때 출발점이 생긴다.

나는 이걸 "패치의 예의"라고 부르고 싶다.

코드는 컴퓨터에게 실행되지만,
패치는 사람에게 읽힌다.

그래서 버그 수정에도 레시피가 필요하다.

레시피가 있으면 다음 요리는 더 빨라진다.
그리고 운이 좋으면, 조금 더 맛있어진다.

오늘의 체크리스트

다음에 버그를 고칠 때는 이 정도만 확인해도 좋다.

[ ] 증상과 기대 동작을 분리했는가?
[ ] 재현 또는 증거가 있는가?
[ ] 테스트 이름이 동작을 설명하는가?
[ ] 테스트 위치가 대상 모듈 구조와 맞는가?
[ ] parser/helper 테스트와 behavior 테스트를 구분했는가?
[ ] 변수명이 역할을 드러내는가?
[ ] magic number를 상수로 뽑았는가?
[ ] 주석은 "왜"를 설명하는가?
[ ] 커밋/PR 제목이 component와 behavior를 말하는가?
[ ] 이번에 배운 기준을 문서나 플레이북으로 남겼는가?

이 체크리스트의 목적은 완벽해지는 게 아니다.

다음 사람이 덜 헤매게 하는 것이다.

그게 결국 좋은 엔지니어링의 꽤 큰 부분이다.