생성 표기 — 멤버와 파일 이름
5. MemberCase — 생성 멤버 표기의 재정의
구현 완료 (2026-08-20). 설계 시점에는 「각 타깃에 옵션 하나 추가」로 적었고, 실측이 그것을 뒤집었습니다 — §5.2에 기록합니다.
각 코드생성 타깃에 옵션 하나를 추가합니다. 레코드 필드 멤버(프로퍼티·게터)의 표기를
정하고, 값 어휘는 §3.1과 같습니다(kebab 제외). 기본값은 각 언어의 현행 관용입니다.
| 기본값 | 언어 |
|---|---|
pascal | C# |
camel | Dart · Java · Kotlin · Lua · PHP · Swift · TypeScript |
snake | C · C++ · Python · Ruby · Rust |
-
Go에는 제공하지 않습니다. 첫 글자의 대소문자가 가시성(export)을 결정하므로, 표기 재정의가 생성 코드의 의미를 바꿉니다 — 소비자가 멤버를 읽을 수 없게 됩니다.
-
Unreal에도 제공하지 않습니다. 설계 시점에는
pascal기본값으로 넣을 생각이었는데, 구현하면서 멤버 이름이 표기가 아니라는 것이 드러났습니다. bool UPROPERTY는bIsOpen— 소문자b뒤에 Pascal이고, 접두어를 멤버의 타입이 고릅니다. 그 형태에는 옮겨 갈 snake도 camel도 없습니다(b+ snake는bis_open이고 어느 관례도 아닙니다). UHT가 이 선언을 읽는 것도 함께 걸립니다.이것은 한 번 잘못 넣었다가 뺀 자리입니다. 옵션을 선언해 두고 헬퍼는 계속 Pascal을 적고 있었으므로 조용히 무시되는 설정이 되었고, 그것은 없는 것보다 나쁩니다.
-
상수·enum 라벨·타입 이름은 범위 밖입니다. 상수 표기는 언어별로
SNAKE_UPPER갈래가 별도로 있어 다른 축이고, 필요가 실측되면ConstantCase등으로 확장합니다. -
데이터 파일에는 영향이 없습니다. 멤버 표기는 코드 표면만 바꾸고, 익스포터 산출물의 내용과 이름은 그대로입니다.
존재 여부 접근자도 멤버입니다. 접두어를 붙인 뒤 표기하지 않고 합성한 이름을 표기해서
얻습니다 — ("has_" + 필드이름).ToCase(표기). 그래서 한 함수가 네 표기 모두에 맞는 답을
냅니다.
| 표기 | 시트의 OpenAt | 존재 여부 접근자 |
|---|---|---|
pascal | OpenAt | HasOpenAt |
camel | openAt | hasOpenAt |
snake | open_at | has_open_at |
upper-snake | OPEN_AT | HAS_OPEN_AT |
각 언어의 기본 표기에서 이 식은 이전과 같은 문자열을 냅니다. 그것이 골든이 움직이지 않은 이유입니다.
5.1 예약어 목록의 전체화
LanguageProfile의 예약어 목록은 기본 표기를 전제로 축약되어 있었습니다 — C#은 모든
키워드가 소문자이고 멤버가 PascalCase라 목록이 비어 있었고, 그 이유가 주석으로 적혀
있었습니다. 사실이었고 지금도 사실이지만, 표기가 고정인 동안만 성립하는 논거입니다.
비어 있는 이유를 목록 자체로는 알 수 없으므로, 그것을 바꾸는 사람에게는 함정입니다.
그래서 C# 키워드를 전부 적었습니다. Pascal에서는 한 항목도 닿지 않아 산출물이 바뀌지 않고,
camel에서는 Class 컬럼이 class로 도착해 @class로 회피됩니다 — 회피 포맷 @{0}이
「그 답이 필요해지면 여기」라고 예약해 둔 자리가 실제로 쓰인 것입니다. 문맥 키워드
(value · var · record)는 넣지 않았습니다. 문법이 키워드를 기대하는 자리에서만
키워드이고, Value라는 멤버는 평범한 C#이라 이스케이프하면 모든 파일이 시끄러워집니다.
5.2 설계가 틀린 자리 — 선행 리팩터
「옵션 하나 추가」가 아니었습니다. 전 생성기를 실측한 결과가 이렇습니다.
| 발견 | 설계가 놓친 것 |
|---|---|
C#만 멤버 헬퍼가 없었습니다. ToPascalCase()를 17곳에 인라인으로 부르고 있었고, 그래서 LanguageProfile.CSharp.MemberName()을 한 번도 부르지 않았습니다 | 나머지 언어에는 XxName() 한 통로가 있었으므로, C#은 통로를 만드는 것이 작업의 절반입니다 |
템플릿이 prop_name을 합성 식별자로 재조립합니다 — HasFoo · NewFoo() · Foo_N · FindByFoo · SetReference_Foo_INTERNAL | 멤버 표기를 바꾸면 HasfooBar · FindByfooBar가 됩니다. prop_name을 멤버와 파생용 Pascal 토큰으로 쪼개야 합니다 — TypeScript는 멤버가 camel이 된 시점부터 이 쌍(pascal_name)을 갖고 있었습니다 |
| 모든 언어에서 enum 라벨·상수·액세서 슬롯이 멤버 헬퍼를 공유합니다 | 멤버 표기를 바꾸면 「범위 밖」이라고 적은 것들이 함께 움직입니다. 비멤버 호출은 언어당 2~6곳뿐이라, 그쪽에 표기 전용 함수를 주고 XxName을 멤버 통로로 남겼습니다 — RustPascalName이 이미 그 선례였습니다 |
| 존재 여부 접근자가 헬퍼를 우회합니다 (그 접근자를 내는 언어 전 부) | "has" + Pascal(이름) 형태라 멤버 표기를 따라가지 않습니다. 위의 합성-후-표기로 바꾸었습니다 |
작업 순서는 선행 리팩터 → 예약어 → 옵션이었고, 앞의 둘은 기본값에서 무동작이므로 골든으로 검증됩니다. 그 순서가 아니면 무엇이 바뀌었는지 골든 diff로 가릴 수 없습니다.
한 가지는 스스로 만든 결함이었습니다. 표기 전용 함수를 처음에 멤버 헬퍼에 위임하도록 썼는데, 그러면 멤버 옵션을 그대로 따라가 분리한 의미가 없어집니다. 각 함수가 표기를 직접 부르도록 고쳤습니다.
5.3 게이트
member-case 픽스처가 워크북 둘을 읽습니다. 하나는 컬럼이 키워드로 이름 지어져 있어
이스케이프 질문을 실재하게 하고, 다른 하나는 다중 단어·옵셔널 컬럼을 갖습니다 — 단일 단어만
있으면 label이 camel과 snake에서 같아 절반이 보이지 않고, 존재 여부 접근자도 생기지
않습니다.
4개 언어를 비기본 표기로 생성해 실제로 컴파일합니다 — C#(camel) · C++(camel) · Java(snake) · Python(pascal). 컴파일러만 답할 수 있는 질문이기 때문입니다.
6. 파일 이름 표기 — FileCase · DataFileCase
6.1 코드 파일 — 타깃별 FileCase
테이블·enum·상수셋별 출력 파일과 액세서 파일의 표기를 정합니다. 기본값은 생성 코드의 이름 체계가 정본화한 언어별 관례이고 — 「타입은 PascalCase, 파일은 언어 관례」 — 이 옵션은 그 관례의 재정의입니다.
- 표기만 바꿉니다.
enum_·const_·_table류 접두·접미와tables/·enums/하위 폴더 배치는 현행 유지입니다. - 파일 이름에서 파생되는 참조도 함께 변환됩니다. Python · Rust · Go · Lua는 파일 이름이 곧 모듈 이름이고, C · C++은 include 경로입니다. 생성물 내부의 import · require · include가 전부 같은 값에서 파생되므로 함께 바뀌어 정합이 유지됩니다.
- Java에는 제공하지 않습니다. 파일 이름과 public 타입 이름의 일치가 언어 규칙입니다.
6.2 데이터 파일 — 레시피 전역 DataFileCase
구현 완료 (2026-08-20). 정본화가 실재하던 결함 하나를 함께 고쳤습니다 — §6.3.
데이터 파일 이름은 익스포터와 모든 언어의 리더가 같은 값을 독립적으로 계산하는 계약입니다. 타깃별 옵션으로 두면 익스포터와 리더가 어긋날 수 있으므로, 이 옵션만은 레시피 전역 공통 설정입니다.
- 구현은 정본화입니다. 쿠킹이 테이블별 데이터 파일 이름을 계산해
Table.DataFileName에 기재하고, 흩어져 있던 17곳이 그 값을 읽습니다 — 13개 생성기의 뷰, C# 액세서 템플릿, TypeScript 액세서 템플릿, Binary · Json 익스포터. 여러 프로그램이 합의해야 하는 값은 누군가 소유해야 합니다. - 행 세트 접미사는 표기를 적용한 뒤 원문 그대로 덧붙입니다. 세트 이름은 구분자까지
사용자가 기재한다는 기존 규정을 유지합니다 —
snake에서 테이블ItemDrop+ 세트_alt는item_drop_alt입니다. - 범위 밖: Text 익스포터(그룹 이름 축), 데이터베이스 익스포터의
NamePrefix축, 확장자 옵션(FileExtension·BinaryTableFileExtension기존 축).
6.3 정본화가 고친 결함
정본화 전에는 17곳이 각자 계산하고 있었고, 한 곳이 다른 답을 냈습니다.
| 계산 주체 | 근거로 쓴 것 |
|---|---|
| Binary · Json 익스포터, 13개 생성기, TypeScript 액세서 | Table.Name (정규화 이름) |
| C# 액세서 | Table.RawName (시트가 적은 그대로) |
그래서 시트가 ~~table:item_drop~~으로 적은 테이블은 ItemDrop.tcb로 내보내지고
item_drop.tcb를 찾습니다. 소비자 프로그램의 런타임 실패이고, 도구는 성공으로 끝납니다.
어느 게이트도 이것을 짚을 수 없었습니다. 모든 픽스처의 테이블 이름이 이미 Pascal이라 두 값이 같았기 때문입니다. 「골든이 통과한다」가 「계약이 성립한다」를 뜻하지 않는 자리였고, 표기 옵션을 넣는 작업이 그것을 드러낸 것입니다.
정본화 과정에서 두 번째 결함도 나왔습니다. 타깃 사이드로 좁힌 모델 복사본
(Model.cs의 사영)이 테이블 프로퍼티를 하나씩 옮겨 적는 방식이라, 새 프로퍼티를 조용히
빠뜨렸습니다 — --target-side client 실행이 데이터 파일을 .tcb(빈 이름)로 내보냈습니다.
골든이 잡았고, 정본화를 옵션과 분리해 먼저 넣은 것이 그것을 잡을 수 있었던 이유입니다.
이 사영은 프로퍼티를 열거하는 방식이라 새 프로퍼티마다 같은 함정이 있습니다.
Table에 값을 더하는 사람은 그 목록을 함께 봐야 합니다.