Skip to content
isdnetworks
Go back

분류 값마다 뜻을 적어 두었다

주문 유형 컬럼에 값이 열두 가지 있었다. 무엇이 무엇인지 아는 사람이 없었다.

Table of contents

Open Table of contents

상황 — 값만 있고 뜻이 없었다

어떤 값이 얼마나 있는지부터 셌다.

SELECT order_type, COUNT(*) FROM orders GROUP BY order_type;
01   842,110
02   412,004
03    88,220
05    41,882
07    12,004
09     8,220
11     4,100
...

04060810 은 아예 없었다. 있다가 없어진 것인지 처음부터 없었는지 모른다.

코드를 뒤져도 사정은 같았다.

if ($order->order_type === '01') { ... }
elseif ($order->order_type === '03') { ... }

숫자만 있고 이름이 없어서 이 분기가 무엇을 가르는지 읽히지 않는다.

이 상태에서 조건을 짜려면 값을 넣어 보고 결과로 짐작해야 한다. 사람마다 다르게 짐작하고 그 짐작이 맞는지 확인할 자리도 없었다.

order_type 을 조건에 넣은 화면이 이미 여럿 있었는데 그것들이 같은 뜻으로 넣은 것인지도 알 수 없었다. 값의 뜻을 모르면 기존 코드가 맞는지조차 판정할 근거가 없다.

하나씩 추적했다

각 값이 어디서 들어가는지부터 찾았다.

$ grep -rn "order_type.*=.*'0[0-9]'\|order_type.*=.*\"[01][0-9]\"" --include=*.php src/

넣는 자리의 앞뒤를 읽으면 어떤 상황에서 그 값이 되는지가 나온다.

01  일반 주문
02  정기 배송
03  선물 주문
05  대량 구매
07  기업 구매
09  체험단
11  임직원 구매

04060810 은 넣는 자리가 아예 없었다.

두 자리 코드에 홀수만 쓴 이유는 아무도 몰랐다. 나중에 사이에 끼워 넣으려고 그랬을 수도 있지만 그 의도를 적어 둔 곳은 없었다.

문맥으로 확정된 것과 짐작으로 채운 것은 따로 표시해 뒀다. 나중에 보는 사람이 둘을 같은 무게로 읽으면 안 된다.

grep 에 안 잡힌 값은 넣는 코드가 지워졌다는 뜻이어서 옛 자료를 직접 열어 봤다. 그 시기의 주문이 어떤 성격이었는지로 짐작하는 것 말고 다른 방법이 없었다.

조치 — 상수와 코드 표

먼저 코드 쪽에 상수를 만들었다.

final class OrderType {
    public const NORMAL       = '01';
    public const SUBSCRIPTION = '02';
    public const GIFT         = '03';
    public const BULK         = '05';
    public const CORPORATE    = '07';
    public const TRIAL        = '09';
    public const EMPLOYEE     = '11';

    public const LABELS = [
        self::NORMAL => '일반 주문',
        ...
    ];
}

OrderType::GIFT 로 쓰면 그 분기가 선물 주문을 가른다는 것이 읽힌다.

상수만 두면 화면이나 조회에서 쓸 수 없어서 표로도 만들었다.

CREATE TABLE code_order_type (
  code       char(2)     NOT NULL,
  name       varchar(50) NOT NULL,
  use_yn     char(1)     NOT NULL DEFAULT 'Y',
  sort_no    int         NOT NULL,
  memo       varchar(200),
  PRIMARY KEY (code)
);

memo 를 둔 것은 그 값이 왜 있는지를 적기 위해서였다.

INSERT INTO code_order_type VALUES
('01','일반 주문','Y',1,''),
('09','체험단','N',6,'2016년 종료. 옛 데이터에만 있음');

안 쓰는 값도 지우지 않고 use_ynN 으로 뒀다. 옛 자료에 그 값이 남아 있으므로 이름은 계속 필요하다.

