날짜 구간별로 다른 값을 적용해야 할 때 코드를 어떻게 쓰는 게 나은지 세 가지 버전을 비교하는 짧은 글이다. if 비교문에서 시작해 날짜와 값의 쌍 목록을 거쳐, 범위를 키로 쓰는 RangeKeyDict로 정리한다. 포인트 적립률 정책을 예제로 삼는다.
핵심 포인트- 날짜 구간 매핑을 if 비교문으로 짜면 구간이 바뀔 때마다 로직을 고쳐야 하고 눈으로 읽기 어렵다.
- 날짜와 값의 쌍을 목록으로 빼면 로직 대신 데이터를 고치게 되지만 내림차순 정렬 전제가 자연스럽지 않다.
- range-key-dict 패키지의 RangeKeyDict는 시작과 끝 튜플을 사전의 키로 쓸 수 있게 해준다.
- RangeKeyDict를 쓰면 조회 함수가 사전 인덱싱 한 줄로 줄어든다.
- 날짜뿐 아니라 자주 바뀌는 법령이나 정책처럼 어떤 범위에 값을 매핑할 때 두루 쓸 수 있다.
상세 정리- 예제 상황: 포인트 적립 정책이 2020년 1월 1일부터 1퍼센트, 2월 2일부터 2퍼센트, 8월 8일부터 8퍼센트로 바뀐다고 가정한다.
- 1안 구현: base_date를 받아 경계 날짜와 차례로 비교하며 Decimal 값을 반환하는 함수다. 시작일 이전은 None을 돌려준다.
- 1안의 문제 1: 구간이 바뀔 때마다 비교식 자체를 수정해야 한다.
- 1안의 문제 2: 코드가 한눈에 들어오지 않는다. 특정 날짜의 적립률이 2퍼센트인지 8퍼센트인지 자세히 보지 않으면 헷갈린다는 것이 저자의 지적이다.
- 2안 발상: 날짜와 값을 쌍으로 지정해 두고 그 안에서 날짜를 맞추는 방식이다.
- 2안 구현: 적용 날짜를 내림차순으로 나열한 튜플 목록을 만들고 순회하며 적용일이 기준일 이하인 첫 항목의 값을 반환한다. 마지막 항목은 date.min과 None으로 채워 기본값 역할을 맡긴다.
- 2안의 이득: 매핑이 로직이 아니라 값으로 빠져 구간 변경이 데이터 수정으로 끝난다.
- 2안의 아쉬움: 내림차순 입력을 전제해야 해서 일반적인 오름차순 감각과 어긋나고 조회 함수 자체도 한 번 더 읽어야 이해된다.
- 3안 도입: 동료의 제안으로 RangeKeyDict를 쓰게 됐다. 값의 범위를 사전의 키로 사용할 수 있는 매핑 컬렉션이다.
- 설치와 사용: pip로 range-key-dict을 설치하고 시작과 끝 튜플을 키로 하는 사전 형태로 매핑을 정의한다.
- 3안 구현: date.min부터 2020년 1월 1일까지는 None, 이후 구간마다 각 적립률을 두고 마지막 구간의 끝을 date.max로 열어둔다. 조회 함수는 사전에 기준일을 넣는 것이 전부다.
- 검증: 세 구현 모두 같은 assert 묶음을 통과한다. 경계일 당일이 새 값에 해당하는지까지 확인한다.
- 적용 범위: 날짜에 한정되지 않으며 어떤 범위에 값을 매핑하는 상황이면 쓸 수 있다.
- 가독성 관점: 세 구현 모두 같은 정책을 표현하지만 3안은 어느 구간에 어떤 값이 붙는지가 선언부에서 그대로 읽혀 코드를 따라 실행해볼 필요가 없다.
- 경계 처리: 1안은 미만 비교를 쌓아 올리고 2안은 이하 비교로 뒤집는데, 3안은 시작과 끝을 명시하므로 경계가 어느 쪽에 속하는지 헷갈릴 여지가 줄어든다.
왜 읽나정책 변경 이력을 if 사다리로 관리하고 있다면 범위를 키로 쓰는 사전으로 옮기는 작은 리팩터링 패턴을 바로 적용할 수 있다.