출금 수수료 정책에 기본 수수료 0.0005 BTC 를 입력했는데 다른 화면에서는 0 으로 표시된다는 문의가 왔다.
Table of contents
Open Table of contents
증상 — 0.0005 와 0 이 함께 있는 상태
DB 부터 봤다.
SELECT base_fee, fee FROM fee_policies WHERE id = ?;
base_fee 0.00050000
fee 0.00000000
base_fee 와 fee 둘 다 값이 있는데 서로 다르다.
각 화면이 무엇을 표시하는지 확인하니 보는 컬럼이 갈려 있었다.
정책 목록 base_fee → 0.0005
회원 상세 fee → 0
같은 fee_policies 행인데 화면마다 다른 컬럼을 읽고 있었다.
원인 — 값을 대입하는 계산 게터
생성 화면의 코드를 열었다.
get withdrawFee() {
if (this.policy.feePolicy === 'waived') {
this.policy.fee = 0; // ← 대입
} else {
this.policy.fee = this.policy.baseFee; // ← 대입
}
return this.policy.fee;
}
withdrawFee 는 계산해서 돌려주는 자리인데 this.policy.fee 에 값을 쓰고 있었다.
get 은 읽어서 계산한 값을 돌려주는 자리이고 읽는 쪽은 아무것도 안 바뀔 것으로 기대한다. 여기서는 읽는 동작이 다른 속성을 바꾸는 부수효과를 갖고 있었다.
전체 흐름 — 0이 저장된 경위
순서를 늘어놓으니 앞뒤가 맞았다.
운영자가 기본 수수료 0.0005 입력
운영자가 유형을 '면제'로 선택
↓
게터가 호출되며 fee = 0 대입
↓
저장
↓
base_fee = 0.0005
fee = 0
입력한 0.0005 는 base_fee 에 남고 fee 는 0 이 된다.
화면을 그리는 동안 withdrawFee 가 불리고 그때 대입이 일어난다. 사용자가 한 조작은 feePolicy 선택 하나인데 그것이 저장될 값까지 바꿔 놓은 것이다.
판단 기준 — 버그가 아니었다
두 컬럼의 뜻이 서로 달랐다.
기본 수수료 → 등급 할인율과 면제 한도를 계산하는 기준
청구액 → 실제로 회원 잔액에서 떼는 금액
feePolicy 가 waived 이면 fee 가 0 인 것이 맞다.
입력한 0.0005 는 사라진 것이 아니라 base_fee 에 다른 용도로 남아 있었다. 그러면 고칠 것은 계산이 아니라 그 사실이 화면에 안 보인다는 쪽이 된다.
대응 — 안내와 표시 추가
문의에는 안내로 답했다.
수수료 유형이 면제이므로 실제 청구 수수료가 0 으로 표시되는 것이 정상입니다.
입력하신 기본 수수료 0.0005 BTC 는 등급 할인과 면제 한도 계산의 기준으로만 사용됩니다.
정상입니다만 적으면 납득이 안 되므로 어디에 쓰이는지를 붙였다.
개선 대상도 함께 적었는데 로직을 고치는 것 대신 표시를 늘리는 쪽을 골랐다. 이유는 값은 설계대로인데 그 값이 어디 쓰이는지가 화면에 없어서다.
[표시 전] 청구액 0
[표시 후] 청구액 0 (기본 0.0005 · 면제 등급)
같은 데이터인데 base_fee 를 함께 보여 주면 오해가 없어진다.
회원 상세 모달에 기본 수수료 항목이 안 나오는 것이 혼란의 원인이었다. 화면이 가진 값을 다 보여 주지 않으면 사용자는 없어진 것으로 읽는다.
교훈 — 읽기와 쓰기를 나눈다
게터의 부수효과는 따로 적어 두었다.
get displayFee() {
return this.policy.feePolicy === 'waived' ? 0 : this.policy.baseFee;
}
// 저장 직전에 별도로
prepareForSave() {
this.policy.fee = this.displayFee;
}
displayFee 는 계산만 하고 대입은 prepareForSave 에서 한 번만 한다.
화면에 보여 줄 값과 저장할 값을 한 곳에서 처리하니 편했을 것이다. 대신 값을 확인했더니 값이 바뀌는 상태가 되어 디버깅할 때 짚기가 어려웠다.
조사 방법 자체는 단순했다. 저장 쪽과 표시 쪽을 각각 따라간 것이고 표시 쪽만 봤으면 왜 0인지에서 막혔을 것이다.
정리
- 비슷한 이름의 두 컬럼이 다른 뜻일 수 있다
- 화면마다 다른 컬럼을 표시할 수 있다
- 계산 게터가 상태를 바꾸는 부수효과를 가질 수 있다
- 읽는 쪽은 아무것도 안 바뀔 것으로 기대한다
- 그래서 입력한 값이 다른 컬럼에만 남는다
- 설계상 의도면 버그가 아니라 표시 문제다
- 정상이라는 답에 어디에 쓰이는지를 붙인다
- 개선이 로직이 아니라 표시 추가일 수 있다
- 읽기와 쓰기를 나누고 게터는 읽기만 한다
- 저장 쪽과 표시 쪽을 각각 따라간다