여러 판매처에서 주문을 받아 오는 기능을 늘리고 있었다. 세 번째 판매처를 붙이는데 코드가 엉키기 시작했다.
Table of contents
Open Table of contents
조건문이 계속 늘었다
받아 온 자료를 처리하는 함수 안에서 판매처를 가르고 있었다.
foreach ($rows as $r) {
if ($channel === 'A') {
$orderNo = $r['ORD_NO'];
$amount = (int)$r['AMT'];
} else if ($channel === 'B') {
$orderNo = $r['order_id'];
$amount = (int)str_replace(',', '', $r['total_price']);
} else if ($channel === 'C') {
$orderNo = $r['no'];
$amount = (int)($r['price'] * $r['qty']);
}
// 이하 공통 처리
}
판매처가 하나 늘 때마다 이 함수가 길어지고 공통 처리를 고치면 셋 다 영향을 받는다. 붙이는 비용이 판매처 수에 따라 커지는 구조였다.
무엇이 다른지 목록으로 만들었다
나누기 전에 무엇이 다른지부터 적었다.
| 항목 | A | B | C |
|---|---|---|---|
| 주문번호 필드 | ORD_NO | order_id | no |
| 날짜 형식 | YYYYMMDD | ISO | DD/MM/YYYY |
| 금액 표기 | 정수 | 쉼표 포함 문자열 | 단가 × 수량 |
| 문자셋 | EUC-KR | UTF-8 | UTF-8 |
| 파일 형식 | 고정폭 | CSV | XML |
다섯 가지가 다르고 나머지는 같았다. 다른 것을 목록으로 만들고 나니 어디를 나누고 어디를 공통으로 둘지가 보였다.
읽는 부분만 나눴다
판매처별로 클래스를 만들고 공통 형태로 바꿔 돌려주게 했다.
class OrderReaderA implements OrderReader {
public function read($filePath) {
$rows = $this->parseFixedWidth($filePath);
$out = [];
foreach ($rows as $r) {
$out[] = [
'order_no' => trim($r['ORD_NO']),
'order_date' => date('Y-m-d', strtotime($r['ORD_DT'])),
'amount' => (int)$r['AMT'],
];
}
return $out;
}
}
부르는 쪽은 판매처를 모른다. 읽어 온 뒤로는 전부 같은 처리를 지난다. 네 번째 판매처를 붙일 때 클래스 하나만 만들었고 공통 처리는 안 건드렸다.
문자셋 변환도 각 클래스 안에서 하게 했다. EUC-KR 로 오는 것이 하나뿐인데 공통 처리에서 변환하면 이미 UTF-8 인 것까지 변환돼 깨진다. 변환은 그 형식이 들어오는 자리에서 한다.
변환할 때 무엇으로 읽을지도 걸렸다. 처음에는 mb_convert_encoding($s, 'UTF-8', 'EUC-KR') 로 했는데 상품명에 「똠」 같은 글자가 들어오면 그 자리가 비었다. 확장 한글이 EUC-KR 표에 없어서다.
CP949 로 읽으니 그대로 왕복했다. 한글 자료는 EUC-KR 이라고 적혀 와도 실제로는 CP949 인 경우가 많아 그쪽으로 맞췄다.
공통 형태의 정의와 표본
무엇이 공통 형태인지 적어 두지 않으면 각 클래스가 조금씩 다른 것을 돌려준다.
/**
* read() 반환 형태
* order_no string 판매처 주문번호 (공백 제거)
* order_date string YYYY-MM-DD
* amount int 원 단위, 부가세 포함
* items array [{product_code, qty, price}, ...]
*/
실제로 한 번 어긋났다. 한 클래스가 amount 를 원이 아니라 십원 단위로 돌려주고 있었다. 그래서 read() 가 준 배열을 검사하는 코드를 넣어 읽는 클래스가 잘못 만들면 그 자리에서 걸리게 했다.
각 판매처의 실제 파일도 하나씩 남겨 뒀다. 코드를 고친 뒤 셋을 다 돌려 read() 결과가 같은지 본다. 이게 없으면 한 판매처를 고치다 다른 것을 깨뜨려도 모른다.
정리
- 대상마다 규격이 다르면 무엇이 다른지 목록으로 만든다
- 다른 것을 아는 만큼만 나누고 나머지는 공통으로 둔다
OrderReader를 대상별로 나누고 공통 형태로 바꿔 돌려준다read()반환 형태를 문서로 정의하고 어긋나면 그 자리에서 걸리게 한다- 문자셋 변환은 그 형식이 들어오는 자리에서 한다
- 공통에서 변환하면 이미 맞는 것까지 건드린다
- EUC-KR 로 읽으면 확장 한글이 사라진다 —
CP949로 읽는다 - 대상별 표본 파일을 남겨 하나를 고치다 다른 것을 깨뜨리는 것을 잡는다