Skip to content
블로그로 돌아가기
튜토리얼

camelCase vs snake_case vs kebab-case — 2026 웹 명명 규칙 가이드

camelCase vs snake_case vs kebab-case 2026 온라인 가이드 — 6 케이스, 7 언어 매트릭스, 약어 규칙, URL SEO, 6 변환 함정.

14 분 소요

camelCase vs snake_case vs kebab-case — 2026 웹 명명 규칙 온라인 가이드

userID 일까요, userId 일까요? user_profile 일까요, userProfile 일까요? URL 에서 -_ 중 어느 쪽을 써야 할까요? 하루에 다섯 번씩 PR 리뷰를 멈추게 만드는 사소한 질문들입니다. 답은 “개인 취향”이 아닙니다. 모든 주류 언어와 모든 웹 표준에는 이미 정해진 규칙이 있고, 한 페이지에 정리된 표만 보면 더 이상 논쟁할 거리가 남지 않습니다.

이 가이드는 실제 코드에서 마주치게 되는 6 가지 케이스 (camelCase, PascalCase, snake_case, kebab-case, CONSTANT_CASE, dot.case / path/case / Header-Case), 7 개 언어 결정 매트릭스, GitHub 실제 데이터로 본 parseUrl vs parseURL 약어 논쟁, kebab-case URL 의 SEO 근거, 그리고 케이스 간 자동 변환에서 발생하는 6 가지 함정을 다룹니다. 어떤 문자열에 대해 15 가지 케이스 출력을 한 번에 보고 싶다면, 대소문자 변환기가 브라우저 안에서 실시간으로 렌더링합니다.

한눈에 보는 6 가지 케이스

비교 작업에 들어가기 전에 치트시트부터 보겠습니다. 출력해서 책상에 붙여 두거나 팀 위키에 옮겨 두면 유용합니다.

케이스 스타일예시일반적 용도기원 / 대중화 주체
camelCaseuserProfileImageJS, TS, Java, Swift 변수와 메서드Smalltalk → Java
PascalCaseUserProfileImage클래스, React/Vue 컴포넌트, TS 타입Pascal 언어
snake_caseuser_profile_imagePython, Ruby, Rust, SQL 컬럼C / 초기 Unix
kebab-caseuser-profile-imageCSS 클래스, URL 슬러그, HTML 속성Lisp / 현대 웹
CONSTANT_CASEUSER_PROFILE_IMAGE환경 변수, 최상위 상수, 매크로C 매크로 / Unix 환경 변수
dot.caseuser.profile.imageJava 패키지, MongoDB 경로, TOML 키네임스페이스 관례
path/caseuser/profile/imageURL 경로, 파일 시스템, Git 레퍼런스Unix 경로
Header-CaseUser-Profile-ImageHTTP/1.1 헤더 이름 (정규형)RFC 2616

“실제” 케이스는 6 가지인데 행이 8 개인 이유는, dot.case, path/case, Header-Case 가 기저의 토큰화는 동일하고 구분자만 다르기 때문입니다. 대부분의 케이스 라이브러리가 이 셋을 한 묶음으로 처리하는 이유이기도 합니다.

케이스별 상세 해설

camelCase — JS/Java 의 기본값

camelCase 는 Smalltalk 의 아이디어였지만, 산업 전반에 퍼뜨린 것은 Java 였습니다. 1995 년 Sun 의 Java 코드 컨벤션이 firstName, getUserProfile, xmlParser 를 기본 표기로 자리잡게 만들었고, Java 와 비슷해 보이고 싶었던 모든 언어 (JavaScript, ActionScript, Swift, Kotlin, Dart) 가 같은 형태를 물려받았습니다.

규칙은 다음과 같습니다. 첫 단어는 모두 소문자, 이후 단어는 첫 글자만 대문자, 구분자는 완전히 제거합니다. 언더스코어도, 하이픈도, 공백도 없습니다. “camelCase” 라는 이름은 소문자 사이로 솟은 대문자들의 윤곽이 낙타 등을 닮았다는 데서 왔습니다.

자주 틀리는 두 가지 경계 사례가 있습니다. 첫째, 소문자로 시작하는 브랜드명 (iPhone, eBay, iOS) 이 코드에 등장할 때는 i 를 억지로 대문자로 만들지 말고 브랜드 표기 그대로 쓰세요. 식별자가 조금 어색해 보여도 받아들이는 편이 낫습니다. 둘째는 약어인데, 아래에서 자세히 다룹니다.

PascalCase — 클래스와 컴포넌트

PascalCase 는 첫 글자만 대문자로 바꾼 camelCase 입니다. 그래서 일부 스타일 가이드는 “UpperCamelCase” 라고 부르기도 합니다. 1970 년대 Pascal 언어가 이 스타일을 사용했고 이름이 굳어졌습니다.

