사흘 사이 같은 형태를 두 번 만났다
한 번은 분석 리포트의 구간 표시에서, 한 번은 테스트가 읽는 저장된 응답에서 나왔다. 겹치는 데가 없어 보였는데 뜯어보니 구조가 같았다. 한 번 적어두고 고정한 것이 계속 움직이는 것을 가리고 있었고, 어긋나도 아무 신호가 없었다.
첫 번째, 라벨은 고정이고 창은 자란다
매주 한 번, 실제로 한 매매를 놓고 "그때 이 규칙이 있었다면 어땠을까"를 다시 계산하는 분석을 돌린다. 매번 전 구간을 다시 돌리면 오래 걸리니, 지난 회차 이후에 새로 쌓인 구간만 보는 모드를 따로 뒀다.
리포트는 그 구간을 이렇게 적었다.
증분(8/28~9/15)
그런데 실제로 데이터를 자르는 조건은 시작일만 있고 끝이 없었다. 시작일 이후 전부, 즉 열린 구간이다. 끝 날짜 9/15는 계산 결과가 아니라 문자열로 박아둔 값이었다.
라벨은 고정이고 창은 회차마다 자란다. 다음 주에 돌려도 리포트에는 여전히 8/28~9/15가 찍힌다.
그 표시를 근거로 결론이 나왔다
여기까지는 표기 실수다. 문제는 그다음이다.
두 회차의 리포트를 나란히 놓으니 구간 표시가 같았다. 그래서 "두 회차가 같은 창을 봤으니 수치 차이는 규칙 차이다"라는 결론이 나왔고, 그대로 보고까지 갔다. 그 결론 위에서 전략 판단 하나를 철회할 뻔했다.
원장을 직접 다시 세는 쪽이 이것을 잡았다.
- 대상 매매: 24건 → 29건
- 규칙이 골라낸 매매: 10건 → 11건
- 골라낸 매매의 세후 합계: -30,548원 → -30,150원 (차이 +398원)
창은 같지 않았다. 뒤 회차가 다섯 건을 더 보고 있었다.
결론은 살고 근거만 죽었다
그런데도 두 회차의 수치가 거의 같았던 데에는 별도의 이유가 있었다. 두 집합의 겹침이 82.8%였고, 더 정확히는 금액의 98.7%가 같은 10건에서 나왔다. 뒤에 추가된 매매는 금액이 작아 합계를 거의 움직이지 못했다.
즉 결론("규칙 차이로 볼 만하다")은 살아남았다. 그러나 그것을 떠받친 문장("두 창이 같다")은 사실이 아니었고, 결론을 지켜준 것은 창이 아니라 겹침이었다. 겹침이 40%였다면 똑같은 절차에서 정반대 결론이 나왔을 것이다.
두 번째, 저장해둔 응답이 사망한 주소를 대신 대답했다
같은 주에 게시판 데이터를 읽어오는 쪽을 손봤다. 테스트는 전량 통과했다. 그리고 실제 주소는 HTTP 410 Gone을 주고 있었다.
410은 404와 다르다. 404는 "지금 없다"이고, 410은 "영구히 없앴다"이다. 제공자가 의도적으로 접은 것이다.
테스트가 통과한 이유는 단순하다. 테스트는 그 주소를 부르지 않는다. 언젠가 받아둔 응답을 파일로 저장해두고 그것을 읽는다. 저장된 응답은 주소가 사라져도 바뀌지 않는다. 통과가 뜻한 것은 "지금 동작한다"가 아니라 "예전에 받아둔 것을 여전히 잘 읽는다"였다.
더 곤란한 것은 직전 기록이었다. 그 자리에는 "실측 캡처로 재확인했다"고 적혀 있었다. 열어보니 그 캡처도 저장된 응답이었다. 라이브는 그 시점에 이미 죽어 있었다.
게다가 그 응답은 없는 것을 지어내고 있었다
저장된 응답에는 글 제목이 title 속성으로 들어 있었다. 파서는 그 속성을 읽었고, 테스트는 제목을 정확히 뽑아냈다.
실제 응답에는 그 속성이 없다. 저장본을 만들 때 사람이 읽기 좋으라고 넣은 것이다. 파서는 실제 데이터에서는 한 건도 뽑지 못하는 코드였는데, 저장본이 그 결함을 정확히 메워주고 있었다.
둘은 같은 기계다
라벨은 화면에 고정된 문자열이고, 저장된 응답은 디스크에 고정된 데이터다. 형태가 다르지만 하는 일이 같다.
| 고정해둔 것 | 움직이는 사실 | 어긋났을 때 | |
|---|---|---|---|
| 라벨 | 8/28~9/15 | 시작일 이후 전부 | 아무 일도 일어나지 않음 |
| 저장된 응답 | 예전 본문 + 없는 속성 | 410 Gone | 테스트 전량 통과 |
둘 다 틀렸을 때 조용하다. 코드가 죽지도, 경고가 뜨지도 않는다. 실패는 신호를 주지만 고정된 표현은 신호를 주지 않는다. 그냥 계속 같은 답을 한다.
그리고 둘 다 마지막에 같은 일을 했다. 그 고정된 것을 읽고 결론이 만들어졌다. 한쪽은 "두 창이 같다", 다른 쪽은 "실측으로 확인했다". 둘 다 사실이 아니었고, 둘 다 다음 판단의 근거로 쓰였다.
💡 에러를 내지 않는 결함은 발견 경로가 하나뿐이다. 누군가 그 표시를 의심하고 원본을 다시 세어보는 것. 그 한 경로를 아무도 밟지 않으면 결함은 수명이 없다.
남은 것
표시는 계산되게 하거나, 계산되지 않는다고 적는다. 구간을 화면에 적을 때 그 값이 실제 조건에서 나온 것인지 손으로 적은 것인지가 구분되지 않으면, 읽는 쪽은 언제나 계산된 값으로 읽는다. 끝이 열려 있으면 8/28 이후라고 적는 편이 짧고 정직하다.
저장된 응답은 파서를 검증하지, 연결을 검증하지 않는다. 둘은 다른 축이다. 외부 주소가 살아 있는지는 저장본으로 영원히 알 수 없다. 실제 주소를 주기적으로 한 번 찔러보고 상태 코드만 확인하는 절차를 따로 두는 수밖에 없다.
저장본은 실제 응답에서 잘라온 것이어야 한다. 손으로 쓰기 시작하면 읽기 좋은 쪽으로 다듬게 되고, 그 다듬은 부분이 정확히 파서의 결함이 숨을 자리가 된다.
"실측으로 확인했다"고 적을 때는 무엇을 봤는지 함께 적는다. 그 문장만 남으면 다음에 읽는 쪽이 검증된 사실로 다룬다. 저장본을 본 것과 라이브를 본 것은 전혀 다른 확인인데, 기록에서는 똑같은 한 줄이 된다.