본문으로 건너뛰기

다중 대상 참조의 접근자

문서 목록으로

되돌렸습니다 — 참조가 내는 이름 §6. foreign의 대상은 테이블 하나이고, 여러 테이블 중 하나여도 되는 값은 참조가 아니라 검사입니다(refs=A;B). 이 문서가 세운 검사는 그대로 살아 있고, 없어진 것은 승격과 그 위에 선 생성 표면입니다. 아래는 그 결정에 이르기까지의 기록입니다.

상태: 됨 — A단계와 B단계 모두. 모든 언어, HTML, 레코드 멤버까지

다중 대상 참조 8절의 구현 순서 7번이 이 문서입니다. 레코드 안의 참조가 그 목록의 앞 항목을 끝냈고, 이것이 그 목록의 마지막이었습니다

한 컬럼의 값이 여러 테이블 중 어느 하나의 행일 때, 모델은 그 대상들을 이미 알고 있고 검사도 돌고 있습니다. 생성 코드만 그것을 모릅니다. 소비하는 쪽이 받는 것은 숫자 하나이고, 어느 카탈로그의 행인지는 사람이 기억해야 합니다.


1. 문제 — 모델은 알고 생성기는 모릅니다

승격은 끝나 있습니다. 필드가 대상 이름 목록을 들고, 대상 테이블이 해석되고, 값이 대상의 키 타입으로 좁혀집니다. 그런데 그 뒤가 없습니다.

다중 대상
제약 행 읽기
필드가 대상 목록을 듦
승격 — 적힌 테이블이 전부 이 빌드에 있을 때
대상 테이블 해석
대상끼리 키 타입이 같은지
대상끼리 id가 겹치지 않는지
값이 대상 중 하나에 있는지
와이어가 대상의 키 타입으로 실림
생성 코드의 접근자없습니다
HTML 문서의 링크·역참조·그래프없습니다

왜 지나가는지

IsRef는 「정확히 한 레코드로 풀린다」를 뜻하고, 그것을 지키려고 승격 패스가 다중 대상에서는 단수 이름을 비워 둡니다. 그 결정 자체는 옳습니다 — ResolvedRefTable is null의 뜻을 안 바꾸는 것이 그 설계가 산 것입니다. 문제는 아무도 목록 쪽을 안 본다는 것입니다.

RefTableNames·ResolvedRefTables·IsMultiRef를 읽는 곳이 src/ 전체에서 한 줄뿐입니다. 그 한 줄이 HTML 생성기의 RefTargetsOf인데, 그것을 부르는 여섯 자리가 전부 IsRef 게이트 뒤에 있어 도달하지 않습니다. 다중 대상을 위해 쓴 코드가 다중 대상에서만 안 돌고 있습니다.

검사는 돕니다. all 빌드에서 컬럼 273개를 132만 행에 대조하여 위반 0건이고, 대상을 다 갖추지 못한 컬럼 125개는 판정하지 않고 경고합니다. 즉 잃은 것은 정확성이 아니라 표현입니다.

2. 실측 — 무엇이 얼마나 있는지

승격 패스에 계측을 넣고 이전 대규모 코퍼스의 전량 빌드를 돌린 결과입니다.

승격된 것 — 접근자만 없는 컬럼

대상 개수컬럼
218
34
51
63
81
101
112
131
합계31

넓은 것부터 ChoiceBox.ChoiceBoxId(13) · Offer.CeilingCashShopBoxIconId(11) · DrawGameReward.RewardId(10) · EventShop.ProductId(8)입니다.

막혀 있는 것 — 레코드 그룹의 멤버

경우컬럼
이름 있는 레코드 멤버, 대상이 전부 있음156
이름 있는 레코드 멤버, 대상이 이 빌드에 없음22
레코드 밖, 대상이 이 빌드에 없음13
합계191

156개가 그 홀드백 하나 때문에 서 있습니다. 컬럼 개수와 생성될 멤버 개수는 다릅니다 — 레코드 배열의 원소는 한 멤버를 공유하므로, 156개 컬럼이 멤버로는 31개입니다. 위의 승격된 컬럼 31개와 수가 같은 것은 우연입니다.

생성 코드가 늘어나는 양

무엇얼마
승격된 컬럼의 대상별 프로퍼티124
레코드 멤버의 대상별 프로퍼티150
합계274

