만들면서 배운 것

"안전하게 지워진다"는 주석, 같은 파일 테스트가 이미 반박하고 있었습니다 📝

생각하는 초록이 아빠

기획서에 적힌 설명, 얼마나 그대로 믿으시나요. 저는 기획 일을 하면서 “이 케이스는 발생하지 않음”이라고 적어놓은 문서를 몇 번 만들어봤습니다. 나중에 QA에서 바로 그 케이스가 터져서 문서를 다시 열어본 적도 있고요. 적혀 있다고 사실인 건 아니라는 걸, 이번엔 제가 만드는 앱의 코드에서 다시 확인했습니다. 이번엔 제가 적은 게 아니라 AI가 붙인 주석이었다는 것만 달랐습니다.

계좌를 지우기 전에 붙어 있던 한 줄

돈독(제가 혼자 만드는 자산관리 앱, https://don-doc.com )에는 잔액 0으로 오래 안 쓴 계좌를 찾아서 “정리해도 될까요” 물어보는 배너가 있습니다. “모두 정리” 버튼이 부르는 함수 위에는 이런 주석이 붙어 있습니다.

/**
 * 정리 후보 일괄 삭제. 후보는 정의상 거래·종목·하위 계좌가 없으므로 force 없이 삭제되며,
 * 그 사이 데이터가 생긴 계좌는 건너뛰고 보고한다.
 */

“후보는 정의상 거래가 없다”는 말이 핵심입니다. 정의상 없다니까, 그 뒤에 이어지는 로직도 전부 이 전제 위에 서 있습니다. 그런데 실제로 “후보”를 고르는 함수를 열어보면 이야기가 다릅니다.

후보를 고르는 조건에, 거래 건수는 없다

계좌를 후보에 올릴지 정하는 함수는 잔액이 0인지, 보유 종목·하위 계좌·연결 부채가 있는지, 최근 6개월 안에 거래·잔액변경·엑셀 갱신이 있었는지만 봅니다. 거래 건수(transactionCount)를 걸러내는 조건은 어디에도 없습니다. 최근 6개월만 조용했다면, 그전에 거래가 수십 건 쌓여 있어도 후보가 됩니다. 실제로 후보 목록 화면에도 “과거 거래 41건” 같은 문구가 그대로 붙어서 나옵니다.

여기까지는 코드를 한 번 읽으면 누구나 알 수 있는 사실입니다. 이틀 전 개발 담당 에이전트가 이미 코드로 확인해둔 내용이기도 하고요. 제가 이번에 새로 본 건 다른 지점이었습니다.

삽질하다 멘붕한 초록이 아빠

반박은 이미 같은 파일에 적혀 있었다

같은 폴더에는 이 후보 판정 함수를 검증하는 테스트 파일이 있습니다. 그 첫 번째 테스트 케이스를 열어봤습니다.

it('잔액 0 + 7개월 전 마지막 거래 → 후보', () => {
  const r = findCleanupCandidates([acc({ id: 'card', transactionCount: 41, lastTransactionAt: monthsAgo(7) })], NOW)
  expect(r.map(c => c.id)).toEqual(['card'])
  ...

transactionCount: 41인 계좌가 후보로 통과하는 걸, 테스트가 직접 검증하고 있었습니다. 이건 이번에 새로 짠 테스트가 아닙니다. 후보 판정 함수를 처음 만든 그날부터 같은 파일에 있던 테스트입니다. 주석은 “정의상 거래가 없다”고 적혀 있고, 바로 같은 파일의 테스트는 “거래가 41건 있어도 후보가 된다”를 통과시키고 있었던 겁니다. 같은 프로젝트, 같은 폴더, 같은 세션에서 나온 둘이 처음부터 서로 반대말을 하고 있었습니다.

이 주석을 믿고 고쳤다면 무슨 일이 났을까

지금 이 배너의 실제 동작은 사실 안전한 쪽으로 굴러가고 있습니다. 삭제 함수가 거래 있는 계좌는 force 옵션 없이는 거부하도록 짜여 있어서, 거래가 남은 후보는 삭제되지 않고 조용히 건너뛰어집니다(대신 그걸 사용자에게 제대로 안내하지 않는 게 진짜 문제였는데, 이건 다른 글에서 이미 다룬 부분이라 여기선 접어둡니다).

문제는 이 버그를 고치는 방법으로 계속 제안돼온 한 줄입니다. “삭제 함수를 부를 때 force: true를 넘기면 된다.” 이 수정 자체는 틀린 말이 아닙니다. 그런데 저 주석을 그대로 믿은 채 이 수정만 넣으면, “후보는 어차피 거래가 없으니 force를 켜도 안전하다”는 잘못된 전제 위에서 결정하게 됩니다. 실제로는 거래가 41건, 그 이상 있는 계좌도 후보에 올라와 있습니다. force를 켜는 순간 그 계좌들은 확인 절차 하나 없이 거래 기록째로 영구 삭제됩니다. 지금은 “조용히 건너뛰는” 버그인데, 저 주석만 믿고 고치면 “거래 기록까지 통째로 사라지는” 버그로 바뀔 뻔했습니다. 고치는 게 오히려 더 위험한 방향이 될 뻔했다는 이야기입니다.

주석은 “정의상 안전하다”고 했지만, 같은 파일의 테스트는 처음부터 그렇지 않다는 걸 증명하고 있었습니다.


따라 하기

AI든 사람이든 누가 “이 한 줄만 고치면 됩니다”라고 할 때, 근거로 붙은 주석을 그대로 받아들이기 전에 확인하는 순서를 이번에 정했습니다.

1. 주석이 정확히 뭐라고 전제하는지 먼저 뽑아본다

grep -n "정의상" -B2 -A2 lib/actions/account-cleanup.ts
  • 주석을 눈으로 훑고 넘기지 않고, 그 주석이 세우는 전제가 정확히 뭔지 문장 단위로 따로 뽑아봅니다. “정의상 ~하다”처럼 단정적인 표현이 나오면 특히 의심할 지점입니다.

2. 그 전제를 실제로 판정하는 함수의 조건과 대조한다

grep -n "transactionCount" lib/account-cleanup-calc.ts
  • 주석이 “거래가 없다”고 전제한 값이, 실제 판정 로직의 필터 조건에 쓰이고 있는지 이름으로 검색해봅니다. 안 나오면 그 값은 애초에 걸러진 적이 없다는 뜻입니다.

3. 같은 파일(또는 같은 폴더)의 기존 테스트부터 연다 — 새로 안 짜도 된다

grep -n "transactionCount" lib/account-cleanup-calc.test.ts
  • 검증을 새로 만들 필요는 없었습니다. 이미 있는 테스트 파일에 답이 있었습니다. 함수를 만든 사람(또는 AI)이 이미 붙여놓은 테스트 케이스의 입력값을, 주석의 주장과 나란히 놓아보면 둘이 맞는지 바로 보입니다.

✅ 이렇게 되면 성공 — 주석의 전제, 실제 필터 조건, 기존 테스트 케이스 셋을 나란히 놓았을 때 셋이 같은 말을 하고 있다는 게 확인됩니다. 그때만 그 주석을 근거로 수정을 진행합니다.

⚠️ 막히면 — 테스트 파일이 아예 없다면, 그게 더 위험한 신호입니다. 주석의 주장을 검증해줄 게 아무것도 없다는 뜻이니, 한 줄 고치기 전에 그 전제부터 재현하는 최소 테스트 하나를 먼저 만드는 게 순서입니다.

배운 것

화이팅하는 초록이 아빠

이번에 배운 건 “AI가 쓴 주석은 못 믿는다”가 아닙니다. 사람이 쓴 주석도 똑같이 못 믿을 수 있다는 겁니다. 주석은 그 순간 그 사람(또는 AI)이 그렇다고 믿은 걸 적어놓은 글일 뿐, 코드가 실제로 하는 일과는 다를 수 있습니다. 그리고 이번 경우엔 그 반박이 새로 검증해야 할 것도 아니었습니다. 이미 같은 파일 안에, 처음부터 적혀 있었습니다. 저는 이제 “이 한 줄만 고치면 됩니다” 소리를 들으면, 그 주석 옆에 테스트 파일부터 나란히 열어봅니다.

오늘도 한 걸음. 🐴

이 글은 산업 구조와 만드는 과정을 정리한 개인 기록입니다.
특정 종목의 매수·매도 추천이 아니며, 투자 판단과 그 결과는 독자 본인에게 있습니다.

← 글 목록으로