본문으로 건너뛰기

이름과 태그

「시트 작성」으로 돌아가기


이름 규칙

모든 엔티티 및 필드 이름은 내부적으로 Pascal case로 자동변환 됩니다. 이는 처리의 단순화를 위해서 결정한 사항입니다.

다만, 다음과 같은 부작용이 발생할 수 있으므로 주의해야합니다.

1. 밑줄 개수로 이름을 구분하지 마세요.

x_y(밑줄 하나)와 x__y(밑줄 둘)는 생성 코드에서 둘 다 Xy가 되므로, 이름이 겹친 것으로 보고됩니다.

밑줄 개수로 구분하는 작명은 가독성도 떨어집니다.

같은 범위에 없어서 이름이 겹치지 않는 경우에도 연속 밑줄 자체가 보고됩니다. 밑줄 개수의 차이는 생성 코드 어디에도 전달되지 않으므로, 의도라면 전달되지 않고 오타라면 드러나야 하기 때문입니다.

2. 이름은 길지 않은 선에서 최대한 의미 있게 짓습니다.

대부분 그대로 코드에 반영되는 요소이므로 함의가 명확할수록 좋습니다.

3. 예약어와 겹치는 이름은 자동으로 회피됩니다.

컴파일이 깨지지 않습니다. 다만 생성된 이름이 시트에 적은 것과 달라지므로 알아 두는 편이 좋습니다.

언어멤버 표기회피 방식
C#PascalCase(거의 불필요)Class — C# 예약어는 전부 소문자라 겹치지 않습니다
TypeScriptcamelCase{이름}_class — TS는 예약어를 멤버로 허용합니다. 문제는 Constructorconstructor_
C++snake_casetb_{이름}Classtb_class
GoPascalCase{이름}_Class — Go 예약어는 소문자이고 export하려면 대문자여야 하므로 겹치지 않습니다
Rustsnake_case{이름}_Typetype_ (Class는 Rust 키워드가 아니라 class 그대로)
Pythonsnake_case{이름}_Classclass_
JavacamelCase{이름}_Classclass_
KotlincamelCase`{이름}`Class`class` — Kotlin은 백틱 이스케이프를 허용합니다
Rubysnake_case{이름}_Classclass_
DartcamelCase{이름}_Classclass_, Intint_ (앞이 아니라 뒤에 붙입니다 — 앞에 _를 붙이면 라이브러리 private이 됩니다)
UnrealPascalCase(불필요)Class — UPROPERTY 이름은 C++ 키워드와 겹치지 않습니다

이 표는 추측이 아닙니다. reserved-words 픽스처가 예약어로 이름 지은 필드를 담고 있고, 회귀 스위트가 그 산출물을 지원 언어 전부에서 실제로 컴파일합니다.

Dart의 Intint_가 그렇게 해서 나왔습니다. int는 Dart 예약어가 아니라 그냥 타입 이름인데, 같은 이름의 필드가 클래스 안에서 그 타입을 가려버려서 int int = 0; 다음 줄부터 컴파일이 깨집니다. 예약어 목록으로는 검출할 수 없는 종류라, 컴파일러에 걸리기 전까지 몰랐습니다.

표기를 강제하고 싶을 때

위 1번의 자동 변환은 단어 경계를 입력의 대소문자에서 얻습니다.

그래서 같은 컬럼을 시트마다 maxHitPoints, maxhitpoints, maxhitPoints로 적으면 각각 MaxHitPoints, Maxhitpoints, MaxhitPoints가 됩니다.

생성 코드에 멤버가 세 개 생기고, 소비 코드는 테이블마다 어느 표기인지 기억해야 합니다.

타입 선언이 없는 언어에서는 틀린 표기로 읽어도 오류가 아니라 값 없음이므로, 증상이 조용한 기능 누락으로 나타납니다.

recipe의 Naming 섹션이 이것을 검사합니다.

종류별로 따라야 할 표기를 선언하고, 선언이 없어도 한 이름이 여러 표기로 적힌 것과 연속 밑줄은 항상 보고합니다.

값과 도입 절차는 recipe 파일 — Naming에 있습니다.

인덱스 필드

기본 인덱스

테이블을 정의할 때 필수로 있어야 하는 필드로, 테이블 로우(행)의 기본 인덱스가 됩니다. 첫 컬럼이 그것이고 — 선언 셀의 key로 옮길 수 있습니다 — 다음 조건을 만족해야 합니다.

  • 값들이 unique 해야 합니다.
  • 옵셔널일 수 없습니다. int?는 거부됩니다 — 옵셔널 참고.
  • TargetSide가 양쪽이어야 합니다. 모든 행이 그것으로 식별되므로 한쪽 빌드에만 있을 수 없습니다.
  • 타입이 행을 구별할 수 있어야 합니다 — 아래.

이름은 정해져 있지 않습니다. index라고 쓰는 것이 관례일 뿐이고, Id라고 쓰면 생성되는 조회가 FindById가 됩니다. 참조 해석도 대상 테이블의 인덱스 이름을 읽어 쓰므로 이름이 무엇이든 같게 풀립니다.

인덱스가 될 수 있는 타입