로우 하나가 드는 양 — 여기가 설계를 바꿉니다

프로퍼티 개수와 달리, 저장 슬롯은 원소마다 생깁니다. 아래는 대상마다 슬롯을 하나씩 두는 경우이고, 선언 전체를 합친 수입니다 — 각 수는 그 선언이 있는 테이블의 로우 하나가 듭니다.

무엇슬롯
승격된 컬럼124
레코드 멤버862

가장 넓은 자리가 RewardFixed.RewardFixed.Id입니다 — 대상 16개 × 원소 20개 = 로우당 320 슬롯. 그 열여섯이 무엇인지 보면 왜 이런 형태인지 알 수 있습니다.

Point, Item, ExpeditionSupply, Market, CharGear, Ship, Roster, Recipe,
GearSlot, Shield, TaxFreePermit, PortraitSkin, CosmeticSkin, Emblem, Pet,
SeasonReward

「보상 종류」 다형성입니다. 같은 목록이 LootPool · CashShopBanner · SalvageRewardGroup에서 되풀이됩니다. 대상이 둘인 컬럼과 열여섯인 컬럼은 개수만 다른 것이 아니라 쓰임이 다른 선언이고, 설계가 뒤쪽을 견뎌야 합니다.

그리고 대상끼리 겹치지 않습니다

all 빌드 전체에서 키 겹침 0건입니다. 이것은 관찰이 아니라 계약입니다 — 겹치면 CheckKeyBandsDoNotOverlap이 에러를 냅니다. 존재 검사와 합치면 이렇습니다.

셀에 값이 있으면, 그 값은 대상 중 정확히 하나의 행 id입니다.

3절이 이 계약 위에서 성립합니다.

3. 결정 — 공개 표면은 대상별, 저장은 슬롯 하나

8절이 정한 공개 표면을 그대로 씁니다. 바꾸는 것은 그 뒤에 무엇을 담는가입니다.

무엇대상마다 슬롯슬롯 하나 + 식별자
슬롯 합 — 레코드 멤버862156
슬롯 합 — 승격된 컬럼12431
RewardFixed 로우 하나32020
언제나 비어 있는 슬롯15/16없음
「어느 카탈로그인가」프로퍼티 16개를 널 검사식별자 하나
공개 프로퍼티같음같음

대상마다 슬롯을 두면 그중 하나만 채워집니다. 2절의 계약이 그렇게 정해 두었으므로, 대상이 열여섯인 컬럼은 슬롯 열여섯 중 열다섯이 언제나 비어 있습니다. 그것은 데이터의 성질이지 우연이 아니므로, 담는 쪽이 그것을 알고 있어야 합니다.

C#에서는 이것이 크기 문제이기도 합니다. 레코드 원소가 struct이므로, 원소에 참조 열여섯 개를 더하면 원소의 크기가 그만큼 커지고 복사될 때마다 따라옵니다. 슬롯 하나면 원소는 포인터 하나와 작은 정수 하나만 늘어납니다.

담는 것

무엇타입
대상의 기본 인덱스 타입 — 지금 그대로
해석된 행대상 무관 슬롯 하나 — 그 언어의 「무엇이든」 타입
식별자어느 대상이었는가. 아무것도 아니면 None

합 타입이 공개 표면에 안 나옵니다. 8절이 합 타입을 피한 근거가 유지됩니다 — 대상 무관 슬롯은 내부이고, 소비하는 쪽이 보는 것은 언제나 타입이 하나인 프로퍼티입니다.

식별자는 더한 기능이 아니라 슬롯 하나의 대가입니다. 슬롯을 하나만 두면 그 안에 어느 테이블의 행이 들었는지 말해 주는 것이 있어야 캐스트가 안전하고, 식별자가 그 태그입니다. 대상마다 슬롯을 두는 설계에서는 필요하지 않습니다 — 그쪽은 「단일 대상 참조 N개」와 완전히 같은 것이고, 그것이 이 설계를 읽을 때 가장 먼저 떠오르는 형태입니다. 아래에 무엇을 사고 무엇을 낸 것인지 적어 둡니다.

