본문으로 건너뛰기

남은 것과 다시 볼 조건

「검증 파이프라인」으로 돌아가기


9. 구현 순서

순서무엇완료 판정
1골격 — recipe 섹션, 폴더 발견, 규칙 파일 컴파일·실행, 심각도 3종 수집, ①·③ 게이트 배선실패하는 검증이 커밋을 막고 데이터베이스가 그대로인 것을 테스트가 확인. Warn·Info가 막지 않는 것도 함께
2타입 액세서 — C# 생성기 재사용, 바이너리 메모리 왕복, 기본 인덱스로 셀 위치 역참조픽스처 워크북 + 검증 스크립트 게이트. 오타 스크립트가 컴파일 오류로 보고되는 것과, 보고에 셀 위치가 나오는 것까지
3전역 — Schema 열거 뷰, 전역 스크립트 실행, 스키마 대상 보고 오버로드규약 위반 픽스처가 컬럼 헤더 셀 위치와 함께 보고되는 것
4편의 — 공용 코드 결합, 테이블별 병렬, 보고 정렬, --validate-only, 머리말과 --new-validator병렬로 돌린 결과의 보고 순서가 실행마다 같은 것. 스캐폴딩이 낸 파일이 그대로 컴파일되고 도는 것
5런타임 게이트웨이 — MySQL·Redis부터, --skip-runtime-validation기존 데이터베이스 테스트 컨테이너에 붙는 게이트
6이 항목은 없어졌습니다. 이식 대상이던 Lua 검사기 141개는 이전 대규모 코퍼스에 딸린 것이었고, 그 코퍼스가 저장소에서 빠지면서 함께 빠졌습니다이식 과정 자체가 컨텍스트 API의 검증입니다. 부족한 헬퍼가 여기서 드러납니다. LootKindValidator처럼 흩어져 있던 규칙이 전역 파일 하나로 모이는 것도 여기서 확인합니다

골든에는 영향이 없습니다 — 검증은 산출물을 바꾸지 않고 통과/실패만 정합니다.

10. 취약점과 개선 여지

설계가 맞아도 구조에서 나오는 위험은 남습니다. 구현 전에 알고 시작하는 것들이고, 완화를 적을 수 없는 것은 완화가 없다고 적습니다.

신뢰 경계 — 임의 코드 실행

규칙 파일은 변환기 프로세스의 권한으로 도는 C#입니다. 파일을 쓸 수 있고, 프로세스를 띄울 수 있고, 네트워크에 나갈 수 있습니다. 샌드박스는 없습니다 — .NET에는 코드 접근 보안이 없고, 어셈블리 참조를 좁히는 것으로는 기본 라이브러리에 이미 있는 것을 막지 못합니다. 「Db()가 읽기 전용」도 편의이지 보증이 아닙니다: 스크립트가 자기 연결을 직접 열면 그 게이트를 지나지 않습니다.

그래서 차단할 수 있다고 주장하지 않고 경계를 명시합니다.

사실따라오는 것
검증 폴더는 코드입니다시트와 같은 취급이 아니라 소스와 같은 취급 — 같은 저장소, 같은 리뷰, 같은 승인 절차
CI가 PR의 스크립트를 도는 것은 PR 작성자가 빌드 머신에서 코드를 도는 것입니다외부 기여를 받는 저장소라면 검증을 신뢰된 리비전으로 고정하거나 격리된 실행자에서 돌립니다
Connections의 자격증명은 스크립트가 읽을 수 있습니다읽기 전용 계정 · 운영 계정 금지 · 자격증명은 그 단계에만 노출

개선 여지는 별도 프로세스 실행입니다. 검증을 자식 프로세스로 분리하면 변환기의 스테이징·데이터베이스 연결과 떨어지고, 컨테이너로 감싸는 선택지가 생깁니다. 지금 설계는 같은 프로세스이므로, 필요해질 때의 경로로 적어둡니다.

메모리 — 같은 데이터의 세 벌

메모리 왕복(§3)의 대가가 여기 있습니다. 검증이 도는 순간 프로세스에 세 벌이 있습니다.

무엇왜 필요한가
Model (RawCell 격자 포함)셀 위치 역참조가 이것을 봅니다
.tcb 바이트리더에 넘기는 것
리더가 만든 객체 그래프스크립트가 보는 것