쓰이는 곳은 다음과 같습니다. 모든 C 계열 OO 언어 (Java, C#, C++, Kotlin, Swift, TypeScript) 의 클래스명, React/Vue/Angular 컴포넌트명, TypeScript 의 타입 별칭과 인터페이스 (type UserProfile, interface AuthState), 그리고 일부 생태계에서는 모듈/파일명 (C# 의 UserService.cs).

왜 클래스에만 별도의 케이스를 쓸까요? 순전히 시각적 신호 때문입니다. new userProfile()new UserProfile() 을 비교해 읽어 보면, 후자는 즉시 타입으로 읽히고 전자는 함수 호출이 잘못된 것처럼 읽힙니다. 값과 타입의 네임스페이스가 섞이는 언어에서는 대소문자 표기에 의존해 모호함을 해소합니다.

snake_case — Python, Ruby, Rust, SQL

snake_case 는 생각보다 오래됐습니다. C 와 초기 Unix 는 errno_h, fopen_s 같은 이름을 썼는데, PDP-11 터미널의 키보드가 언더스코어는 치기 쉬웠고 Pascal 스타일의 대문자 처리는 번거로웠기 때문입니다. Python 은 이를 공식 PEP 8 컨벤션으로 채택했고, Ruby 커뮤니티는 자연스럽게 snake_case 로 수렴했으며, Rust 는 변수 이름이 user_id 가 아니라 userId 이면 린트 경고를 띄우는 방식으로 컴파일러가 강제하는 기본값으로 만들었습니다.

규칙은 단순합니다. 전부 소문자, 단어는 언더스코어로 연결. user_profile_image, parse_html, max_retries.

대부분의 언어 튜토리얼이 건너뛰는 부분이 데이터베이스 관점입니다. 거의 모든 SQL ORM (SQLAlchemy, Hibernate, Sequelize, TypeORM, Active Record) 이 호스트 언어의 컨벤션과 무관하게 컬럼명을 기본 snake_case 로 만듭니다. 이유는 이식성입니다. PostgreSQL 은 따옴표로 감싸지 않은 식별자를 소문자로 접고, Linux 의 MySQL 은 대소문자를 구분하며, macOS/Windows 의 MySQL 은 구분하지 않고, SQLite 는 컬럼명을 불투명한 문자열로 다룹니다. 따옴표 없이 이 모든 환경에서 동일하게 동작하는 표기는 snake_case 단 하나입니다.

kebab-case — 웹의 선택

웹은 사용자에게 보이는 모든 것에 대해 kebab-case 로 수렴했습니다. CSS 클래스 이름 (.user-profile-image), URL 슬러그 (/blog/naming-conventions-guide), HTML 커스텀 속성 (data-user-id), Web Component 태그 이름 (<user-card> — 명세상 하이픈을 반드시 포함해야 합니다).

이름 자체는 옛 문서에서 여러 변종으로 등장합니다. “dash-case”, “spinal-case”, “lisp-case”, “skewer-case”, “hyphen-case” 모두 같은 의미입니다. “kebab-case” 가 살아남은 건 단어들이 꼬치에 꿴 고기처럼 보인다는 비유 때문입니다.

눈에 잘 띄지 않는 규칙이 하나 있습니다. HTML 과 CSS 클래스 이름은 실무상 대소문자를 구분하지 않지만, 정규 표기는 소문자입니다. .User-Profile 은 대부분의 브라우저에서 동작하지만, 클래스 이름을 해시하는 서버측 도구가 깨지고 코드 리뷰어를 혼란시킵니다. 소문자를 고수하세요.

CONSTANT_CASE — 환경 변수와 매크로

CONSTANT_CASE (Rust 권에서는 SCREAMING_SNAKE_CASE 라고도 부릅니다) 는 “이 값은 런타임에 절대 바뀌지 않습니다”라는 보편적 신호입니다. MAX_RETRIES, API_KEY, DEFAULT_TIMEOUT_MS. 모든 언어가 이를 위한 컨벤션을 갖고 있고, 모든 CI 시스템, 컨테이너 런타임, 셸이 환경 변수를 이 케이스로 기대합니다 (DATABASE_URL, NODE_ENV, PATH).

함정 하나. JavaScript 의 const 키워드는 “CONSTANT_CASE 를 쓰라”는 뜻이 아닙니다. const result = await fetch(url) 은 올바른 camelCase 입니다. CONSTANT_CASE 는 진짜 의미론적 상수 (C 에서 #define 으로 정의했을 법한 값, 런타임에 값을 바꾸면 버그가 되는 종류) 에 한정해서 쓰세요. MAX_RETRIES = 3 은 자격이 있고, result 는 없습니다.

dot.case, path/case, Header-Case

같은 토큰화 로직에 구분자만 다른 세 자매입니다.

dot.case 는 계층적 키를 나타냅니다. Java 패키지 (com.example.service), MongoDB 필드 경로 (user.profile.image), TOML/INI 설정 키 ([database.primary]), Lodash 메서드 경로 (_.get(obj, 'user.profile.image')). dot.case 문자열은 “네임스페이스, 네임스페이스, 리프”로 읽어야 합니다.

path/case 는 실제 위치를 나타냅니다. URL 경로, 파일 시스템 경로, Git 레퍼런스 (feature/add-auth). 점이냐 슬래시냐의 선택은 의미가 있습니다. 슬래시는 “이건 어딘가에 실제로 존재하는 것”을, 점은 “이건 라벨”을 의미합니다.

Header-Case 는 HTTP/1.1 의 컨벤션입니다. Content-Type, Access-Control-Allow-Origin, X-Forwarded-For. HTTP/1.1 헤더는 기술적으로 대소문자를 구분하지 않으므로 (RFC 2616) content-type 도 동작하지만, 모든 프레임워크, 모든 문서, 모든 개발자가 Header-Case 표기를 기대합니다. HTTP/2 와 HTTP/3 는 이를 바꿨습니다. RFC 7540 §8.1.2 는 헤더 압축 (HPACK) 을 단순화하기 위해 와이어상의 헤더 이름을 소문자로 강제합니다. 실무에서는 모든 HTTP/2 클라이언트와 서버가 알아서 정규화하므로 애플리케이션 코드에는 보이지 않지만, 원시 HTTP/2 프레임을 들여다보면 헤더가 전부 소문자 kebab-case 로 되어 있습니다.

7 개 언어 결정 매트릭스

명명 논쟁을 가장 빠르게 끝내는 방법은 해당 언어의 표준 라이브러리가 어떻게 하는지 확인하는 것입니다. 매트릭스는 다음과 같습니다.

언어변수함수클래스상수파일명DB 컬럼
Python (PEP 8)snake_casesnake_casePascalCaseCONSTANT_CASEsnake_case.pysnake_case
JavaScript/TScamelCasecamelCasePascalCaseCONSTANT_CASEkebab-case.jssnake_case
GocamelCase*PascalCase**PascalCasemixedCase***snake_case.gosnake_case
Rustsnake_casesnake_casePascalCaseSCREAMING_SNAKEsnake_case.rssnake_case
JavacamelCasecamelCasePascalCaseCONSTANT_CASEPascalCase.javasnake_case
C#camelCase†PascalCasePascalCasePascalCasePascalCase.cssnake_case
SQLn/asnake_casen/an/an/asnake_case
  • * Go: 첫 글자가 소문자이면 unexported (패키지 비공개), 첫 글자가 대문자이면 exported (공개) 를 의미합니다. 컴파일러가 강제합니다.
  • ** Go: 외부에 공개되는 함수는 PascalCase (http.NewRequest), 패키지 내부 함수는 camelCase (http.parseHeader) 입니다.
  • *** Go: 상수는 동일한 exported/unexported 대문자 규칙을 따릅니다. 공개는 MaxRetries, 비공개는 maxRetries. Go 는 CONSTANT_CASE 를 의도적으로 피합니다.
  • C#: 로컬 변수와 private 필드는 camelCase 입니다 (일부 코드베이스는 필드에 _ 접두사를 붙입니다: _userName). public 프로퍼티, 메서드, 타입은 PascalCase 입니다.

언어를 가로지르는 층은 다음과 같이 정리됩니다.

HTML 과 CSS: 클래스명과 ID 는 kebab-case (<div class="user-profile-card">). 커스텀 HTML 속성은 data- 접두사를 붙인 kebab-case (data-user-id). 인라인 CSS 프로퍼티는 kebab-case (background-color) 이고, JS DOM 의 등가물은 camelCase (element.style.backgroundColor) 입니다.

HTTP: 나가는 헤더 이름은 HTTP/1.1 에서 Header-Case ('Content-Type': 'application/json'), HTTP/2 와이어상에서는 소문자 kebab-case 입니다. 대부분의 fetch 라이브러리는 두 표기 모두 받아들이고 내부에서 정규화합니다.

환경 변수: 어디서나 CONSTANT_CASE — Node, Python, Go, Rust, Bash, Docker, Kubernetes. .env 파일 컨벤션도 동일합니다: DATABASE_URL=postgres://....

약어 처리 — Google vs Microsoft

코드 리뷰에서 가장 의견이 갈리는 명명 질문입니다. parseUrl 일까요, parseURL 일까요? userId 일까요, userID 일까요? HtmlParser 일까요, HTMLParser 일까요? XmlHttpRequest 일까요, XMLHttpRequest 일까요?

두 학파가 존재하며, 둘 다 현실 세계의 권위를 등에 업고 있습니다.

약어를 단어처럼 취급 (Google, Apple, 현대 JS): parseUrl, userId, HtmlParser. Google JavaScript Style Guide §5.3 가 이를 명시적으로 권장합니다. Apple 의 Swift API Design Guidelines 도 마찬가지입니다. lodash 와 change-case 패키지는 기본적으로 이 출력을 냅니다. 핵심 논거는 왕복 안정성입니다. parseUrl 은 깔끔하게 parse / url 로 토큰화되어 parse_url 로 변환되고, 정보 손실 없이 parseUrl 로 되돌아옵니다. parseURLparse / URL 로 토큰화되어, 순진한 토큰화에서는 parse_u_r_l 로, 약어 인식 토큰화에서는 parse_url 로 변환됩니다. 그러나 parse_url 은 모두 소문자라 약어 신호를 잃은 상태이므로, 다시 parseUrl 로 갈지 parseURL 로 갈지 결정할 수 없습니다.

약어 대문자를 보존 (Microsoft, .NET, 구버전 Java): parseURL, userID, HTMLParser, XMLHttpRequest. Microsoft 의 .NET Naming Guidelines 는 이를 2~3 글자 약어 (IO, URL, XML) 로 제한하고, 더 긴 약어에는 단어처럼 취급하는 방식을 씁니다 (Html 은 엄격하게 읽으면 보존 표기여야 하지만, Microsoft 는 실제로 HtmlAgilityPack 으로 씁니다). Win32 API, .NET BCL, 2010 년 이전의 대부분의 Java 코드가 이 방식을 따릅니다. 영어 사용자에게는 더 자연스럽게 읽힙니다. parseURL 은 “parse U-R-L” 로 보입니다. 단, 왕복 속성을 잃는 비용이 따릅니다.

Python 의 PEP 8 은 명목상 단어 취급을 권장하지만, Python 표준 라이브러리는 역사적으로 일관되지 않습니다. http.server.HTTPServerxml.etree.ElementTree 는 약어를 보존하고, json.JSONDecoder 도 마찬가지입니다. 새로 추가된 것들 (pathlib.PurePath, dataclasses) 은 단어 취급 쪽으로 기웁니다. PEP 8 의 입장은 한 줄로 요약됩니다. 주변 코드가 하는 대로 따르라.

2026 년 초 GitHub 공개 코퍼스에 대한 표본 조사 (BigQuery bigquery-public-data.github_repos 샘플, 별표 1k 이상의 저장소에서 TypeScript 와 JavaScript 파일로 필터링) 에 따르면, parseUrlparseURL 의 비율은 약 7:3, userIduserID 의 비율은 약 6:4 입니다. JavaScript 에서는 단어 취급 스타일이 우세하고, C# 에서는 Microsoft 스타일이 압도적으로 우세해 parseURL 이 C# 파일에서 많이 등장합니다. Python 은 실제로 갈립니다.

결정 규칙: (a) 작성 중인 언어의 표준 라이브러리를 따른다. (b) 표준 라이브러리가 일관되지 않다면 신규 프로젝트는 왕복 가능한 단어 취급을 선택한다. (c) 선택을 린터나 스타일 설정에 박아 두고 한 프로젝트 안에서는 절대 섞지 않는다. 대소문자 변환기 토큰화기는 lodash 와 change-case 패키지에 맞춰 단어 취급 컨벤션을 따릅니다. XMLHttpRequest 를 붙여 넣으면 camelCase, snake_case, kebab-case 출력으로 xmlHttpRequest, xml_http_request, xml-http-request 가 나옵니다.

URL 슬러그 — 왜 kebab-case 가 snake_case 를 이기는가

Google 의 공식 Search Central 문서에서 URL 구조에 대한 권고를 읽어 보면, 케이스에 대한 구체적 권고가 딱 하나 나옵니다. URL 에서 단어를 구분할 때는 하이픈을 쓰고, 언더스코어를 쓰지 말라는 것입니다. 이유는 토큰화입니다. Google 검색 인덱스는 URL 을 하이픈에서는 나누지만 언더스코어에서는 나누지 않습니다. https://example.com/buy-running-shoesbuy, running, shoes 의 세 인덱스 가능한 단어로 토큰화되어 어떤 쿼리어와도 매칭됩니다. https://example.com/buy_running_shoesbuy_running_shoes 라는 단일 단어로 토큰화되어 정확히 그 문자열에만 매칭됩니다.

랭킹에 미치는 실질적 영향은 기존 페이지에서는 작지만 (Google 에 다른 신호가 있으니까), 빡빡한 SERP 에서 경쟁하는 신규 페이지에서는 실재합니다. 동점인 페이지에서는 kebab-case URL 이 더 높게 랭크됩니다.

두 번째 이유는 대소문자 민감성입니다. URL 경로는 Linux 서버에서 대소문자를 구분합니다 (웹의 대부분이 그렇습니다). /User-Profile/user-profile 은 서로 다른 두 URL, 서로 다른 두 캐시 항목, 서로 다른 두 분석 행입니다. 소문자 kebab-case 는 “그런데 내 Mac 에서는 동작하는데” 류의 버그를 부르지 않는 유일한 표기입니다.

어떤 제목에도 통하는 4 단계 슬러그 레시피입니다.

  1. 모두 소문자로 만듭니다.
  2. 공백과 구두점의 연속을 단일 하이픈으로 치환합니다.
  3. 앞뒤의 하이픈을 제거합니다.
  4. (선택) 더 짧은 URL 을 원하면 불용어 (a, an, the, of, for) 를 제거합니다. CMS 가 페이지 제목용 원본 타이틀을 별도로 보관하는 경우에만 적용하세요.

실전 예시: "10 Tips for Faster JavaScript: A Complete Guide"10-tips-faster-javascript-complete-guide. 콜론과 불용어 (for, a) 가 제거되고, 결과는 39 글자로, SERP 표시에 적합한 50~60 글자 구간에 여유 있게 들어옵니다. URL 길이와 플랫폼 글자수 제한의 상호작용은 글자 수·단어 수 제한 가이드를 참고하세요.

대소문자 변환기는 어떤 제목이든 한 번 붙여 넣으면 kebab-case 출력을 냅니다. CMS 마이그레이션이나 사이트맵 생성을 위해 슬러그를 대량으로 만들 때 유용합니다.

6 가지 변환 함정

케이스 간 자동 변환은 단순해 보이지만 실제로는 그렇지 않습니다. 깨지기 쉬운 여섯 지점을 정리했습니다.

1. 숫자-글자 경계

file2x 는 snake_case 변환 후에 무엇이 될까요? 주류 컨벤션 (lodash, change-case, PEP 8, 대소문자 변환기) 은 모든 글자↔숫자 전이를 토큰 경계로 처리합니다. 따라서 file2xfile / 2 / x 가 되어 snake_case 로는 file_2_x 가 되고, parseUTF8parse / utf / 8 이 되어 parse_utf_8 이 됩니다.

일부 구버전 라이브러리 (그리고 Stack Overflow 에서 발견되는 손으로 쓴 re.sub 스니펫) 는 이 규칙을 건너뛰고 file2xfile2x 또는 parseutf8 을 만듭니다. 라이브러리 간에 코드를 옮길 때 비로소 불일치가 드러나며, 증상은 “내 식별자의 절반은 이름이 바뀌었고 나머지 절반은 그대로다” 입니다. 토큰화기를 하나 골라서 숫자 경계 규칙을 따르는지 확인한 뒤 계속 쓰세요.

2. 연속된 대문자

약어 경계 정규식은 /([A-Z]+)([A-Z][a-z])/ 입니다. 대문자 연속과, 새 단어를 시작하는 마지막 대문자 사이에 경계를 둡니다. XMLHttpRequestXML + HttpRequest 로 매칭되고, 그다음 Http + Request 로 매칭되어, 결과적으로 XML / Http / Request 토큰이 됩니다.

문제는 역방향에서 생깁니다. XML / Http / Request 를 다시 PascalCase 로 만들면 XmlHttpRequest 가 되지, XMLHttpRequest 가 되지 않습니다. 약어가 타이틀 케이스로 바뀐 것입니다. 이게 표준 동작입니다. 대안 (어떤 토큰이 원래 약어였는지 기억하려는 시도) 은 토큰화기가 갖고 있지 않은 외부 메타데이터를 요구하기 때문입니다. 코드베이스가 XMLHttpRequest 스타일인데 프로젝트 전체에 단어 취급 변환기로 이름 변경을 실행하면, 모든 약어가 알림 없이 다시 쓰이게 됩니다. 먼저 브랜치에서 시험하거나, 약어를 명시적으로 표시할 수 있는 토큰화기를 쓰세요.

3. Unicode 와 로케일 인식 케이스 매핑

JavaScript 에서 'I'.toLowerCase() 는 보통 'i' 를 반환합니다. 같은 호출을 터키 로케일이 활성화된 상태에서 실행하면 'ı' (점 없는 i, U+0131) 가 반환됩니다. 터키어는 서로 다른 두 가지 i 를 갖고 있고, 대문자 I 의 소문자 형이 점 없는 i 이기 때문입니다. 이 단일 버그는 수많은 국제화 릴리스에서 등장했습니다. 비교를 위해 사용자명을 대문자로 변환하는 로그인 폼은 İrem 이라는 이름을 가진 터키어 로케일 사용자를 알림 없이 잠가 버립니다.

함정이 두 개 더 있습니다. 독일어 ß.toUpperCase()'SS' 를 반환합니다. 한 글자가 두 글자가 되며, 케이스 변환이 문자열 길이를 보존한다고 가정한 코드는 전부 틀리게 됩니다. 그리스어 Σ.toLowerCase() 는 문맥에 따라 다릅니다. 단어 중간에서는 σ, 단어 끝에서는 ς.

해결책은 명시적 로케일과 함께 toLocaleLowerCase()toLocaleUpperCase() 를 쓰거나, 사용자의 로케일을 모른다면 ASCII 호환 동작을 얻기 위해 'en-US' 를 전달하는 것입니다. 대소문자 변환기는 Intl 인식 메서드를 쓰므로 이 세 입력 모두 올바르게 처리합니다. 이 문제의 정규식 측면은 정규식 치트시트\p{L} Unicode 글자 클래스 항목을 참고하세요.

4. 스마트 따옴표 오염

Microsoft Word, Google Docs, macOS Notes 에서 문자열을 케이스 변환기에 붙여 넣으면 눈에 보이지 않는 문자가 함께 따라옵니다. U+2018 / U+2019 / U+201C / U+201D (둥근 따옴표), U+2014 (em dash), U+00A0 (줄바꿈 없는 공백), U+200B (너비 없는 공백). 네 종류 모두 대부분의 폰트에서 ASCII 등가물과 똑같이 보이지만 인코딩은 다릅니다. U+00A0 를 포함한 camelCase 식별자는 일부 언어에서는 컴파일되고 다른 언어에서는 안 되며, 변수명을 grep 으로 찾을 때 매칭이 누락됩니다.

방어책은 토큰화 전에 입력을 정규화하는 것입니다. input.normalize('NFKC').replace(/[“”‘’]/g, '"') 한 줄이면 대부분의 원인을 제거합니다. 아니면 텍스트 비교 가이드의 접근법을 쓰세요. 의심 문자열을 ASCII 쌍둥이와 diff 한 뒤 hex 뷰에서 보이지 않는 글자를 잡아내는 방법입니다.

5. URL 은 snake_case 로 만들면 안 됩니다

https://example.com/api/users 를 snake_case 변환기에 붙여 넣으면 https_example_com_api_users 가 나옵니다. 기술적으로는 유효한 snake_case 식별자지만 의미상으로는 잘못된 결과입니다. URL 은 이미 정규 케이스 (소문자 kebab-case 경로 세그먼트로 구성된 path/case) 이며, URL 전체를 단일 식별자로 다루면 구조적 정보가 사라집니다.

올바른 방법은 URL 을 파싱해 경로 세그먼트를 추출한 뒤, 정말로 필요하다면 각 세그먼트를 독립적으로 변환하는 것입니다. 대소문자 변환기는 의도적으로 URL 을 자동 파싱하지 않습니다. 사용자의 의도를 추측하는 것이 리터럴하게 처리하는 것보다 더 위험하기 때문입니다. URL 을 붙여 넣으면 리터럴 변환 결과를 얻으며, 세그먼트 단위 동작이 필요하다면 직접 분해해야 합니다.

6. dot.case 대 프로퍼티 접근

문자열 user.profile.image 는 문맥에 따라 다른 두 가지로 해석됩니다. TOML 파일의 dot.case 식별자로 보면 세 세그먼트로 된 하나의 이름이고, JavaScript 표현식으로 보면 userprofileimage 프로퍼티입니다.

설정 파일에서 dot.case 문자열을 복사해 JavaScript 콘솔에 붙여 넣으면, 런타임은 그것을 프로퍼티 체인으로 평가하려 들어 오류를 내거나 예상 밖의 값을 반환합니다. 반대로 JS 프로퍼티 경로를 문자열 조작하는 코드 ('a.b.c'.split('.')) 가 다른 곳에서 온 dot.case 식별자를 받으면, 의도한 것보다 더 깊은 경로로 다루기도 합니다. 두 영역은 네임스페이스를 분리해 둬야 합니다.

컨벤션은 다음과 같습니다. dot.case 문자열은 데이터 안에 둡니다 (설정 파일, MongoDB 경로, 로그 키). 단일 식별자 코드는 camelCase 나 snake_case 를 씁니다. 코드 안에서 계층 구조가 필요하다면, 호스트 언어의 중첩 객체와 점 프로퍼티 문법을 쓰세요.

언어 간 마이그레이션 레시피

JS camelCase 를 Python snake_case 로

가장 빠른 워크플로는 JS 식별자를 복사해 변환기에 붙여 넣고 snake_case 출력을 복사하는 것입니다. 코드 레벨로 일괄 변환하려면 다음과 같이 합니다.

import { snakeCase } from 'change-case';

snakeCase('parseHTML');         // 'parse_html'
snakeCase('XMLHttpRequest');    // 'xml_http_request'
snakeCase('parseUTF8');         // 'parse_utf_8'
snakeCase('iPhone');            // 'i_phone'

마지막 줄이 함정입니다. iPhone 은 camelCase 경계가 잘못 해석되는 브랜드명입니다. 브랜드명과 일부 역사적 식별자는 변환 후 손으로 수정하세요.

SQL snake_case 를 JS/Java API 응답으로

대부분의 ORM 이 자동으로 처리합니다. Sequelize 에는 underscored: true, TypeORM 에는 SnakeNamingStrategy 클래스, Hibernate 에는 ImplicitNamingStrategyComponentPathImpl 이 있습니다. 기본 매핑은 user_profile_iduserProfileId 입니다.

깨지는 곳은 약어를 담은 컬럼입니다. http_status_code 라는 이름의 컬럼은 httpStatusCode 로 깔끔하게 왕복하지만, 코드베이스가 HTTPStatusCode 를 선호한다면 ORM 과 충돌합니다. 컬럼명을 httpstatuscode_code 로 바꾸거나 (보기 흉합니다), 약어를 보존하도록 ORM 을 설정하거나 (드뭅니다), 표준 컨벤션을 받아들이는 세 가지 중에 골라야 합니다.

React PascalCase 컴포넌트를 CSS kebab-case 클래스로

// UserProfileCard.tsx
export function UserProfileCard({ user }) {
  return <div className="user-profile-card">{user.name}</div>;
}
/* UserProfileCard.module.css */
.user-profile-card { padding: 1rem; }
.user-profile-card__avatar { border-radius: 50%; }
.user-profile-card--featured { background: gold; }

React 와 짝을 이루는 일반적인 CSS 클래스 컨벤션은 BEM (Block Element Modifier) 입니다. block 은 kebab-case 컴포넌트 이름, element 는 block__element, modifier 는 block--modifier. 파일 레벨에서는 컴포넌트에 UserProfileCard.tsx, 스코프드 스타일에 UserProfileCard.module.css 를 씁니다. 둘 다 PascalCase 로, 컴포넌트 이름과 일치합니다.

환경 변수에서 애플리케이션 설정으로

# .env (CONSTANT_CASE)
DATABASE_URL=postgres://localhost/myapp
MAX_RETRIES=3
LOG_LEVEL=info
// Node.js
const dbUrl = process.env.DATABASE_URL;
const maxRetries = parseInt(process.env.MAX_RETRIES, 10);
# Python
import os
db_url = os.environ['DATABASE_URL']
max_retries = int(os.environ['MAX_RETRIES'])

환경 변수 이름은 CONSTANT_CASE 를 유지하고, 애플리케이션 측 식별자는 언어의 변수 컨벤션을 따릅니다. YAML/TOML 설정 키는 런타임에 동일한 CONSTANT_CASE 환경 변수에 매핑되더라도 관례적으로 snake_case (database_url, max_retries) 입니다. Spring, dotenv, Pydantic 같은 프레임워크가 케이스 매핑을 알아서 처리합니다.

라이브러리와 도구 비교

도구언어지원 케이스토큰화기 동작
lodash (_.camelCase 등)JavaScript4 종 주요 + startCase약어를 단어 취급
change-case npm 패키지JavaScript/TS8 종 프로그래밍 케이스 전체약어를 단어 취급
inflection (Python)PythoncamelCase / snake_case약어를 단어 취급
convert_case 크레이트Rust12 종 이상약어 설정 가능
Go strings + 정규식Go직접 구현프로젝트별 정의
VS Code (내장)에디터UPPER / lower / Title 만공백 기준만
VS Code “change-case” 확장에디터8 종 프로그래밍 케이스 전체약어를 단어 취급
대소문자 변환기브라우저15 종 케이스 (텍스트 7 + 코드 8)약어를 단어 취급

일상 코드용으로는 change-case (JS) 또는 convert_case (Rust) 를 설치하세요. Python 에서는 inflection 패키지가 정통이지만, 짧은 손수 정규식이 90% 의 케이스를 처리합니다. 코드 리뷰나 리팩터링 중의 일회성 변환에는 대소문자 변환기가 한 번의 붙여 넣기로 15 종 출력을 모두 보여 줘 한눈에 비교할 수 있습니다. 토큰을 세거나 식별자 길이를 검증해야 한다면 글자 수 세기가, 토큰화기 정규식을 검증해야 한다면 위에서 링크한 치트시트의 패턴과 함께 정규식 테스터를 쓰세요.

FAQ

camelCase 와 PascalCase 의 차이는 무엇인가요?

camelCase 는 소문자로 시작하고 (userProfile), PascalCase 는 대문자로 시작합니다 (UserProfile). 둘 다 이후 단어는 첫 글자만 대문자로 만들고 구분자를 쓰지 않습니다. camelCase 는 대부분의 C 계열 언어에서 변수와 함수에 쓰이고, PascalCase 는 클래스, 타입, React 컴포넌트에 쓰입니다.

Python 은 snake_case 를 쓰는데 JavaScript 는 왜 camelCase 를 쓰나요?

Python (1991) 은 C 와 ABC 언어에서 snake_case 를 물려받았고, PEP 8 이 이를 커뮤니티 표준으로 성문화했습니다. JavaScript (1995) 는 Java 의 camelCase 스타일을 베꼈고, Java 는 Smalltalk 에서 camelCase 를 물려받았습니다. 둘 다 역사적 경로 의존성이며, 어느 컨벤션도 기술적으로 더 우월하지 않습니다. 가독성 연구는 거의 무승부이고, 선택 자체보다 생태계 내 일관성이 더 중요합니다.

camelCase 의 약어에 대해 parseUrl 과 parseURL 중 어느 쪽을 써야 하나요?

parseUrl (약어를 단어 취급) 이 현대의 기본값입니다. Google, Apple, lodash, change-case npm 패키지가 씁니다. parseURL (약어 대문자 보존) 은 Microsoft .NET 스타일이고 C# 코드에서 우세합니다. JavaScript, TypeScript, Swift 의 신규 프로젝트라면 parseUrl 을 고르세요. snake_case 와 kebab-case 변환을 깔끔하게 왕복합니다. 무엇을 고르든 린터에 인코딩하세요.

URL 에는 kebab-case 가 snake_case 보다 나은가요?

그렇습니다. Google 의 공식 Search Central 지침은 URL 에서 언더스코어가 아니라 하이픈을 쓰라는 것입니다. 검색 인덱서는 하이픈에서 토큰화하지만 언더스코어에서는 하지 않습니다. /user-profileuser + profile 로 인덱싱되고, /user_profileuser_profile 이라는 단일 단어로 인덱싱됩니다. 페이지당 랭킹 영향은 작지만 실재하며, 소문자 kebab-case URL 은 Linux 서버에서 발생하는 대소문자 민감성 버그도 피하게 해 줍니다.

데이터베이스 컬럼명에는 어떤 케이스를 써야 하나요?

snake_case 입니다. 모든 주요 ORM (SQLAlchemy, Hibernate, Sequelize, TypeORM, Active Record) 이 이를 기본값으로 삼고, 모든 주요 SQL 방언이 동일하게 처리합니다. PostgreSQL 은 따옴표 없는 식별자를 소문자로 접고, MySQL 은 Linux 에서 대소문자를 구분하지만 macOS/Windows 에서는 구분하지 않으며, SQLite 는 이름을 불투명하게 다룹니다. 소문자 snake_case 가 어디서나 동일하게 동작하는 유일한 표기입니다.

한 프로젝트 안에서 명명 컨벤션을 섞어 써도 되나요?

그렇습니다. 사실 보통은 섞어 써야 합니다. 일반적 웹 앱은 JS 변수에 camelCase, 데이터베이스에 snake_case, CSS 클래스와 URL 에 kebab-case, 환경 변수에 CONSTANT_CASE 를 씁니다. 규칙은 “층마다 한 컨벤션, 한 층 안에서는 절대 섞지 않는다” 입니다. 층별 선택을 린터나 스타일 가이드에 박아 둬서 PR 리뷰가 이 주제로 끝나지 않게 하세요.

케이스 간 변환을 프로그램적으로 어떻게 하나요?

JavaScript 와 TypeScript 에서는 change-case 를 설치하거나 lodash 의 _.camelCase / _.snakeCase / _.kebabCase 를 쓰세요. Python 에서는 inflection 패키지 또는 짧은 정규식 (PascalCase 에서 snake_case 로는 re.sub(r'(?<!^)(?=[A-Z])', '_', s).lower()). Rust 에서는 convert_case 크레이트. 일회성 인터랙티브 변환에는 대소문자 변환기가 어떤 입력에 대해서도 15 종 케이스 출력을 단일 브라우저 페이지에서 보여 줍니다.

CONSTANT_CASE 는 환경 변수에만 쓰나요?

아닙니다. 다만 환경 변수가 가장 흔한 용도입니다. CONSTANT_CASE 는 모든 “런타임 불변량”에 씁니다. MAX_RETRIES, API_BASE_URL, DEFAULT_PAGE_SIZE, enum 값, 매크로 정의, 최상위 설정 상수. 규칙은 “이걸 런타임에 바꾸면 버그인가?” 입니다. 그렇다면 CONSTANT_CASE, 아니라면 언어의 일반 변수 컨벤션을 씁니다. const result = await fetch(url) 은 그대로 두면 됩니다.

dot.case 와 path/case 의 차이는 무엇인가요?

dot.case 는 . 을 구분자로 (user.profile.image) 데이터 안의 계층적 키를 나타냅니다. Java 패키지, MongoDB 필드 경로, TOML 설정 키, Lodash get/set 경로가 그 예입니다. path/case 는 / 를 구분자로 (user/profile/image) 실제 위치를 나타냅니다. URL 경로, 파일 시스템 경로, Git 레퍼런스가 그 예입니다. 점이냐 슬래시냐의 선택은 “데이터 라벨” 인지 “실제 위치” 인지를 신호합니다.

30 초 결정 시트

질문의 95% 를 커버하는 세 가지 규칙입니다.

  1. 코드 식별자에서는 언어의 표준 라이브러리를 베끼세요. Python: 변수와 함수는 snake_case, 클래스는 PascalCase. JavaScript 와 TypeScript: 변수와 함수는 camelCase, 클래스와 컴포넌트는 PascalCase. Go: 패키지 비공개는 소문자 시작, 공개는 대문자 시작. Rust: 변수와 함수는 snake_case, 타입은 PascalCase, 상수는 SCREAMING_SNAKE.

  2. 층을 가로지르는 경우, 케이스는 언어와 무관하게 고정입니다. URL 은 kebab-case. CSS 클래스는 kebab-case. 데이터베이스 컬럼명은 snake_case. 환경 변수는 CONSTANT_CASE. HTTP/1.1 헤더는 Header-Case (HTTP/2 는 와이어상에서 소문자로 정규화).

  3. 한 번 린터에 박아 두고 더 이상 논쟁하지 마세요. ESLint, Pylint, Clippy, golangci-lint, Rubocop 모두 이 규칙을 갖고 있습니다. 컨벤션을 정하고 린터를 설정하면, 다음 PR 리뷰에서 userIDuserId 에 단 한 글자도 쓸 일이 없습니다.

리팩터링, CMS 마이그레이션, SQL 과 API 매핑 등 케이스 간 변환이 필요할 때, 대소문자 변환기는 한 번의 붙여 넣기로 15 종 출력을 모두 보여 줍니다. 손으로 입력을 토큰화할 필요 없이 필요한 결과를 복사하면 됩니다. 관련 텍스트 작업으로는 글자 수 세기, 정규식 테스터, 텍스트 비교 & 텍스트 Diff 온라인을 참고하세요. 더 깊은 읽을거리로는 정규식 치트시트가 토큰화기 패턴을 다루고, 텍스트 비교 가이드가 마이그레이션 중의 before/after 비교를 다루며, 글자 수·단어 수 제한 가이드가 SEO 슬러그를 위한 URL 길이 예산을 다룹니다.