기간 비교를 새 메뉴로 만들지 않았더니, 기존점 정의 한 줄이 몸통이었습니다
요구는 한 줄이었습니다. 올해 명절 기간 매출을 작년 명절 기간과 비교하고 싶다는 것이었습니다. 그리고 명절 전에 필요했습니다.
프랜차이즈 ERP 의 전기 비교 화면은 기준일 하나를 받아 당월누적과 연누적을 만들고, 전년 같은 날짜와 맞대는 구조였습니다. 이 구조로는 저 한 줄에 답할 수 없습니다. 명절은 해마다 날짜가 바뀌기 때문입니다.
해마다 날짜가 바뀌는 기간은 같은 날짜로 잴 수 없습니다
올해 명절이 9월 말이고 작년 명절이 10월 초라고 하겠습니다. 같은 날짜끼리 대는 순간 한쪽은 명절이고 다른 쪽은 평일입니다. 숫자는 나오지만 그 숫자가 답하는 질문이 요청받은 질문과 다릅니다.
이런 요구가 들어오면 손이 먼저 가는 곳은 새 메뉴입니다. 기간 비교라는 화면을 하나 새로 만들면 기존 화면을 건드리지 않아도 되고, 회귀 위험도 없습니다. 실제로 그쪽이 더 싸 보였습니다.
새 메뉴가 복제하는 것은 화면이 아니라 정의입니다
그 화면에는 표만 있는 것이 아닙니다. 모집단 정의(기존점이 무엇인가), 제외 매장 규칙, 매장 팝업, 매입 표, 엑셀 내려받기가 한 벌로 붙어 있습니다. 메뉴를 하나 더 만든다는 것은 이 한 벌을 통째로 복제한다는 뜻입니다.
복제된 정의는 처음에는 같습니다. 문제는 한쪽만 고쳐지는 날이 반드시 온다는 점입니다. 그때부터 두 화면이 같은 질문에 다른 숫자를 답합니다. 그리고 그 어긋남은 장애로 나타나지 않습니다. 그냥 숫자가 다를 뿐이라 한참 뒤에 사람이 발견합니다.
(A) 새 메뉴를 만든다 | (B) 기존 화면에 축을 더한다 | |
|---|---|---|
기존 화면 위험 | 없음 | 있음 — 기존 모드가 망가질 수 있음 |
모집단 정의 | 두 벌 | 한 벌 |
표·팝업·매입·엑셀 | 두 벌 | 전부 재사용 |
비용이 드는 시점 | 매년, 정의가 갈릴 때마다 | 한 번, 만들 때 |
B 로 갔습니다. 늘어난 회귀 위험은 배포 전후 대조로 한 번 갚으면 되지만, 정의를 두 벌로 두는 비용은 매년 들기 때문입니다.

검색 카드에 비교 방식이라는 축 하나만 더했습니다
기존 화면의 검색 카드에 비교 방식 선택을 붙였습니다. '기준일(현행)' 과 '기간 직접 지정' 두 가지입니다. 기본값은 기존 동작 그대로입니다.
'기간 직접 지정' 을 고르면 올해 기간을 받고, 비교 기간은 작년 같은 날짜로 자동으로 채워진 뒤 사용자가 수정할 수 있습니다
표는 당월·연 두 묶음 대신 '지정 기간' 한 묶음(매출·전년비·일평균)으로 바뀝니다
표·매장 팝업·제외 매장 규칙·매입 표·엑셀은 전부 그대로 재사용됩니다
조회 조건은 주소에 담습니다. 링크로 주고받아도, 새로고침해도 같은 결과가 나옵니다
같은 화면 컴포넌트를 쓰는 다른 모집단용 화면에도 이 기능이 함께 생겼습니다. 한 벌만 고쳤는데 두 화면이 같이 움직였습니다. 이것이 B 를 고른 쪽의 덤입니다.
백엔드에서도 한 번 더 접었습니다. API 넷과 팝업 엑셀 넷에 선택 파라미터 네 개(올해 시작·끝, 비교 시작·끝)를 더하되, 지정 기간을 위한 새 조회 경로는 만들지 않았습니다. 지정 기간을 기존의 당월·연 버킷에 같이 실어 보냈습니다. 그래서 조회 SQL 은 한 줄도 바뀌지 않았고, 화면에서는 표 라벨만 '지정 기간' 으로 바뀝니다.
새로 만들지 않기로 한 판단이 결과적으로 바꾼 코드를 0줄로 만든 사례를 따로 정리한 적이 있습니다. 같은 축의 이야기입니다.
진짜 몸통은 화면이 아니라 기존점 정의였습니다
이 작업에서 가장 손이 많이 간 곳은 UI 가 아니었습니다. 기존점 정의였습니다.
기존점 정의는 '전년 1월 1일 이전 개점' 처럼 연 기준으로 박혀 있었습니다. 축을 기간으로 늘려 놓고 정의를 그대로 두면 화면은 열리는데 답이 틀립니다. 임의 기간을 물었는데 모집단은 여전히 연 단위로 잘리기 때문입니다.
그래서 정의를 기간 기준으로 다시 썼습니다. '비교 기간 시작 전에 개점했고, 올해 기간 끝까지 폐점하지 않은 매장' 입니다. 축을 늘릴 때는 그 축으로 정의도 다시 써야 한다는 것이 이 작업의 핵심이었습니다.
두 구간을 받는 기능이므로 구간의 관계 검증도 기능의 일부로 넣었습니다. 비교 기간이 올해 기간보다 앞서야 하고, 둘이 겹치면 안 되며, 각각 366일 이내여야 합니다. 어기면 400 을 돌려줍니다. 이걸 막지 않으면 답이 안 나오는 것이 아니라 말이 안 되는 답이 나옵니다.