워크북 읽기의 실측이 62 MiB 워크북 하나에서 live 4.2 GB · peak 7.4 GB였습니다(리더를 스트리밍으로 교체하기 전의 값입니다). 그 위에 두 벌이 더 얹히므로 가장 먼저 한계에 닿을 곳은 여기입니다. 완화의 순서는 바이트를 테이블 단위로 넘기고 즉시 놓는 것(전체를 동시에 유지할 이유가 없습니다), 그다음이 위치 역참조에 필요한 최소 인덱스만 남기고 RawCell 격자를 놓는 것입니다. 후자는 「셀 위치를 가리킨다」를 지키면서 격자를 놓는 것이라 설계가 필요합니다.

셀 위치 역참조가 정확하지 않은 자리

Error(row, nameof(row.Field))가 셀을 찾는 것은 필드 이름을 컬럼으로 되돌릴 수 있을 때 입니다. 되돌아가지 않는 경우가 실재합니다.

경우무엇이 문제인가
접힌 배열 (Text1·Text2Text_array)이름 하나가 컬럼 여러 개입니다
레코드 그룹 (Reward)멤버 × 원소로 컬럼 수십 개입니다. nameof(row.Reward)는 그중 어느 것도 특정하지 않습니다
매트릭스 표값이 배열 하나이고 컬럼 id는 파생 테이블입니다
TargetSide로 좁힌 액세서없는 필드는 이름도 없습니다 — 검증용 액세서는 both로 생성해야 합니다

앞의 셋은 그룹의 첫 컬럼을 가리키는 것을 기본으로 두고, 원소를 아는 오버로드 (Error(row, nameof(row.Reward), at: k))를 함께 냅니다. 원소를 아는 검사가 그룹의 첫 컬럼을 가리키는 보고를 내면 사람을 잘못된 셀로 보냅니다.

단일 파일 발행에서 돌지 않는 것

규칙을 컴파일하려면 프레임워크 어셈블리의 경로가 필요합니다. 참조를 손으로 조립하지 않고 실행 중인 프로세스의 목록에서 가져오기 때문이고, 그것이 규칙 파일이 프레임워크의 아무 부분이나 쓸 수 있는 이유입니다 — 유지해야 하는 샌드박스를 만들지 않은 대가로 얻은 것입니다.

그런데 PublishSingleFile은 그 어셈블리들을 실행 파일 안에 넣고, 안에 있는 것에는 열 경로가 없습니다. 이 저장소는 자립 배포본을 릴리스로 내므로 그 배포본에서 검증은 돌지 않습니다.

무엇
그냥 넘어가지 않고 메시지로 보고하고 멈춥니다. 대안은 object가 정의되지 않았다는 보고 수십 개입니다
검증은 프레임워크 의존 빌드로 돌립니다. CI에서 그것이 자연스러운 형태이기도 합니다
고치는 방법은 참조 어셈블리를 리소스로 임베드하는 것입니다. 릴리스 배포본에서 검증을 돌려야 할 때 하는 일이고, 그전에는 이 한계를 적어두는 편이 낫습니다

조용히 통과하는 경로

이 저장소의 원칙(「스스로 꺼지는 게이트는 없는 게이트보다 나쁩니다」)에 걸리는 자리가 셋입니다.

경로차단 방법
Path에 오타가 있어 폴더가 없는 것오류입니다. 지정되었는데 없으면 실패하고, 스크립트가 0개면 경고합니다. 끄는 방법은 Path를 비우는 것뿐이고 그것은 diff에 남습니다
shared/의 공용 코드를 고쳤는데 컴파일 캐시가 유효한 것캐시를 만들 때의 조건입니다. 키에 그 파일이 결합하는 공용 코드 전부가 들어가야 하고, 규칙 파일 하나의 해시만으로는 바뀐 규칙이 돌지 않습니다. 지금은 캐시가 없어서 이 경로도 없습니다
경고를 아무도 확인하지 않는 것경고와 Info의 수를 요약과 히스토리 스냅샷에 기록해 추세가 보이게 합니다. 기존 검증에서 Log:Warn이 6건뿐이었던 것은 경고할 것이 없었다는 뜻이 아니라 경고를 쓰지 않게 되었다는 뜻입니다 — Log:Trace가 87회였던 것과 함께 보면, 판정이 아닌 말은 전부 한 등급으로 몰려 있었습니다

규모에서 오는 두 가지