지우면 그 값이 다시 뜻을 모르는 숫자가 되고 이번 일을 다음 사람이 되풀이한다. 쓰지 않는 것과 없는 것을 use_yn 한 칸으로 갈라 두는 편이 쌌다.

화면에서 쓰게 했다

선택 상자와 목록은 필요한 값이 서로 달랐다.

$types = $this->code->get('order_type');   // use_yn='Y' 인 것만

새로 고를 수 있는 것은 지금 쓰는 값뿐이고 지나간 주문을 보여줄 때는 전부 필요하다.

$label = $this->code->label('order_type', $row['order_type']) ?? $row['order_type'];

표에 없는 값이 오면 코드를 그대로 보여 준다. 이름이 없다고 빈칸을 내보내면 무슨 값인지조차 모르게 된다.

그리고 그런 값이 나오면 기록에 남겼다.

if (!$label) { log_message('error', "정의 없는 order_type: {$row['order_type']}"); }

새 값이 생겼다는 것을 이 줄로 알게 된다. 화면은 안 깨지고 사람은 알게 되는 쪽으로 갈랐다.

label 이 비었을 때 예외를 던지는 안도 검토했지만 목록 한 줄 때문에 화면 전체가 안 뜨는 것은 과했다. 보여줄 수는 있는데 이름만 없는 상태라 화면을 멈출 이유가 없었다.

검증 — 상수와 표의 대조

같은 것이 두 곳에 있으니 한쪽만 고치면 어긋난다.

public function testCodeTableMatchesConstants(): void {
    $inDb = $this->db->column("SELECT code FROM code_order_type");
    $inCode = array_keys(OrderType::LABELS);
    $this->assertEmpty(array_diff($inDb, $inCode), 'DB 에만 있는 코드');
    $this->assertEmpty(array_diff($inCode, $inDb), '상수에만 있는 코드');
}

array_diff 를 양방향으로 둔 것은 어느 쪽에만 있는지까지 갈리게 하려는 것이었다.

한쪽에만 넣고 잊는 것이 이런 구조에서 가장 흔한 실수다. 그 실수가 자료에 값으로 남기 전에 여기서 걸린다.

표 쪽에만 넣으면 화면에는 나오는데 코드가 그 값을 모르고 상수에만 넣으면 반대가 된다. 어긋난 방향에 따라 증상이 달라서 array_diff 를 양쪽으로 둔 것이 실제로 도움이 됐다.

설정 — 새 값을 넣는 순서

새 유형이 생길 때의 순서를 정해 뒀다.

1. 코드 표에 넣는다 (뜻과 메모 포함)
2. 상수에 넣는다
3. 쓰는 코드를 넣는다

표를 먼저 두게 한 것은 뜻을 정하지 않고 값부터 쓰는 것을 막기 위해서였다.

무엇에 쓸 값인지 한 줄로 못 적으면 아직 넣을 때가 아니다. 순서를 반대로 하면 이번에 겪은 상황이 얼마 뒤에 그대로 되풀이된다.

같은 문제가 다른 컬럼에도 있는지 스키마에서 훑었다.

SELECT table_name, column_name FROM information_schema.columns
WHERE table_schema = 'order' AND (column_type LIKE 'char(2)%' OR column_type LIKE 'char(1)%');

스물세 개가 나왔고 그중 뜻이 어디에도 안 적힌 것이 열넷이었다.

한 번에 다 하지 못해서 코드에서 참조 횟수가 많은 것부터 손댔다. 어디까지 했는지를 목록에 표시해 뒀는데 그 표시가 없으면 다음 사람이 처음부터 다시 센다.

열넷 중 넷을 이번에 정리했고 나머지는 char(1) 짜리 여부 표시가 많아 급하지 않았다. 급한 것과 아닌 것을 목록에서 갈라 두면 남은 일이 얼마인지가 그 자리에서 보인다.

정리


Share this post on:

Previous Post
카테고리마다 다른 필수 항목
Next Post
열 번 중 하나가 튄 표