본문으로 건너뛰기

참조의 「없음」 — 빈 칸과 0

문서 목록으로

상태: 구현됨

없음의 표기는 빈 칸과 없음이 대체합니다. 이 문서가 「빈 칸」을 없음으로 읽는 자리는 -가 되었고, 참조 컬럼의 빈 칸은 컬럼이 무엇을 선언했든 거부합니다 — 아무도 채우지 않은 셀이기 때문입니다. 판정하는 자리와 그 이유는 아래 그대로입니다.

필수 참조 컬럼의 빈 칸이 거부되기는 하는데, 거부하는 자리가 값 파서였습니다. 그래서 메시지가 「Int32로 파싱할 수 없습니다」였고, 그것이 참조라는 것도 어떻게 고치는지도 나오지 않았습니다.


1. 문제 — 빈 칸과 0의 뭉개짐

참조 셀의 검사는 이렇게 되어 있었습니다.

// Zero is the conventional "points at nothing". Index values start at
// one, so it can never collide with a real row.
if (cell.Value is int key && key == 0)
continue;

0이면 통과합니다. 빈 칸은 파싱되면서 0이 되므로, 이 검사만 놓고 보면 빈 칸도 통과할 자리입니다.

시트에 적힌 것저장되는 값이 검사의 판정
12 (실재하는 행)12통과
99 (없는 행)99거부 — 맞습니다
0 (없음을 명시)0통과
빈 칸0통과 ← 값만 보면 구별할 방법이 없습니다

실측 — 이 검사에 닿지 않는다는 것

실측하니 필수 참조의 빈 칸은 이 검사에 닿지 않습니다. 참조 셀은 대상의 인덱스를 담으므로 int로 파싱되고, 빈 칸은 그 앞에서 값 파서가 먼저 거부합니다.

Cannot parse `` as a value of type `Int32`.
at test/fixtures/xlsx/reference-required-blank/reference-required-blank.xlsx : Refs : J10

그래서 이 문서가 고치는 것은 통과하던 것을 거부하게 만드는 일이 아니라, 거부하는 자리를 옮기는 일입니다. 위 메시지는 사실이지만 쓸모가 없습니다 — 그 컬럼이 참조라는 것도, 값을 넣거나 컬럼을 옵셔널로 적으면 된다는 것도 나오지 않습니다. 판정을 값 파서에서 검증으로 옮기면 컬럼이 무엇이라고 선언되어 있는지를 보고 판정할 수 있습니다.

같은 구별을 옵셔널 필드가 이미 하고 있습니다. Cell.HasValue가 「셀이 비었다」를 값과 별개로 들고 있고, 참조 검증만 그것을 보지 않았습니다.

참조는 한 레이아웃에만 있습니다. RefTableName을 채우는 것은 tabbit 레이아웃 하나뿐입니다. 그래서 「빈 칸을 없음으로 읽으면서 컬럼은 필수로 두는 레이아웃」에 이 규칙이 대응한다는 식으로 미뤄 둘 수 없습니다 — 그런 레이아웃에는 애초에 참조가 없습니다. 규칙이 실제로 무는 자리는 tabbit이고, 그러려면 그 레이아웃이 참조 컬럼의 빈 칸을 파싱에서 거부하지 않아야 합니다. 2절이 그것을 정합니다.

2. 결정 — 없음은 명시적으로, 그리고 허용된 자리에서만

결정 셋입니다.

  1. 빈 칸은 그 컬럼이 옵셔널일 때만 「없음」입니다. 필수 컬럼의 빈 칸은 어느 셀인지 함께 거부합니다.
  2. 판정은 값이 아니라 Cell.HasValue로 합니다. 0으로 파싱된 것과 비워 둔 것은 다른 사실이고, 값만 보면 그 둘이 같아집니다.
  3. 참조 컬럼의 빈 칸은 파싱에서 거부하지 않습니다. 필수든 아니든 HasValue = false인 셀로 읽고, 필수인지는 검증이 판정합니다. 옮기는 것이지 느슨해지는 것이 아닙니다 — 거부되던 것은 그대로 거부되고, 메시지만 4절의 것으로 바뀝니다. 오늘 통과하는 워크북 중에 필수 참조가 빈 것은 하나도 없습니다. 있었다면 파싱에서 이미 멈췄을 것이기 때문입니다.
시트에 적힌 것필수 컬럼옵셔널 컬럼
실재하는 id통과통과
없는 id거부거부
-거부통과 — 없음
빈 칸거부거부빈 칸과 없음
0통과 — 3절통과

「비었다」의 정의