고려하고 버린 것

  • 대상마다 슬롯. 위 표. 대상이 둘일 때는 차이가 없고 열여섯일 때 8배입니다. RewardFixed(대상 16 · 원소 20 · 약 25,000행)로 재면 64MB 대 8MB이고, 64MB 중 60MB가 영구히 null입니다 — 16개 중 15개는 절대 채워지지 않기 때문입니다. C#에서는 그 포인터들이 struct 원소 안에 인라인이라 원소를 읽을 때마다 복사됩니다. A단계에서는 이 절감이 거의 0입니다 — 승격된 31개는 대상이 대부분 둘이고 원소 배수가 없습니다. 이 결정이 값을 갖는 것은 B단계입니다.
  • 담지 않고 접근할 때 조회. 슬롯이 0이 되지만 로우가 접근자를 되가리켜야 하고, 지금 생성되는 레코드에는 그 되가리킴이 없습니다. 참조를 로드 뒤 한 번 잇는 것이 이 도구의 방식이고, 그것만 다르게 하면 배울 것이 하나 늘어납니다.
  • 식별자를 안 내는 것. 대상이 둘이면 널 검사 두 번으로 되지만, 열여섯이면 소비하는 쪽이 16단 분기를 씁니다. 식별자는 링킹이 이미 알고 있는 것이고, 안 내는 것은 아는 것을 버리는 일입니다.

4. 코어 표기 — 대상을 여럿 적는 법

코어가 표현할 수 있는 것을 코어 표기가 적을 수 없습니다. 모델은 대상 목록을 들고 검사까지 하는데, 그 목록에 둘 이상을 넣는 길이 어떤 프로젝트 레이아웃의 제약 행 하나뿐입니다. 코어 레이아웃의 foreign은 detail-type에 RefTable[.RefFieldName] 하나만 받습니다.

두 가지가 걸립니다.

  • 게이트를 세울 자리가 없습니다. 골든 시나리오는 전부 코어 레이아웃이고, 거기에 다중 대상을 적을 표기가 없습니다. 그러면 이 개정의 판정 기준이 프로젝트 하나의 워크북이 되는데, 그것은 CLAUDE.md가 코어 기능에 대해 금지한 형태입니다.
  • 거꾸로 서 있습니다. 「기능은 일반적으로 만들고 이름은 플러그인 쪽에」가 규칙인데, 지금은 기능 자체가 플러그인 쪽에서만 들어옵니다.

결정. foreign의 detail-type이 세로줄로 여럿을 받습니다.

foreign Weapon 대상 하나 — 지금 그대로
foreign Weapon|Armour 둘 중 하나
foreign Weapon.Name 대상의 필드 — 지금 그대로

세로줄인 근거가 넷입니다. 「또는」으로 읽히고 — 이 선언이 뜻하는 것이 그것입니다 — 점 표기와 부딪히지 않으며(점은 대상 을 가리키고 이것은 대상들을 가릅니다), 역할의 괄호(text(Common))와도 부딪히지 않고, 테이블 이름에 들어갈 수 없는 문자입니다.

  • 하나만 적으면 지금과 완전히 같습니다. 길이 1의 목록이고, 기존 시나리오의 골든이 한 바이트도 안 움직입니다.
  • 점 표기와 섞는 것은 받지 않습니다. Weapon.Name|Armour.Name은 8절.
  • 적힌 대상이 이 빌드에 없으면 거부입니다. 프로젝트 레이아웃의 제약 행은 같은 경우를 경고로 두는데 — 넓은 빌드를 겨냥한 선언일 수 있기 때문입니다 — foreign은 지금도 없는 대상을 거부하고 그 규칙을 그대로 물려받습니다. 표기마다 규칙을 새로 정하는 것이 아니라 각 표기의 기존 규칙이 유지되는 것입니다.

이 표기가 서면 픽스처를 코어 레이아웃으로 적을 수 있고, 10절의 게이트가 프로젝트 워크북 없이 돕니다.

5. 생성되는 것

Shop.ItemIdCharGearItem을 적었을 때, C#입니다.

shopRow.ItemId // int — 키. 지금도 있는 것
shopRow.ItemIdTarget // ShopItemIdTarget.CharGear 또는 .Item 또는 .None
shopRow.CEquipByItemId // CEquipRecord 또는 null
shopRow.ItemByItemId // ItemRecord 또는 null

레코드 멤버도 같은 형태이고, 세 가지 레코드 형태에서 원소 번호가 앉는 자리만 그 문서의 규칙을 따릅니다.