int만도 int·string만도 아닙니다. 생성되는 조회는 인덱스 필드 자신의 타입에 대한 사전이므로(Dictionary<string, Record> · Map<Guid, Record>), 타입이 무엇인지는 조회가 성립하는지와 무관합니다. 그래서 규칙은 「어느 타입인가」가 아니라 「그 값으로 행을 구별할 수 있는가」 하나이고, 기본 인덱스와 보조 인덱스에 똑같이 적용됩니다.

됩니다안 됩니다왜 안 되는가
int · bigint · string · uuid · enumbool값이 둘뿐이라 행이 두 개를 넘을 수 없습니다
float · double정확히 비교되지 않아, 같아 보이는 값에서 조회가 실패 없이 빗나갑니다
T[]한 셀에 값이 여럿인데 키는 하나입니다
datetime · timespan비교는 정확합니다(틱). 행을 시각으로 찾는 시트가 없어서 받지 않습니다

enum이 되는 것은 라벨이 작성자가 적은 목록이고, 라벨마다 한 행인 테이블이 실제로 있기 때문입니다. bool과 다른 점이 그것입니다.

datetimetimespan을 뺀 이유는 카디널리티가 아닙니다.

받으려면 「시각으로 키를 잡은 조회」를 모든 언어에서 보증해야 하는데, 그중 몇 언어는 동등성이 값의 것이 아닌 타입을 시각에 사용합니다.

요구가 없는 것에 그 비용을 쓰지 않습니다. 시각을 들고 싶으면 평범한 컬럼으로 두고 키는 다른 것으로 잡으세요.

가리켜질 수 있는 키 — enum 하나만 예외

무엇int 인덱스bigint·string·uuidenum
GetByIndex / 사전됩니다됩니다됩니다
보조 인덱스(*)와 함께됩니다됩니다됩니다
다른 테이블이 이 테이블을 참조됩니다됩니다안 됩니다

foreign 컬럼은 대상의 인덱스 값을 담고, 그 값의 타입은 대상이 정합니다int는 그중 한 경우입니다 (설계). 그래서 이름으로 키를 잡은 테이블도 그냥 가리키면 되고, 참조 셀에 그 이름을 적습니다.

enum 키만 남았습니다. 규칙이 아니라 구멍입니다 — enum 값은 고정 폭이 아니라 지그재그 인코딩으로 실리고, 그 읽기는 언어마다 자기 enum을 쓰므로 공용 읽기 표에 항목이 없습니다. 그 경우 그 셀을 가리켜 거부하고, 대상을 enum의 바탕 int로 키 잡으라고 안내합니다.

인덱스를 첫 컬럼이 아닌 곳으로 옮기려면 선언 셀의 key입니다 — 선언 셀의 괄호에 그림으로 있습니다.

보조 인덱스

기본 인덱스 외에 추가로 인덱싱하고 싶은 필드가 있을 수 있습니다.

이름으로 빠르게 찾고 싶다면 아래와 같이 Name 필드 앞에 *를 붙이면 됩니다.

값이 유니크해야 하고, 기본 인덱스와 같은 이유로 옵셔널일 수 없으며, 될 수 있는 타입도 기본 인덱스와 같습니다.

와이어 태그 (@N)

바이너리 파일에서 컬럼을 식별하는 번호입니다. 필드 이름 뒤에 @N으로 답니다.

index@1 Name@2 Price@3 #OldColor@4 *Grade@5

왜 필요한가. 태그가 있으면 컬럼을 가리키는 것이 위치가 아니라 번호입니다. 그래서 컬럼을 지우거나 순서를 바꾸거나 이름을 바꿔도, 이미 배포된 클라이언트가 나머지 컬럼을 그대로 읽습니다. 데이터만 패치하거나 구버전 클라이언트가 새 데이터를 받는 흐름이 있다면 태그를 다세요.

규칙내용
전부 또는 전무한 테이블 안에서 전부 달거나 전부 안 답니다. 섞으면 오류입니다
1 이상, 유일중복이나 0 이하는 오류입니다
#이름@N제외된 컬럼도 태그를 예약합니다. 지운 컬럼의 Tombstone이자, 미래를 위해 미리 잡아두는 자리입니다
배열slot[0] · slot[1]…은 파일에서 컬럼 하나이므로 태그는 원소 0에만 답니다. 멀티 로우 그룹은 멤버 컬럼이 곧 와이어 컬럼이므로 멤버마다 답니다
안 달면위치대로 1..N이 배정됩니다. 동작은 같지만 끝에 추가하는 것만 안전합니다 — 중간을 지우면 그 뒤 태그가 전부 밀립니다

한 번 데이터를 실은 태그는 다시 쓸 수 없습니다. 지운 컬럼의 태그를 다른 컬럼에 주면, 삭제 전에 만들어진 테이블 리더가 새 컬럼을 옛 필드로 읽고도 성공합니다 — 그것이 태그 방식이 유일하게 못 견디는 변경입니다. 그래서 지울 때 #이름@N을 남깁니다.

자세한 것과 타입을 바꿀 때의 규칙은 바이너리 형식에 있습니다.