코어가 정합니다. 값이 없는 셀은 -이고, 그것은 레이아웃이 다시 정의할 수 없습니다 (빈 칸과 없음). 검증이 읽는 것은 여전히 Cell.HasValue 하나이고 — 가변 길이 배열의 절단레코드 멤버별 옵셔널이 쓰는 것과 같은 규칙입니다 — 달라진 것은 그 값을 무엇이 거짓으로 만드는가입니다.

3. 0을 그대로 두는 이유

0이라고 적은 셀은 지금처럼 통과합니다. 바꾸지 않습니다.

작성자가 적은 것이기 때문입니다. 2절이 고치려는 것은 「적지 않은 것이 뜻을 갖는 것」이지 「적은 것의 뜻」이 아닙니다. 0은 인덱스가 1부터 시작하므로 어떤 행과도 겹치지 않고, 그것이 이 규약이 성립하는 근거입니다.

그리고 되돌릴 수 없는 쪽이 아닙니다 — 0을 거부하기로 나중에 정하면 그때 거부하면 되지만, 지금 거부하면 그 규약을 쓰던 시트가 전부 멈춥니다.

필수 컬럼의 0은 통과합니다. 「필수」는 값이 적혀 있어야 한다는 뜻이고, 0은 적힌 값입니다. 「가리키는 행이 반드시 있어야 한다」를 뜻하고 싶다면 그것은 별도의 규칙이고, 검증 파이프라인이 그것을 적는 자리입니다.

4. 거부

거부할 것메시지에 나오는 것
필수 참조 컬럼의 빈 칸그 셀. 그리고 그 컬럼이 필수로 선언되어 있다는 것을 말로 함께 적습니다
실재하지 않는 id그 셀 — 지금 그대로

첫 번째의 메시지는 어떻게 고치는지를 적어야 합니다: 값을 넣거나, 그 컬럼을 옵셔널로 적거나입니다. 컬럼을 옵셔널로 만드는 표기는 레이아웃마다 다르므로, 메시지가 그 이름을 부르지 않고 「이 컬럼은 필수로 선언되어 있습니다」까지만 나옵니다.

5. 담지 않는 것

  • 0의 재해석. 3절.
  • 「반드시 실재하는 행을 가리켜야 한다」. 필수와는 다른 규칙입니다. 3절의 인용.
  • 여러 테이블 중 하나여도 되는 값. 참조가 아니라 검사로 남았습니다 — 빈 칸은 검사를 건너뛰는 것이고 이 규칙과 독립입니다(참조가 내는 이름 §6).

6. 검증 게이트

게이트확인하는 것어디에
필수 참조의 빈 칸거부하고 셀 위치가 나오는지, 그리고 메시지가 두 가지 고치는 법을 적는지reference-required-blank
옵셔널 참조의 빈 칸통과하고 null로 나가는지reference-optional
적은 0필수·옵셔널 양쪽에서 통과하고 0으로 나가는지reference-optional
없는 id지금처럼 거부 — 회귀ValidationTests
두 샘플 재생성바뀌는 것이 없어야 합니다 — 아래
골든한 바이트도 바뀌지 않아야 합니다 — 아래

마지막 두 줄이 이 개정에서 위험할 것이라고 적었던 자리인데, 실측 결과 그렇지 않습니다. 필수 참조의 빈 칸은 지금까지 조용히 통과한 적이 없습니다 — 파싱에서 거부되었습니다. 그래서 이 개정이 새로 검출하는 로우는 정의상 0건이고, 통과하던 데이터는 그대로입니다. 골든이 바뀌면 그것은 이 규칙이 무언가를 검출한 것이 아니라 의도하지 않은 부작용이므로 diff를 봐야 합니다.

7. 구현 순서

  1. 레이아웃tabbit이 참조 컬럼의 빈 칸을 파싱에 넘기지 않고 HasValue = false로 읽습니다. 2절의 3번.
  2. 검증ValidateReferencedKeysExist가 값 대신 Cell.HasValue를 보고, 필수 컬럼의 빈 칸을 4절의 메시지로 거부합니다.
  3. 게이트 — 6절.

1번을 빼면 2번은 어느 레이아웃에서도 도달하지 않는 코드가 됩니다. 참조가 tabbit에만 있고 거기서 필수 컬럼은 HasValue가 항상 참이기 때문입니다. 한 번 그 상태로 커밋하였다가 되돌렸으므로 적어 둡니다.

2번 안에서의 순서. 「이 컬럼을 채웠는가」는 대상 테이블을 읽기 전에 가려야 합니다. 참조 검사는 대상에 컬럼이 하나도 없으면 그 자리에서 돌아가는데, 빈 칸 검사를 그 뒤에 두면 그 경우에 함께 빠져나갑니다 — 그리고 그것들은 전에 값 파서가 막던 것이므로, 거부 하나가 알림 없이 사라집니다. 이 질문은 대상의 로우와 아무 상관이 없으므로 앞에 둡니다.