rewardRow.RewardFixed[j].IdTarget // RewardFixedIdTarget.Item
rewardRow.RewardFixed[j].ItemById // ItemRecord

이름

<대상>By<컬럼>입니다. 얻는 것이 앞에 오고 그것에 닿은 키가 뒤에 옵니다 — 레코드를 훑는 사람이 먼저 찾는 것이 타입이기 때문입니다. 한 테이블을 가리키는 컬럼이 여러 개 있어도 구별됩니다.

멤버 이름 통로를 지나갑니다. 모델 표기로 CEquipByItemId·ItemIdTarget을 만들고, 각 생성기가 그 언어의 통로에 넣습니다 — MemberCasesnakec_equip_by_item_id입니다 (이름 규약).

식별자 enum은 선언 하나에 하나이고 테이블과 컬럼으로 이름을 만듭니다 — ShopItemIdTarget. 목록이 같은 선언들을 하나로 합치지 않습니다. 2절의 열여섯짜리 목록이 네 곳에서 되풀이되지만, 합치려면 그 목록의 이름을 이 도구가 지어야 하고 그것은 시트가 적지 않은 것입니다. 합치고 싶은 프로젝트는 그쪽에서 enum을 선언하면 됩니다.

None은 값이 없는 셀입니다. 값이 있으면 2절의 계약이 하나를 보장하지만, 링킹은 그것을 가정하지 않고 못 찾으면 None으로 둡니다 — 다른 빌드가 쓴 파일을 읽는 경우가 있고, 그때 던지는 것은 문서 하나가 오래된 대가로 너무 큽니다.

6. 언어별로 무엇을 두는가

레코드 안의 참조가 세운 분기를 그대로 따릅니다. 통일하지 않는 것이 그 문서의 결정이었고 여기서도 같습니다.

언어원소·로우 안에 두는 것
C#·TypeScript·C·C++·PHP·Java·Kotlin·Dart·Go·Python·Ruby·Lua·Swift키 · 대상 무관 슬롯 · 식별자
Rust·Unreal키만 — 접근자를 내지 않습니다

단일 대상 참조와 달리 여기서는 언어가 갈리지 않습니다. 그쪽은 「해석되었는가」를 C#·TypeScript가 플래그로, 나머지가 널로 나타내어 표가 둘로 갈려 있었는데, 여기서는 식별자가 그것을 이미 나타냅니다None이 「아무것도 안 가리킨다」이므로 플래그를 따로 둘 자리가 없습니다. 3절이 식별자를 담기로 한 것에 이것이 딸려 옵니다.

여기서 내린 판단이 단일 대상 쪽에도 적용되었습니다. 그쪽의 플래그도 키 검사와 같은 값이어서 없앴고, 이제 표가 갈리지 않습니다 — 참조의 해석 여부.

Rust와 Unreal은 접근자를 얻지 않습니다. 그 둘은 레코드 밖의 참조에도 링킹을 하지 않고, 그것은 빌림 그래프와 GC가 추적하지 않는 USTRUCT 안의 원시 포인터 때문입니다. 링킹이 없으면 식별자를 계산할 자리도 없습니다. 다중 대상 컬럼은 키를 싣고, 그 두 언어에서는 그것이 이미 맞는 결과입니다.

다만 「무변경」은 아닙니다. 식별자를 모델의 enum으로 선언했으므로(5절) 그 타입은 모든 언어가 냅니다 — 쓰지 않는 두 언어도 파일 하나씩 늘어납니다. 대신 import는 넣지 않습니다: 안 쓰는 것을 들여오는 것은 Rust에서 경고입니다. 그래서 「이 테이블의 파일이 이름 부르는 타입」을 세는 자리가 둘로 갈립니다 — enum 컬럼의 enum은 모든 언어가 부르고, 식별자는 접근자를 내는 언어만 부릅니다.

대상 무관 슬롯은 각 언어가 이미 가진 것으로 씁니다 — C#·Java·Kotlin은 object/Object/Any?, Go는 any, C·C++는 void*, TypeScript는 대상 타입들의 유니온, 동적 언어는 그냥 변수입니다. 캐스트가 프로퍼티 안에 갇힙니다 — 식별자가 이미 무엇인지 말하므로 프로퍼티는 검사 없이 돌려주고, 소비하는 쪽은 캐스트를 보지 않습니다.

7. 형식과 골든

두 단계가 여기서 갈립니다.

