/code-review medium 1회차 · Claude Code · Opus 5.5 · 클로드의 마지막 답 원문 · 목록
points.py를 리뷰한 결과 문제 9건을 찾았습니다. 이번 리뷰에서는 별도 검증 단계를 거치지 않았습니다.
규칙대로 동작하지 않는 문제 (먼저 고칠 것)
- 생일 배율이 계속 쌓임: 생일 적립 때 self.rate(적립 비율) 값 자체를 두 배로 바꿔 버립니다. 그래서 생일이 지나도 2배가 유지되고, 생일에 여러 번 결제하면 4배, 8배로 계속 커집니다.
- 포인트를 쓸 때 만료된 적립분부터 깎임: 사용 가능 여부는 만료분을 뺀 잔액으로 확인하는데, 실제 차감은 만료분도 포함해서 오래된 순으로 합니다. 그래서 만료된 포인트만 깎이고 쓸 수 있는 잔액은 줄지 않는 경우가 생깁니다.
- 유효기간이 하루 짧음: < EXPIRE_DAYS - 1 조건 때문에, 사장님 메모의 "365일째 되는 날까지 사용 가능" 규칙과 달리 365일째 날에 이미 포인트가 사라집니다. 이 줄은 이번에 바뀐 부분은 아니지만, 새로 추가된 포인트 사용 기능이 이 계산에 의존합니다.
- 하루 한도가 다시 풀림: 오늘 적립한 양을 "남은 포인트"로 계산합니다. 그래서 같은 날 포인트를 쓰고 나면 한도가 다시 생겨, 하루 5,000P 이상 적립할 수 있습니다.
- 음수 입력을 막지 않음: 포인트 사용에 음수를 넣으면 포인트가 오히려 늘어납니다. 적립 금액이 음수일 때도 마찬가지로 막지 않습니다.
덜 급한 문제
- 하루 한도에 도달한 뒤에도 결제할 때마다 0P 적립 기록이 쌓입니다.
- 2월 29일생은 평년에 생일 2배 혜택을 못 받습니다. 평년에 2/28과 3/1 중 어느 날로 처리할지 정해야 합니다.
- self.rate는 저장해 둘 필요 없이 적립할 때 바로 계산하면 됩니다. 그렇게 바꾸면 첫 번째 문제도 같이 해결됩니다.
- 적립 기록이 이미 날짜순이라 포인트를 쓸 때마다 다시 정렬할 필요가 없습니다. 적립할 때마다 전체 기록을 훑어 오늘 합계를 구하는 부분도 줄일 수 있습니다.
적립 비율 계산에서 소수점 오차로 1포인트가 덜 쌓이는지도 확인했습니다. 결제 금액 1원~20만 원 범위에서 5%와 10% 모두 오차가 없었습니다.
찾은 문제 목록 (ReportFindings 도구로 넘긴 원문, 9건)
| # | 위치 | 분류 | 요약 | 일어나는 상황 |
|---|---|---|---|---|
| 1 | points.py:35 | 정확성(버그) | 생일 배율이 self.rate에 영구 누적됨 생일 적립 시 self.rate를 직접 두 배로 바꿔서, 생일이 지나도 배율이 그대로 남고 생일날 결제할 때마다 계속 두 배씩 커집니다. | 생일에 두 번 결제하면 첫 결제는 10%, 두 번째는 20%로 적립되고, 다음 날 일반 결제도 20%로 적립됩니다(원래는 5%). 지역 변수 rate = POINT_RATE * (2 if 생일 else 1)로 계산해야 합니다. |
| 2 | points.py:54 | 정확성(버그) | use()가 만료된 적립분부터 차감함 use()는 모든 적립분을 오래된 순으로 차감하는데, 이미 만료된 적립분도 포함됩니다. 잔액 검사(balance)는 만료분을 빼고 계산하므로 둘이 맞지 않습니다. | 400일 전 적립분 1,000P(만료)와 어제 적립분 1,000P가 있을 때 use(1000)을 하면 잔액 검사는 통과하지만 만료된 항목만 깎이고, 사용 후에도 balance는 1,000으로 남습니다. 결국 차감이 되지 않은 것과 같고, 만료분을 사용 재원으로 쓰게 됩니다. |
| 3 | points.py:45 | 정확성(버그) | 유효기간 비교가 하루 짧음(off-by-one) 적립일을 1일째로 치면 365일째의 (today - d).days는 364입니다. 그런데 조건이 '< EXPIRE_DAYS - 1'(< 364)이라 365일째 되는 날에 포인트가 이미 빠집니다. | 1월 1일에 적립한 포인트가 규칙상 365일째(364일 뒤)까지 써져야 하는데, 그날 balance에서 제외되어 use()가 '포인트가 부족합니다'를 던집니다. 조건은 '< EXPIRE_DAYS'여야 합니다. |
| 4 | points.py:37 | 정확성(버그) | 일일 한도가 적립량이 아니라 남은 포인트로 계산됨 earned_today를 entries의 '남은 포인트'로 합산하는데, use()가 이 값을 깎기 때문에 같은 날 포인트를 쓰고 나면 하루 한도가 다시 생깁니다. | 오늘 5,000P를 적립하고(한도 도달) 5,000P를 쓰면 earned_today가 0이 되어, 같은 날 다시 5,000P를 적립할 수 있습니다. 결과적으로 하루 10,000P 이상 적립됩니다. 원래 적립량을 따로 저장해야 합니다. |
| 5 | points.py:49 | 정확성(버그) | use()에 음수/0 검증 없음 use()가 음수 포인트를 막지 않습니다. 음수가 들어오면 take가 음수가 되어 오히려 적립분이 늘어납니다. | use(-1000, today)를 하면 잔액 검사(-1000 > balance)를 통과하고, 가장 오래된 항목에 1,000P가 더해집니다. 포인트를 공짜로 만들어내는 셈입니다. earn()도 금액이 음수면 마찬가지로 음수 적립분이 생깁니다. |
| 6 | points.py:39 | 정확성(버그) | 한도 초과 시 0P 항목이 계속 추가됨 하루 한도에 도달한 뒤에도 0P 적립 항목이 계속 entries에 추가됩니다. | 한도에 걸린 손님이 같은 날 여러 번 결제하면 [today, 0] 항목이 결제마다 쌓여, entries가 커지고 earn/balance/use 순회 비용도 늘어납니다. |
| 7 | points.py:29 | 정확성(버그) | 2월 29일 생일은 평년에 적용 안 됨 생일 비교가 (월, 일) 완전 일치만 보기 때문에, 2월 29일생은 평년에 생일 2배 적립을 받지 못합니다. | birthday=date(2000,2,29)인 손님은 2025~2027년에 생일 혜택이 없습니다. 평년에는 2/28이나 3/1로 대신 처리할지 정책을 정해야 합니다. |
| 8 | points.py:23 | 단순화 | self.rate 인스턴스 상태가 불필요함 rate는 POINT_RATE와 생일 여부만으로 구할 수 있는데, 이를 가변 인스턴스 상태로 두면서 위의 배율 누적 버그가 생겼습니다. | self.rate를 없애고 earn() 안에서 지역 변수로 계산하면 상태 관리 부담이 사라지고 누적 버그도 막을 수 있습니다. |
| 9 | points.py:54 | 효율 | 사용할 때마다 entries를 정렬함 entries는 항상 시간 순서대로 append되므로 이미 정렬되어 있는데, use()가 호출될 때마다 다시 정렬합니다. earn()도 매번 전체 entries를 훑어 당일 합계를 구합니다. | 적립 이력이 쌓일수록 use()마다 O(n log n), earn()마다 O(n) 비용이 듭니다. 정렬은 빼고, 당일 적립 합계는 날짜별 딕셔너리로 관리하면 됩니다. |