게시글 프런트매터의 tags 필드에 콤마로 구분된 태그 목록을 쓰는데, 태그 이름 자체에 콤마가 들어가야 하는 경우가 생겼습니다. 예를 들어 "Server-Sent Events, Streaming"처럼 하나의 태그 이름 안에 콤마가 포함된 경우입니다. 기존 파서는 문자열을 콤마 기준으로 무조건 쪼개는 방식이라 이런 입력을 넣으면 태그 하나가 둘로 갈라졌습니다.
어떤 작업이었나
["Server-Sent Events, Streaming", AI] 같은 입력을 받아서 태그 두 개로 파싱하는 함수를 손보는 일이었습니다. 쌍따옴표로 감싼 구간 안의 콤마는 구분자로 보지 않고, 바깥쪽 콤마만 구분자로 쓰도록 바꿔야 했습니다. 원래 구현은 이런 식이었습니다.
function parseTagList(raw: string): string[] {
return raw
.replace(/^\[|\]$/, '')
.split(',')
.map((t) => t.trim().replace(/^["']|["']$/g, ''))
.filter(Boolean)
}
split(',')이 쌍따옴표를 전혀 신경 쓰지 않으니, 콤마가 들어간 태그는 무조건 둘로 쪼개졌습니다.
AI를 어떻게 활용했나
Claude Code에게 상황을 설명하고 고쳐달라고 했습니다.
parseTagList가 쌍따옴표로 감싼 구간 안의 콤마는 구분자로 보지 않게 고쳐줘.
["Server-Sent Events, Streaming", AI] 를 넣으면 태그 두 개로 나와야 해.
테스트도 같이 짜줘.
받은 수정본은 split 대신 정규식으로 한 번에 토큰을 긁어오는 방식이었습니다.
function parseTagList(raw: string): string[] {
const inner = raw.trim().replace(/^\[/, '').replace(/\]$/, '')
const tokens = inner.match(/"[^"]*"|[^,]+/g) ?? []
return tokens.map((t) => t.trim().replace(/^["']|["']$/g, '')).filter(Boolean)
}
"[^"]*"를 먼저 시도해서 쌍따옴표로 감싼 구간을 통째로 하나의 토큰으로 가져오고, 그게 아니면 콤마가 아닌 문자들을 토큰으로 가져오는 방식입니다. 같이 받은 테스트는 이거였습니다.
import { describe, expect, it } from 'vitest'
describe('parseTagList', () => {
it('쌍따옴표로 감싼 태그 안의 콤마를 구분자로 보지 않는다', () => {
const result = parseTagList('["Server-Sent Events, Streaming", AI]')
expect(result).toHaveLength(2)
expect(result[0]).toBe('Server-Sent Events, Streaming')
})
})
테스트는 초록불이었습니다. 요구했던 케이스는 정확히 통과했으니 그대로 넘어갔습니다.
통과했는데 왜 깨졌을까
며칠 뒤 태그에 콤마가 들어간 항목이 하나 더 필요해져서, 쌍따옴표로 감싼 태그를 연달아 두 개 넣어봤습니다.
parseTagList('["Server-Sent Events, Streaming", "코드리뷰, 테스트"]')
// 기대: ['Server-Sent Events, Streaming', '코드리뷰, 테스트'] (길이 2)
// 실제: ['Server-Sent Events, Streaming', '코드리뷰', '테스트'] (길이 3)
두 번째 태그가 또 콤마에서 쪼개졌습니다. 테스트는 여전히 초록불이었는데, 이 케이스는 테스트에 없었기 때문입니다.
원인은 정규식이 매치를 다시 시작하는 위치에 있었습니다. 첫 번째 토큰을 긁어온 다음 바로 뒤에 오는 구분자 콤마를 건너뛰고 나면, 그 다음 문자는 공백입니다. "[^"]*" 패턴은 매치가 시작되는 지점이 정확히 쌍따옴표일 때만 걸리는데, 공백에서 시작하면 이 패턴은 바로 실패하고 [^,]+ 쪽이 먼저 걸립니다. 그러면 공백과 여는 따옴표까지 그냥 "콤마가 아닌 문자"로 긁어가 버리고, 그 안에 있던 콤마에서 다시 쪼개집니다. 쌍따옴표 토큰이 맨 앞에 있을 때는 이 문제가 안 보이고, 두 번째부터 드러나는 구조였습니다.
고친 쪽은 토큰을 긁기 전에 공백을 먼저 건너뛰도록 정규식을 바꾸는 거였습니다.
const tokens = inner.match(/\s*"[^"]*"|[^,]+/g) ?? []
결과와 얻은 팁
- AI가 짜준 테스트가 초록불이라는 건 "AI가 생각한 케이스를 통과했다"는 뜻이지 "이 함수가 맞다"는 뜻이 아닙니다. 이번엔 콤마 있는 태그가 하나일 때만 테스트했고, 두 개 연속일 때는 아예 생각하지 못했습니다.
- 엣지케이스를 맡길 때 "이런 입력 하나" 대신 "이런 입력이 연속으로 나올 때도"처럼 반복되는 상황을 구체적으로 짚어줘야 AI가 그 케이스를 테스트에 넣습니다. 처음 프롬프트에 "태그 두 개가 전부 콤마를 포함할 때도 확인해줘"를 넣었으면 이 버그는 테스트 단계에서 잡혔을 겁니다.
- 테스트를 받으면 한 번은 입력값을 일부러 테스트에 없는 조합으로 바꿔서 직접 돌려보는 습관이 남았습니다. 통과한 테스트 개수보다, 테스트가 실제로 덮지 못한 조합이 뭔지를 먼저 생각하는 쪽이 더 믿을만했습니다.