회원 이름 20자, 무엇을 한 글자로 셀 것인가

서로 다른 눈금자 위 문자 모양과 별 모양을 배치한 문자열 길이 개념 삽화

입력창에 이름을 스무 자까지 쓸 수 있다고 적었다고 하자. 사용자는 보이는 글자를 세겠지만 프로그램은 다른 단위를 셀 수 있다. 이모지 하나를 넣자 남은 글자 수가 둘 줄어든다면 누구의 계산이 틀린 걸까. 검증 라이브러리를 고르기 전에 제품이 말하는 “한 글자”부터 정해야 하는 상황이다.

서로 다른 눈금자 위 문자 모양과 별 모양을 배치한 문자열 길이 개념 삽화
주제를 표현한 개념 삽화. Google Gemini 이미지 기능을 사용했습니다.

10월 28일 DEV에 올라온 nyaomaru의 검증 라이브러리 비교는 같은 입력도 도구마다 다르게 처리할 수 있음을 보여 준다. 작성자는 자신이 관리하는 도구와 AI 도움을 밝히고 공통 자료로 비교했다. 특정 묶음의 결과를 모든 서비스의 성능 순위로 읽기는 어렵다. 여기서는 그 비교를 문자열 정책을 설계하는 질문으로 좁혀 본다.

참고 원문: 관련 원문을 직접 읽기

길이를 세는 세 가지 자리

JavaScript 문자열의 length는 UTF-16 코드 단위 수를 센다. 사용자가 화면에서 하나로 인식하는 글자 수와 항상 같지 않다. 코드 포인트를 세는 방법도 있고, 여러 코드 포인트가 모여 보이는 문자 단위를 구분하는 방법도 있다. 어느 방식이든 이름부터 적어야 앞단과 뒷단의 계산을 비교할 수 있다.

설명용 문자열 ‘A😀B’를 생각해 보자. JavaScript length는 4이고 코드 포인트는 3개다. 일반적으로 이 예제에서 보이는 문자 묶음도 3개로 나뉜다. 이 글에서는 이 작은 값들을 로컬 계산으로 별도 확인한다. 특정 서비스의 입력 제한을 시험한 결과나 모든 이모지의 규칙을 대표하는 사례는 아니다.

한글도 눈에 같은 모양이 여러 방식으로 표현될 수 있다. 예를 들어 완성형 ‘가’와 자모를 조합한 표현은 화면상 같아 보일 수 있어도 원래 코드 포인트 배열이 다르다. 자료를 정규화할지, 원래 입력을 보존할지 정책이 필요하다. 이름을 다룬다는 이유로 임의의 문자 변환을 해도 된다는 뜻은 아니다.

작은 입력 표로 요구를 설명하기

가상의 가입 화면이라면 정상 한글, 이모지를 포함한 이름, 조합 문자, 앞뒤 공백, 빈 값처럼 서로 다른 이유의 입력을 적어 볼 수 있다. 각 행 옆에는 허용 여부와 사용자에게 보여 줄 이유를 둔다. 라이브러리의 기본 동작을 먼저 받아들이기보다 서비스가 필요로 하는 규칙을 표로 만드는 제안이다.

예컨대 “앞뒤 공백을 제거한 뒤 보이는 문자 묶음 기준 20개 이하”라는 규칙을 정했다고 가정하자. 그 규칙이 적절한지는 실제 사용자와 제품의 요구로 판단해야 한다. 여기서는 유일한 정답으로 권하지 않는다. 중요한 것은 공백 처리의 시점과 길이 단위가 문장에 들어가 있다는 사실이다. 그래야 검사 순서가 바뀌었을 때 차이를 찾을 수 있다.

앞단에서 통과했는데 서버가 거절하는 상황도 이 표로 읽을 수 있다. 앞단이 보이는 문자 묶음을 세고 서버가 UTF-16 단위를 센다면 같은 제한 숫자를 써도 결과가 다르다. 반대로 서버의 저장 공간 제한은 또 다른 단위일 수 있다. 화면의 이름 길이와 저장 용량 제한을 같은 숫자로 맞출 이유는 없다.

숫자만 같은 규칙의 함정

검증 도구가 문자열을 검사하기 전에 값을 바꾸는지도 확인해야 한다. 문자열이 아닌 값을 강제로 바꾸는지, 추가 필드를 그대로 두는지 같은 차이는 길이 문제 밖에서도 결과를 바꾼다. 원문의 비교가 유용한 지점은 “모두 검증 도구니까 같을 것”이라는 가정을 흔드는 데 있다. 가장 빠르다고 적힌 도구가 우리 규칙까지 대신 정해 주지는 않는다.

시험 자료를 만들 때에는 실제 회원 이름을 모을 필요가 없다. 공개적으로 설명 가능한 인위적인 문자열을 쓰면 된다. 각 입력의 원래 표현과 기대 결과를 버전 관리에 남기고, 실행 환경의 국제화 기능 지원 범위도 확인한다고 제안할 수 있다. 제안한 전체 가입 절차를 이 글에서 구현하거나 배포한 것은 아니다.

오류 문구 역시 정책을 드러내야 한다. 사용자는 단위를 모르므로 “잘못된 값”이라는 말만으로는 고치기 어렵다. 보이는 문자 기준이라면 남은 길이를 같은 기준으로 계산하고, 금지한 문자가 있다면 실제 이유를 안내해야 한다. 특정 언어의 이름을 부당하게 지우는 규칙이 되지 않는지 검토하는 일도 필요하다.

나는 벤치마크를 읽은 뒤 도구의 승자를 고르기보다 입력 표부터 작게 써 보고 싶다. 정상 값 하나만 넣고 통과했다는 기록은 정책의 경계를 알려 주지 않는다. 같은 모양의 다른 표현이나 경계 길이의 예시가 있어야 무엇을 보장하는지 설명할 수 있다. 라이브러리 교체 뒤에도 같은 표를 쓸 수 있다는 점이 장점이다.

로컬 계산에서는 완성형 가의 길이가 UTF-16과 코드 포인트 기준 모두 1이고, 자모로 나눈 표현은 각각 2였다. 두 표현은 문자 묶음 기준으로는 각각 1이며 NFC 정규화 뒤 같은 값이 됐다. 이 결과는 예제 문자열과 사용한 런타임에 한정한다. 모든 이름을 정규화하라는 결론 대신, 같은 모양을 어떤 정책으로 다룰지 설명할 재료로 삼는다.

정책을 바꿨을 때 기존 자료가 어떻게 보일지도 살핀다. 새 입력만 제한하고 이전 이름은 보존할지, 사용자가 수정할 때만 검사할지에 따라 경험이 달라진다. 검증 함수의 한 줄보다 제품의 약속이 먼저 정해져야 한다.

이름 스무 자라는 짧은 문장에는 생각보다 많은 결정이 들어 있다. 단위, 변환 순서, 앞단과 서버의 합의, 오류 설명이다. 오늘 입력 규칙을 하나 정해야 한다면 나는 숫자부터 쓰지 않으려 한다. 화면에서 사용자가 세는 것과 프로그램이 세는 것이 언제 달라지는지 먼저 적어 볼 것이다.

함께 읽기: 통과한 테스트가 놓친 조건