정규표현식 버그 대다수는 문법 오류가 아닙니다. 패턴은 완벽하게 유효하면서도 틀릴 수 있습니다. 너무 많이 잡거나 너무 적게 잡거나, 최악에는 적대적인 문자열을 만나면 페이지가 멈춥니다. 머릿속 예제에서 움직이는 패턴과 실제 데이터에서 움직이는 패턴 사이에 있는 것이 테스트입니다.
이 글은 브라우저에서 정규표현식을 쓰고 테스트하고 디버깅하는 실용적 워크플로와, 가장 자리를 파는 실패 유형을 다룹니다.
장난감이 아니라 실제 데이터로 테스트
장난감 문자열은 패턴을 과신하게 만듭니다. 실제 입력에는 잊은 줄바꿈, 끝 공백, 이모지, 빈 값, 복사해 온 마크업 40킬로바이트가 들어 있습니다.
- 가지고 있는 최악의 실제 샘플을 붙입니다. 이상적으로는 이미 무언가를 망가뜨린 그 샘플이어야 합니다.
- 빈 문자열, 한 글자, 아주 긴 한 줄을 포함합니다.
- 입력에 포함될 수 있다면 비ASCII 텍스트를 포함합니다.
Ü,日本語,🙂. - 첫 매치가 아니라 매치 개수를 봅니다. 오프바이원 정량자와 탐욕스러운 중복은 여기서만 드러납니다.
테스트할 때 라이브 하이라이트가 있으면 저렴해집니다. 패턴을 고칠 때마다 매치 위치가 갱신되어, 사흘 뒤 로그 파일에서 튀어나오지 않습니다.
플래그는 패턴의 의미를 바꾼다
같은 식도 어떤 플래그로 실행하느냐에 따라 다르게 행동합니다.
| 플래그 | 효과 | 흔한 함정 |
|---|---|---|
i |
대소문자 구분 안 함 | 구분하려던 식별자에도 매치됨 |
g |
전역 — 모든 매치를 찾음 | JavaScript에서 test()와 exec()는 호출 사이에 상태를 유지함 |
m |
다줄 — ^와 $가 줄마다 작동 |
^는 더 이상 “문자열 시작”이 아님 |
s |
점이 줄바꿈에도 매치 | .*가 줄 경계를 넘기 시작함 |
u |
유니코드 모드 | 코드 포인트 단위 이스케이프에 필수 |
JavaScript의 g 플래그는 특히 주의해야 합니다. 이게 붙으면 regex.test()가 lastIndex를 전진시켜, 같은 객체에 반복 호출하면 다른 답이 나옵니다. “한 번은 되는데 재시도하면 깨진다”는 버그의 고전적 원인입니다.
캡처 그룹과 이름 있는 그룹
그룹은 데이터를 뽑아주지만 메모리를 쓰고 패턴을 읽기 어렵게 만듭니다.
// 이름 있는 그룹은 순서 변경을 견디고 번호 그룹은 견디지 못한다
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const m = "2026-09-22".match(re);
m.groups.year; // "2026"
m.groups.month; // "09"
번호 그룹보다 이름 있는 그룹을 써서, 패턴 재배치가 참조를 조용히 어긋나지 않게 하세요. 묶기만 하고 잡지는 않아야 한다면 비캡처 그룹을 씁니다: (?:...).
카타스트로픽 백트래킹
(a+)+$나 (\s*)* 같은 중첩 정량자는, 거의 일치하는 문자열에서 엔진을 지수 시간으로 몰아넣을 수 있습니다. 좋은 입력에서는 즉시 끝나고 적대적인 입력에서 멈춥니다 — 그리고 공격자가 보내는 것은 바로 그 적대적인 입력입니다.
- 이미 정량자를 가진 그룹에 정량자를 겹치지 마세요.
- 가능하면 앵커링하세요.
^와$는 엔진이 일찍 탈락하게 합니다. - 거의 일치하지 않는 긴 문자열로 부하를 걸어보세요. 글자를 더할 때마다 매치 시간이 급증한다면 그 패턴은 안전하지 않습니다.
- 서버에서 신뢰할 수 없는 입력에는 선형 시간 엔진(RE2 계열)을 검토하세요.
패턴을 읽기 쉽게 유지하기
- 코드에서 패턴에 이름을 붙이세요.
const ISO_DATE = /.../이 여섯 파일에 반복되는 익명 리터럴보다 낫습니다. - 엔진이 지원하면 프리스페이스 모드(
x)를 써서 패턴 안에 주석과 공백을 두세요. - 숫자에는 명시적이세요.
[0-9]는 ASCII 숫자이고, 유니코드 모드의\d는 다른 문자종에도 매치합니다. - 패턴 옆에 예제를 두 개 남기세요. 매치되어야 하는 것과 매치되면 안 되는 것.
실용적 워크플로
| 단계 | 확인하는 것 |
|---|---|
| 1. 실제 샘플 하나에 매치하는 가장 작은 패턴을 작성 | 하이라이트가 기대한 위치에 떨어짐 |
| 2. 음성 샘플을 추가 | 실패해야 하는 입력에서 매치 없음 |
| 3. 플래그를 의도적으로 켬 | 모든 플래그에는 이유가 있음 |
| 4. 길고 적대적인 입력으로 부하 테스트 | 매치가 즉시 나옴 |
| 5. 이름 있는 그룹으로 코드로 변환 | 위치 기반 참조가 남아 있지 않음 |
직접 해보기
정규표현식 테스터를 열고 패턴과 실제 샘플을 붙여 넣어, 입력할 때마다 매치가 하이라이트되는 것을 지켜보세요. 테스트는 브라우저에서 실행되므로 검증 중인 텍스트는 기기를 떠나지 않습니다. 샘플에 토큰이나 키, 고객 데이터가 들어 있다면야 더욱 그렇죠.