무엇대처
보고 폭발규칙 하나가 잘못되면 269,870행에서 수십만 건이 발생합니다 — 컬럼 제약을 만들 때 실제로 269,426건을 본 적이 있습니다. 규칙별 상한을 두고, 상한에 걸린 사실을 반드시 보고합니다
보고 순서테이블별 스크립트를 병렬로 돌면 출력 순서가 실행마다 달라집니다. 수집 후 (테이블 · 로우 · 컬럼)으로 정렬해 냅니다 — 그러지 않으면 CI 로그의 diff가 매번 달라집니다

병렬과 상태는 컴파일 단위가 정합니다 — 파일마다 독립 컴파일이므로 공용 코드의 static도 파일마다 독립이고 경합이 없습니다. 다만 그 의미를 모르고 여러 파일에 걸쳐 쌓이기를 기대한 코드는 티 나지 않게 다르게 동작하므로, 그런 누적은 전역 스크립트(§4)에 둡니다.

검증 자체의 검증

규칙 파일이 141개가 되면 그것들이 옳은지 누가 보는가가 남습니다. 픽스처 워크북 하나와 기대 진단 목록을 짝지어 게이트로 두는 것이 이 저장소가 다른 기능에 이미 쓰는 방법이고, 검증에도 같은 것이 필요합니다 — 잘못된 검증은 통과시키거나, 더 나쁘게는 옳은 데이터를 차단합니다.

규칙별 억제 목록(Suppress: ["R012"])은 두지 않습니다. 표시 없는 통과를 recipe에 적어두는 장치이고, 억제가 필요한 규칙은 규칙이 틀린 것입니다.

11. 정한 것과 다시 볼 조건

여기에 열린 것은 없습니다. 각 항목은 적힌 대로 진행하고, 다시 보게 만드는 조건만 함께 적어둡니다 — 조건이 있는 것과 결정이 없는 것은 다릅니다.

항목정한 것다시 볼 때
액세서의 세부 형태레코드 그룹·배열·옵셔널(Has{필드})은 C# 생성기의 규칙을 그대로 따릅니다. 검증용 표기를 따로 만들지 않습니다이식(§9-6)에서 검증 특유의 불편이 드러날 때. 표면 API는 규칙 파일 141개가 정하게 두는 것이 맞습니다
파일 형태프로젝트 없는 .cs, 클래스 하나에 Validate(<단계 컨텍스트>) 하나입니다 (§3 · 아래)없습니다
IDE 자동 완성됩니다. 검증 폴더 루트에 액세서와 규칙을 같이 컴파일하는 프로젝트가 실행마다 쓰입니다 (아래). EmitIdeProject로 끌 수 있고 기본은 켜짐입니다 — 액세서 소스는 어차피 쓰이므로편집기가 워크스페이스에서 프로젝트를 고르는 방식이 바뀔 때. 지금은 검증 폴더를 따로 열어야 합니다
데이터 접근 경로메모리 왕복입니다 (§3). 생성된 리더로 읽습니다실측이 269,870행에서 무시할 수 없는 값을 낼 때. 그때의 대안은 액세서를 Model 위의 뷰로 만드는 것이고, 참조 해석과 「게임과 같은 코드」를 잃습니다
경고의 기본 처리TreatWarningsAsErrors의 기본값은 꺼짐입니다. CI에서 켭니다운용해 보고 켜 두는 편이 항상 맞다고 확인될 때
로케일 변종Option("Locale")로 실행을 나눠 돕니다_BC 파일의 모델 표현은 테이블의 행 벌로 정해졌습니다. 컨텍스트가 그 축을 직접 내는 것은 아직입니다
스캐폴딩만듭니다--new-validator Item이 머리말과 파일 뼈대를 씁니다없습니다
컴파일 캐시두지 않았습니다. 실행마다 전부 컴파일합니다규칙 수가 늘어 실측에서 문제가 확인될 때. 그때의 키에는 shared/ 그래프가 들어가야 합니다(§10) — 짐작으로 만들면 무효화가 틀렸을 때 조용히 낡은 규칙을 돌립니다
참조 어셈블리프로세스의 목록에서 경로로 가져옵니다단일 파일 발행에서 검증을 돌려야 할 때. 리소스로 임베드하는 것이 답이고, 지금은 그 배포본에서 메시지를 내고 멈춥니다(§10)

머리말 두 줄, 그리고 그 형태에 이르기까지

