네이밍 컨벤션 실전 — 이름이 경계에서 깨지는 곳
컨벤션은 정했는데 왜 계속 깨질까
1편에서 자리마다 쓸 표기법을 정리했습니다. 팀에서 합의도 했다고 합시다. 그런데도 이름은 계속 깨집니다.
깨지는 자리가 정해져 있기 때문입니다. 표기법이 다른 두 시스템이 만나는 경계입니다. 프론트엔드 안에서만 놀 때는 문제가 없다가, API·파일 시스템·CSS 같은 다른 규칙을 쓰는 쪽과 만나는 순간 어긋납니다.
경계는 네 군데입니다.
경계 1 — API의 snake_case
서버는 snake_case, 프론트는 camelCase. 가장 흔한 경계입니다.
const user = await res.json();
// { user_id: 42, created_at: "...", display_name: "..." }
const id = user.user_id; // 여기서부터 snake_case가 코드 안으로 들어온다
한번 들어오면 퍼집니다. 컴포넌트 props로 넘어가고, 상태에 저장되고, 결국 프로젝트 절반이 두 표기법을 섞어 쓰게 됩니다.
변환은 한 곳에서 합니다. API 응답을 받는 지점에서 바꾸고, 그 안쪽은 전부 camelCase로 통일합니다.
// 경계에서 한 번만 변환
const user = toCamelKeys(await res.json());
const id = user.userId; // 이 아래로는 camelCase만 존재
다만 모든 응답에 자동 변환을 걸어 두는 건 위험합니다. 키 이름 자체가 데이터인 경우가 있기 때문입니다.
// 사용자가 만든 태그별 집계 — 키가 곧 값이다
{ "counts": { "front_end": 12, "back_end": 8 } }
여기에 자동 변환이 돌면 frontEnd가 되어 원래 태그 이름과 달라집니다. 변환은 응답 전체에 무조건 거는 게 아니라 키 구조가 고정된 엔드포인트에만 거는 게 안전합니다.
경계 2 — 약어가 섞인 이름
userID, parseHTML, APIClient. 약어를 대문자로 유지한 이름은 왕복 변환에서 원래대로 돌아오지 않습니다.
| 원본 | snake_case로 | 다시 camelCase로 |
|---|---|---|
userId |
user_id |
userId ✓ |
userID |
user_id |
userId ✗ (원본과 다름) |
userID를 쪼개면 user + ID인데, 다시 붙일 때는 각 단어의 첫 글자만 대문자가 되니 userId가 됩니다. 원본과 다릅니다. 한 프로젝트 안에 userId와 userID가 공존하면 자동 변환을 한 번 돌릴 때마다 이름이 흔들립니다.
그래서 규칙은 하나입니다. 약어도 보통 단어처럼 취급한다.
userId, apiUrl, htmlParser, httpClient // ✓
userID, APIUrl, HTMLParser, HTTPClient // ✗ 왕복이 안 된다
구글 자바 스타일 가이드도 같은 규칙을 씁니다(XmlHttpRequest). 마이크로소프트 .NET 가이드라인은 두 글자 약어만 예외로 두고(IOStream), 세 글자 이상은 HtmlParser처럼 씁니다. 읽기에 어색해 보여도, 변환이 안정적이고 단어 경계가 항상 명확하다는 이점이 훨씬 큽니다.
왕복이 되는지 확인하려면 네이밍 컨벤션 변환기에 이름을 넣어 보세요. 이 도구는 약어를 소문자 한 단어로 정규화하므로
API_BASE_URL은apiBaseUrl이 됩니다. 나온 결과를 다시 넣었을 때 같은 값이 나오면 그 이름은 안전합니다.
경계 3 — 파일명 대소문자
로컬에서는 멀쩡한데 배포하면 404가 나는 전형적인 원인입니다.
import UserCard from './components/usercard'; // 실제 파일은 UserCard.tsx
macOS와 윈도우의 기본 파일 시스템은 대소문자를 구분하지 않습니다. 그래서 로컬에서는 잘 됩니다. 반면 배포 서버와 CI는 대부분 리눅스라 대소문자를 구분합니다. 같은 코드가 거기서만 “모듈을 찾을 수 없음”으로 죽습니다.
정적 사이트의 URL도 마찬가지입니다. /blog/PX-Rem-Basics는 로컬 미리보기에서 열릴 수 있지만 배포된 곳에서는 404입니다.
저도 이걸로 헤맨 적이 있습니다. 이미지 파일 이름에 대문자가 섞여 있었는데, 로컬에서는 잘 나와서 문제가 없다고 생각했습니다. 그런데 본 서버에 올리자 그 이미지만 뜨지 않았습니다. 코드에 적은 이름과 실제 파일 이름이 대소문자만 달랐던 겁니다.
이 문제는 규칙 하나로 사라집니다. 파일명과 URL은 소문자 kebab-case로 고정합니다. 대소문자를 아예 쓰지 않으면 구분하든 안 하든 결과가 같습니다.
한 가지 더. 이미 커밋된 파일의 대소문자만 바꿀 때는 탐색기나 Finder에서 이름을 고치면 안 됩니다. 대소문자를 구분하지 않는 컴퓨터에서는 git이 같은 파일로 보고 변경을 기록하지 않아서, 내 컴퓨터에서는 바뀌었는데 저장소에는 옛 이름이 그대로 남습니다. 이름은 git mv로 바꾸세요.
git mv UserCard.tsx user-card.tsx # git이 이름 변경으로 기록한다
경계 4 — CSS는 대소문자를 가릴 때와 안 가릴 때가 있다
CSS에서 대소문자 규칙은 한 가지가 아닙니다. 이 표를 모르면 “왜 이건 되고 저건 안 되지”가 됩니다.
| 대상 | 대소문자 |
|---|---|
속성 이름 (color, Color) |
구분 안 함 |
요소 선택자 (div, DIV) |
구분 안 함 |
클래스·id 이름 (.userCard) |
구분함 |
커스텀 속성 (--myColor) |
구분함 |
그래서 --Space-4로 정의하고 var(--space-4)로 쓰면 값이 안 나옵니다. 오타처럼 보이지도 않아서 찾기 어렵습니다.
여러 사람이 같은 파일을 고치는 프로젝트에서 특히 자주 생깁니다. 제가 겪었을 때는 어떤 곳은 HTML의 클래스 이름에 대문자가 들어가 있고, 어떤 곳은 CSS 선택자 쪽에 대문자가 들어가 있었습니다. 사람마다 쓰는 버릇이 달라서 생긴 일인데, 눈으로 보면 같은 이름이라 왜 스타일이 안 먹는지 한참 찾게 됩니다.
CSS 쪽 이름을 전부 소문자 kebab-case로 통일하면 이 구분을 기억할 필요가 없어집니다.
이름을 일괄로 바꿀 때
기존 코드의 표기법을 정리할 때 편집기의 전체 치환을 그냥 돌리면 안 됩니다. 부분 일치 때문입니다.
user_id → userId로 치환하면 user_id_list도 userIdlist처럼 망가집니다. user_ids, parent_user_id처럼 앞뒤에 뭔가 붙은 이름이 다 걸립니다.
- 단어 경계를 포함해서 치환합니다 (
\buser_id\b) - 변수명 변경은 편집기의 이름 바꾸기(rename symbol) 기능을 씁니다. 문자열 치환이 아니라 코드 구조를 보고 바꾸므로 안전합니다
- 여러 이름을 한꺼번에 정리할 때는 목록을 변환기에 넣어 결과를 먼저 눈으로 확인하고 나서 적용합니다
작업 중간에 구조가 바뀔 때도 이 문제가 생깁니다. 저는 감싸는 요소가 하나 더 들어가 뎁스가 깊어지면서 클래스와 아이디 이름을 고쳐야 했던 적이 있습니다. 이름을 한꺼번에 바꿨더니 앞부분이 같은 하위 자식 클래스들까지 같이 바뀌어서 이름이 꼬였습니다. 바꿀 이름이 다른 이름의 앞부분이기도 하다면, 모두 바꾸기 대신 하나씩 확인하면서 바꾸는 편이 안전합니다.
요약
- 이름은 표기법이 다른 시스템의 경계에서 깨진다. 경계는 API·약어·파일명·CSS 네 군데다
- API 변환은 응답을 받는 한 지점에서만. 단 키 자체가 데이터인 응답에는 자동 변환을 걸지 않는다
- 약어도 한 단어로 취급한다 (
userId,apiUrl).userID는 왕복 변환에서 원래대로 돌아오지 않는다 - 파일명·URL은 소문자
kebab-case로 고정한다. 로컬(대소문자 무시)과 배포 서버(구분)의 차이를 아예 없앤다 - CSS는 속성·요소 이름은 대소문자를 안 가리지만 클래스와 커스텀 속성은 가린다
- 일괄 치환은 부분 일치로 망가진다. 단어 경계를 넣거나 편집기의 이름 바꾸기를 쓴다