규칙 우선순위 — 티어와 차단점
상태: 구현 완료 (2026-08-14) · 사용법은 검증 · 상위 계획: 검증 사용성과 C# 어셈블리 산출의 8단계 · 선행 설계는 검증 파이프라인
세 가지를 정합니다.
- 적용 범위는 순차 스테이지뿐입니다 —
rules/pre/·rules/global/·rules/runtime/.rules/tables/는 병렬 실행이고 규칙이 테이블 단위이므로 순서에 의미가 없습니다. - 우선순위는 티어이고, 티어 경계는 차단점입니다 — 앞 티어가 오류를 냈으면 뒤 티어를 실행하지 않습니다. 기반 규칙이 실패했을 때 그것에 의존하는 규칙들이 파생 오류를 쏟아내는 것을 막는 것이 이 기능의 값입니다.
- 지정은 어트리뷰트로, 파악은 조회 명령으로 합니다 — 순서가 규칙과 한 몸이므로 어긋날 수 없고, 전체를 한눈에 보는 요구는 해결된 순서를 출력하는 명령이 받습니다.
1. 현재 구조 — 실행 순서가 반영되지 않는 지점
우선순위를 「먼저 실행한다」로만 정의하면 관측 가능한 변화가 없습니다. 네 가지 사실이 그 이유입니다.
| 사실 | 근거 |
|---|---|
| 파일 수집이 이미 결정적입니다 — 경로 기준 정렬 | RuleFolders.cs:199 (OrderBy(..., StringComparer.Ordinal)) |
| 보고 순서가 실행 순서와 무관합니다 — 수집기가 보고 전에 위치(파일 → 시트 → 행)로 재정렬합니다 | Diagnostics.cs:120-127 (SortByLocation) |
스테이지 안에 조기 종료가 없습니다 — RunStage는 파일 전부를 실행하고, 실행을 세우는 것은 마지막 Finish입니다 | ValidationPipeline.cs:144-172 |
| 스테이지 사이에도 차단이 없습니다 — 테이블 스테이지가 오류를 내도 전역 스테이지가 그대로 실행됩니다 | ValidationPipeline.cs:107-134 |
rules/tables/가 병렬인 것도 설계된 것입니다 — Parallel.ForEach로 도는 유일한 스테이지이고,
주석이 「규칙은 순서에 의존해서는 안 되며, 여러 테이블을 함께 봐야 하는 규칙은 rules/global/에
속한다」고 규정합니다(ValidationPipeline.cs:155-161, 174-183).
따라서 이 설계는 순서를 도입하는 것이 아니라 차단점을 도입하는 것입니다. 순서는 이미 결정적이고, 없는 것은 「앞의 것이 실패하면 뒤를 실행하지 않는다」는 의미론입니다.
2. 설계
2.1 범위
순차 스테이지에만 적용합니다 — rules/pre/ · rules/global/ · rules/runtime/.
rules/tables/에 어트리뷰트가
붙어 있으면 무시가 아니라 오류로 보고합니다. 조용히 무시하면 지정한 사람은 그것이
적용되었다고 읽습니다.
2.2 티어와 차단점
- 같은 우선순위 값을 가진 규칙들이 하나의 티어입니다. 티어 안의 실행 순서는 기존과 같이 파일명 기준이며, 따라서 전체 순서는 완전히 결정적입니다.
- 작은 값이 먼저 실행됩니다. 어트리뷰트가 없는 규칙은 기본 티어에 들어갑니다.
- 티어 경계가 차단점입니다. 한 티어가 끝난 시점에 그 티어의 규칙이 오류를 냈으면, 같은 스테이지의 남은 티어를 실행하지 않습니다.
- 건너뛴 것은 반드시 보고합니다. 몇 개의 규칙이 실행되지 않았는지를 기록합니다. 이는
--skip-runtime-validation이 이미 지키는 원칙과 같습니다 — 「꺼진 게이트는 꺼졌다고 말해야 하며, 그러지 않으면 건너뛴 실행이 통과한 실행과 똑같이 읽힙니다」 (ValidationPipeline.cs:114-120).
보고 순서는 바뀌지 않습니다. 진단은 지금처럼 위치로 정렬됩니다. 우선순위는 실행과 차단에만 관여합니다.
2.3 지정 — 어트리뷰트
규칙 클래스에 어트리뷰트를 답니다. 어트리뷰트 타입은 검증 사용성과 C# 어셈블리 산출 5a의 계약 어셈블리에 둡니다 — 규칙 작성자에게 열린 표면이 그 어셈블리이기 때문입니다.
컴파일하지 않고 읽습니다. 순서는 실행 전에 정해져야 하는데 컴파일은 규칙 파일마다 실행
시점에 일어나므로, 어트리뷰트는 수집 단계에서 구문 파싱으로 읽습니다. 엔트리 메서드를
찾을 때 이미 구문 트리를 쓰고 있으므로(RuleCompiler.cs:244-296) 같은 방식입니다.
코어에 선례가 있습니다 — 타깃 실행 순서가 어트리뷰트로 정해집니다
([TabbitTarget("csharp", …, Order = 20)]). 「무엇이 먼저 실행되는가」를 어트리뷰트로
적는 것이 이 저장소의 방식입니다.
2.4 파악 — 조회 명령
해결된 순서를 출력하는 명령을 둡니다. 스테이지 · 티어 · 규칙 파일을 실행 순서대로 보여주며, 설정 파일과 달리 출력이 항상 실제 실행 순서와 일치합니다.
3. 채택하지 않은 것
| 안 | 이유 |
|---|---|
| 설정 파일 | 한눈에 보이는 것은 강점이나 이중 관리가 됩니다. 규칙 하나를 추가하는 데 편집이 두 곳이고, 「목록에 있는데 파일이 없음」·「파일이 있는데 목록에 없음」이라는 오류 종류가 새로 생깁니다. 무엇보 다 파악한 내용이 사실인지를 별도로 보장해야 합니다 — 조회 명령은 그 보장이 필요 없습니다 |
파일명 접두어 (01-Foo.cs) | 구현 없이 지금도 동작합니다(경로 기준 정렬). 그러나 이름이 두 가지 일을 하게 되고, 순서를 바꾸려면 파일을 이름 변경해야 하며, rules/tables/의 파일명 규약(파일명이 테이블에 결속)과 규약이 갈라집니다 |
4. 하위호환
어트리뷰트가 없는 규칙은 기본 티어 하나에 모두 들어가고, 티어가 하나뿐이면 차단점도 없습니다 — 즉 아무것도 지정하지 않은 프로젝트의 동작은 현재와 같습니다. 기존 샘플·픽스처는 수정 대상이 아닙니다.
5. 구현에서 정한 것
| 항목 | 정한 것 |
|---|---|
| 차단점의 세기 | 실행을 세우는 보고 기준입니다. Diagnostics.Count를 보므로 TreatWarningsAsErrors가 별도 설정 없이 맞물립니다 |
| 차단의 범위 | 스테이지 안입니다. 스테이지를 넘는 차단은 현재 없는 동작이라 두지 않았습니다 |
| 어트리뷰트의 이름 | [RulePriority(n)] — src/Validation/RulePriorityAttribute.cs |
| 조회 명령의 이름 | --list-validators. --new-validator와 짝을 이룹니다 |
| 기본 티어 | 0입니다. 한 규칙만 표시해도 나머지를 표시하지 않고 그 앞이나 뒤에 놓을 수 있습니다 |
| 값의 형태 | 평범한 정수 리터럴입니다(부호 포함). 티어는 어느 규칙도 컴파일되기 전에 정해지므로 이름 붙인 상수는 읽을 수 없고, 그 경우를 넘기지 않고 오류로 보고합니다 |
읽기는 RuleFolders.ReadTier가 구문 파싱으로 합니다 — 엔트리 메서드를 찾는 방식과 같습니다.
rules/tables/의 어트리뷰트와 읽을 수 없는 값은 ValidationPipeline.RefuseTiersThatDoNothing이
실행 전에 보고합니다.
6. 게이트
- 티어 순서가 결정적인지 — 같은 입력이 같은 실행 순서를 내는지.
- 차단점 — 앞 티어의 오류가 뒤 티어의 실행을 막고, 건너뛴 규칙 수가 보고되는지.
- 어트리뷰트가 없는 경우 현재와 같은 동작인지.
rules/tables/가 영향을 받지 않는지(병렬 유지), 그리고rules/tables/의 어트리뷰트가 오류로 보고되는지.
EOD