docs(TechLog): Reference 15편의 규칙 표기를 게시된 기록에 맞추고 주제 6 을 다시 쓴다

게시된 Reference 15편이 전부 규칙을 `### N. 제목` 으로 쓰고 적용 조건·예외·예시를 항목으로
쓴다. 내 15편은 규칙을 `**굵게**` 로, 나머지 셋을 문단으로 쓰고 있었다 — Studio 의
rules[]·applyWhen[]·exceptions[]·examples[] 는 배열이라 문단으로 두면 항목이 하나로 접힌다.

  규칙 68개를 `### N. 제목` 으로 바꿨다 (편당 3~7개, 게시된 것은 4~10개)
  적용 조건·예외·예시를 항목으로 갈랐다. 한 항목뿐이던 아홉 편은 조건을 나눠 적었다

주제 6 은 본문을 다시 썼다 — location = 이 정확히 일치하는 경로만 잡아 27개가 얼어붙은
구조, 여덟 곳이 우는 시점을 셋으로 가른 표, digest 를 다시 계산할 때 옛 값을 먼저
재현하는 이유.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-07 19:01:44 +09:00
co-authored by Claude Opus 5
parent 53537e37e8
commit 193da20d09
18 changed files with 327 additions and 198 deletions
@@ -31,32 +31,39 @@ source:
## 규칙 ## 규칙
**만드는 쪽에서 생성한 경로를 공개 라우트 패턴에 맞춘다** ### 1. 만드는 쪽에서 생성한 경로를 공개 라우트 패턴에 맞춘다
종류마다 만들어 낸 주소가 실제 라우트에 걸리는지 백엔드 테스트가 본다. 종류마다 만들어 낸 주소가 실제 라우트에 걸리는지 백엔드 테스트가 본다.
**그리는 쪽에서 서버가 준 주소를 라우트 표에 맞추고, 맞는 라우트가 없으면 링크로 그리지 않는다** ### 2. 그리는 쪽에서 서버가 준 주소를 라우트 표에 맞추고, 맞는 라우트가 없으면 링크로 그리지 않는다
같은 부류가 또 생겨도 방문자가 404 를 만나지는 않는다. 같은 부류가 또 생겨도 방문자가 404 를 만나지는 않는다.
**주소를 고쳤으면 이미 저장된 행도 함께 고친다** ### 3. 주소를 고쳤으면 이미 저장된 행도 함께 고친다
주소가 게시 시점에 굳어져 저장되면 코드만 고쳐도 기존 링크는 깨진 채 남는다. 주소가 게시 시점에 굳어져 저장되면 코드만 고쳐도 기존 링크는 깨진 채 남는다.
**배포 뒤 서버가 내보내는 주소를 전수로 훑는다** ### 4. 배포 뒤 서버가 내보내는 주소를 전수로 훑는다
저장소를 훑는 감사와 다른 것을 본다. 실제로 나가는 주소는 DB 에 있다. 저장소를 훑는 감사와 다른 것을 본다. 실제로 나가는 주소는 DB 에 있다.
## 적용 조건 ## 적용 조건
주소를 서버가 만들어 내보내고 화면은 받은 문자열을 링크로 그리는 구조. 게시 시점에 주소가 굳어져 저장되면 특히 걸린다. - 주소를 서버가 만들어 내보내고 화면은 받은 문자열을 링크로 그릴 때
- 게시 시점에 주소가 굳어져 저장되는 구조일 때. 코드만 고치면 이미 저장된 행이 깨진 채 남는다
- 공개 라우트를 더하거나 지워 주소의 모양이 바뀔 때
- 배포 뒤 서버가 내보내는 주소를 전수로 훑을 때
## 예외 ## 예외
외부 주소는 라우트 표에 없으므로 이 대조의 대상이 아니다. - 외부 주소는 라우트 표에 없으므로 이 대조의 대상이 아니다.
그리는 쪽에만 가드를 두면 링크가 아예 그려지지 않는 것으로 끝나고 원인이 남는다. 만드는 쪽에도 같은 검사를 둔다. - 그리는 쪽에만 가드를 두면 링크가 아예 그려지지 않는 것으로 끝나고 원인이 남는다. 만드는 쪽에도 같은 검사를 둔다.
## 예시 ## 예시
결정 링크가 404 였다. 계약은 앵커라고 적었고 만드는 두 곳이 경로를 만들었다. - 결정 링크가 404 였다. 계약은 앵커라고 적었고 만드는 두 곳이 경로를 만들었다.
배포 후 서버가 내보내는 주소 26개와 주제·축 9개를 더해 35개 전부 200 인 것을 확인했다. - 배포 후 서버가 내보내는 주소 26개와 주제·축 9개를 더해 35개 전부 200 인 것을 확인했다.
저장소 안의 링크 리터럴을 훑는 감사로는 이 결함이 잡히지 않았다. 그 주소는 코드에 없다. - 저장소 안의 링크 리터럴을 훑는 감사로는 이 결함이 잡히지 않았다. 그 주소는 코드에 없다.
@@ -31,31 +31,36 @@ source:
## 규칙 ## 규칙
**우회를 넣는 커밋에 되돌릴 조건을 적는다** ### 1. 우회를 넣는 커밋에 되돌릴 조건을 적는다
무엇이 채워지면 되돌리는지 한 줄로 적는다. 그 조건이 충족됐을 때 실제로 되돌린다. 무엇이 채워지면 되돌리는지 한 줄로 적는다. 그 조건이 충족됐을 때 실제로 되돌린다.
**우회할 때 무엇이 비어 있어서 우회하는지 함께 적는다** ### 2. 우회할 때 무엇이 비어 있어서 우회하는지 함께 적는다
채울 것이 없어서 돌린 것과 구조상 그쪽이 맞아서 돌린 것은 다르다. 채울 것이 없어서 돌린 것과 구조상 그쪽이 맞아서 돌린 것은 다르다.
**되돌릴 생각이 없으면 우회가 아니라 결정으로 적는다** ### 3. 되돌릴 생각이 없으면 우회가 아니라 결정으로 적는다
그때는 조건이 아니라 근거와 감수한 비용을 적는다. 그때는 조건이 아니라 근거와 감수한 비용을 적는다.
## 적용 조건 ## 적용 조건
그 화면이 줄 수 있는 것이 아직 비어 있어 링크나 흐름을 다른 곳으로 돌릴 때. - 그 화면이 줄 수 있는 것이 아직 비어 있어 링크나 흐름을 다른 곳으로 돌릴 때
- 임시라고 말하면서 코드에 남기는 변경을 커밋할 때
- 왜 이 링크가 저기로 가는지 다음 사람이 물을 만한 변경을 넣을 때
## 예외 ## 예외
되돌릴 생각이 없는 영구 변경은 우회가 아니다. 조건 대신 근거를 적는다. - 되돌릴 생각이 없는 영구 변경은 우회가 아니다. 조건 대신 근거를 적는다.
조건을 적을 수 없으면 그것은 우회가 아니라 아직 정하지 않은 것이다. 열린 질문으로 남긴다. - 조건을 적을 수 없으면 그것은 우회가 아니라 아직 정하지 않은 것이다. 열린 질문으로 남긴다.
## 예시 ## 예시
주제 화면이 주제 셋을 하드코딩해 두고 있어 실제 주제는 무엇이든 404 였다. 그때 주제 페이지를 채우는 대신 링크를 탐색 필터로 돌렸다. - 주제 화면이 주제 셋을 하드코딩해 두고 있어 실제 주제는 무엇이든 404 였다. 그때 주제 페이지를 채우는 대신 링크를 탐색 필터로 돌렸다.
돌린 이유는 그 페이지만 줄 수 있는 것 — 설명, 범위, 선별한 대표 기록 — 이 전부 비어 있었고 Studio 에 주제 설명을 쓸 칸조차 없었기 때문이다. - 돌린 이유는 그 페이지만 줄 수 있는 것 — 설명, 범위, 선별한 대표 기록 — 이 전부 비어 있었고 Studio 에 주제 설명을 쓸 칸조차 없었기 때문이다.
우회를 남겨 두면 「왜 주제 링크가 탐색으로 가지?」라는 질문이 계속 따라온다. - 우회를 남겨 두면 「왜 주제 링크가 탐색으로 가지?」라는 질문이 계속 따라온다.
그 조건을 커밋 메시지에 적었고, 주제 화면을 계약에 잇고 하드코딩을 없앤 뒤 링크를 곧장 주제 화면으로 되돌렸다. - 그 조건을 커밋 메시지에 적었고, 주제 화면을 계약에 잇고 하드코딩을 없앤 뒤 링크를 곧장 주제 화면으로 되돌렸다.
@@ -34,34 +34,41 @@ source:
## 규칙 ## 규칙
**보고된 증상과 같은 축의 값을 잰다** ### 1. 보고된 증상과 같은 축의 값을 잰다
「제목이 작아 보인다」는 글자 크기와 굵기다. 격자와 열은 다른 축이다. 「제목이 작아 보인다」는 글자 크기와 굵기다. 격자와 열은 다른 축이다.
**촬영을 스크립트로 고정한다** ### 2. 촬영을 스크립트로 고정한다
손으로 찍으면 뷰포트와 축소 배율이 매번 달라진다. 손으로 찍으면 뷰포트와 축소 배율이 매번 달라진다.
**폭을 고정해 여러 개를 돌고, 폭마다 측정값도 남긴다** ### 3. 폭을 고정해 여러 개를 돌고, 폭마다 측정값도 남긴다
스크린샷만 남기면 나중에 그 값이 얼마였는지 다시 잴 수 없다. 스크린샷만 남기면 나중에 그 값이 얼마였는지 다시 잴 수 없다.
**전체 페이지를 한 장으로 찍지 않는다** ### 4. 전체 페이지를 한 장으로 찍지 않는다
축소되어 글자 크기가 실제와 달라진다. 화면 높이만큼 잘라 찍는다. 축소되어 글자 크기가 실제와 달라진다. 화면 높이만큼 잘라 찍는다.
## 적용 조건 ## 적용 조건
화면이 잘못됐다는 보고를 확인하는 일. 배포본의 실제 렌더 결과를 판단 근거로 삼을. - 화면이 잘못됐다는 보고를 확인
- 배포본의 실제 렌더 결과를 판단 근거로 삼을 때
- 고쳤다고 답하기 전에 무엇을 쟀는지 대야 할 때
- 여러 폭에서 배치가 갈리는 사이트를 검토할 때
## 예외 ## 예외
측정 대상이 좌표나 간격 자체라면 바운딩 박스는 프록시가 아니라 대상이다. - 측정 대상이 좌표나 간격 자체라면 바운딩 박스는 프록시가 아니라 대상이다.
CSS 선언이 정본과 같은지를 보는 검사는 렌더 결과를 재지 않는다. 그 검사가 덮는 범위를 알고 쓰면 된다. - CSS 선언이 정본과 같은지를 보는 검사는 렌더 결과를 재지 않는다. 그 검사가 덮는 범위를 알고 쓰면 된다.
## 예시 ## 예시
격자와 열만 재고 「정상」이라 답했다. 사용자가 다시 지적한 뒤 전체 페이지 스크린샷을 찍어서야 26px 과 400 을 봤다. - 격자와 열만 재고 「정상」이라 답했다. 사용자가 다시 지적한 뒤 전체 페이지 스크린샷을 찍어서야 26px 과 400 을 봤다.
목록 간격을 바운딩 박스로만 재서 「판단 기준」을 결함으로 잘못 지목한 적이 있다. - 목록 간격을 바운딩 박스로만 재서 「판단 기준」을 결함으로 잘못 지목한 적이 있다.
브라우저 세션이 리셋되면 창이 약 877px 로 돌아가는데 이 사이트의 분기는 1179·1050·900·767 이라, 877px 에서 본 배치는 방문자 대부분이 보는 배치가 아니다. - 브라우저 세션이 리셋되면 창이 약 877px 로 돌아가는데 이 사이트의 분기는 1179·1050·900·767 이라, 877px 에서 본 배치는 방문자 대부분이 보는 배치가 아니다.
전체 페이지를 한 장으로 찍으면 1425x4466 이 638x2000 으로 들어와 17px 글자가 7~8px 이 된다. - 전체 페이지를 한 장으로 찍으면 1425x4466 이 638x2000 으로 들어와 17px 글자가 7~8px 이 된다.
@@ -31,32 +31,39 @@ source:
## 규칙 ## 규칙
**배치 속성은 그 배치를 쓰는 요소까지 좁혀 적는다** ### 1. 배치 속성은 그 배치를 쓰는 요소까지 좁혀 적는다
`display: grid|flex`, `grid-template-columns`, `padding` 을 구역 클래스 아래 태그 선택자로 걸지 않는다. `display: grid|flex`, `grid-template-columns`, `padding` 을 구역 클래스 아래 태그 선택자로 걸지 않는다.
**CSS 만으로 막지 않고 구조도 본다** ### 2. CSS 만으로 막지 않고 구조도 본다
선택자를 좁히면 같은 구조가 다시 생겼을 때 다시 샌다. 제목 안에 링크를 두지 않는 편이 낫다. 선택자를 좁히면 같은 구조가 다시 생겼을 때 다시 샌다. 제목 안에 링크를 두지 않는 편이 낫다.
**이미 좁혀 둔 곳은 검사에서 명시적으로 뺀다** ### 3. 이미 좁혀 둔 곳은 검사에서 명시적으로 뺀다
예외를 적어 두지 않으면 검사 결과가 늘 빨갛고 곧 읽히지 않는다. 예외를 적어 두지 않으면 검사 결과가 늘 빨갛고 곧 읽히지 않는다.
**전역 규칙과 CSS module 이 섞인 화면을 따로 센다** ### 4. 전역 규칙과 CSS module 이 섞인 화면을 따로 센다
전역 규칙을 고쳐도 module 을 쓰는 화면에는 닿지 않는다. 한 화면 안에서 두 언어가 섞이면 더 눈에 띈다. 전역 규칙을 고쳐도 module 을 쓰는 화면에는 닿지 않는다. 한 화면 안에서 두 언어가 섞이면 더 눈에 띈다.
## 적용 조건 ## 적용 조건
구역 클래스 아래에 태그 선택자로 배치를 거는 CSS. 같은 구역 안에 제목과 목록이 같이 있으면 특히 걸린다. - 구역 클래스 아래에 태그 선택자로 배치를 거는 CSS 를 쓸 때
- 같은 구역 안에 제목과 목록이 함께 있을 때
- 전역 규칙과 CSS module 이 한 화면에 섞여 있을 때
- 「디자인이 안 된 것처럼 보인다」는 보고를 받았을 때
## 예외 ## 예외
색이나 글꼴만 거는 규칙은 새어도 티가 나지 않으므로 대상이 아니다. - 색이나 글꼴만 거는 규칙은 새어도 티가 나지 않으므로 대상이 아니다.
구역 안의 모든 같은 태그가 실제로 같은 배치를 써야 하는 화면이라면 좁히지 않아도 된다. 그때는 그 의도를 검사의 예외 목록에 적는다. - 구역 안의 모든 같은 태그가 실제로 같은 배치를 써야 하는 화면이라면 좁히지 않아도 된다. 그때는 그 의도를 검사의 예외 목록에 적는다.
## 예시 ## 예시
비교 행을 위한 격자가 제목 안의 링크까지 잡아 제목이 200px 칸에 갇혔다. - 비교 행을 위한 격자가 제목 안의 링크까지 잡아 제목이 200px 칸에 갇혔다.
같은 모양이 다른 구역 아홉 곳에도 있어 선택자 51개를 고쳤다. - 같은 모양이 다른 구역 아홉 곳에도 있어 선택자 51개를 고쳤다.
`section-selector-scope.test.ts``.클래스 태그` 모양에 배치 속성이 걸려 있으면 멈춘다. `section-selector-scope.test.ts``.클래스 태그` 모양에 배치 속성이 걸려 있으면 멈춘다.
@@ -34,42 +34,48 @@ source:
## 규칙 ## 규칙
**서버 쪽은 매핑을 리플렉션으로 모아 계약의 경로와 전수 대조한다** ### 1. 서버 쪽은 매핑을 리플렉션으로 모아 계약의 경로와 전수 대조한다
`@RestController` 들을 훑어 실제 매핑을 모으고 계약이 선언한 경로 전부와 맞춘다. 기대 목록을 손으로 적으면 그 목록이 또 하나의 손 목록이 되므로 계약에서 읽는다. `@RestController` 들을 훑어 실제 매핑을 모으고 계약이 선언한 경로 전부와 맞춘다. 기대 목록을 손으로 적으면 그 목록이 또 하나의 손 목록이 되므로 계약에서 읽는다.
**화면 쪽은 계약이 선언한 연산이 기여 목록에 등록됐는지 본다** ### 2. 화면 쪽은 계약이 선언한 연산이 기여 목록에 등록됐는지 본다
타입은 계약에서 생성되므로 등록을 빠뜨려도 에디터에서 그 연산이 보이고 컴파일이 통과한다. 그 상태에서 부르면 게이트웨이가 등록된 것 중에서 고르므로 옆 분기로 떨어지고, 서버는 그 요청에 정상 응답한다. 타입은 계약에서 생성되므로 등록을 빠뜨려도 에디터에서 그 연산이 보이고 컴파일이 통과한다. 그 상태에서 부르면 게이트웨이가 등록된 것 중에서 고르므로 옆 분기로 떨어지고, 서버는 그 요청에 정상 응답한다.
**구현하지 않기로 한 연산은 이유와 함께 명시 목록에 넣는다** ### 3. 구현하지 않기로 한 연산은 이유와 함께 명시 목록에 넣는다
「빠뜨린 것」과 구분되지 않으면 대조 결과가 곧 무시된다. 이 저장소는 작업본 API 로 대체된 옛 연산 51개를 그 이름의 상수에 담고, 봉투 없이 바이트를 주는 연산 하나를 별도 상수로 면제한다. 면제가 코드에 이름으로 남아 다음 사람이 세어 볼 수 있다. 「빠뜨린 것」과 구분되지 않으면 대조 결과가 곧 무시된다. 이 저장소는 작업본 API 로 대체된 옛 연산 51개를 그 이름의 상수에 담고, 봉투 없이 바이트를 주는 연산 하나를 별도 상수로 면제한다. 면제가 코드에 이름으로 남아 다음 사람이 세어 볼 수 있다.
**두 쪽 다 돌린다** ### 4. 두 쪽 다 돌린다
한쪽만 대조하면 다른 쪽을 지웠을 때 잡히지 않는다. 한쪽만 대조하면 다른 쪽을 지웠을 때 잡히지 않는다.
**생성 모델 검사를 이 대조로 세지 않는다** ### 5. 생성 모델 검사를 이 대조로 세지 않는다
모델 생성은 스키마와 속성만 본다. 구현이 없어도 모델은 멀쩡히 만들어진다. 모델 생성은 스키마와 속성만 본다. 구현이 없어도 모델은 멀쩡히 만들어진다.
**전수 대조가 무거우면 깨지는 모양으로 좁힌다** ### 6. 전수 대조가 무거우면 깨지는 모양으로 좁힌다
관리 계약은 86 operation 이라 전수 대조가 무겁다. 이 저장소는 대신 「한 종류만 빠진 항목」을 보게 했다 — 깨진 것이 늘 그 모양이었기 때문이다. 좁힌 기준은 무엇을 보지 않는지도 함께 적는다. 관리 계약은 86 operation 이라 전수 대조가 무겁다. 이 저장소는 대신 「한 종류만 빠진 항목」을 보게 했다 — 깨진 것이 늘 그 모양이었기 때문이다. 좁힌 기준은 무엇을 보지 않는지도 함께 적는다.
## 적용 조건 ## 적용 조건
계약이 한 저장소에 있고 두 저장소가 그것을 반입해 각자 구현하는 구조. 연산을 더하거나 지우는 변경에서 이 대조를 돌린다. - 계약이 한 저장소에 있고 두 저장소가 그것을 반입해 각자 구현하는 구조. 연산을 더하거나 지우는 변경에서 이 대조를 돌린다.
화면이 「데이터가 없습니다」를 그리는데 저장소에는 값이 있을 때 이 대조를 먼저 본다. 구현이 없어서 404 인 것과 정말 0건인 것이 화면에서 같아 보인다. - 화면이 「데이터가 없습니다」를 그리는데 저장소에는 값이 있을 때 이 대조를 먼저 본다. 구현이 없어서 404 인 것과 정말 0건인 것이 화면에서 같아 보인다.
## 예외 ## 예외
계약과 구현이 같은 저장소에 있고 같은 빌드를 지나면 컴파일러가 이 대조를 대신한다. - 계약과 구현이 같은 저장소에 있고 같은 빌드를 지나면 컴파일러가 이 대조를 대신한다.
연산이 봉투 규약을 따르지 않으면 경로 대조에서 뺀다. 다만 뺀 이유를 목록에 적는다. - 연산이 봉투 규약을 따르지 않으면 경로 대조에서 뺀다. 다만 뺀 이유를 목록에 적는다.
## 예시 ## 예시
매핑 하나를 떼어 보고 대조 검사가 그 연산 하나를 정확히 짚는 것을 확인한 뒤 커밋했다. - 매핑 하나를 떼어 보고 대조 검사가 그 연산 하나를 정확히 짚는 것을 확인한 뒤 커밋했다.
두 목록 조회에 컨트롤러가 없어 홈 편집기가 「이 프로젝트에 열린 질문이 없습니다」를 그렸다. 실제로는 넷이 있었고 공개 사이트에도 나오고 있었다. - 두 목록 조회에 컨트롤러가 없어 홈 편집기가 「이 프로젝트에 열린 질문이 없습니다」를 그렸다. 실제로는 넷이 있었고 공개 사이트에도 나오고 있었다.
옛 연산 51개를 명시하지 않았다면 대조 결과가 51건의 실패로 나와 아무도 읽지 않았을 것이다. - 옛 연산 51개를 명시하지 않았다면 대조 결과가 51건의 실패로 나와 아무도 읽지 않았을 것이다.
기여 목록에 등록하지 않은 연산 넷을 만났다. 둘은 옆 분기로 떨어져 다른 기록을 다뤘고 둘은 빈 목록이 됐다. - 기여 목록에 등록하지 않은 연산 넷을 만났다. 둘은 옆 분기로 떨어져 다른 기록을 다뤘고 둘은 빈 목록이 됐다.
@@ -31,30 +31,36 @@ source:
## 규칙 ## 규칙
**기대된 실패는 조건을 적어 뺀다** ### 1. 기대된 실패는 조건을 적어 뺀다
미리보기가 아직 없는 문서는 현재 미리보기를 물으면 404 를 답하고, 화면은 그것을 「미리보기를 만드세요」로 바꾼다. 이런 응답은 실패가 아니다. 미리보기가 아직 없는 문서는 현재 미리보기를 물으면 404 를 답하고, 화면은 그것을 「미리보기를 만드세요」로 바꾼다. 이런 응답은 실패가 아니다.
**뺀 나머지는 전부 실패시킨다** ### 2. 뺀 나머지는 전부 실패시킨다
조건에 걸리지 않는 4xx 와 5xx 는 모두 스윕을 실패시킨다. 조건에 걸리지 않는 4xx 와 5xx 는 모두 스윕을 실패시킨다.
**조건을 적을 수 없으면 빼지 않는다** ### 3. 조건을 적을 수 없으면 빼지 않는다
조건 없이 빼면 진짜 실패도 같이 빠진다. 조건 없이 빼면 진짜 실패도 같이 빠진다.
**뺀 조건을 사람이 읽을 수 있는 곳에 남긴다** ### 4. 뺀 조건을 사람이 읽을 수 있는 곳에 남긴다
왜 그 응답이 기대된 것인지 적혀 있지 않으면 다음 사람이 조건을 넓힌다. 왜 그 응답이 기대된 것인지 적혀 있지 않으면 다음 사람이 조건을 넓힌다.
## 적용 조건 ## 적용 조건
배포 뒤 전 화면을 훑는 스윕처럼 결과를 사람이 훑어보는 검사. 정상 동작이 오류 상태 코드로 나타나는 화면이 있는 서비스에서 걸린다. - 배포 뒤 전 화면을 훑는 스윕처럼 결과를 사람이 훑어보는 검사를 만들 때
- 정상 동작이 오류 상태 코드로 나타나는 화면이 있는 서비스에서
- 같은 빨간 줄이 매번 남기 시작했을 때
## 예외 ## 예외
기계가 판정하고 사람이 결과를 읽지 않는 검사라면 빨간 줄이 쌓여도 무뎌지지 않는다. 그래도 통과 기준은 정해야 한다. - 기계가 판정하고 사람이 결과를 읽지 않는 검사라면 빨간 줄이 쌓여도 무뎌지지 않는다. 그래도 통과 기준은 정해야 한다.
## 예시 ## 예시
미리보기가 없는 문서의 404 를 스윕이 실패로 셌다. 건강한 배포 아래에 매번 같은 빨간 줄이 남았다. - 미리보기가 없는 문서의 404 를 스윕이 실패로 셌다. 건강한 배포 아래에 매번 같은 빨간 줄이 남았다.
> 매번 늑대를 외치는 검사는 읽히지 않게 되고, 진짜 실패가 그 옆에 눈에 띄지 않은 채 앉아 있게 된다. > 매번 늑대를 외치는 검사는 읽히지 않게 되고, 진짜 실패가 그 옆에 눈에 띄지 않은 채 앉아 있게 된다.
로그인 전 세션 탐침의 401 도 같은 부류라 같은 조건으로 제외하고, 나머지 4xx·5xx 는 전부 스윕을 실패시킨다. - 로그인 전 세션 탐침의 401 도 같은 부류라 같은 조건으로 제외하고, 나머지 4xx·5xx 는 전부 스윕을 실패시킨다.
@@ -32,32 +32,39 @@ source:
## 규칙 ## 규칙
**요청이 실패하면 실패했다고 적는다** ### 1. 요청이 실패하면 실패했다고 적는다
빈 배열로 삼키지 않는다. 0건과 실패는 다른 문구를 쓴다. 빈 배열로 삼키지 않는다. 0건과 실패는 다른 문구를 쓴다.
**한 칸의 실패가 옆 칸을 끌고 내려가지 않게 한다** ### 2. 한 칸의 실패가 옆 칸을 끌고 내려가지 않게 한다
여러 목록을 하나로 묶어 기다리면 한쪽의 실패가 전체를 실패로 만든다. 따로 읽고 실패한 목록에만 적는다. 여러 목록을 하나로 묶어 기다리면 한쪽의 실패가 전체를 실패로 만든다. 따로 읽고 실패한 목록에만 적는다.
**거절만 잡는 처리로는 부족하다** ### 3. 거절만 잡는 처리로는 부족하다
호출이 동기적으로 던지면 그 처리기를 지나지 않는다. 던지는 경로도 잡는다. 호출이 동기적으로 던지면 그 처리기를 지나지 않는다. 던지는 경로도 잡는다.
**항목을 걸러 낼 때 걸러 낸 것을 세어 둔다** ### 4. 항목을 걸러 낼 때 걸러 낸 것을 세어 둔다
매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 거르면, 목록이 한 줄 짧아지는 것 말고는 흔적이 없다. 매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 거르면, 목록이 한 줄 짧아지는 것 말고는 흔적이 없다.
## 적용 조건 ## 적용 조건
목록·요약·카운트처럼 「비어 있음」이 정상값이라 실패와 구분되지 않는 화면. 작성 도구에서 특히 걸린다 — 작성자가 자기 작업물과 화면을 대조하기 때문이다. - 목록·요약·카운트처럼 「비어 있음」이 정상값이라 실패와 구분되지 않는 화면을 만들 때
- 작성 도구를 만들 때. 작성자가 자기 작업물과 화면을 대조하므로 오독이 곧바로 작업 판단이 된다
- 한 화면이 여러 목록을 함께 받아 그릴 때
- 매퍼가 모르는 값에 빈 값을 돌려주고 호출부가 그것을 거를 때
## 예외 ## 예외
정말로 0건인 것과 못 읽은 것을 구분할 수 없는 화면이라면 그 구분을 먼저 만든다. 구분 없이 문구만 바꾸면 0건이 실패로 읽힌다. - 정말로 0건인 것과 못 읽은 것을 구분할 수 없는 화면이라면 그 구분을 먼저 만든다. 구분 없이 문구만 바꾸면 0건이 실패로 읽힌다.
읽는 사람이 그 데이터를 만들지 않는 화면 — 공개 조회 — 에서는 실패를 화면 전체의 오류로 다뤄도 된다. - 읽는 사람이 그 데이터를 만들지 않는 화면 — 공개 조회 — 에서는 실패를 화면 전체의 오류로 다뤄도 된다.
## 예시 ## 예시
「이 프로젝트에 열린 질문이 없습니다」가 적혀 있는 동안 그 프로젝트에는 질문이 넷 있었고 공개 사이트에도 나오고 있었다. - 「이 프로젝트에 열린 질문이 없습니다」가 적혀 있는 동안 그 프로젝트에는 질문이 넷 있었고 공개 사이트에도 나오고 있었다.
탭 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그리고, 못 받은 탭에는 못 받았다고 적는다. - 탭 하나를 못 받아도 탭 줄과 나머지 탭은 그대로 그리고, 못 받은 탭에는 못 받았다고 적는다.
매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 걸러 내, 질문과 개념이 목록에서 사라졌다. - 매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 걸러 내, 질문과 개념이 목록에서 사라졌다.
@@ -35,51 +35,58 @@ source:
## 규칙 ## 규칙
**유한한 집합의 분기는 값마다 항목을 요구하는 형태로 쓴다** ### 1. 유한한 집합의 분기는 값마다 항목을 요구하는 형태로 쓴다
`Record<K, V>` 는 키 집합이 `K` 와 정확히 같은 객체 타입이라, 키가 하나 모자라면 그 리터럴이 그 타입이 아니게 된다. `K` 에 값을 더하면 리터럴을 쓴 곳이 전부 타입 오류가 된다. Java 에서는 sealed 타입을 대상으로 switch 를 식으로 쓴다 — 식은 값을 내놓아야 하므로 모든 경우에 무엇을 반환할지 컴파일러가 요구한다. 문으로 쓴 switch 는 요구하지 않는다. `Record<K, V>` 는 키 집합이 `K` 와 정확히 같은 객체 타입이라, 키가 하나 모자라면 그 리터럴이 그 타입이 아니게 된다. `K` 에 값을 더하면 리터럴을 쓴 곳이 전부 타입 오류가 된다. Java 에서는 sealed 타입을 대상으로 switch 를 식으로 쓴다 — 식은 값을 내놓아야 하므로 모든 경우에 무엇을 반환할지 컴파일러가 요구한다. 문으로 쓴 switch 는 요구하지 않는다.
**키가 그 열거형이 아니면 표로 좁히지 말고 그 이유를 코드 옆에 적는다** ### 2. 키가 그 열거형이 아니면 표로 좁히지 말고 그 이유를 코드 옆에 적는다
주소 앞머리로 종류를 거꾸로 찾는 코드는 키가 종류가 아니라 주소다. 담기는 값이 열거형 밖으로 나가는 칸도 같다. 억지로 표로 바꾸면 실제 값 집합과 타입이 어긋나므로, 배열이나 문자열 분기로 두고 왜 좁힐 수 없는지를 주석으로 남긴다. 주소 앞머리로 종류를 거꾸로 찾는 코드는 키가 종류가 아니라 주소다. 담기는 값이 열거형 밖으로 나가는 칸도 같다. 억지로 표로 바꾸면 실제 값 집합과 타입이 어긋나므로, 배열이나 문자열 분기로 두고 왜 좁힐 수 없는지를 주석으로 남긴다.
**표에 있는 「그렇게 정했다」를 표 안에서 읽히게 한다** ### 3. 표에 있는 「그렇게 정했다」를 표 안에서 읽히게 한다
표의 어느 칸이 다른 값들과 다른 규칙을 따르면, 그것이 빠뜨린 것인지 정한 것인지 표만 봐서는 갈리지 않는다. 이 저장소의 종류별 목록 표에서 결정만 자기 목록이 아니라 프로젝트 목록으로 가는데, 그 이유가 표 위에 적혀 있어 다음 사람이 구분한다. 표의 어느 칸이 다른 값들과 다른 규칙을 따르면, 그것이 빠뜨린 것인지 정한 것인지 표만 봐서는 갈리지 않는다. 이 저장소의 종류별 목록 표에서 결정만 자기 목록이 아니라 프로젝트 목록으로 가는데, 그 이유가 표 위에 적혀 있어 다음 사람이 구분한다.
**한 파일을 표로 바꿨다고 그 파일이 다 바뀐 것으로 보지 않는다** ### 4. 한 파일을 표로 바꿨다고 그 파일이 다 바뀐 것으로 보지 않는다
작업본 검증기는 유형별 칸을 표로 갖고 있으면서 문자열 칸 목록은 사슬로 남겨 두었다. 같은 함수 안에서 두 목록의 상태가 갈린다. 작업본 검증기는 유형별 칸을 표로 갖고 있으면서 문자열 칸 목록은 사슬로 남겨 두었다. 같은 함수 안에서 두 목록의 상태가 갈린다.
**컴파일러가 못 보는 경계에는 계약을 읽어 대조하는 검사를 둔다** ### 5. 컴파일러가 못 보는 경계에는 계약을 읽어 대조하는 검사를 둔다
계약이 다른 저장소에 있고 생성기를 지나 타입으로 들어오면, 계약 쪽 열거형이 좁아도 그 타입은 유효하다. 계약 문서를 파싱해 코드의 표와 맞춰 본다. 계약이 다른 저장소에 있고 생성기를 지나 타입으로 들어오면, 계약 쪽 열거형이 좁아도 그 타입은 유효하다. 계약 문서를 파싱해 코드의 표와 맞춰 본다.
**가드를 넣었으면 그 가드가 실제로 잡는지 되돌려 확인한 뒤 커밋한다** ### 6. 가드를 넣었으면 그 가드가 실제로 잡는지 되돌려 확인한 뒤 커밋한다
계약에서 값을 하나 빼고 검사가 빨개지는 것을 본다. 확인하지 않은 가드는 그 값이 원래 없었는지 검사가 안 도는지 구별되지 않는다. 계약에서 값을 하나 빼고 검사가 빨개지는 것을 본다. 확인하지 않은 가드는 그 값이 원래 없었는지 검사가 안 도는지 구별되지 않는다.
**한 열거형을 여러 계약이 따로 적고 있으면 그 목록을 기계로 뽑는다** ### 7. 한 열거형을 여러 계약이 따로 적고 있으면 그 목록을 기계로 뽑는다
세 계약을 파싱해 「일부 값만 열거한 enum」을 전부 뽑는 편이 눈으로 찾는 것보다 빠르고 빠뜨림이 없다. 세 계약을 파싱해 「일부 값만 열거한 enum」을 전부 뽑는 편이 눈으로 찾는 것보다 빠르고 빠뜨림이 없다.
## 적용 조건 ## 적용 조건
값이 유한한 집합인 것을 코드나 계약 여러 곳에서 분기하거나 열거하는 곳. 문서 종류, 상태, 역할, 오류 코드, 라우트 이름이 여기 해당한다. - 값이 유한한 집합인 것을 코드나 계약 여러 곳에서 분기하거나 열거하는 곳. 문서 종류, 상태, 역할, 오류 코드, 라우트 이름이 여기 해당한다.
새 값을 더하는 변경을 시작할 때 이 규칙을 먼저 건다. 다 더한 뒤에 빠진 곳을 찾는 순서로는 조용히 빠진 곳을 못 찾는다 — 조용히 빠진 곳은 증상이 없으므로 훑어서는 나오지 않는다. - 새 값을 더하는 변경을 시작할 때 이 규칙을 먼저 건다. 다 더한 뒤에 빠진 곳을 찾는 순서로는 조용히 빠진 곳을 못 찾는다 — 조용히 빠진 곳은 증상이 없으므로 훑어서는 나오지 않는다.
계약을 소유한 저장소가 따로 있으면 계약 쪽 열거형에도 같이 건다. 이 저장소에서 열세 건 중 셋이 계약 안에 있었다. - 계약을 소유한 저장소가 따로 있으면 계약 쪽 열거형에도 같이 건다. 이 저장소에서 열세 건 중 셋이 계약 안에 있었다.
## 예외 ## 예외
그 칸이 집합 밖의 값도 담으면 표로 좁힐 수 없다. 공개 투영의 resource_type 이 그런 칸이다 — 문서 종류 다섯에 더해 PROJECT 와 RELEASE 를 담는다. 이때는 대조 검사를 대신 둔다. - 그 칸이 집합 밖의 값도 담으면 표로 좁힐 수 없다. 공개 투영의 resource_type 이 그런 칸이다 — 문서 종류 다섯에 더해 PROJECT 와 RELEASE 를 담는다. 이때는 대조 검사를 대신 둔다.
키가 그 열거형이 아닌 자료 구조도 대상이 아니다. 주소 앞머리로 종류를 찾는 배열이 그렇다. - 키가 그 열거형이 아닌 자료 구조도 대상이 아니다. 주소 앞머리로 종류를 찾는 배열이 그렇다.
값이 하나뿐이거나 분기가 한 곳에만 있으면 표로 바꾸는 비용이 이득보다 크다. - 값이 하나뿐이거나 분기가 한 곳에만 있으면 표로 바꾸는 비용이 이득보다 크다.
## 예시 ## 예시
종류를 하나 더한 커밋에서 컴파일러가 게시 상태 코드와 활동 유형과 소유자 유형과 slug 중복 검사와 렌더 모델까지 짚었다. 식으로 쓴 switch 였기 때문이다. - 종류를 하나 더한 커밋에서 컴파일러가 게시 상태 코드와 활동 유형과 소유자 유형과 slug 중복 검사와 렌더 모델까지 짚었다. 식으로 쓴 switch 였기 때문이다.
같은 종류를 더할 때 프론트엔드에서는 열세 곳이 조용히 지나갔다. 삼항 사슬과 배열 리터럴이었다. - 같은 종류를 더할 때 프론트엔드에서는 열세 곳이 조용히 지나갔다. 삼항 사슬과 배열 리터럴이었다.
계약에서 CONCEPT 을 빼자 백엔드의 계약 대조 테스트가 빨개졌다. 그 확인을 하고 커밋했다. - 계약에서 CONCEPT 을 빼자 백엔드의 계약 대조 테스트가 빨개졌다. 그 확인을 하고 커밋했다.
라우트에 딸린 청크 이름 표도 같은 부류라 다섯 검사 안에서 대조하게 했다. - 라우트에 딸린 청크 이름 표도 같은 부류라 다섯 검사 안에서 대조하게 했다.
종류별 목록 주소가 세 화면에 흩어져 있던 동안 셋 다 개념을 빠뜨렸다. 표 하나로 모은 뒤에는 화면들이 목록을 손으로 적지 않고 그 표에서 뽑는다. - 종류별 목록 주소가 세 화면에 흩어져 있던 동안 셋 다 개념을 빠뜨렸다. 표 하나로 모은 뒤에는 화면들이 목록을 손으로 적지 않고 그 표에서 뽑는다.
@@ -60,25 +60,35 @@ tech-log-frontend : ab8c6c1 · 6784eb1
## 내부 이동은 되고 하드 로드는 안 된다 ## 내부 이동은 되고 하드 로드는 안 된다
SPA 안에서 이동할 때는 라우터가 화면을 그리므로 웹 서버가 개입하지 않는다. 주소를 직접 넣거나 새로고침하면 웹 서버가 먼저 그 경로를 받는다. SPA 안에서 이동할 때는 라우터가 화면을 그리므로 웹 서버가 개입하지 않는다. 주소를 직접 넣거나 새로고침하면 웹 서버가 그 경로를 먼저 받는다.
서빙 계약에 그 경로가 없으면 nginx 는 SPA 로 넘기지 않고 404 를 준다. 서빙 계약에 그 경로가 없으면 nginx 는 SPA 로 넘기지 않고 평문 404 를 준다. 그래서 「내부에서는 되는데 새로고침하면 안 된다」로 나타나고, 개발 중에는 대개 내부 이동으로만 화면에 닿으므로 배포 뒤에 드러난다.
## 손으로 유지하는 절반 ## 손으로 유지하는 절반
서빙 계약의 절반은 라우트 레지스트리에서 유도하고 있었고 나머지 절반은 배열이었다.
> 서빙 계약의 공개 절반은 라우트 레지스트리에서 패턴을 유도한다. **Studio 절반은 손으로 유지하는 배열이었고, 손으로 유지하는 배열이 실패하는 방식 그대로 실패했다** — `^/studio/assets$` 위의 주석이 바로 그 버그를 한 번 고친 기록이고, 라우트를 더하니 즉시 반복됐다. > 서빙 계약의 공개 절반은 라우트 레지스트리에서 패턴을 유도한다. **Studio 절반은 손으로 유지하는 배열이었고, 손으로 유지하는 배열이 실패하는 방식 그대로 실패했다** — `^/studio/assets$` 위의 주석이 바로 그 버그를 한 번 고친 기록이고, 라우트를 더하니 즉시 반복됐다.
그 주석이 남아 있었다는 것이 이 건의 성격을 말한다. 같은 배열을 한 번 고치면서 왜 고쳤는지를 적어 두었는데, 다음 사람이 그 배열에 줄을 더할 때 그 주석을 읽지 않았다.
## 얼어붙은 27개 ## 얼어붙은 27개
공개 절반도 유도가 아니었다. 서빙 계약이 번들된 픽스처에 우연히 들어 있던 공개 경로를 전부 열거하고, 생성된 nginx 가 정확히 그것들을 `location =` 블록으로 게시했다. 공개 절반도 유도라고 하기 어려웠다. 서빙 계약이 번들된 픽스처에 우연히 들어 있던 공개 경로를 전부 열거하고, 생성된 nginx 가 정확히 그것들을 `location =` 블록으로 게시했다.
빌드 이후에 게시된 기록 — 백엔드를 두는 이유 그 자체 — 은 SPA 에 묻기도 전에 엣지에서 404 였다. 경로 27개가 얼어 있었고 28번째는 무엇이든 닿을 수 없었다. `location =` 은 정확히 일치하는 경로만 잡는다. 그래서 빌드 시점에 픽스처에 있던 27개는 열리고, 빌드 이후에 게시된 기록은 SPA 에 묻기도 전에 엣지에서 404 였다 — 백엔드를 두는 이유 그 자체가 그 경계에서 막혔다.
## 라우트 계약에서 유도한다 ## 라우트 계약에서 유도한다
지금은 라우트 계약에서 등록된 Public 라우트마다 정규식 하나를 만든다. 파라미터는 한 세그먼트만 잡고 슬래시는 잡지 않으므로 `/cases/a/b` 는 404 로 남는다. 지금은 라우트 계약에서 등록된 Public 라우트마다 정규식 하나를 만든다.
같은 구조 때문에 `robots.txt` 도 404 였다. 파일은 이미지에 있었지만 nginx 설정이 서빙할 파일을 하나씩 명시하는 구조라 등록되지 않은 것은 SPA 폴백으로 떨어졌고, 크롤러가 index.html 을 규칙으로 읽을 수는 없으므로 규칙이 없는 것과 같았다. 파라미터는 한 세그먼트만 잡고 슬래시는 잡지 않는다. 그래서 `/cases/a/b` 는 404 로 남고, 그 주소가 잘못됐다는 것이 방문자에게도 감사에게도 드러난다.
## 같은 구조가 파일에도 걸렸다
`robots.txt` 도 404 였다. 파일은 이미지에 있었지만 nginx 설정이 서빙할 파일을 하나씩 명시하는 구조라 등록되지 않은 것은 SPA 폴백으로 떨어진다.
폴백은 index.html 을 준다. 사람에게는 화면이 뜨는 것처럼 보이지만 크롤러는 그 HTML 을 robots 규칙으로 읽을 수 없다.
## 확인하지 못한 것 ## 확인하지 못한 것
@@ -60,6 +60,8 @@ CI : FE-GATE-009 — 라우트마다 수동 접근성 증거 1개
## 여덟 곳 ## 여덟 곳
개념 라우트를 더한 커밋이 그 목록을 남겼다.
```text ```text
라우트 계약 tech-log-route-contract.ts 라우트 계약 tech-log-route-contract.ts
런타임 등록 route-runtime-contract 런타임 등록 route-runtime-contract
@@ -73,13 +75,21 @@ CI 게이트 형상 digest 게이트 집합의 sha256
## 우는 시점이 다르다 ## 우는 시점이 다르다
주제 편집 화면을 더하고 청크 이름 표를 빠뜨렸더니 번들은 만들어지는데 빌드 매니페스트 단계에서 `Missing built route chunk: TECH_LOG_STUDIO_TOPIC_EDIT` 로 멈췄다. 검사 다섯 개를 다 통과한 뒤 배포 직전에야 드러났다. 여덟이 같은 시점에 울면 한 번에 고치면 된다. 시점이 갈리므로 다섯 검사를 다 통과한 뒤에도 남는 것이 있다.
| 언제 우나 | 무엇이 |
|---|---|
| 빌드 매니페스트 단계 | vite chunk 이름 표 |
| 배포 직전 CI | 아티팩트 개수 상수 · 수동 접근성 증거 개수 · 게이트 집합의 sha256 |
| 배포 뒤 | nginx 서빙 패턴 |
주제 편집 화면을 더하고 청크 이름 표를 빠뜨렸더니 번들은 만들어지는데 매니페스트 단계에서 `Missing built route chunk: TECH_LOG_STUDIO_TOPIC_EDIT` 로 멈췄다. 검사 다섯 개를 다 통과한 뒤 배포 직전에야 드러났다.
이 표도 손으로 나열한 목록이므로 다섯 검사 안에서 대조하게 했다. 이 표도 손으로 나열한 목록이므로 다섯 검사 안에서 대조하게 했다.
## 게이트 기준값 셋은 라우트마다 움직인다 ## 게이트 기준값 셋이 함께 움직인다
FE-GATE-009 는 설치된 라우트마다 수동 접근성 증거를 하나씩 요구하고, 그 집합이 정확히 일치하지 않으면 거절한다. 접근성 게이트는 설치된 라우트마다 수동 증거를 하나씩 요구하고, 그 집합이 정확히 일치하지 않으면 거절한다. 빠뜨림이 통과가 되지 않게 하려고 정확한 일치를 요구하는 것이고, 그래서 라우트를 더할 때마다 상수 셋이 함께 움직인다.
| 커밋 | 라우트 | 아티팩트 기준선 | 증거 개수 | digest | | 커밋 | 라우트 | 아티팩트 기준선 | 증거 개수 | digest |
|---|---|---|---|---| |---|---|---|---|---|
@@ -88,12 +98,16 @@ FE-GATE-009 는 설치된 라우트마다 수동 접근성 증거를 하나씩
| `048c1b2` | `/concepts/:slug` | +1 | +1 | fb138e7c… 재계산 | | `048c1b2` | `/concepts/:slug` | +1 | +1 | fb138e7c… 재계산 |
| `fe6b56a` | `/topics`, `/topics/:s/:v`, `/studio/topics/:id` | 135 → 138 | 114 → 117 | 87a22f68… 재계산 | | `fe6b56a` | `/topics`, `/topics/:s/:v`, `/studio/topics/:id` | 135 → 138 | 114 → 117 | 87a22f68… 재계산 |
표의 마지막 줄인 주제 화면 셋을 더할 때는 이 목록을 또 빠뜨렸다. 게이트가 빨간 채로 여러 커밋을 지나갔고, 결정 404 를 고치던 `fe6b56a` 에서야 함께 맞췄다. ## 옛 값을 먼저 재현한다
digest 를 다시 계산할 때는 매번 이전 gates.json 에서 옛 상수를 먼저 재현해 계산 방법이 맞는지 확인한 뒤 새 파일을 해싱했다. 그렇게 하지 않으면 계산이 달라져서 새 값이 나온 것과 파일이 바뀌어서 새 값이 나온 것을 구분할 수 없다. digest 를 다시 계산할 때는 매번 이전 gates.json 에서 옛 상수를 먼저 재현해 계산 방법이 맞는지 확인한 뒤 새 파일을 해싱했다.
그렇게 하지 않으면 「계산이 달라졌는데 새 값이 나왔다」와 「파일이 바뀌어서 새 값이 나왔다」를 구분할 수 없다. 둘 다 새 값이 나오고 둘 다 게이트를 통과시킨다.
## 확인하지 못한 것 ## 확인하지 못한 것
게이트 기준값 셋은 여전히 손으로 움직인다. 옛 값을 먼저 재현하는 절차는 사람이 기억해야 하고 검사가 강제하지 않는다. 게이트 기준값 셋은 여전히 손으로 움직인다. 옛 값을 먼저 재현하는 절차는 사람이 기억해야 하고 검사가 강제하지 않는다.
주제 화면 셋을 더할 때 이 목록을 또 빠뜨렸고, 게이트가 빨간 채로 여러 커밋을 지나갔다.
<!-- body:end --> <!-- body:end -->
@@ -30,20 +30,26 @@ source:
## 결정문 ## 결정문
라우트 계약에서 nginx 서빙 패턴을 생성할 때, catch-all 라우트는 패턴으로 번역하지 않고 버린다. 등록된 Public 라우트마다 정규식 하나를 만들고, 파라미터는 한 세그먼트만 잡되 슬래시는 잡지 않는다. 라우트 계약에서 nginx 서빙 패턴을 생성할 때, catch-all 라우트는 패턴으로 번역하지 않고 버린다.
등록된 Public 라우트마다 정규식 하나를 만들고, 파라미터는 한 세그먼트만 잡되 슬래시는 잡지 않는다.
## 판단 이유 ## 판단 이유
모든 미매치 URL 에 index.html 을 주면 엣지에서 404 였을 요청이 200 으로 바뀐다. 그러면 깨진 링크를 크롤러도 우리도 볼 수 없다. 모든 미매치 URL 에 index.html 을 주면 엣지에서 404 였을 요청이 200 으로 바뀐다. 라우터는 그 주소를 모르므로 「없는 화면」을 그리지만, 상태 코드는 200 이다.
서버가 내보내는 주소를 전수 감사할 때 그 감사가 상태 코드로 판정한다. soft 200 이 섞이면 감사가 통과하 방문자만 빈 화면을 만난다. 그러면 깨진 링크를 상태 코드로 판정하는 쪽이 전부 못 본다. 크롤러도 못 보고, 서버가 내보내는 주소를 전수로 훑는 감사도 못 본다. 그 감사는 이 저장소에서 실제로 결함을 잡은 방법이고, soft 200 이 섞이면 감사가 통과하면서 방문자만 빈 화면을 만난다.
파라미터가 슬래시를 잡지 않게 한 것도 같은 이유다. `/cases/a/b` 가 404 로 남아야 그 주소가 잘못됐다는 것이 드러난다. 파라미터가 슬래시를 잡지 않게 한 것도 같은 이유다. `/cases/a/b` 가 404 로 남아야 그 주소가 잘못됐다는 것이 드러난다. 슬래시까지 잡으면 세그먼트가 몇 개든 라우트에 걸리고, 라우터가 그것을 「없는 기록」으로 그린다.
대안은 catch-all 을 번역하고 라우터가 404 화면을 그리게 하는 것이었다. 사람에게 보이는 화면은 같지만 기계가 읽는 상태 코드가 달라지므로 고르지 않았다.
## 영향 ## 영향
라우트를 더할 때마다 서빙 패턴 움직인다. 이 비용은 라우트 계약에서 유도해 없앴다 — 손으로 배열을 고치지 않는다. 라우트를 더할 때마다 서빙 패턴이 함께 움직인다. 이 비용은 라우트 계약에서 유도해 없앴다 — 손으로 배열을 고치지 않는다.
등록되지 않은 주소는 SPA 에 닿지 못한다. 라우트를 더하고 프론트를 배포하기 전까지 그 경로는 엣지에서 404 다. 그래서 새 라우트는 프론트를 먼저 배포한다. 등록되지 않은 주소는 SPA 에 닿지 못한다. 라우트를 더하고 프론트를 배포하기 전까지 그 경로는 엣지에서 404 이고, 그래서 새 라우트는 프론트를 먼저 배포한다.
감사에서 200 을 받은 35개 주소는 실제로 화면이 그려지는 주소다. 배포 뒤 감사에서 200 을 받은 35개 주소는 실제로 화면이 그려지는 주소다. catch-all 을 번역했다면 그 수는 아무 주소나 세어도 나왔을 것이다.
파일도 같은 규칙을 받는다. 서빙 목록에 등록되지 않은 정적 파일은 SPA 폴백으로 떨어지므로, 크롤러가 읽어야 하는 파일은 그 목록에 명시해야 한다.
@@ -28,36 +28,50 @@ source:
## 목적 ## 목적
라우트를 더할 때 같이 고쳐야 하는 목록이 빠지는 것을 막는다. 이 부류는 우는 시점이 제각각이라, 어떤 것은 배포한 뒤 방문자가 먼저 만난다. 라우트를 더할 때 함께 움직여야 하는 목록이 빠지는 것을 막는다.
이 부류는 우는 시점이 제각각이다. 어떤 것은 빌드 매니페스트 단계에서, 어떤 것은 배포 직전 CI 에서, 어떤 것은 배포한 뒤 방문자가 먼저 만난다.
## 규칙 ## 규칙
**서빙 패턴은 라우트 계약에서 유도한다** ### 1. 서빙 패턴은 라우트 계약에서 유도한다
등록된 Public 라우트마다 정규식 하나를 만든다. 파라미터는 한 세그먼트만 잡고 슬래시는 잡지 않는다.
**빌드가 아는 목록을 서빙 계약의 근거로 쓰지 않는다** 등록된 Public 라우트마다 정규식 하나를 만든다. 파라미터는 한 세그먼트만 잡고 슬래시는 잡지 않는다 — 그래야 잘못된 주소가 404 로 남아 감사에 잡힌다.
번들된 픽스처에 우연히 들어 있던 경로를 열거하면 빌드 이후에 게시된 기록이 엣지에서 404 가 된다.
**유도할 수 없는 목록에는 대조 검사를 둔다** ### 2. 빌드가 아는 목록을 서빙 계약의 근거로 쓰지 않는다
vite chunk 이름 표가 그렇다. 이 표를 빠뜨리면 다섯 검사를 다 통과한 뒤 빌드 매니페스트 단계에서 멈춘다.
**기준값 상수는 옛 값을 먼저 재현한 뒤 갱신한다** 번들된 픽스처에 우연히 들어 있던 경로를 열거하면 그 목록이 빌드 시점에 얼어붙는다. `location =` 은 정확히 일치하는 경로만 잡으므로, 빌드 이후에 게시된 기록은 SPA 에 묻기도 전에 엣지에서 404 가 된다.
그렇게 하지 않으면 계산 방법이 달라져 새 값이 나온 것과 파일이 바뀌어 새 값이 나온 것을 구분할 수 없다.
### 3. 유도할 수 없는 목록에는 대조 검사를 둔다
청크 이름 표가 그렇다. 번들러가 그 표를 읽어 이름을 정하므로 라우트 계약에서 만들어 낼 수 없고, 대신 두 목록이 같은지 보는 검사를 다섯 검사 안에 넣는다.
### 4. 기준값 상수는 옛 값을 먼저 재현한 뒤 갱신한다
새 값만 계산해 적으면 계산 방법이 달라져 새 값이 나온 것과 파일이 바뀌어 새 값이 나온 것을 구분할 수 없다. 둘 다 게이트를 통과시킨다.
### 5. 정확한 일치를 요구하는 게이트는 그 대가를 함께 적는다
빠뜨림이 통과가 되지 않게 하려면 집합이 정확히 일치해야 한다. 그러면 라우트를 더할 때마다 상수들이 함께 움직이고, 그 갱신을 사람이 한다.
## 적용 조건 ## 적용 조건
라우트 하나가 서빙 패턴·청크 이름·게이트 기준값 같은 목록을 동시에 움직이는 프론트엔드. 라우트를 더하거나 지우는 변경에서 걸린다. - 라우트 하나가 서빙 패턴·청크 이름·게이트 기준값 같은 목록을 함께 움직이는 프론트엔드. 라우트를 더하거나 지우는 변경에서 걸린다.
- 새 화면을 여는 작업을 시작할 때 그 목록부터 세고 시작한다. 다 만든 뒤에 세면 배포 직전에 걸린다.
## 예외 ## 예외
catch-all 라우트는 서빙 패턴으로 번역하지 않는다. 모든 미매치 URL 에 index.html 을 주면 엣지 404 가 soft 200 이 된다. - catch-all 라우트는 서빙 패턴으로 번역하지 않는다. 모든 미매치 URL 에 index.html 을 주면 엣지 404 가 soft 200 이 된다.
목록이 하나이고 그 목록을 빌드가 강제하면 유도 규칙을 따로 두지 않아도 된다. - 목록이 하나이고 그 목록을 빌드가 강제하면 유도 규칙을 따로 두지 않아도 된다.
## 예시 ## 예시
`/studio/releases` 가 평문 404 였다. 라우트도 청크도 있었고 서빙 계약의 손 배열에만 없었다. `/studio/releases` 가 평문 404 였다. 라우트도 청크도 있었고 서빙 계약의 손 배열에만 없었다.
주제 편집 화면을 더하고 청크 이름 표를 빠뜨렸더니 빌드 매니페스트 단계에서 멈췄다. - 같은 배열을 한 번 고치면서 왜 고쳤는지를 주석으로 남겨 두었는데, 다음 사람이 그 배열에 줄을 더할 때 그 주석을 읽지 않았다.
라우트 셋을 더하면서 게이트 기준값을 빠뜨렸다. 게이트가 빨간 채로 여러 커밋을 지나갔다. - 주제 편집 화면을 더하고 청크 이름 표를 빠뜨렸더니 검사 다섯 개를 다 통과한 뒤 빌드 매니페스트 단계에서 멈췄다.
- 라우트 셋을 더하면서 게이트 기준값을 빠뜨렸다. 게이트가 빨간 채로 여러 커밋을 지나갔다.
@@ -31,33 +31,38 @@ source:
## 규칙 ## 규칙
**톤을 지적받으면 고쳐 쓰기 전에 어떤 말을 쓸지 묻는다** ### 1. 톤을 지적받으면 고쳐 쓰기 전에 어떤 말을 쓸지 묻는다
「더 나은 문장」을 제안하는 것과 그 사람의 말투로 쓰는 것은 다른 일이고, 후자는 제안하는 쪽이 잘하지 못한다. 「더 나은 문장」을 제안하는 것과 그 사람의 말투로 쓰는 것은 다른 일이고, 후자는 제안하는 쪽이 잘하지 못한다.
**무엇이 AI 스러운지 구체적으로 받아 적는다** ### 2. 무엇이 AI 스러운지 구체적으로 받아 적는다
무엇을 하는지 말하지 않는 동사로 끝나는 것과 번역투가 실제로 지적된 두 가지였다. 무엇을 하는지 말하지 않는 동사로 끝나는 것과 번역투가 실제로 지적된 두 가지였다.
**작성자가 이미 쓰는 말투를 따른다** ### 3. 작성자가 이미 쓰는 말투를 따른다
Case 소제목에 쓰는 말이 「~한 것」 명사형이면 새 제목도 그 형태로 맞춘다. 의문형 꼬리와 이 기록에서 쓰지 않는 낱말은 쓰지 않는다. Case 소제목에 쓰는 말이 「~한 것」 명사형이면 새 제목도 그 형태로 맞춘다. 의문형 꼬리와 이 기록에서 쓰지 않는 낱말은 쓰지 않는다.
## 적용 조건 ## 적용 조건
공개 화면의 글이 작성자의 목소리인 곳 — 프로필, 프로젝트 소개, 구역 제목, 기록의 소제목. - 공개 화면의 글이 작성자의 목소리인 곳 — 프로필, 프로젝트 소개, 구역 제목, 기록의 소제목
- 톤이 어색하다는 지적을 받았을 때
- 기존 글에 없던 낱말이나 어미를 새로 넣으려 할 때
## 예외 ## 예외
오류 문구처럼 서버가 답한 사실을 그대로 실어야 하는 곳에서는 말투를 묻지 않는다. 무엇을 실을지를 먼저 정한다. - 오류 문구처럼 서버가 답한 사실을 그대로 실어야 하는 곳에서는 말투를 묻지 않는다. 무엇을 실을지를 먼저 정한다.
계약이나 코드가 정한 이름은 이 규칙에서 뺀다. 표시 이름만 바꾸고 계약 값은 건드리지 않는다. - 계약이나 코드가 정한 이름은 이 규칙에서 뺀다. 표시 이름만 바꾸고 계약 값은 건드리지 않는다.
## 예시 ## 예시
「섞는다」·「함께 기록한다」·「흩어지지 않게」가 무엇을 하는지 말하지 않는 동사로 끝난다고 지적받았다. - 「섞는다」·「함께 기록한다」·「흩어지지 않게」가 무엇을 하는지 말하지 않는 동사로 끝난다고 지적받았다.
「결론이 서는 조건」·「프로젝트를 답니다」·「접근을 나눠 견주고」가 번역투로 지적받았다. - 「결론이 서는 조건」·「프로젝트를 답니다」·「접근을 나눠 견주고」가 번역투로 지적받았다.
「무엇을 견줬나」는 의문형 꼬리에 이 기록에서 쓰지 않는 낱말이었다. 작성자가 Case 소제목에 쓰는 말은 「이 구조에서 감수한 것」처럼 「~한 것」 명사형이라 그쪽에 맞췄다. - 「무엇을 견줬나」는 의문형 꼬리에 이 기록에서 쓰지 않는 낱말이었다. 작성자가 Case 소제목에 쓰는 말은 「이 구조에서 감수한 것」처럼 「~한 것」 명사형이라 그쪽에 맞췄다.
「이 프로젝트가 밝힌 것」은 「프로젝트를 통해 확인한 결과」로, 「운영 가능한 설계로 연결합니다」는 「실제 운영에 적용할 수 있는 형태로 정리합니다」로 바꿨다. - 「이 프로젝트가 밝힌 것」은 「프로젝트를 통해 확인한 결과」로, 「운영 가능한 설계로 연결합니다」는 「실제 운영에 적용할 수 있는 형태로 정리합니다」로 바꿨다.
고쳐 쓴 첫 번째 안이 거절당한 뒤 사용자가 직접 쓴 텍스트를 실었다. - 고쳐 쓴 첫 번째 안이 거절당한 뒤 사용자가 직접 쓴 텍스트를 실었다.
@@ -35,44 +35,50 @@ source:
## 규칙 ## 규칙
**스프링 컨텍스트를 띄우는 검사를 하나 둔다** ### 1. 스프링 컨텍스트를 띄우는 검사를 하나 둔다
컴파일도 단위 테스트도, 실제 데이터베이스를 쓰는 통합 테스트도 컨텍스트를 띄우지 않을 수 있다. 컨테이너가 필요해서 통합 테스트인 것과 컨텍스트를 띄우는 것은 다르다. 스캔되는 컴포넌트의 생성자 규칙처럼 정적으로 셀 수 있는 것은 아키텍처 검사로 대신할 수 있고, 그 편이 빌드 시간을 늘리지 않는다. 컴파일도 단위 테스트도, 실제 데이터베이스를 쓰는 통합 테스트도 컨텍스트를 띄우지 않을 수 있다. 컨테이너가 필요해서 통합 테스트인 것과 컨텍스트를 띄우는 것은 다르다. 스캔되는 컴포넌트의 생성자 규칙처럼 정적으로 셀 수 있는 것은 아키텍처 검사로 대신할 수 있고, 그 편이 빌드 시간을 늘리지 않는다.
**persistence SQL 을 실제 데이터베이스에서 돌리는 태스크를 둔다** ### 2. persistence SQL 을 실제 데이터베이스에서 돌리는 태스크를 둔다
표준 검사가 컨테이너를 띄우지 않으면 어댑터의 SQL 은 한 줄도 실행되지 않는다. 컴파일은 문자열 안을 보지 않고 단위 테스트는 어댑터를 스텁으로 바꾸므로, 컬럼 이름은 실행해야만 검증된다. 표준 검사가 컨테이너를 띄우지 않으면 어댑터의 SQL 은 한 줄도 실행되지 않는다. 컴파일은 문자열 안을 보지 않고 단위 테스트는 어댑터를 스텁으로 바꾸므로, 컬럼 이름은 실행해야만 검증된다.
**계약 모양 그대로의 응답을 진짜 게이트웨이에 넣는 검사를 둔다** ### 3. 계약 모양 그대로의 응답을 진짜 게이트웨이에 넣는 검사를 둔다
화면 테스트는 정적 픽스처 어댑터를 쓰므로 계약 모양을 한 번도 통과시키지 않는다. 픽스처를 게이트웨이가 읽는 모양으로 만들면 그 테스트는 늘 통과한다. 화면 테스트는 정적 픽스처 어댑터를 쓰므로 계약 모양을 한 번도 통과시키지 않는다. 픽스처를 게이트웨이가 읽는 모양으로 만들면 그 테스트는 늘 통과한다.
**실제 런타임 어댑터를 실제 응답 본문에 대고 조립하는 검사를 둔다** ### 4. 실제 런타임 어댑터를 실제 응답 본문에 대고 조립하는 검사를 둔다
게이트웨이 테스트는 실행기를 스텁으로 바꾸고 화면 테스트는 게이트웨이를 스텁으로 바꾼다. 합성 루트에서 credential 을 정하는 코드는 둘 다 덮지 않는다. 게이트웨이 테스트는 실행기를 스텁으로 바꾸고 화면 테스트는 게이트웨이를 스텁으로 바꾼다. 합성 루트에서 credential 을 정하는 코드는 둘 다 덮지 않는다.
**생성기는 모델이 만들어졌는지가 아니라 property 가 계약과 같은지로 본다** ### 5. 생성기는 모델이 만들어졌는지가 아니라 property 가 계약과 같은지로 본다
모델은 필드가 빠져도 만들어진다. 아직 그 필드를 쓰는 코드가 없으면 컴파일도 통과한다. 모델은 필드가 빠져도 만들어진다. 아직 그 필드를 쓰는 코드가 없으면 컴파일도 통과한다.
**전용 태스크로 뺐으면 그것을 돌리는 것이 사람 몫이라는 것도 적는다** ### 6. 전용 태스크로 뺐으면 그것을 돌리는 것이 사람 몫이라는 것도 적는다
컨테이너를 띄우는 검사를 표준 검사에 넣으면 모든 빌드가 느려진다. 빼는 것은 되지만, 뺀 뒤에 그 태스크가 돌지 않으면 검사가 없는 것과 같다. 컨테이너를 띄우는 검사를 표준 검사에 넣으면 모든 빌드가 느려진다. 빼는 것은 되지만, 뺀 뒤에 그 태스크가 돌지 않으면 검사가 없는 것과 같다.
## 적용 조건 ## 적용 조건
스텁으로 층을 나눠 시험하는 구조. 계약이 다른 저장소에 있고 생성기를 지나 들어오거나, 컨테이너가 필요한 검사를 별도 태스크로 뺀 저장소에서 걸린다. - 스텁으로 층을 나눠 시험하는 구조. 계약이 다른 저장소에 있고 생성기를 지나 들어오거나, 컨테이너가 필요한 검사를 별도 태스크로 뺀 저장소에서 걸린다.
「모든 검사가 통과했는데 운영에서 깨졌다」가 나오면 무엇이 깨졌는지보다 어느 이음매를 아무 검사도 지나지 않았는지를 먼저 센다. - 「모든 검사가 통과했는데 운영에서 깨졌다」가 나오면 무엇이 깨졌는지보다 어느 이음매를 아무 검사도 지나지 않았는지를 먼저 센다.
## 예외 ## 예외
그 층을 실제로 지나는 검사가 이미 있으면 더 두지 않는다. - 그 층을 실제로 지나는 검사가 이미 있으면 더 두지 않는다.
스텁을 쓰는 테스트를 늘리는 것은 이 문제를 덮지 않는다. 스텁의 개수가 아니라 어느 층을 대신했는지가 기준이다. - 스텁을 쓰는 테스트를 늘리는 것은 이 문제를 덮지 않는다. 스텁의 개수가 아니라 어느 층을 대신했는지가 기준이다.
정적으로 셀 수 있는 규칙은 컨텍스트를 띄우지 않고도 걸린다. 그때는 무거운 검사를 새로 두지 않는다. - 정적으로 셀 수 있는 규칙은 컨텍스트를 띄우지 않고도 걸린다. 그때는 무거운 검사를 새로 두지 않는다.
## 예시 ## 예시
컨텍스트를 띄우지 않아 파드가 두 번 CrashLoopBackOff 로 들어갔다. 실제 PostgreSQL 위에서 도는 통합 테스트 26개도 통과한 상태였다. - 컨텍스트를 띄우지 않아 파드가 두 번 CrashLoopBackOff 로 들어갔다. 실제 PostgreSQL 위에서 도는 통합 테스트 26개도 통과한 상태였다.
삭제 경로의 SQL 이 한 번도 실행된 적이 없어서 전용 통합 테스트 태스크를 만들었다. - 삭제 경로의 SQL 이 한 번도 실행된 적이 없어서 전용 통합 테스트 태스크를 만들었다.
화면 테스트가 픽스처를 쓰므로 질문 상세의 매핑을 아무도 지나지 않았다. 계약 모양 응답을 진짜 게이트웨이에 넣으니 되돌려 보면 운영과 같은 오류로 실패한다. - 화면 테스트가 픽스처를 쓰므로 질문 상세의 매핑을 아무도 지나지 않았다. 계약 모양 응답을 진짜 게이트웨이에 넣으니 되돌려 보면 운영과 같은 오류로 실패한다.
생성 모델 대조를 schema 이름에서 property 로 바꿨다. 이름 대조는 필드 넷이 빠진 모델을 통과시켰다. - 생성 모델 대조를 schema 이름에서 property 로 바꿨다. 이름 대조는 필드 넷이 빠진 모델을 통과시켰다.
@@ -34,43 +34,48 @@ Studio 편집기에서는 값이 다 보이는데 공개 화면만 비어 있으
## 규칙 ## 규칙
**Studio 에서는 보이고 공개 쪽만 비면 그 사이의 계약을 먼저 본다** ### 1. Studio 에서는 보이고 공개 쪽만 비면 그 사이의 계약을 먼저 본다
두 화면이 같은 데이터베이스를 보는데 한쪽만 비면, 다른 것은 그 사이에 놓인 계약이다. 작성 쪽은 작성 계약을 지나고 조회 쪽은 조회 계약을 지난다. 이 저장소에서 같은 신호가 여덟 번 같은 원인을 가리켰다. 두 화면이 같은 데이터베이스를 보는데 한쪽만 비면, 다른 것은 그 사이에 놓인 계약이다. 작성 쪽은 작성 계약을 지나고 조회 쪽은 조회 계약을 지난다. 이 저장소에서 같은 신호가 여덟 번 같은 원인을 가리켰다.
**화면이 그리는 칸을 먼저 적고 응답에 있는지 하나씩 맞춘다** ### 2. 화면이 그리는 칸을 먼저 적고 응답에 있는지 하나씩 맞춘다
응답에서 출발하면 없는 칸은 보이지 않는다. 상세 endpoint 가 없는 종류에서 특히 그렇다 — 부를 상세가 없으므로 목록 항목이 문서 전체를 실어야 하고, 그 목록에 없는 칸은 화면이 각자 메운다. 응답에서 출발하면 없는 칸은 보이지 않는다. 상세 endpoint 가 없는 종류에서 특히 그렇다 — 부를 상세가 없으므로 목록 항목이 문서 전체를 실어야 하고, 그 목록에 없는 칸은 화면이 각자 메운다.
**한 종류의 저장 구조가 다른 종류와 다르면 조회 쪽이 그것을 알아야 한다** ### 3. 한 종류의 저장 구조가 다른 종류와 다르면 조회 쪽이 그것을 알아야 한다
Reference 의 본문은 문서 본문 칸이 아니라 규칙과 예시를 담는 별도 테이블에 있다. 이름이 맞아도 읽는 곳이 틀리면 빈 문자열이 나온다. Reference 의 본문은 문서 본문 칸이 아니라 규칙과 예시를 담는 별도 테이블에 있다. 이름이 맞아도 읽는 곳이 틀리면 빈 문자열이 나온다.
**칸을 더할 때 required 로 올릴지는 따로 판단한다** ### 4. 칸을 더할 때 required 로 올릴지는 따로 판단한다
이미 나가 있는 응답에는 그 칸이 없다. required 로 올리면 계약을 반입한 쪽이 배포되기 전까지 그 응답이 검증에 걸린다. 배포 순서에 따라 깨지는 것과 값이 안 오는 것 중에서 고른다. 이미 나가 있는 응답에는 그 칸이 없다. required 로 올리면 계약을 반입한 쪽이 배포되기 전까지 그 응답이 검증에 걸린다. 배포 순서에 따라 깨지는 것과 값이 안 오는 것 중에서 고른다.
**값이 아니라 이름을 지키는 검사를 둔다** ### 5. 값이 아니라 이름을 지키는 검사를 둔다
게이트웨이가 읽는 이름이 계약의 타입에 있는지를 묻는다. 값을 비교하는 검사는 픽스처를 게이트웨이가 읽는 이름으로 만들면 그대로 통과한다 — 테스트 작성자와 게이트웨이 작성자가 이름에 대해 합의한 것을 확인할 뿐이다. 게이트웨이가 읽는 이름이 계약의 타입에 있는지를 묻는다. 값을 비교하는 검사는 픽스처를 게이트웨이가 읽는 이름으로 만들면 그대로 통과한다 — 테스트 작성자와 게이트웨이 작성자가 이름에 대해 합의한 것을 확인할 뿐이다.
## 적용 조건 ## 적용 조건
같은 데이터를 두 표면이 각자의 계약으로 읽고, 한쪽만 비어 보이는 화면. 작성 계약과 조회 계약이 나뉜 구조에서 걸린다. - 같은 데이터를 두 표면이 각자의 계약으로 읽고, 한쪽만 비어 보이는 화면. 작성 계약과 조회 계약이 나뉜 구조에서 걸린다.
계약에 칸을 더하거나 화면에 칸을 더하는 변경에서도 건다. 화면이 먼저 늘면 그 칸이 응답에 있는지 확인할 곳이 없다. - 계약에 칸을 더하거나 화면에 칸을 더하는 변경에서도 건다. 화면이 먼저 늘면 그 칸이 응답에 있는지 확인할 곳이 없다.
## 예외 ## 예외
두 표면이 같은 계약을 쓰면 이 신호는 성립하지 않는다. 그때는 매퍼나 질의를 먼저 본다. - 두 표면이 같은 계약을 쓰면 이 신호는 성립하지 않는다. 그때는 매퍼나 질의를 먼저 본다.
저장 구조가 종류마다 다르면 계약이 아니라 조회가 원인일 수 있다. 이름이 계약과 맞는데도 값이 비면 그쪽을 본다. - 저장 구조가 종류마다 다르면 계약이 아니라 조회가 원인일 수 있다. 이름이 계약과 맞는데도 값이 비면 그쪽을 본다.
값이 있는데 겹쳐 보이는 경우는 이 신호가 아니다. 빈 칸을 다른 값으로 메우면 같은 글이 두 번 나온다. - 값이 있는데 겹쳐 보이는 경우는 이 신호가 아니다. 빈 칸을 다른 값으로 메우면 같은 글이 두 번 나온다.
## 예시 ## 예시
공개 Reference 가 통째로 비었을 때 게이트웨이가 읽던 네 이름이 전부 계약에 없었다. - 공개 Reference 가 통째로 비었을 때 게이트웨이가 읽던 네 이름이 전부 계약에 없었다.
프로젝트의 「주요 주제」는 테이블도 조인도 가능했는데 응답에 실을 칸이 없었다. - 프로젝트의 「주요 주제」는 테이블도 조인도 가능했는데 응답에 실을 칸이 없었다.
질문 목록만 주제가 빠져 있어서 질문 줄의 맥락이 「· 프로젝트」로 시작했다. 지식 목록은 처음부터 그 칸을 싣고 있었다. - 질문 목록만 주제가 빠져 있어서 질문 줄의 맥락이 「· 프로젝트」로 시작했다. 지식 목록은 처음부터 그 칸을 싣고 있었다.
프로젝트 목록 행에 slug 가 없었다. 다른 목록이 프로젝트를 가리킬 때 쓰는 것은 id 가 아니라 slug 다. - 프로젝트 목록 행에 slug 가 없었다. 다른 목록이 프로젝트를 가리킬 때 쓰는 것은 id 가 아니라 slug 다.
문서 요약 자리에 유형별 요약을 대신 넣었더니 머리말이 바로 아래와 같은 글을 두 번 말했다. - 문서 요약 자리에 유형별 요약을 대신 넣었더니 머리말이 바로 아래와 같은 글을 두 번 말했다.
@@ -35,39 +35,44 @@ source:
## 규칙 ## 규칙
**고친 값이 실제로 그려지는 곳까지 가서 본다** ### 1. 고친 값이 실제로 그려지는 곳까지 가서 본다
배포본에서 그 화면을 열거나 실제 요청을 보내 응답을 읽는다. 관계 요약은 세 경계에서 연달아 버려졌고, 매번 화면을 보고 나서야 다음 경계가 버리는 것을 알았다. 배포본에서 그 화면을 열거나 실제 요청을 보내 응답을 읽는다. 관계 요약은 세 경계에서 연달아 버려졌고, 매번 화면을 보고 나서야 다음 경계가 버리는 것을 알았다.
**타입 검사 통과를 반영의 증거로 쓰지 않는다** ### 2. 타입 검사 통과를 반영의 증거로 쓰지 않는다
메서드 매개변수의 bivariance, `as` 단언, 검사 대상이 없는 tsconfig 가 각각 통과시킨 사례가 있다. 통과는 「코드가 맞다」가 아니라 「검사가 그 질문을 하지 않았다」를 뜻할 수 있다. 메서드 매개변수의 bivariance, `as` 단언, 검사 대상이 없는 tsconfig 가 각각 통과시킨 사례가 있다. 통과는 「코드가 맞다」가 아니라 「검사가 그 질문을 하지 않았다」를 뜻할 수 있다.
**게이트웨이를 실제로 불러 어떤 연산이 나가는지 확인한다** ### 3. 게이트웨이를 실제로 불러 어떤 연산이 나가는지 확인한다
등록을 빠뜨린 연산은 옆 분기로 떨어지므로 서버는 정상 응답을 준다. 나가는 경로를 봐야 알 수 있다. 등록을 빠뜨린 연산은 옆 분기로 떨어지므로 서버는 정상 응답을 준다. 나가는 경로를 봐야 알 수 있다.
**여정이 끝나는 곳을 먼저 정하고 시작한다** ### 4. 여정이 끝나는 곳을 먼저 정하고 시작한다
어디까지 가면 확인이 끝나는지 모르면 중간에서 멈추게 된다. 공개 화면의 한 줄이면 그 줄이 그려지는 화면이 끝이다. 어디까지 가면 확인이 끝나는지 모르면 중간에서 멈추게 된다. 공개 화면의 한 줄이면 그 줄이 그려지는 화면이 끝이다.
**검사가 덮는 구간을 적어 둔다** ### 5. 검사가 덮는 구간을 적어 둔다
값이 아니라 이름을 지키는 검사를 두면 여정의 한 구간을 그 검사가 대신한다. 그 구간이 어디까지인지 적어 두지 않으면 다음 사람이 검사를 여정 전체로 읽는다. 값이 아니라 이름을 지키는 검사를 두면 여정의 한 구간을 그 검사가 대신한다. 그 구간이 어디까지인지 적어 두지 않으면 다음 사람이 검사를 여정 전체로 읽는다.
## 적용 조건 ## 적용 조건
값이 계약·매퍼·포트를 여러 번 갈아타는 구조에서 「고쳤다」를 판단할 때. 계약을 소유한 저장소가 따로 있고 생성기를 지나 들어오면 특히 걸린다. - 값이 계약·매퍼·포트를 여러 번 갈아타는 구조에서 「고쳤다」를 판단할 때. 계약을 소유한 저장소가 따로 있고 생성기를 지나 들어오면 특히 걸린다.
앞선 커밋이 같은 값을 고치려다 못 고친 이력이 있으면 반드시 건다. 그 커밋이 무엇을 근거로 고쳤다고 판단했는지가 대개 중간 지점이다. - 앞선 커밋이 같은 값을 고치려다 못 고친 이력이 있으면 반드시 건다. 그 커밋이 무엇을 근거로 고쳤다고 판단했는지가 대개 중간 지점이다.
## 예외 ## 예외
경계가 하나뿐이거나 고친 그 곳이 여정의 끝이면 중간 확인으로 충분하다. - 경계가 하나뿐이거나 고친 그 곳이 여정의 끝이면 중간 확인으로 충분하다.
배포본을 열 수 없는 변경 — 아직 배포되지 않은 경로 — 은 여정의 끝까지 갈 수 없다. 그때는 어디까지 확인했는지를 적는다. - 배포본을 열 수 없는 변경 — 아직 배포되지 않은 경로 — 은 여정의 끝까지 갈 수 없다. 그때는 어디까지 확인했는지를 적는다.
## 예시 ## 예시
관계 요약을 세 번 고쳤다. 매번 화면을 보고 나서야 다음 경계가 버리는 것을 알았다. - 관계 요약을 세 번 고쳤다. 매번 화면을 보고 나서야 다음 경계가 버리는 것을 알았다.
개념 삭제가 계속 질문 삭제 경로로 나갔다. 타입 검사가 통과해서 반영된 줄 알았고, 배포된 번들의 서버 로그에서 404 를 보고 알았다. - 개념 삭제가 계속 질문 삭제 경로로 나갔다. 타입 검사가 통과해서 반영된 줄 알았고, 배포된 번들의 서버 로그에서 404 를 보고 알았다.
CONCEPT 을 질문 삭제로 되돌려 가드가 깨지는 것을 확인했다. - CONCEPT 을 질문 삭제로 되돌려 가드가 깨지는 것을 확인했다.
한 경계를 고치고 판단해 세 번 틀렸다. 값이 지나는 경계가 열한 개다. - 한 경계를 고치고 판단해 세 번 틀렸다. 값이 지나는 경계가 열한 개다.
@@ -35,34 +35,40 @@ source:
## 규칙 ## 규칙
**결함을 되돌려 그 가드가 실제로 멈추는 것을 확인한 뒤 커밋한다** ### 1. 결함을 되돌려 그 가드가 실제로 멈추는 것을 확인한 뒤 커밋한다
계약에서 값을 빼고 대조 검사가 빨개지는지 본다. 매핑을 떼어 보고 그 연산 하나를 짚는지 본다. 계약에서 값을 빼고 대조 검사가 빨개지는지 본다. 매핑을 떼어 보고 그 연산 하나를 짚는지 본다.
**가드가 짚는 대상이 하나인지 본다** ### 2. 가드가 짚는 대상이 하나인지 본다
전부를 짚으면 어디가 문제인지 알 수 없고, 결과가 곧 읽히지 않는다. 전부를 짚으면 어디가 문제인지 알 수 없고, 결과가 곧 읽히지 않는다.
**되돌릴 수 없는 것은 현재 상태를 대신 증거로 남긴다** ### 3. 되돌릴 수 없는 것은 현재 상태를 대신 증거로 남긴다
이미 마이그레이션으로 고친 데이터는 실패 상태를 다시 만들 수 없다. 그럴 때는 지금 고쳐져 있다는 것을 남긴다. 이미 마이그레이션으로 고친 데이터는 실패 상태를 다시 만들 수 없다. 그럴 때는 지금 고쳐져 있다는 것을 남긴다.
**가드를 CI 에 묶는다** ### 4. 가드를 CI 에 묶는다
사람이 기억해서 돌리는 가드는 절반만 존재한다. 사람이 기억해서 돌리는 가드는 절반만 존재한다.
## 적용 조건 ## 적용 조건
재발 방지로 넣는 테스트·아키텍처 규칙·CI 게이트. 결함을 고치는 커밋에서 함께 넣을 때 걸린다. - 재발 방지로 테스트·아키텍처 규칙·CI 게이트를 넣을 때
- 결함을 고치는 커밋에서 가드를 함께 넣을 때
- 이미 있는 가드가 무엇을 잡는지 확인해야 할 때
## 예외 ## 예외
결함을 되돌릴 수 없는 것 — 이미 마이그레이션으로 고친 데이터, 지난 배포에서만 나던 상태 — 은 현재 상태가 고쳐져 있음을 증거로 남긴다. - 결함을 되돌릴 수 없는 것 — 이미 마이그레이션으로 고친 데이터, 지난 배포에서만 나던 상태 — 은 현재 상태가 고쳐져 있음을 증거로 남긴다.
기존 가드를 옮기거나 이름만 바꾸는 변경은 되돌려 확인하지 않아도 된다. 다만 옮긴 뒤에 한 번은 돌린다. - 기존 가드를 옮기거나 이름만 바꾸는 변경은 되돌려 확인하지 않아도 된다. 다만 옮긴 뒤에 한 번은 돌린다.
## 예시 ## 예시
가드 셋을 각각 결함으로 되돌려 실제로 빨개지는 것을 확인한 기록을 남겼다. - 가드 셋을 각각 결함으로 되돌려 실제로 빨개지는 것을 확인한 기록을 남겼다.
계약에서 CONCEPT 을 빼자 백엔드의 계약 대조 테스트가 빨개졌다. - 계약에서 CONCEPT 을 빼자 백엔드의 계약 대조 테스트가 빨개졌다.
매핑을 떼어 보고 계약 대조 테스트가 그 연산 하나를 정확히 짚는 것을 확인했다. - 매핑을 떼어 보고 계약 대조 테스트가 그 연산 하나를 정확히 짚는 것을 확인했다.
굵기 선언을 빼 보고 제목 급 검사가 실제로 멈추는 것을 확인했다. - 굵기 선언을 빼 보고 제목 급 검사가 실제로 멈추는 것을 확인했다.
@@ -35,37 +35,43 @@ CI 에 묶이지 않은 검증이 남아 있으면 그것을 돌리는 것은
## 규칙 ## 규칙
**프론트는 다섯 개를 다 돌린다** ### 1. 프론트는 다섯 개를 다 돌린다
타입 검사 · lint · 단위 테스트 · 컴포넌트 테스트 · 화면 테스트다. 화면 테스트는 단위 테스트 명령이 돌리지 않는다. 타입 검사 · lint · 단위 테스트 · 컴포넌트 테스트 · 화면 테스트다. 화면 테스트는 단위 테스트 명령이 돌리지 않는다.
**타입 검사는 프로젝트를 순회하는 명령으로 돌린다** ### 2. 타입 검사는 프로젝트를 순회하는 명령으로 돌린다
루트 tsconfig 를 직접 부르는 명령은 한 파일도 검사하지 않고 성공한다. 루트 tsconfig 를 직접 부르는 명령은 한 파일도 검사하지 않고 성공한다.
**백엔드는 커밋한 뒤에 빌드한다** ### 3. 백엔드는 커밋한 뒤에 빌드한다
빌드 산출물 이름에 커밋 해시가 들어간다. 작업 트리가 더러우면 해시가 달라져 stale 산출물 검사가 멈춘다. 빌드 산출물 이름에 커밋 해시가 들어간다. 작업 트리가 더러우면 해시가 달라져 stale 산출물 검사가 멈춘다.
**테스트를 npm 이나 npx 로 감싸 돌리지 않는다** ### 4. 테스트를 npm 이나 npx 로 감싸 돌리지 않는다
`npm_config_*` 환경 변수가 설정되어 CI 워크플로 생성 테스트가 실패한다. 그 변수를 지우고 실행기를 직접 부른다. `npm_config_*` 환경 변수가 설정되어 CI 워크플로 생성 테스트가 실패한다. 그 변수를 지우고 실행기를 직접 부른다.
## 적용 조건 ## 적용 조건
CI 에 묶이지 않은 검증이 남아 있는 저장소에서 배포 직전에 하는 일. - CI 에 묶이지 않은 검증이 남아 있는 저장소에서 배포 직전에 하는 일
- 명령이 여럿이고 그중 일부만 도는 것이 가능할 때
- 빌드 산출물이 작업 트리 상태에 따라 달라지는 저장소에서
## 예외 ## 예외
CI 가 그 명령을 돌리면 이 목록에서 뺀다. - CI 가 그 명령을 돌리면 이 목록에서 뺀다.
환경 때문에 실패하는 것은 실패로 세지 않는다. 하위 프로세스를 띄우는 세 케이스는 이 환경에서 실패하고 같은 리비전의 다른 실행에서도 똑같이 재현되므로 코드 변경과 무관하다. - 환경 때문에 실패하는 것은 실패로 세지 않는다. 하위 프로세스를 띄우는 세 케이스는 이 환경에서 실패하고 같은 리비전의 다른 실행에서도 똑같이 재현되므로 코드 변경과 무관하다.
## 예시 ## 예시
프론트 다섯 명령 : - 프론트 다섯 명령 :
`npm run check:types` `npm run check:types`
`npm run lint` `npm run lint`
단위 · 컴포넌트 · 화면 테스트 단위 · 컴포넌트 · 화면 테스트
백엔드 : 커밋한 뒤 stale 산출물을 지우고 빌드한다. 이 순서를 몰라 두 번 헤맸다. - 백엔드 : 커밋한 뒤 stale 산출물을 지우고 빌드한다. 이 순서를 몰라 두 번 헤맸다.
설계 패키지 : 계약 자체의 유효성 · 세 계약 사이의 정합 · 프론트와 백엔드가 아는 종류와 오류 코드가 같은지, 셋을 돌린다. - 설계 패키지 : 계약 자체의 유효성 · 세 계약 사이의 정합 · 프론트와 백엔드가 아는 종류와 오류 코드가 같은지, 셋을 돌린다.
테스트 JVM 힙이 기본값이면 컨텍스트 캐시와 아키텍처 검사와 컨테이너가 겹치면서 메모리가 모자란다. 증상이 테스트 실패가 아니라 실행기를 완료할 수 없다는 메시지라 원인을 가린다. - 테스트 JVM 힙이 기본값이면 컨텍스트 캐시와 아키텍처 검사와 컨테이너가 겹치면서 메모리가 모자란다. 증상이 테스트 실패가 아니라 실행기를 완료할 수 없다는 메시지라 원인을 가린다.