값이 달라졌을 때 코드 탓인지 원천 탓인지 가르는 방법
기존 화면을 건드렸으니 기존 모드가 그대로인지부터 확인해야 했습니다. 기준일 3개 × 매출·매입 × 모집단 2종, 모두 12건을 배포 전후로 맞댔습니다.
두 기준일은 완전히 동일했습니다. 한 기준일에서만 올해 칸 금액이 늘었습니다. 여기서 멈추면 배포로 숫자가 틀어졌다고 읽게 됩니다.
어느 칸이 같이 움직였는지를 봤습니다. 작년 칸과 영업일수는 그대로였고, 당월과 연이 같은 폭으로 움직였습니다. 계산 코드가 바뀌었다면 작년 칸도 같이 흔들렸을 겁니다. 현재 칸만, 그것도 같은 폭으로 움직였다는 것은 원천 데이터가 그 사이에 재수집됐다는 뜻입니다.
새 모드 자체는 교차 검증으로 봤습니다. '기간 직접 지정' 으로 올해 초부터 기준일까지를 조회한 결과와, 같은 기준일의 당월누적을 맞댔습니다. 양쪽에 다 있는 매장의 금액 불일치는 0건이었습니다. 모집단·매입 세 조합 모두 같았습니다.
매장 수의 차이는 남았습니다. 다만 그 차이가 모집단 정의 차이로 전부 설명됐습니다. 한쪽은 작년에 개점한 매장이 새로 들어와 늘었고, 다른 쪽은 올해에만 매출이 있던 매장이 비교 기간에서 빠져 줄었습니다. 방향까지 설명되면 그건 불일치가 아닙니다. 설명하지 못하는 차이만 결함입니다.

명절 프리셋 버튼은 넣지 않았습니다
명절 날짜를 눌러 한 번에 채우는 프리셋 버튼을 넣자는 이야기가 있었습니다. 기능이 어려워서가 아니라 유지가 사람 손에 걸려서 접었습니다.
프리셋 버튼 하나는 곧 매년 연휴 날짜표를 누가 채우는가라는 상시 업무입니다. 그 표의 관리자가 정해지지 않으면 버튼은 언젠가 작년 날짜를 그대로 들고 있게 됩니다. 사용자는 버튼을 믿고 누릅니다. 자주 쓰이는 것이 확인되면 그때 붙이기로 하고, 날짜표 관리자를 먼저 정하는 것을 조건으로 달았습니다.
남긴 빚도 하나 있습니다
기존 버킷에 다른 뜻의 값을 실어 보낸 것은 빚입니다. 당월 칸에 임의 기간이 들어 있으니 표 라벨을 바꿔야 성립하고, 다음 사람이 그 칸 이름을 그대로 믿으면 틀립니다. 라벨과 실제가 갈리는 자리를 하나 만든 셈입니다.
그리고 이번 결정은 정의가 조회 코드 안에 들어 있는 시스템의 결론입니다. 정의를 한 곳에서 내려 쓰는 구조가 이미 있다면 메뉴를 갈라도 갈리지 않습니다. 축을 더하는 방식도 무한하지 않습니다. 조건이 조합으로 늘면 이 조합에서만 안 되는 결함이 생기고, 검색 카드 자체가 설명을 요구하게 됩니다. 어느 선을 넘으면 메뉴를 가르는 쪽이 맞습니다.
정의가 여러 벌로 갈렸을 때 어떤 모습으로 나타나는지는 상태값을 다룬 글에 더 자세히 적어 뒀습니다.
새 비교 요구를 받았을 때 점검할 것
새로 만들려는 화면이 복제하는 것이 코드인지 정의인지 먼저 가릅니다. 정의를 복제하면 두 벌이 됩니다
복제될 정의가 한 곳에서 내려오는 구조인지 확인합니다. 그렇다면 메뉴를 갈라도 됩니다
축을 늘릴 때 모집단 정의가 옛 축(연·월)에 박혀 있는지 봅니다. 박혀 있으면 정의부터 다시 씁니다
새 입력을 기존 계산 경로에 실어 보낼 수 있는지 봅니다. 가능하면 계산부를 건드리지 않습니다
두 구간을 받는다면 순서·겹침·길이 상한 검증을 기능의 일부로 넣습니다
프리셋·표 같은 편의 기능은 매년 누가 갱신하는지가 정해진 뒤에 붙입니다
마무리
이 작업에서 남은 흔적의 절반은 만들지 않은 것입니다. 새 메뉴가 없고, 두 번째 기존점 정의가 없고, 새 조회 SQL 이 없고, 명절 프리셋 버튼이 없습니다.
만든 것은 얇습니다. 검색 카드의 토글 하나, 선택 파라미터 네 개, 기간 기준으로 다시 쓴 기존점 정의 한 줄, 그리고 두 기간의 관계를 보는 검증입니다. 화면에 축을 더하는 일은 UI 작업처럼 보이지만, 기존점이 무엇인가가 연 단위로 박혀 있는 한 기간으로는 물을 수 없습니다.
새 메뉴가 싸 보이는 이유는 복제되는 것이 코드가 아니라 정의여서 눈에 보이지 않기 때문입니다. 눈에 보이지 않는 것은 청구서도 나중에 옵니다.