docs(TechLog): 설명 뒤에 붙은 평가·예고·되풀이를 걷어낸다
rewriting-technical-prose-naturally 를 서브에이전트 셋으로 나눠 56편에 적용했다. ai-tells.md 의 첫 절대로 다른 표현으로 바꾸는 대신 문장을 통째로 지웠다. 설명한 것의 중요성을 다시 평가하는 꼬리 19 이미 설명한 것을 추상어로 되풀이 19 독자에게 읽는 법을 지시하거나 오해를 가정 9 자료가 뒷받침하지 않는 덧붙인 이득 4 문서군 전체의 문형 편중도 풀었다 — 함께 27→7(한 묶음), 그대로 22→12(두 묶음), 하게 된다 1→0. 한 편에서 세 번 반복되던 「같은 병이 ~에서도 났다」와 두 기록에 같은 문장으로 있던 세 쌍을 갈랐다. 계약 제목 「여덟 자리」가 본문의 「여덟 곳」과 어긋나 있었다. 제목이 spatial-metaphor 규칙에도 걸리므로 계약과 기록을 함께 「여덟 곳」으로 맞췄다. 검사 넷 전부 통과한다 — check_prose 56편 error 0 · check_body PASS · check_evidence --repo 문제 없음 · verify-tech-log-tree error 0 warn 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
fc23660871
commit
0650d91def
+3
-3
@@ -39,7 +39,7 @@ source:
|
||||
1차 : 축의 주소를 주제 화면 안의 앵커로 바꿨다 — 주제 화면에서는 그 링크가 자기 자신을 가리켰다
|
||||
2차 : 축에 자기 화면을 줬다 — 목록 조회에 축 필터를 더해 걸러 낸다
|
||||
|
||||
축 slug 는 주제 안에서만 유일하므로 조회에서 주제까지 함께 맞춘다. 주제를 빼면 다른 주제의 같은 이름 축이 함께 걸린다.
|
||||
축 slug 는 주제 안에서만 유일하므로 조회에서 주제까지 맞춘다. 주제를 빼면 다른 주제의 같은 이름 축까지 걸린다.
|
||||
|
||||
같은 시기에 주제가 없는 기록이 이름 없는 주제 링크를 달고 있던 것도 고쳤다. 문서 머리말의 breadcrumb 과 탐색의 「주제 없음」 묶음 둘 다였고, 프로젝트 조각은 처음부터 조건부였는데 주제 쪽만 아니었다.
|
||||
|
||||
@@ -60,7 +60,7 @@ tech-log-design-package : 71bab4c · b93d62a
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
## 주소만 바뀌고 화면은 그대로였다
|
||||
## 앵커로 옮겼더니 자기 자신을 가리켰다
|
||||
|
||||
처음에 축의 주소를 `/topics/{주제}/{축}` 이라 적어 두었는데 그런 화면이 없었다. 그래서 축의 주소를 주제 화면 안의 앵커로 바꿨다.
|
||||
|
||||
@@ -68,7 +68,7 @@ tech-log-design-package : 71bab4c · b93d62a
|
||||
|
||||
## 축에 자기 화면을 줬다
|
||||
|
||||
목록 조회에 축 필터를 더하고 `record_variant` 로 거른다. 축 slug 는 주제 안에서만 유일하므로 주제까지 함께 맞춘다 — 주제를 빼면 다른 주제의 같은 이름 축이 함께 걸린다.
|
||||
목록 조회에 축 필터를 더하고 `record_variant` 로 거른다. 축 slug 는 주제 안에서만 유일하므로 주제까지 맞춰야 하고, 주제를 빼면 다른 주제의 같은 이름 축까지 걸린다.
|
||||
|
||||
## 배포 순서로 만든 2차 사고
|
||||
|
||||
|
||||
+5
-5
@@ -9,7 +9,7 @@ status: 게시 전
|
||||
lastVerifiedOn: 2026-09-04
|
||||
assets:
|
||||
- key: decision-path-404
|
||||
file: ../../../final/assets/tech-log-studio/decision-path-404.svg
|
||||
file: ../../../final/assets/diagrams/decision-path-404/decision-path-404.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/db/decision-path-after-v15.txt
|
||||
- ../../../final/evidence/raw/api/decision-anchor-fixed.txt
|
||||
@@ -82,19 +82,19 @@ tech-log-frontend : fe6b56a
|
||||
|
||||
## 계약은 이미 맞게 적혀 있었다
|
||||
|
||||
이 사건에서 계약은 고칠 것이 없었다. 공개 주소가 `#{slug}` 앵커라는 것이 계약에 있었고, 만드는 쪽 두 곳이 그것을 따르지 않았다.
|
||||
공개 주소가 `#{slug}` 앵커라는 것이 계약에 이미 있었고, 만드는 쪽 두 곳이 그것을 따르지 않았다.
|
||||
|
||||
## 저장된 행까지 고쳐야 한다
|
||||
|
||||
주소가 게시 시점에 굳어져 저장되므로 코드만 고치면 이미 게시된 링크는 깨진 채 남는다. V15 마이그레이션에서 저장된 행을 함께 고쳤다.
|
||||
주소가 게시 시점에 굳어져 저장되므로 코드만 고치면 이미 게시된 링크는 깨진 채 남는다. V15 마이그레이션에서 저장된 행도 고쳤다.
|
||||
|
||||
`public_route.slug` 도 함께 손봤다. 마지막 슬래시 뒤를 자르면 앵커가 붙은 주소에서 `decisions#slug` 전체가 slug 로 저장된다. 앵커가 있으면 그 뒤를 조각으로 읽게 했다.
|
||||
`public_route.slug` 도 손봤다. 마지막 슬래시 뒤를 자르면 앵커가 붙은 주소에서 `decisions#slug` 전체가 slug 로 저장된다. 앵커가 있으면 그 뒤를 조각으로 읽게 했다.
|
||||
|
||||
## 두 겹 가드
|
||||
|
||||
`PublicPathsTest` 는 백엔드에서 종류마다 만들어 낸 경로가 실제 공개 라우트 패턴에 맞는지 본다.
|
||||
|
||||
`resolvesToPublicRoute` 는 프론트에서 라우트 계약이 준 표에 서버가 준 주소를 맞춰 보고, 맞는 라우트가 없으면 링크로 그리지 않는다. 이 부류가 또 생겨도 방문자가 404 를 만나지는 않는다.
|
||||
`resolvesToPublicRoute` 는 프론트에서 라우트 계약이 준 표에 서버가 준 주소를 맞춰 보고, 맞는 라우트가 없으면 링크로 그리지 않는다.
|
||||
|
||||
## 배포 뒤 전수 감사
|
||||
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ nginx 설정은 라우트 계약에서 생성된다. 프론트 이미지가 배
|
||||
|
||||
백엔드를 먼저 배포하면 서버는 이미 그 주소를 링크로 내보낸다. 방문자는 화면에 그려진 링크를 누르고 404 를 만난다. 축 화면을 만들 때 실제로 그렇게 배포했고 사용자가 네 링크 전부 404 인 화면을 봤다.
|
||||
|
||||
catch-all 을 서빙 패턴으로 번역하지 않기로 했으므로 이 구간이 soft 200 으로 덮이지 않는다. 그 결정과 이 순서는 함께 간다.
|
||||
catch-all 을 서빙 패턴으로 번역하지 않기로 했으므로 이 구간이 soft 200 으로 덮이지 않는다.
|
||||
|
||||
## 영향
|
||||
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ source:
|
||||
|
||||
## 적용 조건
|
||||
|
||||
주소를 서버가 만들어 내보내고 화면이 그대로 링크로 그리는 구조. 게시 시점에 주소가 굳어져 저장되면 특히 걸린다.
|
||||
주소를 서버가 만들어 내보내고 화면은 받은 문자열을 링크로 그리는 구조. 게시 시점에 주소가 굳어져 저장되면 특히 걸린다.
|
||||
|
||||
## 예외
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ source:
|
||||
|
||||
# 우회를 남길 때는 되돌릴 조건을 함께 적는다
|
||||
|
||||
그 화면이 줄 수 있는 것이 아직 비어 있어 링크를 다른 곳으로 돌린 적이 있다. 우회 자체는 틀리지 않았다. 문제는 우회를 남겨 두면 「왜 이 링크가 저기로 가지?」라는 질문이 계속 남는다는 것이다. 우회할 때 되돌릴 조건을 함께 적는다.
|
||||
그 화면이 줄 수 있는 것이 아직 비어 있어 링크를 다른 곳으로 돌린 적이 있다. 우회 자체는 틀리지 않았다 — 그때는 채울 내용이 없었다. 되돌릴 조건은 우회를 넣는 커밋에 적고, 그 조건이 충족되면 되돌린다.
|
||||
|
||||
## 관계
|
||||
|
||||
|
||||
+4
-4
@@ -18,7 +18,7 @@ source:
|
||||
|
||||
# 홈의 비교 구역이 세 번 바뀌었다 — 상한을 없애고 요청을 목록 하나와 주제 하나로 고정했다
|
||||
|
||||
홈의 비교 구역을 세 번 바꿨다. 처음에는 주제 하나만 펼치고 아래에 다른 주제로 가는 줄을 뒀고, 다음에는 주제 이름을 탭으로 세웠고, 마지막에 탭을 칩 크기로 낮추고 개수 상한을 없앴다. 상한을 없앨 때 요청 구조를 함께 바꿔 주제가 몇 개가 되든 첫 요청이 고정되게 했다.
|
||||
홈의 비교 구역을 세 번 바꿨다. 처음에는 주제 하나만 펼치고 아래에 다른 주제로 가는 줄을 뒀고, 다음에는 주제 이름을 탭으로 세웠고, 마지막에 탭을 칩 크기로 낮추고 개수 상한을 없앴다. 상한을 없앨 때 요청 구조를 바꿔 주제가 몇 개가 되든 첫 요청이 고정되게 했다.
|
||||
|
||||
## 관계
|
||||
|
||||
@@ -35,7 +35,7 @@ source:
|
||||
|
||||
## 결론
|
||||
|
||||
세 단계를 거쳤고 각 단계가 앞 단계의 무엇을 고치려 했는지가 남아 있다.
|
||||
세 단계를 거쳤다.
|
||||
|
||||
| 단계 | 무엇 | 왜 바꿨나 |
|
||||
|---|---|---|
|
||||
@@ -72,11 +72,11 @@ tech-log-frontend : 604ded5 → de4cb8b → 3bb724b · 2b2f443
|
||||
|
||||
탭 줄은 목록 호출 하나가 주는 전부다. 상세는 고른 탭만 그때 받아 캐시한다.
|
||||
|
||||
그래서 주제가 몇 개가 되든 홈이 처음 보내는 요청은 목록 하나와 주제 하나로 고정된다. 상한을 없앨 수 있었던 이유가 이것이다.
|
||||
주제가 몇 개가 되든 홈이 처음 보내는 요청은 목록 하나와 주제 하나로 고정된다.
|
||||
|
||||
## 시각 언어를 두 번 고쳤다
|
||||
|
||||
고른 탭의 파란 밑줄을 없앴다. 주제가 스무 개면 밑줄 설 곳 스무 개가 함께 늘어선다.
|
||||
고른 탭의 파란 밑줄을 없앴다. 주제가 스무 개면 밑줄 설 곳 스무 개가 나란히 늘어선다.
|
||||
|
||||
칩으로 낮추니 목록 위에 글자만 떠 있는 것처럼 보였다. 고른 탭에 알약 형태를 주고, 묶음의 윗선을 목록이 아니라 패널이 갖게 해서 탭 줄이 그 선에 바로 얹히게 했다.
|
||||
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@ status: 게시 전
|
||||
basisVersion: tech-log-backend 2026-09-01 의 축 스키마 · record_variant 에 외래키 없음 · studio_validation 과 publication 이 쓰는 방식을 따름
|
||||
assets:
|
||||
- key: topic-variant-model
|
||||
file: ../../../final/assets/tech-log-studio/topic-variant-model.svg
|
||||
file: ../../../final/assets/diagrams/topic-variant-model/topic-variant-model.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/db/topic-variant-rows.txt
|
||||
- ../../../final/evidence/raw/db/record-variant-links.txt
|
||||
@@ -62,7 +62,7 @@ topic (주제)
|
||||
|
||||
`record_variant` 는 외래키를 갖지 않는다. 기록이 종류마다 다른 테이블에 살기 때문이다 — 문서·열린 질문·프로젝트 결정이 각각 다른 테이블이다. 종류와 아이디의 쌍으로만 가리킨다.
|
||||
|
||||
이 방식은 이 저장소에서 처음 쓰는 것이 아니다. 검증 상태와 게시 기록이 이미 같은 방식으로 기록을 가리키고 있었다.
|
||||
검증 상태와 게시 기록이 이미 같은 방식으로 기록을 가리키고 있었다.
|
||||
|
||||
## 사람이 쓰는 칸
|
||||
|
||||
@@ -86,6 +86,6 @@ jpa-feed-query-performance 축 이름 「조회 전략」 축 3개
|
||||
fetch-join-paging ← CASE 1
|
||||
```
|
||||
|
||||
축마다 기록이 하나씩이고 축 제목을 그 기록 제목과 비슷하게 적으면 「문서가 그대로 나온다」로 보인다. 구조 차이가 아니라 내용 양의 차이다.
|
||||
축마다 기록이 하나씩이고 축 제목을 그 기록 제목과 비슷하게 적으면 「문서가 그대로 나온다」로 보인다.
|
||||
|
||||
<!-- body:end -->
|
||||
|
||||
+3
-3
@@ -38,7 +38,7 @@ source:
|
||||
|
||||
주제를 넷으로 쪼개면 PKCE·CSRF·Authorization Code 처럼 네 구조가 함께 쓰는 기록을 어디에 둘지 애매해진다. 어느 한 주제에 넣으면 나머지 셋에서 그 기록에 닿을 수 없고, 넷에 복사하면 같은 글이 넷이 된다.
|
||||
|
||||
비교도 어려워진다. 네 주제가 나란히 서면 그것이 같은 질문의 네 답이라는 것을 화면이 말하지 못한다.
|
||||
네 주제가 나란히 서면 그것이 같은 질문의 네 답이라는 것을 화면이 말하지 못해 비교도 어려워진다.
|
||||
|
||||
축을 주제 안에 두면 공통 기록은 축을 고르지 않고 두면 되고, 여러 구조에 걸치는 기록은 여러 축에 건다. 화면은 축을 나란히 세워 비교로 그린다.
|
||||
|
||||
@@ -46,10 +46,10 @@ source:
|
||||
|
||||
## 영향
|
||||
|
||||
축의 이름·요약·결론이 기록에서 자동으로 나오지 않는다. 주제의 논지, 축의 요약과 결론은 사람이 쓰는 칸이고, 기록을 스무 개 붙여도 그 문장은 누가 고치기 전까지 그대로다.
|
||||
주제의 논지와 축의 이름·요약·결론은 기록에서 자동으로 나오지 않는다. 사람이 쓰는 칸이라 기록을 스무 개 붙여도 그 문장은 누가 고치기 전까지 바뀌지 않는다.
|
||||
|
||||
홈의 비교 구역에서 줄은 문서가 아니라 축이다. 줄을 늘리려면 Studio 에서 축을 추가해야 한다.
|
||||
|
||||
기록이 어느 축에 걸리는지를 담는 표에 외래키를 걸 수 없다. 기록이 종류마다 다른 테이블에 살기 때문이다.
|
||||
|
||||
축이 붙은 기록 수가 적으면 「문서가 그대로 나온다」로 보인다. 축마다 기록이 하나씩이고 축 제목을 그 기록 제목과 비슷하게 적으면 그렇게 읽힌다.
|
||||
축에 걸린 기록이 하나뿐이면 축 제목과 그 기록 제목이 비슷해져 「문서가 그대로 나온다」로 보인다.
|
||||
|
||||
+4
-4
@@ -17,7 +17,7 @@ source:
|
||||
|
||||
# 축의 결론 문장과 기록 수는 기록을 붙여도 따라오지 않는다
|
||||
|
||||
홈의 비교 구역에서 줄은 문서가 아니라 축이다. 기록을 스무 개 붙여도 줄 수는 그대로이고, 줄에 보이는 결론 문장은 축에 손으로 쓴 글이라 누가 고치기 전까지 바뀌지 않는다. 주제 화면에는 축마다 기록 수가 붙는데 홈에는 없다.
|
||||
홈의 비교 구역에서 줄은 문서가 아니라 축이다. 기록을 스무 개 붙여도 줄 수는 늘지 않고, 줄에 보이는 결론 문장은 축에 손으로 쓴 글이라 누가 고치기 전까지 바뀌지 않는다. 주제 화면에는 축마다 기록 수가 붙는데 홈에는 없다.
|
||||
|
||||
## 관계
|
||||
|
||||
@@ -30,9 +30,9 @@ source:
|
||||
|
||||
## 사실
|
||||
|
||||
홈의 비교 구역에서 한 줄은 축 하나다. 기록을 스무 개 붙여도 줄 수는 그대로다.
|
||||
홈의 비교 구역에서 한 줄은 축 하나다. 기록을 스무 개 붙여도 줄 수는 늘지 않는다.
|
||||
|
||||
줄에 보이는 결론 문장은 축에 손으로 쓴 글이다. 기록을 붙여도 그 문장은 누가 고치기 전까지 그대로다.
|
||||
줄에 보이는 결론 문장은 축에 손으로 쓴 글이다. 기록을 붙여도 그 문장은 누가 고치기 전까지 바뀌지 않는다.
|
||||
|
||||
주제 화면에는 축마다 「기록 N」이 붙는다. 홈에는 그 수가 없다.
|
||||
|
||||
@@ -63,7 +63,7 @@ DB 에 직접 넣은 초안을 누가 언제 검토하는가. 검토 전까지
|
||||
## 선택지
|
||||
|
||||
**홈 비교표에 기록 수를 붙인다**
|
||||
목록 호출이 이미 그 수를 실을 수 있으면 요청이 늘지 않는다. 결론 문장은 그대로 사람이 쓴다.
|
||||
목록 호출이 이미 그 수를 실을 수 있으면 요청이 늘지 않는다. 결론 문장은 여전히 사람이 쓴다.
|
||||
|
||||
**결론 문장이 마지막으로 고쳐진 때를 함께 보인다**
|
||||
기록이 그 뒤에 늘었으면 낡았다는 것이 드러난다. 화면에 날짜가 하나 더 늘어난다.
|
||||
|
||||
+2
-2
@@ -84,11 +84,11 @@ tech-log-frontend : 344dadb · 805d400 · 8c5dbe1
|
||||
|
||||
## CSS 만으로 막지 않았다
|
||||
|
||||
선택자를 좁히는 것은 같은 구조가 다시 생기면 다시 새게 한다. 제목 안에 링크를 두지 않도록 구조를 바꿨고, 주제로 가는 길은 아래 한 줄이 맡는다.
|
||||
선택자를 좁혀도 같은 구조가 다시 생기면 다시 샌다. 제목 안에 링크를 두지 않도록 구조를 바꿨고, 주제로 가는 길은 아래 한 줄이 맡는다.
|
||||
|
||||
## 전역 규칙이 닿지 않는 화면
|
||||
|
||||
버튼에서 상자를 걷어내는 변경이 앱 전역 규칙만 고쳤다. 게시 기록·게시 흐름·워크플로 게이트는 CSS module 을 쓰므로 그 규칙이 닿지 않아, 다른 화면에서 상자를 걷어낸 뒤에도 세 버튼만 테두리와 파란 채움으로 남았다. 한 화면 안에서 두 언어가 섞여 더 눈에 띄었다.
|
||||
버튼에서 상자를 걷어내는 변경이 앱 전역 규칙만 고쳤다. 게시 기록·게시 흐름·워크플로 게이트는 CSS module 을 쓰므로 그 규칙이 닿지 않아, 다른 화면에서 상자를 걷어낸 뒤에도 세 버튼만 테두리와 파란 채움으로 남았다.
|
||||
|
||||
## 검사
|
||||
|
||||
|
||||
+2
-2
@@ -59,13 +59,13 @@ tech-log-frontend : 68538f2
|
||||
|
||||
> 규칙이 없었던 게 아니라 **절반만 있었다.** 정본은 `.section-heading-row h2` 인데 그 안에 들어가지 않는 두 구역이 **크기만 각자 적어 두어 굵기를 아무도 정하지 않았고**, 그래서 기본값 400 으로 떨어졌다.
|
||||
|
||||
크기를 각자 적어 두었기 때문에 「규칙이 없다」로 보이지 않는다. 선언이 있고 그 선언이 절반만 덮는다.
|
||||
크기를 각자 적어 두었기 때문에 「규칙이 없다」로 보이지 않는다.
|
||||
|
||||
## 처음에 잘못 판단한 것
|
||||
|
||||
> **이때 제가 저지른 판단 오류:** 처음에 grid/columns 만 측정하고 "정상"이라고 답했습니다. 사용자가 다시 지적한 뒤 **전체 페이지 스크린샷**을 찍어서야 26px/400 을 봤습니다. **프록시 지표가 아니라 보이는 것을 측정해야 합니다.**
|
||||
|
||||
격자와 열은 정상이었다. 문제는 글자 크기와 굵기였고, 그것은 격자를 재서는 나오지 않는다.
|
||||
격자와 열은 정상이었다. 어긋난 것은 글자 크기와 굵기였고, 격자를 재서는 그 값이 나오지 않는다.
|
||||
|
||||
## 검사
|
||||
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@ source:
|
||||
**촬영을 스크립트로 고정한다**
|
||||
손으로 찍으면 뷰포트와 축소 배율이 매번 달라진다.
|
||||
|
||||
**폭을 고정해 여러 개를 돌고, 폭마다 측정도 함께 남긴다**
|
||||
**폭을 고정해 여러 개를 돌고, 폭마다 측정값도 남긴다**
|
||||
스크린샷만 남기면 나중에 그 값이 얼마였는지 다시 잴 수 없다.
|
||||
|
||||
**전체 페이지를 한 장으로 찍지 않는다**
|
||||
@@ -62,6 +62,6 @@ CSS 선언이 정본과 같은지를 보는 검사는 렌더 결과를 재지
|
||||
|
||||
목록 간격을 바운딩 박스로만 재서 엉뚱한 구역을 결함으로 지목한 적이 있다.
|
||||
|
||||
브라우저 세션이 리셋되면 창이 약 877px 로 돌아가는데 이 사이트의 분기는 1179·1050·900·767 이라, 방문자 대부분이 보지 않는 배치를 놓고 디자인을 논하게 된다.
|
||||
브라우저 세션이 리셋되면 창이 약 877px 로 돌아가는데 이 사이트의 분기는 1179·1050·900·767 이라, 877px 에서 본 배치는 방문자 대부분이 보는 배치가 아니다.
|
||||
|
||||
전체 페이지를 한 장으로 찍으면 1425x4466 이 638x2000 으로 들어와 17px 글자가 7~8px 이 된다.
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ source:
|
||||
|
||||
## 적용 조건
|
||||
|
||||
구역 클래스 아래에 태그 선택자로 배치를 거는 CSS. 같은 구역 안에 제목과 목록이 함께 있으면 특히 걸린다.
|
||||
구역 클래스 아래에 태그 선택자로 배치를 거는 CSS. 같은 구역 안에 제목과 목록이 같이 있으면 특히 걸린다.
|
||||
|
||||
## 예외
|
||||
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ tech-log-frontend : 15e6ea8 이후
|
||||
|
||||
개념 삭제가 계속 질문 삭제 경로로 나갔고, 배포된 번들에서 서버 로그에 `DELETE /api/v1/studio/questions/{id} 404` 가 찍혔다. 개념 상세 주소도 마찬가지로 질문 조회를 불러 404 를 받았다.
|
||||
|
||||
증상이 「연산을 찾을 수 없습니다」였다면 바로 보였을 것이다. 게이트웨이가 옆 분기로 떨어지므로 서버는 정상적으로 응답하고, 다만 다른 기록을 다룬다.
|
||||
게이트웨이가 옆 분기로 떨어지므로 서버는 정상적으로 응답하고, 다만 다른 기록을 다룬다.
|
||||
|
||||
## 네 번의 누락
|
||||
|
||||
|
||||
-2
@@ -62,5 +62,3 @@ source:
|
||||
매핑 하나를 떼어 보고 대조 검사가 그 연산 하나를 정확히 짚는 것을 확인한 뒤 커밋했다.
|
||||
|
||||
관리 계약은 86 operation 이라 전수 대조 대신 「한 종류만 빠진 항목」을 보게 했다. 깨진 것이 늘 그 모양이었다.
|
||||
|
||||
옛 연산 51개를 명시하지 않았다면 대조 결과가 51건의 실패로 나와 아무도 읽지 않았을 것이다.
|
||||
|
||||
+4
-4
@@ -35,11 +35,11 @@ source:
|
||||
|
||||
편집기가 질문과 결정 목록을 못 읽으면 빈 배열로 삼키고 있었다. 서버는 404 를 주고 있었다 — 그 두 연산에 컨트롤러가 없었다.
|
||||
|
||||
작성자에게는 「아직 안 쓴 것」으로 읽힌다. 실제로는 쓴 것을 못 읽은 것이다.
|
||||
작성자에게는 「아직 안 쓴 것」으로 읽힌다.
|
||||
|
||||
> 거짓말을 하느니 못 읽었다고 말한다.
|
||||
|
||||
못 읽었을 때 못 읽었다고 적게 고쳤다. 같은 판단을 주제 탭에도 적용했다 — 탭 하나를 못 받아도 탭 줄과 나머지는 그대로 남고, 못 받은 탭에는 못 받았다고 적는다.
|
||||
못 읽었을 때 못 읽었다고 적게 고쳤다. 같은 판단을 주제 탭에도 적용했다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
@@ -61,7 +61,7 @@ tech-log-backend : 911e8ba — 없던 두 컨트롤러를 구현
|
||||
|
||||
편집기는 질문 목록을 받아 고를 수 있게 그린다. 목록이 비면 「이 프로젝트에 열린 질문이 없습니다」를 적는다.
|
||||
|
||||
요청이 실패했을 때도 빈 배열이 되고 있었다. 그래서 「없다」와 「못 읽었다」가 같은 화면이 됐다.
|
||||
요청이 실패했을 때도 빈 배열이 되고 있었다.
|
||||
|
||||
## 작성자가 무엇으로 읽었나
|
||||
|
||||
@@ -73,7 +73,7 @@ tech-log-backend : 911e8ba — 없던 두 컨트롤러를 구현
|
||||
|
||||
> 거짓말을 하느니 못 읽었다고 말한다.
|
||||
|
||||
요청이 실패하면 실패했다고 적는다. 0건은 0건이라고 적는다. 이 둘을 구분할 수 있어야 작성자가 다음에 무엇을 할지 정할 수 있다.
|
||||
요청이 실패하면 실패했다고 적는다. 0건은 0건이라고 적는다.
|
||||
|
||||
## 같은 판단을 다른 화면에
|
||||
|
||||
|
||||
+2
-2
@@ -39,7 +39,7 @@ source:
|
||||
|
||||
> `Promise.all([gateway.foo()])` 은 foo 가 **거절하는 것만** 잡는다. 호출이 **동기적으로 던지면** 배열을 만드는 중에 터져 rejection handler 를 지나지 못하고, 그러면 홈 focus 한 칸 때문에 대시보드 전체가 빈 화면이 된다.
|
||||
|
||||
같은 판단을 주제 탭에도 적용했다. 탭 하나를 못 받아도 탭 줄과 나머지는 그대로 남고, 못 받은 탭에는 못 받았다고 적는다.
|
||||
같은 판단을 주제 탭에도 적용했다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
@@ -66,7 +66,7 @@ tech-log-frontend : 6e784ed · fd73bc8 · 3bb724b
|
||||
|
||||
> `Promise.all([gateway.foo()])` 은 foo 가 **거절하는 것만** 잡는다. 호출이 **동기적으로 던지면** 배열을 만드는 중에 터져 rejection handler 를 지나지 못하고, 그러면 홈 focus 한 칸 때문에 대시보드 전체가 빈 화면이 된다.
|
||||
|
||||
거절과 던짐이 다른 경로를 탄다. 배열을 만드는 표현식 안에서 던지면 그 표현식이 완성되지 않으므로 거절 처리기가 붙을 대상이 없다.
|
||||
배열을 만드는 표현식 안에서 던지면 그 표현식이 완성되지 않으므로 거절 처리기가 붙을 대상이 없다.
|
||||
|
||||
## 탭에도 같은 판단을
|
||||
|
||||
|
||||
+3
-3
@@ -14,7 +14,7 @@ source:
|
||||
|
||||
# 매퍼가 null 을 돌려주고 호출부가 걸러 내, 기록이 조용히 사라졌다
|
||||
|
||||
프로젝트 기록 목록에서 Open Question 이 보이지 않았다. 이 목록은 탐색의 지식 목록과 응답 모양이 다른데 그쪽 매퍼를 그대로 쓰고 있었다. 그 매퍼는 두 종류가 아니면 `null` 을 돌려주고 호출부가 걸러 내므로, 질문과 개념은 오류도 빈 줄도 남기지 않고 사라진다.
|
||||
프로젝트 기록 목록에서 Open Question 이 보이지 않았다. 이 목록은 탐색의 지식 목록과 응답 모양이 다른데 그쪽 매퍼를 쓰고 있었다. 그 매퍼는 두 종류가 아니면 `null` 을 돌려주고 호출부가 걸러 내므로, 질문과 개념은 오류도 빈 줄도 남기지 않고 사라진다.
|
||||
|
||||
## 관계
|
||||
|
||||
@@ -41,7 +41,7 @@ source:
|
||||
오류 : 없음
|
||||
빈 줄 : 없음
|
||||
|
||||
응답 모양에 맞는 매퍼를 쓰고 모든 종류를 싣게 고쳤다. 요약과 주제와 게시일도 함께 실었다.
|
||||
응답 모양에 맞는 매퍼를 쓰고 모든 종류를 싣게 고쳤다. 요약과 주제와 게시일도 실었다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
@@ -64,7 +64,7 @@ tech-log-design-package : 76a7ccb
|
||||
|
||||
매퍼가 아는 종류가 아니면 `null` 을 돌려준다. 호출부는 그 목록에서 `null` 을 걸러 낸다.
|
||||
|
||||
이 조합에서는 오류가 나지 않고 빈 줄도 생기지 않는다. 목록의 길이만 줄어든다. 목록에 몇 개가 있어야 하는지 아는 사람만 알아챌 수 있다.
|
||||
이 조합에서는 오류가 나지 않고 빈 줄도 생기지 않는다. 목록의 길이만 줄어든다.
|
||||
|
||||
## 응답 모양이 다른 목록에 다른 매퍼를 썼다
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ source:
|
||||
|
||||
## 목적
|
||||
|
||||
매번 우는 검사가 읽히지 않게 되는 것을 막는다. 진짜 실패가 그 옆에 앉아 있어도 아무도 보지 않는다.
|
||||
매번 우는 검사가 읽히지 않게 되는 것을 막는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ source:
|
||||
여러 목록을 하나로 묶어 기다리면 한쪽의 실패가 전체를 실패로 만든다. 따로 읽고 실패한 목록에만 적는다.
|
||||
|
||||
**거절만 잡는 처리로는 부족하다**
|
||||
호출이 동기적으로 던지면 그 처리기를 지나지 않는다. 던지는 경로도 함께 잡는다.
|
||||
호출이 동기적으로 던지면 그 처리기를 지나지 않는다. 던지는 경로도 잡는다.
|
||||
|
||||
**항목을 걸러 낼 때 걸러 낸 것을 세어 둔다**
|
||||
매퍼가 모르는 종류에 빈 값을 돌려주고 호출부가 거르면, 목록이 한 줄 짧아지는 것 말고는 흔적이 없다.
|
||||
|
||||
+2
-2
@@ -100,7 +100,7 @@ CONCEPT 을 더해도 이 코드는 컴파일된다. `/concepts/idp-brokering`
|
||||
|
||||
10·11·12 는 계약 안에 있다. 계약이 종류를 열거하는 곳이 여러 곳이라, 계약을 고치는 커밋에서 같은 실수를 다시 했다.
|
||||
|
||||
같은 병이 종류가 아닌 곳에서도 났다. `latestEntries` 가 투영의 모든 `resource_type` 을 흘리는데 계약의 `LatestEntry.entryType` 은 네 값뿐이라, QUESTION 이 섞이면 매퍼가 500 을 내고 홈 화면 전체를 못 쓰게 만든다. 그래서 질의가 먼저 걸러 냈고, 게시한 Open Question 이 홈 최근 기록에 나오지 않았다. `pathOf` 는 이미 `/questions/{slug}` 를 만들고 있었고 projection 에도 질문 행이 채워져 있었다 — 막고 있던 것은 그 `IN` 목록 하나였다.
|
||||
같은 병이 종류가 아닌 곳에서도 났다. `latestEntries` 가 투영의 모든 `resource_type` 을 흘리는데 계약의 `LatestEntry.entryType` 은 네 값뿐이라, QUESTION 이 섞이면 매퍼가 500 을 내고 홈 화면 전체를 못 쓰게 만든다. 그래서 질의가 먼저 걸러 냈고, 게시한 Open Question 이 홈 최근 기록에 나오지 않았다. `pathOf` 는 이미 `/questions/{slug}` 를 만들고 있었고 projection 에도 질문 행이 채워져 있었다.
|
||||
|
||||
## 표로 바꾼 곳
|
||||
|
||||
@@ -126,7 +126,7 @@ const PATH_PREFIX_KINDS: Record<PublicRecord["kind"], string> = {
|
||||
- `contract-operation-coverage.test.ts` — 계약이 선언한 연산이 기여 목록에 등록됐는지 본다
|
||||
- `StudioContractUnionJacksonTest` — 모든 `RecordKind` 가 `CatalogEntry.KindEnum` 으로 변환되는지 순회한다. 계약에서 CONCEPT 을 빼면 실제로 빨개지는 것을 확인했다
|
||||
|
||||
설계 패키지 쪽은 눈으로 찾을 일이 아니었다. 세 계약을 파싱해 「CASE 와 REFERENCE 를 함께 열거하면서 CONCEPT 이 없는 enum」을 전부 뽑았다.
|
||||
설계 패키지 쪽은 세 계약을 파싱해 「CASE 와 REFERENCE 를 함께 열거하면서 CONCEPT 이 없는 enum」을 전부 뽑았다.
|
||||
|
||||
## 지금 확인한 범위
|
||||
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ source:
|
||||
|
||||
# 컴파일러가 빠진 가지를 요구하게 만드는 두 가지 — Record 표와 sealed switch 식
|
||||
|
||||
같은 언어 안에서도 어떤 분기는 새 값을 더할 때 컴파일러가 빠진 값을 짚고 어떤 분기는 아무 말도 하지 않는다. 삼항 사슬과 배열 리터럴은 후자이고, `Record<Kind, _>` 와 식으로 쓴 sealed switch 는 전자다. 갈림은 문법이 아니라 그 문법이 값을 전부 요구하는가에 있다.
|
||||
같은 언어 안에서도 어떤 분기는 새 값을 더할 때 컴파일러가 빠진 값을 짚고 어떤 분기는 아무 말도 하지 않는다. 삼항 사슬과 배열 리터럴은 후자이고, `Record<Kind, _>` 와 식으로 쓴 sealed switch 는 전자다.
|
||||
|
||||
## 관계
|
||||
|
||||
@@ -51,7 +51,7 @@ const PATH_PREFIX_KINDS: Record<PublicRecord["kind"], string> = {
|
||||
|
||||
Java 에서도 같은 갈림이 있다. `switch` 를 문으로 쓰면 어떤 가지도 없는 값이 그냥 지나간다. 식으로 쓰면 그 값에 대해 무엇을 반환할지 컴파일러가 요구한다. sealed 인터페이스와 함께 쓰면 하위 타입이 늘어날 때도 같은 요구가 걸린다.
|
||||
|
||||
이 저장소에서 종류를 하나 더했을 때 백엔드가 프론트보다 조용히 넘어간 곳이 적었던 이유가 그것이다. 게시 상태 코드, 활동 유형, 소유자 유형, slug 중복 검사, 렌더 모델이 전부 식으로 쓰인 switch 를 지나고 있었다.
|
||||
이 저장소에서도 게시 상태 코드와 활동 유형과 소유자 유형과 slug 중복 검사와 렌더 모델이 전부 식으로 쓰인 switch 를 지나고 있어서, 종류를 하나 더했을 때 백엔드에서 조용히 넘어간 곳이 프론트보다 적었다.
|
||||
|
||||
## 표로 못 바꾸는 칸
|
||||
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ source:
|
||||
- **개념을 하나 더하자 열세 곳이 그것을 조용히 삼켰다**
|
||||
이 질문이 남은 사건이다.
|
||||
- **컴파일러가 빠진 가지를 요구하게 만드는 두 가지 — Record 표와 sealed switch 식**
|
||||
왜 이 두 곳가 표로 바뀌지 못했는지가 그 개념에 있다.
|
||||
왜 이 두 곳이 표로 바뀌지 못했는지가 그 개념에 있다.
|
||||
- **계약은 앵커라고 적었고 만드는 쪽은 경로를 만들었다**
|
||||
pathOf 가 만든 주소가 틀렸던 다른 사건이다.
|
||||
|
||||
@@ -74,4 +74,4 @@ CONCEPT 이 실제로 이 분기에서 빠져 있었고, 경로가 null 로 나
|
||||
2. 홈 focus 의 `recentDecision` 경로가 `PublicPathsTest` 에 덮이는지 확인하고, 덮이지 않으면 그 경로를 테스트에 넣는다
|
||||
3. `stringFields` 를 `Record<Kind, string[]>` 로 바꾸고 종류 하나를 빼서 컴파일이 멈추는지 본다
|
||||
|
||||
닫는 조건 : 새 종류를 더했을 때 이 두 곳가 컴파일 오류로 먼저 멈추면 닫는다. 구조상 좁힐 수 없다는 것이 확인되면 대조 검사를 두는 Decision 으로 넘긴다
|
||||
닫는 조건 : 새 종류를 더했을 때 이 두 곳이 컴파일 오류로 먼저 멈추면 닫는다. 구조상 좁힐 수 없다는 것이 확인되면 대조 검사를 두는 Decision 으로 넘긴다
|
||||
|
||||
+4
-4
@@ -22,7 +22,7 @@ source:
|
||||
- **라우트에 딸린 목록은 라우트 계약에서 유도하고, 유도할 수 없는 것은 대조 검사를 둔다**
|
||||
이 사건에서 굳힌 기준이다.
|
||||
- **catch-all 라우트는 nginx 패턴으로 번역하지 않는다**
|
||||
유도 규칙에서 함께 정한 것이다.
|
||||
같은 유도 규칙의 일부로 정했다.
|
||||
- **라우트 하나가 건드리는 여덟 곳과, 그것들이 우는 시점**
|
||||
같은 부류가 다른 목록에서 어떻게 우는지가 그 기록에 있다.
|
||||
|
||||
@@ -38,7 +38,7 @@ SPA 안에서 이동하면 화면이 열린다. 주소창에 그 주소를 직
|
||||
|
||||
> 서빙 계약의 공개 절반은 라우트 레지스트리에서 패턴을 유도한다. **Studio 절반은 손으로 유지하는 배열이었고, 손으로 유지하는 배열이 실패하는 방식 그대로 실패했다** — `^/studio/assets$` 위의 주석이 바로 그 버그를 한 번 고친 기록이고, 라우트를 더하니 즉시 반복됐다.
|
||||
|
||||
공개 절반도 유도라고 하기 어려웠다. 번들된 픽스처에 우연히 들어 있던 공개 경로를 전부 열거하고, 생성된 nginx 가 정확히 그것들을 `location =` 블록으로 게시했다. 빌드 이후에 게시된 기록 — 백엔드를 두는 이유 그 자체 — 은 SPA 에 묻기도 전에 엣지에서 404 였다. 경로 27개가 얼어 있었고 28번째는 무엇이든 닿을 수 없었다.
|
||||
공개 절반도 유도라고 하기 어려웠다. 번들된 픽스처에 우연히 들어 있던 공개 경로를 전부 열거하고, 생성된 nginx 가 정확히 그것들을 `location =` 블록으로 게시했다. 빌드 이후에 게시된 기록은 SPA 에 묻기도 전에 엣지에서 404 였다. 경로 27개가 얼어 있었고 28번째는 무엇이든 닿을 수 없었다.
|
||||
|
||||
지금은 라우트 계약에서 등록된 Public 라우트마다 정규식 하나를 만든다. 파라미터는 한 세그먼트만 잡고 슬래시는 잡지 않으므로 `/cases/a/b` 는 404 로 남는다.
|
||||
|
||||
@@ -62,7 +62,7 @@ tech-log-frontend : ab8c6c1 · 6784eb1
|
||||
|
||||
SPA 안에서 이동할 때는 라우터가 화면을 그리므로 웹 서버가 개입하지 않는다. 주소를 직접 넣거나 새로고침하면 웹 서버가 먼저 그 경로를 받는다.
|
||||
|
||||
서빙 계약에 그 경로가 없으면 nginx 는 SPA 로 넘기지 않고 404 를 준다. 그래서 「내부에서는 되는데 새로고침하면 안 된다」로 나타난다.
|
||||
서빙 계약에 그 경로가 없으면 nginx 는 SPA 로 넘기지 않고 404 를 준다.
|
||||
|
||||
## 손으로 유지하는 절반
|
||||
|
||||
@@ -78,7 +78,7 @@ SPA 안에서 이동할 때는 라우터가 화면을 그리므로 웹 서버가
|
||||
|
||||
지금은 라우트 계약에서 등록된 Public 라우트마다 정규식 하나를 만든다. 파라미터는 한 세그먼트만 잡고 슬래시는 잡지 않으므로 `/cases/a/b` 는 404 로 남는다.
|
||||
|
||||
같은 구조 때문에 `robots.txt` 도 404 였다. 파일은 이미지에 있었지만 nginx 설정이 서빙할 파일을 하나씩 명시하는 구조라 등록되지 않은 것은 SPA 폴백으로 떨어진다. 크롤러가 index.html 을 규칙으로 읽을 수는 없다.
|
||||
같은 구조 때문에 `robots.txt` 도 404 였다. 파일은 이미지에 있었지만 nginx 설정이 서빙할 파일을 하나씩 명시하는 구조라 등록되지 않은 것은 SPA 폴백으로 떨어졌고, 크롤러가 index.html 을 규칙으로 읽을 수는 없으므로 규칙이 없는 것과 같았다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+6
-6
@@ -1,7 +1,7 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: eight-places-a-single-route-touches
|
||||
title: 라우트 하나가 건드리는 여덟 자리와, 그것들이 우는 시점
|
||||
title: 라우트 하나가 건드리는 여덟 곳과, 그것들이 우는 시점
|
||||
topic: one-route-many-hand-kept-lists
|
||||
topicName: 라우트 하나가 울리는 손 목록
|
||||
project: TechLog
|
||||
@@ -14,9 +14,9 @@ source:
|
||||
- final/document.md#§8.4
|
||||
---
|
||||
|
||||
# 라우트 하나가 건드리는 여덟 자리와, 그것들이 우는 시점
|
||||
# 라우트 하나가 건드리는 여덟 곳과, 그것들이 우는 시점
|
||||
|
||||
라우트를 하나 더하면 여덟 곳이 함께 울린다. 어떤 것은 빌드 직전에, 어떤 것은 배포 직전에, 어떤 것은 배포 뒤에 운다. 개념 라우트를 더한 커밋이 그 목록을 남겼다.
|
||||
라우트를 하나 더하면 여덟 곳이 같이 운다. 어떤 것은 빌드 직전에, 어떤 것은 배포 직전에, 어떤 것은 배포 뒤에 운다. 개념 라우트를 더한 커밋이 그 목록을 남겼다.
|
||||
|
||||
## 관계
|
||||
|
||||
@@ -73,11 +73,11 @@ CI 게이트 형상 digest 게이트 집합의 sha256
|
||||
|
||||
## 우는 시점이 다르다
|
||||
|
||||
주제 편집 화면을 더하고 청크 이름 표를 빠뜨렸더니 번들은 만들어지는데 빌드 매니페스트 단계에서 `Missing built route chunk: TECH_LOG_STUDIO_TOPIC_EDIT` 로 멈췄다. 다섯 개의 검사를 다 통과한 뒤 배포 직전에야 드러난다는 뜻이다.
|
||||
주제 편집 화면을 더하고 청크 이름 표를 빠뜨렸더니 번들은 만들어지는데 빌드 매니페스트 단계에서 `Missing built route chunk: TECH_LOG_STUDIO_TOPIC_EDIT` 로 멈췄다. 검사 다섯 개를 다 통과한 뒤 배포 직전에야 드러났다.
|
||||
|
||||
이 표도 손으로 나열한 목록이므로 다섯 검사 안에서 대조하게 했다.
|
||||
|
||||
## 게이트 기준값 셋이 함께 움직인다
|
||||
## 게이트 기준값 셋은 라우트마다 움직인다
|
||||
|
||||
FE-GATE-009 는 설치된 라우트마다 수동 접근성 증거를 하나씩 요구하고, 그 집합이 정확히 일치하지 않으면 거절한다.
|
||||
|
||||
@@ -88,7 +88,7 @@ FE-GATE-009 는 설치된 라우트마다 수동 접근성 증거를 하나씩
|
||||
| `048c1b2` | `/concepts/:slug` | +1 | +1 | fb138e7c… 재계산 |
|
||||
| `fe6b56a` | `/topics`, `/topics/:s/:v`, `/studio/topics/:id` | 135 → 138 | 114 → 117 | 87a22f68… 재계산 |
|
||||
|
||||
digest 를 다시 계산할 때는 매번 이전 gates.json 에서 옛 상수를 먼저 재현해 계산 방법이 맞는지 확인한 뒤 새 파일을 해싱했다. 그렇게 하지 않으면 「계산이 달라졌는데 새 값이 나왔다」와 「파일이 바뀌어서 새 값이 나왔다」를 구분할 수 없다.
|
||||
digest 를 다시 계산할 때는 매번 이전 gates.json 에서 옛 상수를 먼저 재현해 계산 방법이 맞는지 확인한 뒤 새 파일을 해싱했다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+3
-3
@@ -17,7 +17,7 @@ source:
|
||||
|
||||
# catch-all 라우트는 nginx 패턴으로 번역하지 않는다
|
||||
|
||||
라우트 계약에서 nginx 서빙 패턴을 만들 때 catch-all 라우트는 번역하지 않는다. 모든 미매치 주소에 index.html 을 주면 엣지의 404 가 soft 200 이 되고, 깨진 링크가 크롤러와 우리 감사에서 함께 사라진다.
|
||||
라우트 계약에서 nginx 서빙 패턴을 만들 때 catch-all 라우트는 번역하지 않는다. 모든 미매치 주소에 index.html 을 주면 엣지의 404 가 soft 200 이 되고, 깨진 링크가 크롤러에도 우리 감사에도 잡히지 않는다.
|
||||
|
||||
## 근거
|
||||
|
||||
@@ -42,8 +42,8 @@ source:
|
||||
|
||||
## 영향
|
||||
|
||||
라우트를 더할 때마다 서빙 패턴이 함께 움직인다. 이 비용은 라우트 계약에서 유도해 없앴다 — 손으로 배열을 고치지 않는다.
|
||||
라우트를 더할 때마다 서빙 패턴도 움직인다. 이 비용은 라우트 계약에서 유도해 없앴다 — 손으로 배열을 고치지 않는다.
|
||||
|
||||
등록되지 않은 주소는 SPA 에 닿지 못한다. 라우트를 더하고 프론트를 배포하기 전까지 그 경로는 엣지에서 404 다. 그래서 새 라우트는 프론트를 먼저 배포한다.
|
||||
|
||||
감사에서 200 을 받은 35개 주소는 실제로 화면이 그려지는 주소다. 이 결정이 없으면 그 수는 아무것도 뜻하지 않는다.
|
||||
감사에서 200 을 받은 35개 주소는 실제로 화면이 그려지는 주소다.
|
||||
|
||||
+3
-3
@@ -34,7 +34,7 @@ FE-GATE-009 는 설치된 라우트마다 증거 파일 하나를 요구하고,
|
||||
|
||||
`review:a11y-manual` 스크립트는 그래서 실패하는 것이 지금 정상이다.
|
||||
|
||||
라우트를 더할 때마다 이 증거 개수가 함께 움직였고, 커밋 넷에서 111 → 117 로 늘었다.
|
||||
라우트를 더할 때마다 이 증거 개수도 늘어, 커밋 넷에서 111 → 117 이 됐다.
|
||||
|
||||
## 가정
|
||||
|
||||
@@ -48,11 +48,11 @@ FE-GATE-009 는 설치된 라우트마다 증거 파일 하나를 요구하고,
|
||||
|
||||
서명을 요구하지 않기로 한다면 이 게이트가 파일 개수를 세는 것이 무엇을 막는가.
|
||||
|
||||
라우트 하나를 사람이 실제로 검토하는 데 얼마가 드는가. 그 비용을 모르면 어느 쪽도 고를 수 없다.
|
||||
라우트 하나를 사람이 실제로 검토하는 데 얼마가 드는가.
|
||||
|
||||
## 제약
|
||||
|
||||
FE-GATE-009 는 라우트 집합과 증거 집합이 정확히 일치하기를 요구한다. 이 규칙은 바꾸지 않는다 — 빠뜨림이 통과가 되면 게이트가 아니다.
|
||||
FE-GATE-009 는 라우트 집합과 증거 집합이 정확히 일치하기를 요구한다. 이 규칙은 바꾸지 않는다.
|
||||
|
||||
수동 접근성 증거는 사람이 만든다. 자동 검사로 대신하지 않는다.
|
||||
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ source:
|
||||
|
||||
## 목적
|
||||
|
||||
라우트를 더할 때 함께 움직여야 하는 목록이 빠지는 것을 막는다. 이 부류는 우는 시점이 제각각이라, 어떤 것은 배포한 뒤 방문자가 먼저 만난다.
|
||||
라우트를 더할 때 같이 고쳐야 하는 목록이 빠지는 것을 막는다. 이 부류는 우는 시점이 제각각이라, 어떤 것은 배포한 뒤 방문자가 먼저 만난다.
|
||||
|
||||
## 규칙
|
||||
|
||||
@@ -46,7 +46,7 @@ vite chunk 이름 표가 그렇다. 이 표를 빠뜨리면 다섯 검사를 다
|
||||
|
||||
## 적용 조건
|
||||
|
||||
라우트 하나가 서빙 패턴·청크 이름·게이트 기준값 같은 목록을 함께 움직이는 프론트엔드. 라우트를 더하거나 지우는 변경에서 걸린다.
|
||||
라우트 하나가 서빙 패턴·청크 이름·게이트 기준값 같은 목록을 동시에 움직이는 프론트엔드. 라우트를 더하거나 지우는 변경에서 걸린다.
|
||||
|
||||
## 예외
|
||||
|
||||
|
||||
+3
-3
@@ -63,13 +63,13 @@ tech-log-backend : 857e6a9 — 삭제 거절 사유를 클라이언트 안전
|
||||
|
||||
## 무엇이 구분되지 않았나
|
||||
|
||||
버전 충돌은 「누가 먼저 고쳤다」이고 참조 존재는 「사용 중」이다. 문구가 셋을 함께 적으므로 작성자는 둘을 구분할 수 없다.
|
||||
버전 충돌은 「누가 먼저 고쳤다」이고 참조 존재는 「사용 중」이다. 문구가 셋을 한꺼번에 적으므로 작성자는 둘을 구분할 수 없다.
|
||||
|
||||
두 경우에 해야 할 일이 다르다. 버전 충돌이면 다시 받아서 지우면 되고, 참조가 있으면 그 참조를 먼저 풀어야 한다.
|
||||
버전 충돌이면 다시 받아서 지우면 되고, 참조가 있으면 그 참조를 먼저 풀어야 한다.
|
||||
|
||||
## 서버의 답을 실어 나른다
|
||||
|
||||
게이트웨이가 서버의 클라이언트 안전 메시지를 그대로 싣고 화면이 그것을 보인다. 서버가 답하지 않은 것은 화면이 만들지 않는다.
|
||||
게이트웨이가 서버의 클라이언트 안전 메시지를 그대로 싣고 화면이 그것을 보인다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+4
-6
@@ -15,7 +15,7 @@ source:
|
||||
|
||||
# 한 화면에 종류 이름이 아홉 개 떠 있었다 — 표가 여섯 벌이었다
|
||||
|
||||
홈 한 화면에 문서 종류 이름이 아홉 개 떠 있었다. 최근 기록 목록은 계약의 enum 이름을, 바로 아래 「종류별로 읽기」는 사람이 붙인 이름을 쓰고 있었다. 독자는 둘이 같은 것이라는 단서를 어디서도 받지 못했다. 원인은 종류 이름 표가 화면마다 복사되어 여섯 벌이었다는 것이다.
|
||||
홈 한 화면에 문서 종류 이름이 아홉 개 떠 있었다. 최근 기록 목록은 계약의 enum 이름을, 바로 아래 「종류별로 읽기」는 사람이 붙인 이름을 쓰고 있었다. 독자는 둘이 같은 것이라는 단서를 어디서도 받지 못했다. 표가 화면마다 복사되어 여섯 벌이었기 때문이다.
|
||||
|
||||
## 관계
|
||||
|
||||
@@ -39,13 +39,11 @@ source:
|
||||
|
||||
## 결론
|
||||
|
||||
종류 이름 표가 화면마다 복사되어 여섯 벌이었고, 그래서 갈라졌다.
|
||||
|
||||
> 표가 화면마다 복사되어 **여섯 벌**이었고 그래서 갈라졌다: 같은 QUESTION 이 공개 화면에서 "Open Question", 작업본 목록과 게시 기록에서 "Question", 편집기 상태 줄에서 "QUESTION" 이었다. **쓰는 사람은 같은 문서를 화면마다 다른 이름으로 만난다.**
|
||||
|
||||
종류에서 이름으로 가는 표 하나로 모았다.
|
||||
|
||||
편집기 칸 이름도 공개 화면과 맞췄다. 쓰는 사람이 지금 채우는 칸이 공개 화면 어디로 가는지 외우지 않아도 된다.
|
||||
편집기 칸 이름도 공개 화면과 맞췄다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
@@ -71,7 +69,7 @@ tech-log-frontend : dc2fda7 · ca1cfa2 계열 · 82e992d
|
||||
|
||||
두 목록이 세로로 붙어 있다. 위는 계약의 enum 이름을, 아래는 사람이 붙인 이름을 쓴다.
|
||||
|
||||
이름을 바꾸기 전보다 나빠진 유일한 화면이었다. 바꾸기 전에는 양쪽이 다 enum 이름이라 적어도 같아 보였다.
|
||||
이 화면은 이름을 바꾸기 전보다 나빠졌다. 바꾸기 전에는 양쪽이 다 enum 이름이라 적어도 같아 보였다.
|
||||
|
||||
## 표가 여섯 벌이었다
|
||||
|
||||
@@ -79,7 +77,7 @@ tech-log-frontend : dc2fda7 · ca1cfa2 계열 · 82e992d
|
||||
|
||||
## 표 하나로 모았다
|
||||
|
||||
종류에서 표시 이름으로 가는 표를 하나 만들고 여섯 곳이 그것을 쓰게 했다. 종류가 늘면 그 표에 자리가 비었다고 컴파일러가 잡는다.
|
||||
종류에서 표시 이름으로 가는 표를 하나 만들고 여섯 곳이 그것을 쓰게 했다. 종류가 늘면 그 표에서 빠진 값을 컴파일러가 잡는다.
|
||||
|
||||
## 편집기 칸 이름도 맞췄다
|
||||
|
||||
|
||||
+2
-4
@@ -36,7 +36,7 @@ source:
|
||||
두 번 바꿨다.
|
||||
|
||||
1차 : 이름이 하는 일을 말하게 했다 — 직접 해보니 · 다음에 쓸 기준 · 아직 모르는 것 · 어떻게 동작하나 · 이렇게 하기로
|
||||
2차 : 역할은 그대로 말하되 문어체로 다시 세웠다 — 검증 기록 · 적용 기준 · 열린 질문 · 동작 원리 · 설계 결정
|
||||
2차 : 하는 일을 말하는 방향은 두고 문어체로 다시 세웠다 — 검증 기록 · 적용 기준 · 열린 질문 · 동작 원리 · 설계 결정
|
||||
|
||||
1차 안이 기술 기록의 톤에 비해 가벼웠다.
|
||||
|
||||
@@ -84,9 +84,7 @@ QUESTION → 열린 질문
|
||||
|
||||
## 계약의 kind 는 그대로 뒀다
|
||||
|
||||
바꾼 것은 화면에 보이는 이름이다. 계약의 `RecordKind` 는 다섯 값 그대로이고, 주소도 그대로다.
|
||||
|
||||
표시 이름과 계약 값을 갈라 두면 이름을 다시 바꿀 때 계약을 건드리지 않아도 된다.
|
||||
바꾼 것은 화면에 보이는 이름이다. 계약의 `RecordKind` 는 다섯 값 그대로이고 주소도 바뀌지 않았다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+2
-2
@@ -16,12 +16,12 @@ source:
|
||||
|
||||
# 삭제를 막는 이유 다섯 가지가 전부 같은 한 문장으로 나온다
|
||||
|
||||
작업본 삭제가 막히는 이유는 다섯 가지인데 전부 같은 한 문장으로 나온다. 실제 사례에서 막은 것은 프로젝트 링크 한 행이었고, 문구는 「다른 기록이 참조한다」고 말했다. 문구가 잘못된 것을 가리키고 있다.
|
||||
작업본 삭제가 막히는 이유는 다섯 가지인데 전부 같은 한 문장으로 나온다. 실제 사례에서 막은 것은 프로젝트 링크 한 행이었고, 문구는 「다른 기록이 참조한다」고 말했다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **서버는 하나를 답했는데 화면은 추측 셋을 출력했다**
|
||||
화면 쪽 문구를 고친 사건이고, 서버 쪽 문구는 그대로 남았다.
|
||||
화면 쪽 문구를 고친 사건이고, 서버 쪽 문구는 고치지 않았다.
|
||||
- **그 SQL 은 한 번도 실행된 적이 없었다**
|
||||
이 참조 검사를 실제 DB 에서 돌리게 만든 사건이다.
|
||||
- **화면은 못 읽은 것을 없다고 말하지 않는다**
|
||||
|
||||
+4
-4
@@ -14,7 +14,7 @@ source:
|
||||
|
||||
# 톤을 지적받으면 고쳐 쓰지 말고 어떤 말을 쓸지 묻는다
|
||||
|
||||
사용자가 프로필의 문구가 AI 스럽다고 지적했다. 고쳐 쓴 첫 번째 안도 거절당했고, 결국 사용자가 직접 쓴 텍스트를 그대로 실었다. 이 사이트의 글은 작성자가 자기 말로 쓴다. 더 나은 문장을 제안할 때와 그 사람의 말투로 쓸 때 필요한 것이 다르다.
|
||||
사용자가 프로필의 문구가 AI 스럽다고 지적했다. 고쳐 쓴 첫 번째 안도 거절당했고, 결국 사용자가 직접 쓴 텍스트를 그대로 실었다. 이 사이트의 글은 작성자가 자기 말로 쓴다.
|
||||
|
||||
## 관계
|
||||
|
||||
@@ -27,7 +27,7 @@ source:
|
||||
|
||||
## 목적
|
||||
|
||||
작성자의 목소리로 쓰인 글을 고쳐 쓰다 두 번 거절당하는 것을 막는다. 톤을 지적할 때 사용자가 가리키는 것은 문장의 품질이 아니라 그 말을 누가 쓰는가다.
|
||||
작성자의 목소리로 쓰인 글을 고쳐 쓰다 두 번 거절당하는 것을 막는다.
|
||||
|
||||
## 규칙
|
||||
|
||||
@@ -48,7 +48,7 @@ Case 소제목에 쓰는 말이 「~한 것」 명사형이면 새 제목도 그
|
||||
|
||||
오류 문구처럼 서버가 답한 사실을 그대로 실어야 하는 곳에서는 말투를 묻지 않는다. 무엇을 실을지를 먼저 정한다.
|
||||
|
||||
계약이나 코드가 정한 이름은 이 규칙에서 뺀다. 표시 이름만 바꾸고 계약 값은 그대로 둔다.
|
||||
계약이나 코드가 정한 이름은 이 규칙에서 뺀다. 표시 이름만 바꾸고 계약 값은 건드리지 않는다.
|
||||
|
||||
## 예시
|
||||
|
||||
@@ -58,4 +58,4 @@ Case 소제목에 쓰는 말이 「~한 것」 명사형이면 새 제목도 그
|
||||
|
||||
「무엇을 견줬나」는 의문형 꼬리에 이 기록에서 쓰지 않는 낱말이었다. 작성자가 Case 소제목에 쓰는 말은 「이 구조에서 감수한 것」처럼 「~한 것」 명사형이라 그쪽에 맞췄다.
|
||||
|
||||
고쳐 쓴 첫 번째 안이 거절당한 뒤 사용자가 직접 쓴 텍스트를 그대로 실었다.
|
||||
고쳐 쓴 첫 번째 안이 거절당한 뒤 사용자가 직접 쓴 텍스트를 실었다.
|
||||
|
||||
+1
-1
@@ -78,6 +78,6 @@ SOURCE_DATE_EPOCH
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
빌드가 이 인자를 요구하도록 막지 않았다. 빠뜨리면 여전히 빌드는 성공하고 배포본만 틀린다. 지금 남은 것은 인자 목록을 적어 둔 것뿐이다.
|
||||
빌드가 이 인자를 요구하도록 막지 않았다. 빠뜨리면 여전히 빌드는 성공하고 배포본만 틀린다. 인자 목록을 적어 둔 것으로 그쳤다.
|
||||
|
||||
<!-- body:end -->
|
||||
|
||||
+2
-2
@@ -62,7 +62,7 @@ tech-log-frontend : 83409be
|
||||
|
||||
헬스 판정은 헬스 엔드포인트가 응답하는지를 본다. nginx 프로세스가 살아 있고 그 경로를 돌려주면 통과한다.
|
||||
|
||||
SPA 가 부팅에 필요한 설정 파일은 그 판정에 들어 있지 않다. 그래서 컨테이너는 정상이고 사이트만 안 된다.
|
||||
SPA 가 부팅에 필요한 설정 파일은 그 판정에 들어 있지 않다.
|
||||
|
||||
## 빌드가 쓴 권한
|
||||
|
||||
@@ -72,7 +72,7 @@ SPA 가 부팅에 필요한 설정 파일은 그 판정에 들어 있지 않다.
|
||||
|
||||
## 브라우저가 묻는 주소
|
||||
|
||||
같은 배포에서 favicon 도 404 였다. 이유가 달랐다 — `index.html` 이 `public/favicon.svg` 를 참조한 적이 없다. 파일은 이미지에 들어 있었고 nginx 도 서빙했지만, 브라우저는 참조가 없으면 `/favicon.ico` 를 묻는다. 그 이름의 파일이 없어 404 를 받고 기본 아이콘으로 떨어졌다.
|
||||
같은 배포에서 favicon 도 404 였다. `index.html` 이 `public/favicon.svg` 를 참조한 적이 없다. 파일은 이미지에 들어 있었고 nginx 도 서빙했지만, 브라우저는 참조가 없으면 `/favicon.ico` 를 묻는다. 그 이름의 파일이 없어 404 를 받고 기본 아이콘으로 떨어졌다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+1
-1
@@ -66,6 +66,6 @@ SOURCE_DATE_EPOCH
|
||||
|
||||
## 이 경로가 늦게 알려 주는 것
|
||||
|
||||
이미지가 healthy 로 올라오는 것과 사이트가 동작하는 것은 다르다. 컨테이너 안의 파일 권한, nginx 가 서빙하기로 한 파일 목록, 빌드에 굳은 주소는 전부 이 단계 뒤에 드러난다.
|
||||
컨테이너 안의 파일 권한, nginx 가 서빙하기로 한 파일 목록, 빌드에 굳은 주소는 전부 이 단계 뒤에 드러난다.
|
||||
|
||||
<!-- body:end -->
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ tech-log-frontend : 03986da · 7600711
|
||||
|
||||
> 엄격한 enum 을 잘못된 표면의 계약에 대고 검사해도 여전히 엄격해 보인다.
|
||||
|
||||
검증은 돌고 있었고 통과하고 있었다. 다만 비교 대상이 틀렸다.
|
||||
검증은 돌고 있었고 통과하고 있었다.
|
||||
|
||||
## 스텁이 이음매를 덮지 않는다
|
||||
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ DB : 실제 PostgreSQL (Testcontainers)
|
||||
|
||||
## 그 SQL 은 한 번도 실행되지 않았다
|
||||
|
||||
컬럼 이름보다 더 큰 문제는 검사 구조였다. 표준 `check` 는 Testcontainers 를 띄우지 않으므로 persistence SQL 이 한 줄도 실행되지 않은 채 빌드가 통과한다.
|
||||
표준 `check` 는 Testcontainers 를 띄우지 않으므로 persistence SQL 이 한 줄도 실행되지 않은 채 빌드가 통과한다.
|
||||
|
||||
컴파일은 SQL 문자열 안을 보지 않는다. 단위 테스트는 어댑터를 스텁으로 바꾼다. 컬럼 이름이 맞는지 묻는 검사가 어디에도 없었다.
|
||||
|
||||
|
||||
+2
-2
@@ -78,11 +78,11 @@ Jackson : 이 빌드는 Jackson 3, 클래스패스에 Jackson 2 타입이 전이
|
||||
| 스캔되는 컴포넌트에 생성자 둘, `@Autowired` 없음 | 어떤 테스트도 애플리케이션 컨텍스트를 띄우지 않는다 | `ca63d7d` |
|
||||
| Jackson 2 `ObjectMapper` 를 요구(이 빌드는 Jackson 3) | Jackson 2 타입이 전이 의존성으로 클래스패스에 남아 있어 import 가 정상 해석된다 | `0da7c7e` |
|
||||
|
||||
## 규칙 하나로 막은 절반
|
||||
## D20 규칙으로 막은 것
|
||||
|
||||
D20 규칙을 세웠다. 스캔되는 컴포넌트는 생성자가 하나이거나, 여럿이면 그중 하나에 `@Autowired` 가 붙어야 한다. 결함을 되돌려 규칙이 실제로 멈추는 것을 확인한 뒤 커밋했다.
|
||||
|
||||
## 막지 못한 절반
|
||||
## D20 이 보지 않는 것
|
||||
|
||||
D20 은 생성자 쪽만 본다. 클래스패스에 남은 옛 라이브러리 타입을 import 하는 것은 이 규칙이 잡지 않는다. 그 경로를 막는 검사는 아직 없고, 컨테이너가 뜰 때 알게 된다.
|
||||
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ source:
|
||||
|
||||
그 층을 실제로 지나는 검사가 이미 있으면 더 두지 않는다.
|
||||
|
||||
스텁을 쓰는 테스트를 늘리는 것은 이 문제를 덮지 않는다. 스텁의 개수가 아니라 스텁이 대신한 층이 문제다.
|
||||
스텁을 쓰는 테스트를 늘리는 것은 이 문제를 덮지 않는다.
|
||||
|
||||
## 예시
|
||||
|
||||
|
||||
@@ -789,7 +789,7 @@
|
||||
"evidenceFiles": []
|
||||
},
|
||||
{
|
||||
"title": "라우트 하나가 건드리는 여덟 자리와, 그것들이 우는 시점",
|
||||
"title": "라우트 하나가 건드리는 여덟 곳과, 그것들이 우는 시점",
|
||||
"slug": "eight-places-a-single-route-touches",
|
||||
"readiness": "READY",
|
||||
"source": [
|
||||
@@ -802,7 +802,7 @@
|
||||
"TECH_LOG_STUDIO_TOPIC_EDIT",
|
||||
"FE-GATE-009"
|
||||
],
|
||||
"classification": "여덟 자리를 목록으로 확정하고 각각이 우는 시점 — 빌드 직전, 배포 직전, 배포 뒤 — 까지 갈랐다. 유도할 수 있는 것은 유도하고 기준값은 재현 절차로 닫는다",
|
||||
"classification": "여덟 곳을 목록으로 확정하고 각각이 우는 시점 — 빌드 직전, 배포 직전, 배포 뒤 — 까지 갈랐다. 유도할 수 있는 것은 유도하고 기준값은 재현 절차로 닫는다",
|
||||
"missing-verification": "게이트 기준값 셋은 여전히 손으로 움직인다. 옛 값을 먼저 재현하는 절차는 사람이 기억해야 하고 검사가 강제하지 않는다",
|
||||
"relations": [
|
||||
"reference:derive-the-route-lists-from-the-route-contract",
|
||||
|
||||
+6
-10
@@ -14,7 +14,7 @@ source:
|
||||
|
||||
# 결정에는 상세 화면이 없어 목록 항목이 문서 전체를 실어야 했다
|
||||
|
||||
공개 결정 화면에서 제목 자리에 결정문 전문이 나오고, 요약이 없고, 줄바꿈이 전부 접히고, 영향과 근거가 늘 비어 있었다. 네 증상이 한 구조에서 나왔다 — 결정에는 상세 화면이 없고 공개 주소가 목록 위의 앵커다. 그래서 화면이 그리는 칸이 전부 목록 항목에 있어야 했다.
|
||||
공개 결정 화면에서 제목 칸에 결정문 전문이 나오고, 요약이 없고, 줄바꿈이 전부 접히고, 영향과 근거가 늘 비어 있었다. 네 증상이 한 구조에서 나왔다 — 결정에는 상세 화면이 없고 공개 주소가 목록 위의 앵커다. 그래서 화면이 그리는 칸이 전부 목록 항목에 있어야 했다.
|
||||
|
||||
## 관계
|
||||
|
||||
@@ -35,7 +35,7 @@ source:
|
||||
|
||||
네 증상이 전부 목록 항목의 빈칸에서 나왔다.
|
||||
|
||||
제목 자리 : statement 를 대신 썼다
|
||||
제목 칸 : statement 를 대신 썼다
|
||||
요약 : 실을 칸이 없었다
|
||||
줄바꿈 : 접혔다
|
||||
영향과 근거 : 프론트가 빈 배열로 고정해 뒀다
|
||||
@@ -63,26 +63,22 @@ tech-log-frontend : 31afb4d 이후
|
||||
|
||||
결정은 자기 화면을 갖지 않는다. 공개 라우트는 `/projects/{slug}/decisions` 하나이고, 개별 결정은 그 목록 위의 앵커로 간다.
|
||||
|
||||
이 구조에서는 화면이 그리는 칸이 전부 목록 항목에 있어야 한다. 상세를 부를 곳이 없기 때문이다.
|
||||
상세를 부를 곳이 없으므로 화면이 그리는 칸이 전부 목록 항목에 있어야 한다.
|
||||
|
||||
## 네 증상이 한 원인이었다
|
||||
|
||||
`ProjectDecisionItem` 에 `title` 이 없어서 프론트가 `statement` 를 제목 자리에 썼다. 결정문은 한 문장이 아니라 문단일 수 있으므로 제목 자리에 전문이 들어갔다.
|
||||
`ProjectDecisionItem` 에 `title` 이 없어서 프론트가 `statement` 를 제목 칸에 썼다. 결정문은 한 문장이 아니라 문단일 수 있으므로 제목 칸에 전문이 들어갔다.
|
||||
|
||||
`summary` 가 없어서 요약 줄이 비었다. `consequences` 와 `evidence` 가 없어서 프론트가 그 둘을 빈 배열로 고정해 뒀다.
|
||||
|
||||
줄바꿈은 다른 이유였다. 마크다운이 아닌 칸의 줄바꿈을 화면이 접고 있었다.
|
||||
|
||||
## 저장된 값은 그대로 있었다
|
||||
## DB 에는 값이 다 있었다
|
||||
|
||||
DB 를 조회하면 작성자가 쓴 제목과 여러 줄 요약과 영향 4건이 있었다. 어느 것도 화면까지 오지 못했다.
|
||||
|
||||
## 화면 쪽에서 역으로 확인한다
|
||||
|
||||
이 부류는 응답에서 출발하면 보이지 않는다. 응답에 없는 칸을 찾는 일이기 때문이다. 화면이 그리는 칸을 먼저 적고 그 칸이 응답에 있는지 하나씩 맞춰야 한다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
저장된 값이 그대로였다는 것은 조회로 확인했다. 그 시점의 화면 캡처는 남기지 않았다.
|
||||
저장된 값은 조회로 확인했다. 그 시점의 화면 캡처는 남기지 않았다.
|
||||
|
||||
<!-- body:end -->
|
||||
|
||||
+4
-4
@@ -28,7 +28,7 @@ source:
|
||||
|
||||
## 문제
|
||||
|
||||
관계 목록은 한 줄에 대상의 종류와 작성자가 쓴 이유와 대상의 요약을 보인다. 라벨은 고쳤는데 요약 자리가 계속 비어 있었다.
|
||||
관계 목록은 한 줄에 대상의 종류와 작성자가 쓴 이유와 대상의 요약을 보인다. 라벨은 고쳤는데 요약 칸이 계속 비어 있었다.
|
||||
|
||||
계약에는 요약이 있었다. DB 에도 값이 있었다. 화면까지 오지 못했다.
|
||||
|
||||
@@ -74,7 +74,7 @@ tech-log-backend : 92679f5 이후
|
||||
|
||||
## 계약에 칸을 더할 때 required 를 따로 판단한다
|
||||
|
||||
`ResolvedRelation` 에 `summary` 를 더했다. required 에는 넣지 않았다. 이미 나가 있는 응답에는 그 칸이 없으므로, required 로 올리면 배포 순서에 따라 검증이 깨진다.
|
||||
`ResolvedRelation` 에 `summary` 를 더하면서 required 에는 넣지 않았다. 이미 나가 있는 응답에는 그 칸이 없어서, required 로 올리면 배포 순서에 따라 검증이 깨진다.
|
||||
|
||||
## 한 칸에 셋이 뭉쳐 있었다
|
||||
|
||||
@@ -82,11 +82,11 @@ tech-log-backend : 92679f5 이후
|
||||
|
||||
| 무엇 | 뜻 | 경로별로 어떻게 나왔나 |
|
||||
|---|---|---|
|
||||
| 대상의 종류 | 「근거」「관련 기준」 같은 분류 | 렌더 모델 경로: 작성자의 문장이 이 자리에 눌려 나옴 |
|
||||
| 대상의 종류 | 「근거」「관련 기준」 같은 분류 | 렌더 모델 경로: 작성자의 문장이 이 칸에 눌려 나옴 |
|
||||
| 작성자가 쓴 이유 | 「다음에 무엇을 읽을지」의 답 | 공개 조회 경로: **아예 버려짐** |
|
||||
| 대상의 요약 | 대상이 무엇인지 | — |
|
||||
|
||||
셋을 `label` · `note` · `summary` 로 갈랐다. 설명 자리에는 문장이 있으면 문장을, 없으면 요약을 보인다. 요약은 대상이 무엇인지 말하고, 문장은 왜 지금 그것을 읽어야 하는지 말한다.
|
||||
셋을 `label` · `note` · `summary` 로 갈랐다. 설명 칸에는 작성자가 쓴 문장이 있으면 그것을, 없으면 대상의 요약을 보인다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ status: 게시 전
|
||||
basisVersion: tech-log-backend · tech-log-frontend 2026-09-02 · OpenAPI 3.1 계약 3종을 반입해 쓰는 구조
|
||||
assets:
|
||||
- key: value-boundaries
|
||||
file: ../../../final/assets/tech-log-studio/value-boundaries.svg
|
||||
file: ../../../final/assets/diagrams/value-boundaries/value-boundaries.svg
|
||||
sourceRevision: tech-log@2026-09-02
|
||||
source:
|
||||
- final/document.md#§1.2
|
||||
@@ -68,7 +68,7 @@ JDBC 어댑터의 SQL 은 컬럼 이름을 문자열로 적는다. 컬럼이 없
|
||||
|
||||
## 값을 버려도 오류가 나지 않는다
|
||||
|
||||
이 경계들은 값을 담지 않았다고 말하지 않는다. 담지 않은 채 다음으로 넘긴다.
|
||||
이 경계들은 값을 담지 않아도 그렇다고 말하지 않고 다음으로 넘긴다.
|
||||
|
||||
`undefined` 는 화면에서 빈 문자열이 된다. 빈 배열은 「항목이 없습니다」가 된다. 그래서 화면만 보면 값이 없는 것과 값을 잃은 것이 같아 보인다.
|
||||
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ source:
|
||||
등록을 빠뜨린 연산은 옆 분기로 떨어지므로 서버는 정상 응답을 준다. 나가는 경로를 봐야 알 수 있다.
|
||||
|
||||
**여정이 끝나는 곳을 먼저 정하고 시작한다**
|
||||
어디까지 가면 확인이 끝나는지 모르면 중간에서 멈추게 된다.
|
||||
어디까지 가면 확인이 끝나는지 모르면 중간에서 멈춘다.
|
||||
|
||||
## 적용 조건
|
||||
|
||||
|
||||
+2
-2
@@ -76,9 +76,9 @@ deleteDocument(kind: "CASE" | "REFERENCE" | "QUESTION", id: string) { … }
|
||||
|
||||
배포된 번들에서 서버 로그를 보고서야 알았다. 삭제 요청이 질문 경로로 나가고 있었다.
|
||||
|
||||
## 같은 병이 필터에서도 났다
|
||||
## 포트와 어댑터가 필터 타입을 따로 들고 있었다
|
||||
|
||||
포트와 정적 어댑터가 필터 타입을 따로 들고 있었다. 포트에 축 필터를 더해도 어댑터의 타입은 그대로였고, `satisfies` 도 같은 이유로 통과했다.
|
||||
포트에 축 필터를 더해도 어댑터의 타입은 그대로였고, `satisfies` 도 같은 이유로 통과했다.
|
||||
|
||||
타입을 하나로 합쳐서 고쳤다. 포트가 아는 필터와 어댑터가 아는 필터가 같은 타입이면 한쪽만 늘어날 수 없다.
|
||||
|
||||
|
||||
+2
-2
@@ -68,7 +68,7 @@ tsconfig : 루트가 project references 만 나열
|
||||
|
||||
## 통과가 무엇을 뜻했나
|
||||
|
||||
이 명령이 성공했을 때 확인된 것은 「루트 tsconfig 가 유효하다」까지다. 코드가 컴파일되는지는 확인되지 않았다.
|
||||
이 명령은 루트 tsconfig 가 유효한지까지만 확인하고, 코드가 컴파일되는지는 묻지 않는다.
|
||||
|
||||
## 올바른 명령으로 돌렸을 때
|
||||
|
||||
@@ -76,6 +76,6 @@ tsconfig : 루트가 project references 만 나열
|
||||
|
||||
## 남은 것
|
||||
|
||||
루트 tsconfig 는 그대로 뒀다. 누군가 다시 `npx tsc --noEmit` 을 쓰는 것을 막는 검사는 없고, 배포 전에 돌릴 다섯 명령의 목록에 적어 둔 것이 지금의 대책이다.
|
||||
루트 tsconfig 는 그대로 뒀다. 누군가 다시 `npx tsc --noEmit` 을 쓰는 것을 막는 검사는 없고, 배포 전에 돌릴 다섯 명령의 목록에 적어 뒀다.
|
||||
|
||||
<!-- body:end -->
|
||||
|
||||
+1
-3
@@ -51,7 +51,7 @@ const summary = body.purposeSummary as string; // 계약에 그런 칸이 없
|
||||
|
||||
`as` 는 「이 값을 이 타입으로 다루겠다」는 선언이라, 그 이름이 응답 타입에 있는지 묻지 않는다. 실행하면 `undefined` 가 나온다.
|
||||
|
||||
모양이 다른 경우에는 더 나빠진다. 객체를 배열로 읽고 `.filter` 를 부르면 매핑이 통째로 터지는데, `as` 캐스트가 그 어긋남을 타입 검사에서 가린다.
|
||||
객체를 배열로 읽고 `.filter` 를 부르면 매핑이 통째로 터지는데, `as` 캐스트가 그 어긋남을 타입 검사에서 가린다.
|
||||
|
||||
## never 로 받으면 아무것도 요구하지 않는다
|
||||
|
||||
@@ -67,6 +67,4 @@ const summary = body.purposeSummary as string; // 계약에 그런 칸이 없
|
||||
|
||||
넷 다 「이 코드가 그 타입과 맞는가」라는 질문을 다른 질문으로 바꾼다. bivariance 는 시그니처 호환으로, `as` 는 작성자의 선언으로, `never` 는 검사 없음으로, 빈 tsconfig 는 대상 없음으로 바꾼다.
|
||||
|
||||
그래서 이 넷을 지난 통과는 「코드가 맞다」가 아니라 「검사가 그 질문을 하지 않았다」를 뜻한다.
|
||||
|
||||
<!-- body:end -->
|
||||
|
||||
+3
-3
@@ -68,9 +68,9 @@ tech-log-frontend : fd73bc8 · fe6b56a
|
||||
|
||||
## 게이트 기준값을 빠뜨렸다
|
||||
|
||||
주제 화면 셋을 더할 때 CI 게이트가 요구하는 기준값 셋을 함께 올리지 않았다. 게이트는 정확히 그것을 거절한다.
|
||||
주제 화면 셋을 더하면서 CI 게이트가 요구하는 기준값 셋을 올리지 않았다. 게이트는 정확히 그것을 거절한다.
|
||||
|
||||
게이트가 빨간 채로 여러 커밋을 지나갔고, 결정 링크 404 를 고치던 커밋에서야 함께 맞췄다.
|
||||
게이트가 빨간 채로 여러 커밋을 지나갔고, 결정 링크 404 를 고치던 커밋에서야 맞췄다.
|
||||
|
||||
## 가드가 아니라 실행이 빠졌다
|
||||
|
||||
@@ -80,6 +80,6 @@ tech-log-frontend : fd73bc8 · fe6b56a
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
다섯 명령을 CI 에 묶는 작업은 하지 않았다. 지금 남은 것은 메모리와 배포 전 검증 목록이다.
|
||||
다섯 명령을 CI 에 묶는 작업은 하지 않았다. 메모리와 배포 전 검증 목록으로만 남겨 뒀다.
|
||||
|
||||
<!-- body:end -->
|
||||
|
||||
+2
-2
@@ -18,7 +18,7 @@ source:
|
||||
|
||||
# 가드는 결함을 되돌려 실제로 멈추는 것을 확인한 뒤 커밋한다
|
||||
|
||||
가드를 넣었다는 것과 그 가드가 무엇을 잡는다는 것은 다르다. 결함을 되돌려 실제로 빨개지는 것을 확인한 뒤에 커밋한다. 확인하지 않은 가드는 그 결함이 원래 없었는지 검사가 안 도는지 구별되지 않는다.
|
||||
결함을 되돌려 실제로 빨개지는 것을 확인한 뒤에 커밋한다. 확인하지 않은 가드는 그 결함이 원래 없었는지 검사가 안 도는지 구별되지 않는다.
|
||||
|
||||
## 관계
|
||||
|
||||
@@ -53,7 +53,7 @@ source:
|
||||
|
||||
## 예외
|
||||
|
||||
결함을 되돌릴 수 없는 것 — 이미 마이그레이션으로 고친 데이터, 지난 배포에서만 나던 상태 — 은 현재 상태가 고쳐져 있음을 대신 증거로 남긴다.
|
||||
결함을 되돌릴 수 없는 것 — 이미 마이그레이션으로 고친 데이터, 지난 배포에서만 나던 상태 — 은 현재 상태가 고쳐져 있음을 증거로 남긴다.
|
||||
|
||||
기존 가드를 옮기거나 이름만 바꾸는 변경은 되돌려 확인하지 않아도 된다. 다만 옮긴 뒤에 한 번은 돌린다.
|
||||
|
||||
|
||||
+3
-3
@@ -53,15 +53,15 @@ CI 에 묶이지 않은 검증이 남아 있는 저장소에서 배포 직전에
|
||||
|
||||
## 예외
|
||||
|
||||
CI 가 그 명령을 돌리면 이 목록에서 뺀다. 사람이 기억해서 돌리는 가드는 절반만 존재한다.
|
||||
CI 가 그 명령을 돌리면 이 목록에서 뺀다.
|
||||
|
||||
환경 때문에 실패하는 것은 실패로 세지 않는다. 하위 프로세스를 띄우는 세 케이스는 이 환경에서 실패하고 같은 리비전의 다른 실행에서도 똑같이 재현되므로 코드 변경과 무관하다.
|
||||
|
||||
## 예시
|
||||
|
||||
프론트 다섯 명령 :
|
||||
npm run check:types
|
||||
npm run lint
|
||||
`npm run check:types`
|
||||
`npm run lint`
|
||||
단위 · 컴포넌트 · 화면 테스트
|
||||
|
||||
백엔드 : 커밋한 뒤 stale 산출물을 지우고 빌드한다. 이 순서를 몰라 두 번 헤맸다.
|
||||
|
||||
Reference in New Issue
Block a user