/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건)

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