무엇와이어골든
이미 승격된 컬럼 31개무변경 — 이미 대상 키 타입으로 실립니다한 바이트도 안 움직입니다
레코드 멤버 156개double → 대상 키 타입움직입니다
식별자안 실립니다 — 링킹이 계산합니다
형식 버전무변경

식별자를 안 싣는 근거는 그 문서 4절과 같습니다 — 파일에 있는 것으로 알 수 있는 것을 파일에 또 적지 않습니다. 키가 어느 대상에 있는지는 로드 시점에 대상 테이블을 보면 나오고, 링킹은 이미 그 자리를 지나갑니다.

이전 소규모 코퍼스는 양쪽 단계에서 무변경이어야 합니다.

8. 담지 않는 것

  • 합 타입. 3절. 대상 무관 슬롯은 내부이고 공개 표면에 안 나옵니다.
  • 점 표기(Table.Field)의 다중 대상. 대상의 필드를 가리키는 형태는 받지 않습니다. 전체 행부터 열고, 요구가 확인되면 그때 봅니다 — 레코드 안의 참조와 같은 자리입니다.
  • 이름 없는 레벨의 멤버. 번호로만 닿는 원소에는 키를 담을 이름이 없습니다. 홀드백에서 이것만 남습니다.
  • 대상이 이 빌드에 없는 컬럼. 35개. 접근자를 내면 컴파일되지 않는 코드가 나오므로 그 문서 3절의 검사로 남습니다.
  • 목록이 같은 선언의 식별자 통합. 5절.
  • _BC 변종. 그 문서 4절.

9. 단계를 둘로 끊는 자리

단계무엇되돌릴 것
A승격된 컬럼 31개에 접근자와 식별자. HTML의 게이트도 여기서 열립니다없습니다 — 골든 무변경
B레코드 멤버 홀드백을 걷어냅니다. 156개가 승격됩니다데이터가 움직입니다

끊은 것이 값을 했습니다. A에서 언어마다 새 멤버가 생기는 결함을 다 털었고 — 자르는 참조 배열이 병렬 키 배열을 만들지 않는 것이 모든 언어에 있었습니다 — B에서는 그 형태가 한 단계 안쪽으로 들어가는 것만 남았습니다.

그런데도 B에서 언어마다 하나씩 나온 것이 있습니다. 원소마다 병렬로 놓이는 슬롯과 식별자를 행과 함께 만들어야 하는 언어입니다 — Go는 nil 슬라이스, C++는 크기 0인 벡터, Java는 null로 찬 배열, Python·Ruby·Lua는 선언 목록에 없는 이름입니다. 어느 것도 컴파일 게이트에 걸리지 않고 되읽기에서만 걸립니다. C만 예외인데, 아레나가 calloc이라 슬롯이 NULL이고 식별자가 None으로 이미 시작합니다.

그리고 골든이 잡은 것이 하나 더 있습니다. TypeScript의 접근자 블록이 빈 줄을 하나 더 남겼고, 다중 대상 멤버가 있는 시나리오가 아니라 전부에서였습니다 — 언어별 게이트만으로는 보이지 않고, 시나리오 22개를 다 비교하는 자리에서만 보입니다.

A를 먼저 하는 근거는 A가 무변경 게이트를 가진다는 것입니다. 모든 언어에 새 멤버가 생기는 개정이고, 참조 키 타입레코드 안의 참조에서 그 형태가 언어마다 다른 결함을 냈습니다 — 골든이 안 움직이는 단계에서 그것을 다 털고 나서, 움직이는 단계로 갑니다.

B가 A의 값을 크게 합니다 — 274개 중 150개가 B에 있습니다. 그러나 B만 먼저 하면 데이터가 움직이는 것과 언어마다 새 멤버가 생기는 것이 한 diff에 섞입니다.

10. 검증 게이트

