수정 이력(6건, 최종 수정 2026년 09월 03일)
이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.
- Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- permalink·저자 표기·지식 맵 래퍼·깨진 내부 링크 등 CI가 지적한 표시용 수정을 반영했습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
- 기사 서두에 「이 기사의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
- 외부 리뷰(1283건)에 대응하여 본문을 업데이트했습니다. 개별 변경 내용은 아래 이력을 참조합니다.
- Luhn check digit를 손으로 계산해 따라갈 수 있는 예를 추가하고, 인접 전치와 1자리 오류가 실제로 검출되는지까지 보였습니다. 아울러 자릿수 지침의 표현을 다듬고, modulus와 weight의 읽는 법 주석, Excel 수식과 VBA 구현 예를 추가했습니다.
- 자릿수 가늠에 check digit용 1자리가 빠져 있어 보완했습니다.
- 최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.22174834)
아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.
Go Komura (2026). 「업무 시스템의 코드 설계 ── 상품 코드·고객 코드를 정하는 법과 check digit」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/business-code-design-check-digit/
- DOI(등록된 아카이브)
- 10.5281/zenodo.22174834
- DOI(마지막 등록 버전)
- 10.5281/zenodo.22174835
「새 시스템으로 옮기니 상품 코드 체계를 정해 주세요」──업무 시스템 구축이나 Excel 대장의 이행에서는 반드시 이 숙제가 나옵니다. 그리고 그 자리에서 떠올린 코드는, 평균적으로 시스템 본체보다 오래 갑니다. 시스템은 10년이면 바뀌어도, 거래처에 나눠 준 고객 번호와 과거 전표에 찍힌 상품 코드는 남기 때문입니다.
한편 코드 설계에는 앞선 경험의 축적이 놀랄 만큼 있습니다. JAN 코드나 신용카드 번호, 마이넘버처럼 「대규모로 운용되는 번호」는, 입력 실수 검출 방법부터 자리 할당까지 설계 판단의 이유까지 포함해 공개되어 있습니다. 이 기사에서는 이를 바탕으로, 업무 시스템에서 상품 코드·고객 코드·전표 번호 등을 정할 때의 규칙과, 쓸 수 있는 장치(check digit)를 정리합니다.
1. 먼저 결론
- 코드는 식별에 집중하고, 의미는 속성으로 DB에 둡니다. 부서·분류·연도를 코드 자리에 넣는 「유의미 코드」는 조직 개편·분류 변경에서 반드시 깨집니다. 마이넘버는 의미가 없는 번호로 설계되어 있습니다.1
- 사람이 입력·전사·읽어 주는 코드에는 check digit를 붙입니다. 입력 오류의 대부분은 「1자의 오타」와 「이웃끼리 뒤바꿈」임이 실측으로 알려져 있으며2, check digit는 바로 이 둘을 겨냥해 검출합니다.
- 문자 종류는 운용으로 정합니다. 전화·FAX·손글씨가 끼면 숫자만. 자리를 아끼려면 혼동하기 쉬운 문자(I·L·O 등)를 뺀 영숫자(Crockford Base32 등3).
- 자릿수는 「장래 건수의 10배+1자리」를 가늠으로 확보하고, 선행 0을 쓸 거면 전 시스템에서 문자열로 다룹니다. Excel은 숫자로 해석하는 순간 선행 0을 떨어뜨리고, 16자리 이후를 0으로 바꿉니다.4
- 한 번 쓴 코드는 결번이 되어도 재사용하지 않습니다. 과거 전표·로그·거래처 시스템에 남은 번호가 다른 대상을 가리키게 됩니다.
- 상품에 바코드를 붙여 유통시키려면 자체 코드가 아니라 JAN 코드(GS1 표준)를 탑니다.5
그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 26건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle
2. 코드에 의미를 넣을지 ── 처음이자 가장 큰 갈림길
코드 설계에서 먼저 정할 것은 자릿수도 문자 종류도 아니라, 「코드에 의미를 넣을지」입니다.
흔한 설계는 이렇습니다. 「상품 코드는 8자리. 앞 2자리가 부서, 다음 3자리가 분류, 나머지 3자리가 일련번호」. 정한 순간에는 정돈되어 보이지만, 몇 년이면 이렇게 됩니다.
- 조직 개편으로 부서가 합쳐져 옛 부서 코드를 가진 상품이 붕 뜹니다. 다시 매기면 과거 전표와 대조할 수 없고, 그대로 두면 「코드의 부서와 실제 부서가 다르다」는 예외가 master에 늘어납니다.
- 분류를 가로지르는 상품(식품이기도 하고 잡화이기도 한)이 나타나, 어느 코드를 붙일지의 사내 규칙이 필요해집니다.
- 어떤 분류만 상품이 늘어 일련번호 3자리가 바닥나고, 「이 분류만 예외로 다른 부서의 빈 번호를 빌린다」는 운용이 시작됩니다.
원인은 분명합니다. 조직이나 분류는 바뀌는 것인데, 바뀌지 않는다는 전제로 코드에 박아 넣었기 때문입니다. 그래서 원칙은 그 반대입니다. 코드는 대상을 유일하게 가리키기 위한 기호로만 두고, 부서·분류 같은 속성은 database 열에 둡니다. 속성이면 아무리 바뀌어도 UPDATE 한 번으로 끝나고, 코드는 그대로입니다.
국가 번호 제도도 이 원칙으로 만들어져 있습니다. 마이넘버는 주민표 코드를 「작위가 들어가지 않는 방법」으로 변환해 생성하는, 의미가 없는 11자리+검사용 숫자 1자리입니다.1 의미를 넣지 않는 것은 번호에서 개인의 속성을 추측하지 못하게 하려는 이유이기도 하고, 속성 변경(이사나 개명)으로 번호가 바뀌지 않게 하려는 이유이기도 합니다.
그래도 실무에서는 「앞만 보면 고객인지 매입처인지만은 알았으면 한다」는 요청도 뿌리 깊으므로, 판단표로 정리하면 다음과 같습니다.
| 방식 | 예 | 강점 | 깨지는 지점 | 맞는 장면 |
|---|---|---|---|---|
| 완전 유의미 코드 | 02-104-317(부서-분류-일련번호) |
읽으면 속성이 보인다 | 조직 개편·분류 변경·부분적 자리 고갈로 재부여 | 대상이 늘거나 줄지 않고, 분류가 제도상 고정된 경우만 |
| 무의미 일련번호 | 10000317 |
무엇이 바뀌어도 깨지지 않는다. 채번이 단순하다 | 사람이 코드에서 아무것도 읽지 못한다 | 속성을 화면·전표에 항상 병기할 수 있는 시스템 전제의 운용 |
| 하이브리드 | C-0317-5(종류 1자+일련번호+검사 숫자) |
종류 혼동만은 막을 수 있다. 붕괴 요소가 최소 | 종류 정의 변경(드묾) | 헤매면 이것. 종류는 「고객/매입처/상품」 정도의 거친 구분에 멈춘다 |
권장은 표의 아래 둘입니다. 넣는 의미는 늘릴수록 붕괴의 싹이 는다고 보면 됩니다.
3. 문자 종류와 자릿수 ── 정보 효율과 가독성은 트레이드오프
다음에 정하는 것은 문자 종류와 자릿수입니다. 이는 순수히 산수의 문제로, 한 자리에 쓸 수 있는 문자 종류가 많을수록 같은 자릿수로 더 많은 대상을 나타냅니다.
| 문자 종류 | 6자리로 나타낼 수 있는 수 | 읽어 주기·손글씨 내성 |
|---|---|---|
| 숫자만(10종) | 100만 | ◎ 전화·FAX·손글씨에 강함 |
| 영문 대문자+숫자에서 혼동 문자를 뺀 32종(Crockford Base323) | 약 10.7억 | ○ 오독 대책은 되어 있으나 읽어 주기는 숫자보다 약함 |
| 영문 대문자+숫자(36종) | 약 21.8억 | △ 0과 O, 1과 I의 오독이 반드시 일어남 |
판단 기준은 다음 둘입니다.
- 그 코드를 전화로 읽어 주는가, 손글씨·FAX로 쓰는가. 하나라도 해당하면 숫자만 권합니다. 영문이 섞이는 순간 「아이? 원?」 확인 비용과 오독 위험이 생깁니다.
- 숫자만이면 자릿수가 너무 길어지는가. 대상이 수천만 건을 넘는 등 자릿수를 압축하고 싶을 때 비로소 영숫자를 검토합니다. 그때도 그냥 36종이 아니라,
I·L·O(와 우연히 생기는 비어를 피하기 위한U)를 뺀 Crockford Base32처럼 이미 설계된 알파벳을 써야 합니다. 이 사양은 오독 대책이 철저하고, decode 때는 소문자도 받아들이며,i나l은1,o는0으로 해석합니다.3
자릿수는 「현재 건수」가 아니라 「시스템과 전표가 계속 쓰이는 20〜30년에 도달할 수 있는 건수」에서 역산하고, 거기에 여유를 1자리 더합니다. 도달할 수 있는 건수의 10배를 나타낼 자릿수를 확보한다고 바꿔 말해도 같습니다(10만 건이면 6자리). check digit를 붙이면 그 1자리는 이 여유와는 별도로 셉니다(제4장). 제1장과 제9장에서 「장래 건수의 10배+1자리」라고 적은 것은, 이 「본체=10배분」과 「검사용 숫자 1자리」의 합이라는 뜻입니다. 10만 건을 보면, 본체 6자리+검사용 숫자 1자리로 7자리, 라는 세는 법이 됩니다.
자릿수 초과의 무서움은 제8장에서 다루지만, 우편번호가 5자리에서 7자리로 바뀌었을 때(1998년) 같은 전면 수정을 자사에 일으키지 않기 위한 비용은, 고작 1자리입니다.
또 하나, 눈에 잘 안 띄지만 효과가 있는 것이 구분입니다. 신용카드 번호가 4자리마다 인쇄되듯, 사람은 긴 숫자열을 3〜4자리 덩어리로만 정확히 다룹니다. 8자리 이상 코드를 전표나 화면에 낼 때는 1234-5678처럼 구분해 표시합니다. 다만 구분 기호는 데이터에 넣지 않고, 표시할 때 붙이는 것이 원칙입니다(제6장).
4. check digit ── 입력 실수를 코드 스스로 검출하게 한다
4.1 입력 실수의 정체는 두 종류
check digit(검사용 숫자)는 코드의 끝(또는 앞)에 붙이는 1자리로, 다른 자리에서 계산으로 이끌어 내도록 해 두어, 입력된 코드의 오류를 그 자리에서 검출하는 장치입니다. 마이넘버의 검사용 숫자도, 시행령에 「전자계산기에 입력할 때 오류가 없음을 확인하는 것을 목적으로」 목적이 명시되어 있습니다.1
어떤 계산식이 좋은지는, 사람이 어떤 실수를 하는지로 정해집니다. 네덜란드 우편 지로 시스템 등의 오기 데이터를 분석한 Verhoeff의 고전적 조사에 따르면, 오류의 60〜95%는 1자리만의 오타(single error)이고 이것이 압도적 다수입니다. 이어서 2자리 오류가 10〜20%를 차지하고, 그 대부분은 인접한 2자리, 특히 ab→ba 형 뒤바꿈(전치)입니다. 그 밖의 유형(aa→bb 형이나 한 칸 건너 뒤바꿈 등)은 각각 전체의 0.5〜1.5% 정도에 지나지 않습니다.2
즉 check digit 방식의 성능은 실질적으로 「1자리 오류를 얼마나 검출하는가」와 「인접 전치를 얼마나 검출하는가」 두 점으로 평가할 수 있습니다.
4.2 실제로 쓰이는 방식과 검출 능력(판단표)
표를 읽기 전에 방식 이름의 읽는 법만 정해 둡니다. 「modulus ○○」는 「○○로 나눈 나머지를 쓰는 방식」이라는 뜻입니다(modulus 11이면 11로 나눈 나머지). 「weight」는 각 자리에 곱하는 배수로, 「weight 3-1」이면 오른쪽 끝부터 3배·1배·3배·1배…로 번갈아 곱합니다. 어느 방식이든 하는 일은 「각 자리에 정해진 배수를 곱해 합하고, 정해진 수로 나눈 나머지에서 검사용 1자리를 만든다」뿐입니다.
| 방식 | 쓰이는 곳 | 1자리 오류 | 인접 전치 | 특징 |
|---|---|---|---|---|
| modulus 10 weight 3-1 | JAN 코드 등의 GS1 표준, ISBN-136 | 전부 검출 | 차가 5인 쌍(05↔50, 16↔61 등)을 놓침 |
바코드 운용이면 사실상 이것뿐 |
| Luhn(modulus 10) | 신용카드 번호7 | 전부 검출 | 09↔90 한 쌍만 놓침 |
구현이 가장 간단. 자체 코드의 기본 후보 |
| modulus 11(가중) | 마이넘버8, 구 ISBN | 거의 전부 검출 | 거의 전부 검출 | 검출력은 높으나 「나머지 맞추기」 문제가 있음(후술) |
| modulus 9(가중) | 법인번호9 | 0↔9를 놓침 |
0↔9 쌍을 놓침 |
9로 나누는 형편상 0과 9를 구별하지 못함 |
| Damm / Verhoeff | 학술적 방식 | 전부 검출 | 전부 검출 | 숫자표 lookup(Damm10)이나 군 연산(Verhoeff2)으로 구현 |
몇 가지 보완합니다.
- JAN 방식(modulus 10 weight 3-1)은 오른쪽 끝 자리부터 차례로 3배·1배를 번갈아 곱해 합하고, 「10 − (합을 10으로 나눈 나머지)」의 1의 자리를 check digit로 합니다.6 전치로 합이 바뀌지 않는 것은 3배와 1배의 차×(자리의 차)가 10의 배수가 되는 경우, 즉 두 자리의 차가 딱 5일 때이며, 이 패턴만은 검출하지 못합니다.
- Luhn은 1954년에 IBM의 H. P. Luhn이 특허 출원한 방식으로7, 한 자리 걸러 2배하고, 9를 넘으면 9를 빼 합합니다. 인접 전치의 놓침은
09↔90단 한 쌍입니다. 구현의 단순함과 검출력의 균형이 좋아, 자체 코드에 새로 채택한다면 우선 이것으로 충분합니다. - 마이넘버의 modulus 11은 11이 소수인 덕에 이론상 검출력은 높지만, 나머지가 0 또는 1일 때 check digit를 일률 0으로 하는 접어 넣기가 있어8, 이 두 클래스 사이를 이동하는 오류만은 검출하지 못합니다. 구 ISBN(ISBN-10)은 같은 modulus 11에서 이 문제를 「나머지 10을
X로 쓴다」로 풀었으나, 이번에는 「숫자여야 할 자리에 X가 나타난다」는 운용상의 번거로움을 안았습니다. modulus 11 계열을 채택한다면, 이 「11종류의 나머지를 10종류의 숫자에 어떻게 밀어 넣을까」 문제가 반드시 따라온다는 점은 알아 두어야 합니다. - 법인번호의 modulus 9는 앞에 check digit를 두는 드문 설계이지만11, 9로 나눈 나머지를 쓰므로
0과9가 합동이 되어, 이 둘의 오타·뒤바꿈은 그대로 통과합니다. - Damm이나 Verhoeff 방식은 1자리 오류와 인접 전치 양쪽을 전부 검출합니다. 모든 1자리 오류와 모든 인접 전치를 검출하는 십진 코드를 Verhoeff가 처음 구성했고2, Damm은 준군(quasigroup) 연산표를 한 장 조회해 나가기만 하면 되는, 더 단순한 구성을 주었습니다.10 검출력 최우선이면 이들이지만, 업무 시스템의 입력 실수 대책으로는 Luhn이나 JAN 방식으로 실용상 충분합니다.
이 표가 「1자리 오류」와 「인접 전치」 두 열로 방식을 비교하는 것은, 4.1에서 본 Verhoeff의 조사――네덜란드 우편 지로 시스템 등, 사람이 실제로 전사·입력하던 번호의 오기 기록을 유형별로 집계한 것――에서 이 두 유형이 오류의 대부분을 차지했기 때문입니다.2 거꾸로 말하면, 자사 코드에서 일어나는 오류 경향이 분명히 다르면(예를 들어 손글씨 1과 7의 오독이 대부분) 방식 비교보다 문자 종류나 전표 서식을 고치는 쪽이 효과가 있습니다.
4.3 check digit를 붙여야 할 코드, 필요 없는 코드
판단 기준은 「사람의 손과 눈을 거치는가」입니다. 종이 주문서에서 치는 상품 코드, 전화로 전하는 회원 번호, 현장에서 손으로 쓰는 전표 번호에는 붙일 가치가 있습니다. 반대로 시스템 간 연동으로만 흐르는 내부 ID, 화면에서 선택으로만 입력되는 코드에는 필요 없습니다. 실수의 발생원(사람)이 없으면 검출할 의미도 없기 때문입니다.
5. 구현 예 ── C#과 Excel·VBA
주요 방식은 어느 것이든 몇 줄에서 십수 줄로 구현할 수 있습니다. 코드는 항상 문자열로 받는 점만 주의합니다(이유는 제6장).
C#
먼저 JAN 방식(GTIN-13). 12자리 본체에서 check digit를 계산합니다.6
public static int Gtin13CheckDigit(string body12)
{
if (body12.Length != 12 || !body12.All(char.IsAsciiDigit))
throw new ArgumentException("12자리 숫자를 지정하세요", nameof(body12));
int sum = 0;
for (int i = 0; i < 12; i++)
{
int digit = body12[11 - i] - '0'; // 오른쪽 끝부터 센다
sum += (i % 2 == 0) ? digit * 3 : digit; // 오른쪽 끝이 ×3, 이후 교차
}
return (10 - sum % 10) % 10;
}
다음이 Luhn. 자사 고객 코드·회원 번호에 붙인다면 이 둘로 끝납니다.
public static int LuhnCheckDigit(string body)
{
int sum = 0;
for (int i = 0; i < body.Length; i++)
{
int digit = body[body.Length - 1 - i] - '0';
if (i % 2 == 0) // check digit의 옆부터 한 자리 걸러 2배
{
digit *= 2;
if (digit > 9) digit -= 9;
}
sum += digit;
}
return (10 - sum % 10) % 10;
}
public static bool IsValidLuhn(string code) =>
code.Length >= 2 && code.All(char.IsAsciiDigit) &&
LuhnCheckDigit(code[..^1]) == code[^1] - '0';
이 코드가 무엇을 하는지는 한 번만 손으로 따라가면 머리에 남습니다. 고객 코드 본체를 1000317로 두고 check digit를 계산해 봅니다. 본체의 오른쪽 끝부터 세어 1번째·3번째·5번째… 자리를 2배하고, 2배한 결과가 9를 넘으면 9를 뺀다, 그것뿐입니다.
| 본체의 자리(오른쪽부터) | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| 숫자 | 7 | 1 | 3 | 0 | 0 | 0 | 1 |
| 2배한다 | ○ | ○ | ○ | ○ | |||
| 계산 후 | 14→5 | 1 | 6 | 0 | 0 | 0 | 2 |
합은 5+1+6+0+0+0+2 = 14. check digit는 「10 −(합을 10으로 나눈 나머지)」의 1의 자리이므로, 10 − 4 = 6. 완성된 코드는 10003176입니다. 위의 LuhnCheckDigit("1000317")가 반환하는 값과 일치합니다.
완성된 코드를 검증할 때는 check digit를 포함해 오른쪽 끝부터 세고, 오른쪽에서 2번째·4번째…를 2배해 합합니다. 6+5+1+6+0+0+0+2 = 20이고, 10으로 나누어떨어지므로 올바른 코드입니다. 흔한 오타를 넣어 봅니다.
- 이웃끼리 바꿔
10003716으로 친 경우: 6+2+7+6+0+0+0+2 = 23. 10으로 나누어떨어지지 않으므로 검출됩니다. - 1자리만 틀려
10008176으로 친 경우: 6+5+1+7+0+0+0+2 = 21. 이것도 검출됩니다.
이 「합이 10의 배수가 되는가」만 판정 기준이므로, 계산기만 있는 현장에서도 검산할 수 있습니다. 제4장에서 본 대로, Luhn이 빠져나가는 것은 09↔90 뒤바꿈뿐입니다.
거래처 master의 입력 검사에 쓸 수 있는 법인번호 검증도 실었습니다. 성령의 산식9을 그대로 풀어 쓴 것입니다(앞 1자리가 검사용 숫자, 이어지는 12자리가 기초 번호).
public static bool IsValidCorporateNumber(string code)
{
if (code.Length != 13 || !code.All(char.IsAsciiDigit)) return false;
int sum = 0;
for (int n = 1; n <= 12; n++)
{
int p = code[13 - n] - '0'; // 기초 번호의 최하위를 n=1번째 자리로 한다
sum += p * (n % 2 == 0 ? 2 : 1); // 홀수 자리×1, 짝수 자리×2
}
return 9 - sum % 9 == code[0] - '0';
}
국세청 자료의 예(회사법인등번호 700110005901 → 법인번호 8700110005901)로 검산하면, 홀수 자리의 합 11+짝수 자리의 합 13×2=37, 37을 9로 나눈 나머지 1, 9−1=8로 일치합니다.11
Excel의 수식과 VBA
이행 전 기존 master를 Excel로 점검·정리하는 장면이나, 업무 쪽에서 나눠 준 목록을 정보시스템이 점검하는 장면에서는, 개발 환경을 나가지 않고 그 자리에서 검증하고 싶을 때가 있습니다. Luhn 검증이라면 수식 한 줄로도 쓸 수 있습니다(A2에 코드가 들어 있다는 전제. Microsoft 365 또는 Excel 2021 이후의 LET·SEQUENCE를 씁니다).
=LET(s,A2&"", n,LEN(s), i,SEQUENCE(n),
d,MID(s,i,1)*1,
x,IF(MOD(n-i,2)=1, d*2, d),
y,IF(x>9, x-9, x),
MOD(SUM(y),10)=0)
n-i가 홀수인 자리, 즉 오른쪽에서 2번째·4번째…를 2배하고, 9를 넘으면 9를 빼 합한 뒤, 10으로 나누어떨어지는지를 봅니다. 앞에서 손으로 계산한 것과 같은 절차입니다. 코드가 숫자로 들어 있는 셀에서는 선행 0이 사라진 점에 주의합니다(제6장). 열을 문자열로 만든 뒤 검증합니다.
오래된 Excel까지 포함해 나눠 주고 싶거나, 여러 방식을 나누어 쓰고 싶다면 VBA 함수로 두어 =IsValidLuhn(A2)처럼 워크시트에서 부를 수 있습니다.
' Luhn 방식 검증. 선행 0이 떨어지지 않도록 반드시 문자열로 넘길 것.
Public Function IsValidLuhn(ByVal code As String) As Boolean
Dim i As Long, n As Long, d As Long, total As Long
Dim c As String
n = Len(code)
If n < 2 Then Exit Function ' 기본값 False를 반환
For i = 1 To n
c = Mid$(code, n - i + 1, 1) ' 오른쪽 끝부터 센다
If c < "0" Or c > "9" Then Exit Function
d = CLng(c)
If i Mod 2 = 0 Then ' 오른쪽에서 2번째·4번째…를 2배
d = d * 2
If d > 9 Then d = d - 9
End If
total = total + d
Next i
IsValidLuhn = (total Mod 10 = 0)
End Function
같은 형태로 JAN 방식(오른쪽 끝부터 3배·1배)이나 법인번호(홀수 자리×1·짝수 자리×2)도 쓸 수 있습니다. 배수와 나누는 수만 다릅니다.
입력 화면에서의 쓰임새에도 한 가지 요령이 있습니다. check digit가 일치하지 않을 때 「코드가 올바르지 않습니다」만 내지 말고, master를 조회해 명칭을 다시 보여 주기(「10003176: 주식회사 ○○가 맞습니까?」)까지 하면, check digit를 빠져나간 오입력(실재하는 다른 코드를 친 경우)도 사람의 눈으로 잡을 수 있습니다.
6. 현장에서 반드시 밟는 함정
코드 체계 자체가 좋아도, 구현과 운용에서 망가지는 정석 패턴이 있습니다.
- Excel의 선행 0 소실과 15자리 절삭. Excel은 셀 내용을 숫자로 해석하면 선행 0을 지우고, 숫자 유효 정밀도가 15자리라 16자리 이후를 0으로 바꿉니다.4 고객 코드
00123이123이 되고, 신용카드 번호급의 긴 번호는 끝이0으로 바뀐다는 뜻입니다. CSV 연동이 있는 시스템의 코드는 모든 경로에서 문자열로 다루기(Power Query의 텍스트형 지정, 가져오기 쪽의 열 형식 지정)를 처음부터 운용 절차에 넣습니다. - database에 숫자형으로 저장하기. 선행 0이 사라지는 것은 Excel과 같고, 「코드 구간으로 좁힌다」는 뜻의
BETWEEN이 자릿수가 다른 코드를 끌어들입니다. 코드는 계산 대상이 아니므로 자릿수 고정 문자열형이 원칙입니다. 정렬 순서도 문자열로 설계합니다(자릿수를 고정하면 문자열 정렬=숫자 정렬이 됩니다). - 하이픈을 데이터에 넣기.
1234-5678과12345678이 다른 레코드로 섞이기 시작하면 끝입니다. 저장은 순수 코드, 구분은 표시 때 부여. 입력 때는 하이픈·공백을 제거한 뒤 검증합니다. - 대소문자 흔들림. 영문을 쓰면 저장 전에 대문자로 정규화하고, collation에 의존하지 않고 자체로 통일합니다.
- 결번 재사용. 「고객 코드 1000317은 해지됐으니 신규 고객에게 돌리자」는 금물입니다. 과거 청구서·로그·거래처 시스템에는 옛 대응 관계가 남아 있어, 감사나 장애 조사에서 다른 사람을 가리킵니다. 코드는 영구 결번이 원칙입니다.
7. 스스로 정해선 안 되는 코드 ── 기존 표준을 탈 때
사내에서 닫히는 코드는 자유롭게 설계할 수 있지만, 상품에 바코드를 인쇄해 사외(소매·EC·물류)로 흘리면 자체 코드가 아니라 JAN 코드(GTIN)를 씁니다. JAN 코드는 GS1 사업자 코드를 대여받아 설정하는 것으로, 자사에서 마음대로 정할 수 없습니다.5 check digit 계산 방법도 표준으로 정해져 있습니다.6
이때 설계상의 요점은 사내 코드와 JAN 코드를 억지로 하나로 합치지 않는 것입니다. 같은 상품이라도 포장 입수 차이로 JAN이 갈리고, 규격 변경으로 JAN이 바뀌는 사정이 있으므로, 상품 master에는 「사내 상품 코드(primary key에 해당, 자사에서 채번)」와 「JAN 코드(속성, 여러 개 가질 수 있음)」를 다른 열로 두는 것이 정석입니다. 여기서도 「코드는 식별, 의미(외부 표준과의 대응)는 속성」 원칙이 그대로 적용됩니다.
8. 코드 체계의 수명과 이행
아무리 정성껏 설계해도 코드 체계는 언젠가 수명을 맞습니다. 전형은 자릿수 초과입니다. 일련번호 상한이 보이기 시작하면 선택지는 「자리를 늘린다」뿐인데, 자릿수는 master 열 정의뿐 아니라 전표 레이아웃, 바코드 인쇄 폭, 거래처와의 연동 파일 사양, 그리고 거래처 쪽 시스템에까지 박혀 있습니다. 우편번호 7자리화(1998년) 같은 이행을 자사와 전 거래처에서 하게 되는 셈입니다.
그래서 제3장의 「여유 1자리」가 효과를 내지만, 그래도 이행이 필요해졌을 때의 원칙은 다음 셋입니다.
- 신구 대조 master를 만들고, 이행 기간 중에는 양쪽 코드로 검색할 수 있게 한다. 거래처 문의는 옛 코드로 옵니다.
- 내부 키와 코드를 분리해 둔 경우, 이행은 표시 층과 master의 문제로 닫힙니다. database primary key에 업무 코드 자체를 쓰면 모든 테이블의 foreign key가 따라갑니다. 신규 설계에서는 내부 키(자동 채번)와 표시용 코드를 나누어 두기를 권합니다.
- 스키마 변경은 버전 관리된 migration으로 배포한다. 코드 열의 자릿수 변경을 모든 고객사·모든 환경에 확실히 퍼뜨리는 방법은 DB 스키마의 migration 기사에서 다룬 대로입니다.
9. 정리
- 코드 설계의 첫 원칙은 「코드는 식별, 의미는 속성」. 부서나 분류를 코드에 박아 넣으면 조직과 분류의 변화가 그대로 코드의 붕괴가 됩니다. 마이넘버가 무의미 번호인 것은 우연이 아닙니다.1
- 문자 종류와 자릿수는 정보 효율과 가독성의 트레이드오프. 읽어 주기·손글씨가 있으면 숫자만, 자리를 압축하려면 오독 대책이 된 알파벳(Crockford Base323). 자릿수는 장래 건수의 10배+1자리.
- 입력 실수의 실태는 「1자리 오류가 6〜9할, 인접 전치가 나머지 대부분」.2 손을 거치는 코드에는 check digit를 붙이고, 방식은 헤매면 Luhn, 바코드에 실으면 GS1 표준6. modulus 11 계열의 「나머지 맞추기」 문제와, 법인번호의 modulus 9가 0과 9를 구별하지 않는 점은 방식 선정 때 알아 두어야 할 성질입니다.
- 구현은 문자열로 일관하는 것이 원칙. Excel의 선행 0 소실·15자리 절삭4, 숫자형 저장, 하이픈 혼입, 결번 재사용이 현장의 네 가지 대형 사고입니다.
- 밖으로 나가는 상품 코드는 JAN(GS1)을 타고, 사내 코드와는 다른 열로 둡니다. 자릿수 초과 이행에 대비해 내부 키와 표시용 코드는 분리해 둡니다.
코드 체계는 한 번 나눠 주면 나중에 고치는 비용이 차원이 다를 만큼 큰 「사실상의 외부 사양」입니다. 신규 시스템의 요건 정의에서 코드 이야기가 나오면, 화면이나 기능보다 먼저 이 기사의 체크리스트를 한 바퀴 돌아 보시기 바랍니다.
관련 기사
- 업무 앱의 DB 스키마를 버전 관리한다 ── 「고객사마다 DB가 다르다」를 막는 migration의 실천
- Excel 대장을 SharePoint 리스트로 바꾼다 ── 공유·이력·플로 연동으로 「대장이 깨진다」를 졸업한다
- PowerShell로 Excel·CSV 업무 처리를 자동화한다 ── 집계·대조·전표 출력의 실무 레시피
- Windows 앱의 데이터 저장 위치 고르기 ── SQLite / JSON / 레지스트리 / Access 판단표
관련 상담 영역
合同会社小村ソフト에서는 업무 시스템 신규 구축·리플레이스 때의 코드 체계·master 설계, 기존 코드 체계의 자릿수 초과·중복 조사와 이행 계획, 입력 검사(check digit·master 조회) 구현을 다룹니다. 이 기사의 check digit 방식 비교처럼 「어느 방식이 어떤 실수를 얼마나 검출하는가」를 수식으로 평가해 설계 판단으로 내리는 상담은, 수리 컨설팅 영역으로도 받습니다.
참고 링크
-
行政手続における特定の個人を識別するための番号の利用等に関する法律施行令(平成26年政令第155号) 제6조. 개인번호로 해야 할 번호가, 주민표 코드를 변환해 얻어지며 작위가 들어가지 않는 방법으로 생성되는 11자리 번호와, 그 뒤에 붙인 1자리 검사용 숫자(개인번호를 전자계산기에 입력할 때 오류가 없음을 확인하는 것을 목적으로 산출되는 0부터 9까지의 정수)로 구성된다는 점에 대해. ↩ ↩2 ↩3 ↩4
-
J. Verhoeff, Error Detecting Decimal Codes, Mathematical Centre Tracts 29, Mathematisch Centrum, Amsterdam. 실제 시스템의 오기 샘플 분석에 따른 오류 유형의 빈도(1자리 오류가 60〜95%로 최대 유형, 2자리 오류가 10〜20%이고 그 대부분이 인접 자리 전치, twin error 등 소수 유형이 각각 0.5〜1.5%), 그리고 모든 1자리 오류와 모든 인접 전치(해당 서에서 transposition은 인접 자리 뒤바꿈을 가리킴)를 검출하는 십진 코드를 저자가 구성한 점에 대해. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Douglas Crockford, Base 32. 32자 알파벳에서 1과 혼동하기 쉬운 I·L, 0과 혼동하기 쉬운 O(및 우연히 생기는 비어를 피하기 위해 U)를 제외한 점, decode 때 대소문자를 받아들이고 i·l을 1, o를 0으로 다루는 점, mod 37에 의한 check 기호 장치에 대해. ↩ ↩2 ↩3 ↩4
-
Microsoft 지원, 先頭のゼロと大きい数値を保持する. Excel 숫자의 유효 정밀도가 최대 15자리이며, 신용카드 번호처럼 16자리 이상 숫자에서는 15자리를 넘는 부분이 0으로 바뀌는 점, 선행 0이 삭제되는 점, 그리고 열을 텍스트로 다루는 회피책에 대해. ↩ ↩2 ↩3
-
GS1 Japan, GS1事業者コード・GTIN(JANコード). JAN 코드 이용에는 GS1 사업자 코드 대여를 받는 등록 절차가 필요하다는 점에 대해. ↩ ↩2
-
GS1 Japan, チェックデジットの計算方法. GTIN-13(JAN 코드 표준 타입)의 check digit가 오른쪽 끝 자리부터 차례로 3배·1배를 번갈아 곱한 합을 써서 「10에서 (합을 10으로 나눈 나머지)를 뺀다」로 산출되는 점에 대해. ↩ ↩2 ↩3 ↩4 ↩5
-
H. P. Luhn, US Patent 2,950,048 “Computer for Verifying Numbers” (1954년 출원, 1960년 등록). 원래 번호의 오른쪽 끝에 check digit를 붙이고, 대체 숫자(2배한 숫자의 각 자리 합)를 쓴 교차 가산으로 번호를 검증하는 방식에 대해. ↩ ↩2
-
行政手続における特定の個人を識別するための番号の利用等に関する法律に規定する個人番号、個人番号カード、特定個人情報の提供等に関する命令(平成26年総務省令第85号) 제5조. 검사용 숫자의 산식(검사용 숫자 이외 11자리의 최하위부터 n번째 숫자 Pn에, 1≦n≦6일 때 n+1, 7≦n≦11일 때 n−5의 가중치 Qn을 곱한 합을 11로 나누고, 11에서 나머지를 뺀다. 나머지가 1 이하이면 0으로 한다)에 대해. ↩ ↩2
-
法人番号の指定等に関する省令(平成26年財務省令第70号) 제2조. 법인번호 검사용 숫자의 산식(기초 번호의 최하위부터 n번째 숫자 Pn에, n이 홀수이면 1, 짝수이면 2의 가중치 Qn을 곱한 합을 9로 나누고, 9에서 나머지를 뺀다)에 대해. ↩ ↩2
-
H. M. Damm, Totally anti-symmetric quasigroups for all orders n≠2,6, Discrete Mathematics, Vol. 307, 2007. 모든 1자리 오류와 인접 전치를 검출하는 check digit 방식의 기초가 되는 완전 반대칭 준군이 위수 2·6을 제외한 모든 위수에서 존재한다는 점에 대해. ↩ ↩2
-
国税庁, チェックデジットの計算. 법인번호가 12자리 기초 번호와 그 앞에 붙인 1자리 검사용 숫자로 구성된다는 점, 그리고 회사법인등번호 700110005901에서 check digit 8을 산출하는 계산 예에 대해. ↩ ↩2
관련 기사
같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.
업무 앱의 DB 스키마를 버전 관리한다 ── 「고객사마다 DB가 다르다」를 막는 마이그레이션 실무
고객사마다 흩어진 업무 앱의 DB 스키마를 버전 관리하는 실무 가이드. PRAGMA user_version과 전진 마이그레이션의 C# 구현, EF Core Migrations·DbUp·자체 구현 판단표, 2단계 릴리스까지 정리합니다.
WinForms / WPF 앱의 CI/CD 실전 ── GitHub Actions로 빌드부터 서명·배포까지 자동화하기
WinForms / WPF 앱의 CI/CD를 GitHub Actions로 구성하는 실무 가이드입니다. windows-latest에서 빌드+테스트를 돌리는 최소 YAML, 태그 기반 버전 부여, signtool 서명 연동, MSI/MSIX/Clic...
VB6 앱은 언제까지 동작하는가 ── 런타임 지원 현황과 현실적인 .NET 이전 진행 방법
VB6 앱은 언제까지 동작할까요. 런타임은 Windows 11에서도 동작 대상이고 IDE는 지원이 종료된 현황을 정리하고, 전면 재작성·자동 변환·단계 이전의 판단표, 이전 전 자산 파악, VB6와 .NET의 비호환까지 정리합니다.
Windows의 프로세스 간 통신을 어떻게 고를까 ── Named Pipe / TCP / gRPC / 공유 메모리 / COM 판단표
Windows 앱끼리의 연동 수단을 어떻게 고를지 정리합니다. Named Pipe, 로컬 TCP, gRPC, 공유 메모리, 파일 연동, COM의 강점과 함정을 판단표로 정리하고, 정석 구성과 Named Pipe 구현 예까지 설명합니다.
Windows 앱 데이터 저장 위치 고르기 ── SQLite / JSON / 레지스트리 / Access 판단표
Windows 데스크톱 앱의 데이터를 어디에, 무엇으로 저장할지. AppData/ProgramData 구분, SQLite·JSON 파일·레지스트리·Access(.accdb) 각각의 강점과 함정을 판단표와 함께 정리하고, 손상 대책과 비트 수 문제...
관련 토픽
이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.
Windows 기술 토픽
Windows 개발, 장애 조사, 기존 자산 활용에 관한 KomuraSoft LLC 기사를 모은 토픽 허브입니다.
이 주제와 연결되는 서비스
이 기사는 다음 서비스 페이지로 이어집니다. 가까운 입구부터 확인해 주세요.
Windows 앱 개발
상주 처리, 장비 연동, 운영 로그, 유지 보수 가능한 구조가 필요한 Windows 데스크톱 애플리케이션을 지원합니다.
기술 상담 & 설계 리뷰
설계 방향, 아키텍처 경계, 수명 관리, 기존 Windows 자산 처리 방법을 정리하는 데 도움을 드립니다.
자주 묻는 질문
이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.
- check digit는 어떤 코드에 붙여야 합니까?
- 사람이 입력·전사·읽어 주는 코드에 붙입니다. 종이 전표에서 치는 상품 코드, 전화로 전달하는 회원 번호, 손으로 쓰는 전표 번호가 전형입니다. 반대로 시스템 사이에서만 오가는 내부 ID(database의 primary key 등)에는 필요 없습니다. 손을 거치지 않는 코드는 오타가 나지 않기 때문이며, check digit의 목적은 입력 오류 검출에 있습니다. 마이넘버의 검사용 숫자도, 법령상 「전자계산기에 입력할 때 오류가 없음을 확인하는 것」을 목적으로 명시합니다.
- 상품 코드에 부서나 분류의 의미를 넣어도 됩니까?
- 원칙은 「코드는 식별에 집중하고, 의미는 속성으로 database에 둔다」입니다. 부서·분류·연도 등을 코드 자리에 넣으면, 조직 개편이나 분류 변경마다 코드를 다시 매겨야 하고, 과거 전표·거래처에 건넨 번호와의 대조가 깨집니다. 사람이 보고 구분이 꼭 필요하면, 종류를 나타내는 접두사 1자 정도로 멈추고 나머지는 일련번호로 두는 것이 실무적인 타협점입니다.
- 기존 코드 체계에 check digit를 나중에 붙일 수 있습니까?
- 기술적으로는 가능하지만, 자리가 하나 늘어나므로 master·모든 전표·거래처와의 데이터 연동·인쇄물 전부에 영향이 갑니다. 실질적으로는 코드 체계 이행 프로젝트가 되므로, 자릿수 초과 대응 등으로 체계를 새로 잡을 타이밍에 맞추는 것이 현실적입니다. 그때까지의 임시 대응으로는, 입력 화면에서 master 존재 확인(입력한 코드가 실재하는지 조회)과 명칭을 다시 보여 주기만 해도 오입력으로 인한 실제 피해는 상당히 줄어듭니다.
- UUID나 ULID를 업무 코드로 써도 됩니까?
- database의 내부 키로는 문제 없지만, 사람이 읽고 전사하는 「표시용 코드」에는 맞지 않습니다. UUID는 36자로 너무 길어 전화나 FAX 전달을 견디지 못하기 때문입니다. 내부 키(UUID나 자동 일련번호)와 사람에게 보이는 표시용 코드(짧은 일련번호+check digit)를 나누는 2계층 구성이면 양쪽 요구를 맞출 수 있습니다. 표시용 코드 체계를 나중에 바꾸게 되어도, 내부 키가 안정되어 있으면 영향 범위를 표시 층에 가둘 수 있습니다.