sku 문자열을 저장할 때는 정리해서 넣고 찾을 때는 정리하지 않고 찾고 있었다. 저장 시점에 strtoupper() 와 trim() 을 거치는데 조회는 입력 그대로 하니 방금 넣은 값도 못 찾았다.
Table of contents
Open Table of contents
해결 — 한 함수로 묶은 양쪽
두 가지 방법이 있었는데 조회 쪽에도 같은 strtoupper() 와 trim() 을 적어 넣는 것과 SkuNormalizer::normalize() 하나를 만들어 저장과 조회 양쪽에서 부르게 하는 것이다. 뒤쪽을 골랐다. 이유는 규칙이 바뀌더라도 양쪽이 언제나 같이 바뀌어서다. 대신 한 줄짜리 처리를 위해 클래스 하나가 늘어나는 것을 감수했다.
양쪽에 같은 함수를 쓰는 것이 대칭을 지키는 방법이었다. 각자 같은 처리를 적어 두면 한쪽만 고쳐지는 것은 시간문제다.
검증 — 다른 대칭 쌍의 점검
같은 성격의 쌍이 더 있는지를 저장소 전체에서 훑었다. 암호화와 복호화와 인코딩과 디코딩과 직렬화와 역직렬화와 압축과 해제가 나왔다.
각각 양쪽이 맞는지 확인하다가 인코딩에서 하나가 걸렸다. 한 자리에서 json_encode() 에 JSON_NUMERIC_CHECK 를 쓰고 있어서 PHP 매뉴얼대로 숫자처럼 보이는 문자열이 숫자로 인코딩돼 앞자리 0 이 사라졌고 받는 쪽은 문자열을 기대하고 있었다.
왕복 시험과 경계 값
넣은 것을 다시 꺼내면 같은 값이 나오는지를 assertSame() 으로 확인하는 시험을 만들었다. 정규화와 암호화 양쪽에 같은 형태의 시험을 넣었다.
경계 값도 함께 넣었는데 빈 문자열과 아주 긴 것과 특수문자와 여러 언어를 섞어서 돌렸다. 그중 빈 문자열에서 하나가 걸렸다.
조치 — 이미 저장된 것의 정리
정규화를 나중에 넣었으므로 그 전에 저장된 것은 규칙에 안 맞을 수 있었다. WHERE sku != UPPER(TRIM(sku)) 로 세어 보니 수백 건이 그런 상태였다.
일괄로 맞추기 전에 정규화하면 서로 같아지는 것이 생기는지를 GROUP BY UPPER(TRIM(sku)) HAVING COUNT(*) > 1 로 먼저 확인했고 셋이 겹쳤다. 정규화하면 같아지는 다른 상품이라 따로 처리했다. 맞추는 작업이 유일성을 깨뜨릴 수 있으므로 겹침 확인이 먼저다.
어디까지 정규화할지도 적었다. 앞뒤 공백과 대소문자는 하고 가운데 공백과 하이픈과 특수문자는 안 하는데 가운데 공백을 없애면 다른 코드가 같아질 수 있기 때문이다. 안 적으면 나중에 누가 더 넣는다.
들어오는 자리로 옮긴 정규화
정규화를 양쪽에 넣고도 여전히 안 맞는 자리가 하나 있었다. 비교하는 코드에 한쪽만 정규화된 값이 들어오고 있었고 그것은 외부에서 받은 값이었다.
비교하는 자리마다 양쪽을 정규화하는 대신 값이 들어오는 ProductRequest 에서 한 번 정규화하게 바꿨다. 생성 시점에 정규화하니 그 뒤로는 어디서 비교해도 맞았다. 들어오는 자리에서 한 번 하는 것이 쓰는 자리마다 하는 것보다 확실하다.
비슷한 정규화를 하는 자리가 넷 더 있었고 조금씩 달라서 전부 한 함수를 쓰게 바꿨다. 여러 개면 또 갈린다.
정리
- 넣을 때와 찾을 때 처리가 다르면 값이 안 맞는다
- 양쪽에 같은
normalize()를 쓰면 규칙이 바뀌어도 같이 바뀐다 - 암호화와 인코딩과 직렬화와 압축도 양쪽이 맞는지 본다
- 왕복 시험으로 넣은 것을 꺼내면 같은지 본다
- 경계 값으로 빈 것과 긴 것과 특수문자를 넣는다
- 이미 저장된 것 중 규칙에 안 맞는 것을 찾아 맞춘다
- 맞추면 겹치는 것이 생기는지 먼저 확인한다
- 들어오는 자리에서 한 번 하는 것이 쓰는 자리마다 하는 것보다 확실하다