게이트확인하는 것
단일 대상 참조 무변경지금 있는 참조 테스트 전부. 한 글자도 달라지지 않아야 합니다
다중 대상 접근자대상 중 하나를 가리키는 값에서 그 프로퍼티만 차고 나머지가 전부 비는지
식별자값이 있는 셀에서 대상을 맞게 대는지, 빈 셀에서 None인지
대상이 넓은 것대상 열여섯짜리 선언 하나를 픽스처로. 좁은 것만으로는 3절이 성립하는 것이 안 보입니다
레코드 멤버 (B)세 가지 레코드 형태 각각에서 원소 번호가 맞는 자리에 앉는지
레코드 배열의 멤버 (B)원소마다 제 대상을 무는지, 자르는 배열에서 그 길이만 도는지
모든 언어 컴파일·되읽기원소·로우에 멤버가 늘어나므로 언어마다. 컴파일 다음에 되읽기
Rust·Unreal접근자가 안 나오고 경고도 안 나는지 — 6절. 식별자 타입은 나옵니다
겹치는 대상겹치면 지금도 에러입니다. 3절이 그 위에 서므로 그 에러가 살아 있는지
어디에도 없는 키링킹이 던지지 않고 None으로 두는지 — 5절
골든 (A)한 바이트도 바뀌지 않아야 합니다
골든 (B)움직입니다. double → 키 타입인지 미리 재고 diff를 봅니다
이전 소규모 코퍼스양쪽 무변경
샘플 재생성A는 31개, B는 156개. 그리고 슬롯 합이 187인지 — 986이 나오면 3절이 안 든 것입니다

마지막 줄이 3절을 지키는 자리입니다. 대상마다 슬롯을 두는 구현도 게이트를 전부 통과하므로, 세는 것이 없으면 두 설계가 구별되지 않습니다.

실측 — 이전 대규모 코퍼스의 전량 빌드

무엇
승격된 컬럼31
레코드 멤버 선언31
그 선언들이 차지하는 컬럼156 — 레코드 배열의 멤버가 원소마다 한 컬럼입니다
슬롯 합187
대상마다 슬롯을 뒀다면986
대상 언급 총계274

식별자 enum은 62개입니다 — 컬럼 31개와 멤버 선언 31개. 멤버 쪽이 156이 아닌 것은 3절이 성립하는 자리입니다: 한 멤버의 원소들이 「이 원소는 어느 테이블의 행인가」를 각자 가리지만 타입은 하나를 공유합니다.

all 빌드는 지금 변환을 거부합니다 — 시트 데이터 문제 5,831건이고, 홀드백을 걷기 전과 같은 수입니다. 그래서 위 숫자는 산출물이 아니라 모델에서 셌습니다.

11. 구현 순서

#무엇단계
1HTMLIsRef 게이트 여섯 자리. 값 링크·타입 칸·역참조 목록·그래프·역할 분포A
2코어 표기foreign의 detail-type이 세로줄로 여럿을 받게. 4절A
3픽스처와 시나리오 — 대상 둘짜리와 넓은 것 하나. 코어 레이아웃으로A
4모델·링킹 — 대상을 순서대로 찾아 슬롯과 식별자를 채웁니다. IsRef는 안 건드립니다A
5식별자 enum — 선언마다 하나, 이름 통로를 지나서A
6생성기 — 대상별 프로퍼티와 식별자. Rust·Unreal은 제외A
7게이트 — 10절의 A 줄들A
8홀드백 제거 — 이름 있는 레코드 멤버의 다중 대상B
9계측·재생성 — 156개가 승격되는지, 골든 diffB
10게이트 — 10절의 B 줄들B

전부 됐습니다. 8번은 익명 레벨만 남겨 두고 걷었습니다 — 번호로 닿는 레벨에는 키를 둘 이름이 없어서이고, 대상 수와는 상관이 없는 이유입니다. 9번의 숫자는 10절에 있습니다. 골든은 multi-target 51개 파일과 PHP 3개만 움직였고, 이전 소규모 코퍼스는 생성 시각 말고는 한 줄도 바뀌지 않았습니다 — 7절이 요구한 그대로입니다.

1번이 맨 앞인 이유는 그것만 이미 있는 코드를 켜는 일이기 때문입니다. HTML은 다중 대상을 그리는 코드를 처음부터 갖고 있었고 게이트 뒤에 있어서 안 돌았을 뿐이라, 뒤의 것들과 얽히지 않고 모델이 옳게 서 있다는 것을 먼저 눈으로 확인해 줍니다.

2번과 3번이 생성기보다 앞인 이유는 그것이 없으면 판정할 자리가 없기 때문입니다. 지금 다중 대상을 적을 수 있는 것이 named-range 레이아웃뿐이므로, 표기를 먼저 세우지 않으면 생성기의 게이트가 그 레이아웃의 워크북 하나에 매달립니다.