배송비 계산에 조건이 계속 붙었다.
if ($total >= 50000) { $fee = 0; }
else if ($isIsland) { $fee = 5000; }
else if ($category == '대형') { $fee = 8000; }
else { $fee = 2500; }
볼 때마다 조건이 늘어 있었다.
Table of contents
Open Table of contents
원인 — 순서에 따라 결과가 달라졌다
if 를 위에서부터 검사하니 순서가 결과를 정했다. 무료 조건을 먼저 보면 섬이어도 무료가 되고 섬 조건을 먼저 보면 금액과 무관하게 5,000원이 붙는다.
$total 을 먼저 보느냐 $isIsland 를 먼저 보느냐로 답이 갈리는데 이건 코드를 짜는 내가 정할 문제가 아니었다. 물어보니 경우마다 달랐다.
섬 + 5만원 이상 → 섬 배송비만
대형 + 5만원 이상 → 대형 배송비만
무료가 조건부였다. 그런 조합이 실제로 생기는 것을 생각해 본 적이 없다고 했는데 정해지지 않은 조합이 코드에는 이미 어떤 결과로 정해져 있었다.
표로 만들었다
말로 정리가 안 돼서 표로 만들었다.
금액 지역 분류 배송비
5만 미만 일반 일반 2,500
5만 이상 일반 일반 0
5만 미만 섬 일반 5,000
5만 이상 섬 일반 5,000
...
PHP 코드에서는 있는 조건만 보이는데 표를 만드니 안 정해진 조합이 보였다.
5만 이상 + 섬 + 대형 → ?
물어보니 정해져 있지 않았다. 코드에서는 있는 조건만 보이는데 표에서는 빠진 조합이 보인다.
표를 들고 다시 물어보니 이번에는 답이 나왔다. 어떤 조합인지 눈앞에 있으니 판단할 수 있었고 조합이 열몇 개였다. 표가 무엇을 물어야 할지 알려 준 셈이었다.
자료로 뺀 규칙
표가 채워지고 나니 코드가 아니라 자료로 둘 수 있었다.
shipping_rule
min_amount
region_type
category_type
fee
priority
MySQL 에 조건과 결과를 행으로 담고 코드는 SELECT 로 찾아 쓰기만 한다.
$rules = $model->get_rules_ordered();
foreach ($rules as $r) {
if (matches($r, $order)) { return $r->fee; }
}
priority 순으로 읽어 먼저 맞는 것을 쓴다. 순서를 코드 수정과 배포 없이 값만 바꿔 조정할 수 있게 된 것이다.
아무것도 안 맞을 때를 위해 기본 규칙도 하나 뒀다. 우선순위가 가장 낮고 조건이 없는 행이라 항상 하나는 맞는다. 이게 없으면 조합이 늘었을 때 아무것도 안 맞아 오류가 난다.
어느 규칙이 적용됐는지 남겼다
계산 결과에 어느 규칙인지도 남겼다.
order
shipping_fee
shipping_rule_id
배송비가 이상하다는 문의가 오면 shipping_rule_id 로 어느 규칙이 걸렸는지 바로 본다. 안 남기면 다시 계산해 봐야 하는데 그 사이 규칙이 바뀌었으면 그때 값이 안 나온다.
규칙 자체도 고칠 때 이력을 남겼다.
shipping_rule_history
shipping_rule_history 에 옛 규칙이 남으니 지난 주문의 계산 근거를 찾을 수 있다.
자료로 빼고 나니 AdminLTE 로 관리 화면도 만들 수 있었다. 개발자가 코드를 고치던 것을 운영이 값으로 고치게 된 것이다. 다만 아무 값이나 들어가면 안 돼서 저장할 때 priority 가 같은 규칙이 겹치는지 검사했다.
금액과 지역과 분류를 넣으면 어느 규칙이 맞고 얼마인지 보여 주는 시험 기능도 넣었다. 저장 전에 확인할 수 있으니 틀린 규칙이 운영에 나가는 것이 줄었고 이것이 가장 많이 쓰였다.
정리
- 조건이 계속 붙으면 순서에 따라 결과가 달라진다
- 정해지지 않은 조합도 코드에는 이미 정해져 있다
- 말로 안 되면 표로 만든다
- 표를 만들면 안 정해진 조합이 보인다
- 표가 무엇을 물어야 할지 알려 준다
- 표가 나오면
shipping_rule같은 자료로 뺄 수 있다 - 순서는
priority컬럼으로 두고 기본 규칙을 하나 둔다 - 계산에 어느 규칙이 적용됐는지
shipping_rule_id로 남긴다 - 규칙 자체도 이력을 남기고 저장 전 시험 기능을 둔다