// validation/rules/tables/ItemRules.cs
using Tabbit.Rules; // Tables — 생성된 액세서
using Tabbit.Validation; // Context — Run이 받는 인자의 타입

두 줄 다 필요합니다. 호스트가 여는 것은 System · System.Linq · System.Collections.Generic 셋뿐이고, 규칙이 실제로 쓰는 이름 둘은 이 줄들이 엽니다. 그래서 「명시적 import 없이」가 깨지는 정도는 두 줄이고, 그 줄을 사람이 쓰지도 않습니다 — 스캐폴딩이 씁니다. pre/만 첫 줄이 없습니다: 시트를 읽기 전이라 액세서가 아직 없습니다.

이 형태에 이르기까지 세 번 틀렸고, 세 번 다 편집기가 무엇을 해석하는가에서 틀렸습니다.

시도왜 틀렸나
#:project로 프로젝트를 가리키기 (기본 켜짐)#:는 C# 문법이 아니라 SDK가 컴파일 전에 걷어내는 지시자여서, 편집기의 Roslyn은 1행의 구문 오류로 보고 그 파일의 컴파일을 중단합니다. TablesCS0103입니다. 게다가 저장소에 프로젝트 파일이 흩뿌려져 Visual Studio가 그것을 로드하려다 Microsoft.NET.Sdk 해석에 실패했습니다 — .gitignore는 커밋만 차단하고 편집기가 훑는 것은 차단하지 못합니다
최상위 문장 파일을 프로젝트에 넣기실측입니다 — 한 프로젝트에 파일 셋을 넣고 각자 없는 심볼을 하나씩 두었을 때 CS8802 4개가 나오고 CS0103은 0개입니다. 최상위 문장이 컴파일 단위 하나에만 허용되므로 나머지 파일은 본문이 바인딩되지 않습니다. 클래스로 감싼 대조군에서는 CS0103이 6개, 세 심볼 전부 나옵니다
프로젝트를 .generated/에 두기편집기는 프로젝트를 찾을 때 점으로 시작하는 폴더를 건너뜁니다. 어느 편집기도 찾지 못하는 프로젝트입니다. 지금은 검증 폴더 루트에 씁니다 — bin·obj가 그 옆에 생기므로 폴더 검사가 그 둘을 면제합니다
알게 된 것
확인되지 않은 이득에 확실한 비용을 지불하고 있었습니다. 이 문서가 「편집기가 해석하는지는 편집기의 사정」이라고 적어두고도 #:project를 기본으로 켰습니다. 컴파일 캐시에는 「실측 전에는 만들지 않는다」를 적용했는데 이쪽에는 적용하지 않은 것이 실수입니다
「빌드가 통과한다」는 「편집기가 해석한다」가 아닙니다. 프로젝트가 오류 0으로 빌드되는 것을 확인하고 넘어갔지만, 그것은 프로젝트가 성립한다는 뜻일 뿐이고 규칙 파일이 해석되는지는 별개의 측정이었습니다. 없는 심볼을 넣어 컴파일러가 검출하는지 보는 것이 그 측정입니다
형태는 실행이 아니라 편집기가 정했습니다. 최상위 문장이 실행에는 아무 문제가 없었고, 그것을 버린 이유는 전부 편집기 쪽입니다. 규칙을 사람과 에이전트가 쓴다면 타이핑 중에 타입이 보이는 것이 그 값의 절반입니다

#: 줄을 컴파일 전에 비워내는 처리는 남겨두었습니다 — 지시자를 쓰는 파일이 들어와도 SDK가 지웠을 줄 때문에 컴파일이 깨지지는 않도록 하는 것이고, 줄 번호는 유지합니다.

컴파일 단위 — 규칙 파일 하나가 하나

규칙 파일마다 독립 컴파일입니다. 언어의 제약이 아니라 호스트가 그렇게 나눈 것이고, 결과 두 가지를 위해 그렇게 합니다 — 공용 코드의 static 상태가 파일마다 독립이므로 병렬 실행에 경합이 없고, 한 파일의 컴파일 오류가 다른 파일을 막지 않습니다.

Lua의 모듈 수준 캐시가 파일 안에서만 쌓였던 것과 같은 의미이므로 이식에서 동작이 달라지지 않습니다. 여러 테이블을 가로지르는 누적은 전역 스크립트(§4)에 둡니다.