// APP FACTORY · EP. 13
실수 장부가 774줄에 63건까지 쌓였습니다. 기록만으로는 재발을 못 막아서, 같은 유형이 두 번 나오면 규약이나 코드 가드로 올리는 규칙을 뒀습니다. 앱 공장 시리즈 마지막 글입니다.
1. 장부에 63건이 쌓였습니다
지금까지 앱이 만들어지고 검증되고 배포되고 다시 도는 구조를 말씀드렸습니다. 마지막으로 공장 자신이 나아지는 방법을 이야기하겠습니다.
이 공장에는 실수 장부가 하나 있습니다. LESSONS.md이고 지금 774줄에 63건입니다. 첫 항목이 6월 10일, 마지막이 7월 21일이니 여섯 주 동안 쌓인 것입니다. 하루에 한 건이 넘습니다. 앱을 12개 만들면서 밟은 함정이 이만큼이라는 뜻입니다.
이게 많은 건지 적은 건지는 저도 모르겠습니다. 다만 중요한 건 개수가 아닙니다. 이 중 몇 건이 다시는 안 일어나게 됐느냐가 중요합니다.
2. 고친 그 자리에서 바로 적습니다
기록 시점부터 정해 뒀습니다. 버그를 고친 직후, 같은 턴 안에서 씁니다. 미루면 잊기 때문입니다. 외부 환경의 함정을 발견했을 때, 리뷰에서 심각한 지적이 나왔을 때도 씁니다.
회고 시간을 따로 잡지 않습니다. 회고를 미루면 그때 남는 건 기억이지 기록이 아닙니다.
형식은 네 줄로 고정돼 있습니다.
### YYYY-MM-DD <한 줄 제목>
- 증상: 무엇이 잘못됐나
- 원인: 왜 발생했나
- 규칙: 다음부터 무엇을 다르게 하나 (한 문장, 명령형)
- 적용: 규칙이 박힌 위치 (스킬·코드·훅·문서)
앞의 셋은 흔한 회고 형식인데 네 번째가 이 장부의 핵심입니다. 규칙을 어디에 박았는지를 같이 적게 했습니다. 규칙 칸은 한 문장 명령형으로만 쓰게 했고요 — 길게 쓰면 옮겨 붙일 수가 없습니다.
왜 적용 칸을 강제했느냐. 기록만으로는 재발을 못 막기 때문입니다. 장부에 적어 두고 다음에 읽겠다고 하면 안 읽습니다. 사람도 안 읽고 에이전트도 안 읽습니다. 문서는 참조될 때만 힘을 갖는데, 참조를 사람의 기억에 맡기면 그 규칙은 없는 것과 같습니다. 박을 자리가 안 떠오르면 그 규칙은 아직 덜 정리된 것입니다.
박는 자리에도 우선순위가 있습니다.
| 강함 — 기억 불필요 | 약함 — 읽어야 지켜진다 |
|---|---|
| 코드 가드 · 타입으로 불가능하게 | 스킬 문서에 절차로 |
| 커밋 훅 · 자동으로 돈다 | 일반 문서에 서술로 |
| 검증 스크립트가 거부한다 | 읽지 않으면 없는 규칙 |
같은 규칙이라도 어디에 박느냐에 따라 수명이 달라집니다.
3. 두 번이면 승격합니다
그럼 언제 올리느냐. 기준이 하나 있습니다.
같은 유형 2회 반복 시 코드 가드·훅으로 승격.
— CLAUDE.md
한 번은 실수고 두 번은 구조라는 겁니다. 처음 겪었을 때는 장부에만 적습니다. 그런데 같은 성격의 일이 다시 벌어지면, 그때부터는 사람이 조심해서 될 일이 아니라고 봅니다. 조심은 반복되지 않지만 코드는 반복되니까요.
이 기준이 중요한 이유는 승격에 근거를 요구하기 때문입니다. 실수 하나 날 때마다 규칙을 만들면 규약이 금방 100줄을 넘고 아무도 안 읽게 됩니다. 규칙이 많아지면 규칙이 없는 것과 같아집니다. 반대로 아무것도 안 올리면 장부가 그냥 일기가 됩니다. 두 번이라는 조건이 그 사이를 가릅니다.
4. 실제로 올라간 셋
첫째, 전형적인 두 번 사례입니다. 가면 노트라는 앱에서 네이티브 의존성을 추가했는데 정적 분석도 테스트도 통과했습니다. 그런데 실제로 빌드하니 깨졌습니다. 그때는 그 앱의 문제라고 보고 장부에만 적었습니다. 열흘 뒤 문장 수리소라는 앱에서 똑같은 일이 벌어졌습니다.
왜 검사가 못 잡았는지가 중요합니다. 정적 분석과 테스트는 코드를 보지 빌드 설정을 보지 않습니다. 안드로이드 권한이나 리시버 설정은 실제로 빌드를 돌려야 드러납니다.
네이티브 의존성(알림·IAP·카메라·ML 등) 추가·변경 시
flutter build apk --debug 필수
# analyze/test 는 gradle 빌드 설정을 보지 않는다
# mask_note 07-09 최초 → sentence_repair 07-19 재발로 승격
둘째는 경로가 다릅니다. 파이프라인이 기각이나 블로커를 만나면 보고하고 멈췄는데, 그걸 보고 제가 지시를 내렸습니다. 멈추지 말고 기각 사유를 해소해서 다시 넣으라고요. 두 번 겪은 게 아니라 한 번의 지시로 올라간 것입니다. 반복이 아니어도 판단이 바뀌면 규칙이 됩니다.
셋째가 과실 인지입니다. 앱 공장 11화에서 말씀드린 건데, 콘솔 셋업을 통째로 사람에게 넘겼다가 그게 과실이었다고 스스로 판정하고 규약에 재발 방지로 적은 경우입니다. 아무도 지적하지 않았고 사고가 난 것도 아닌데 올라간 규칙입니다.
승격 경로가 셋인 셈인데 셋 다 장부를 거쳐 갔습니다.
이렇게 올라간 것들이 규약 문서에 따로 모여 있습니다. ## 승격된 규칙이라는 절이 있고 지금 일곱 줄입니다.
· 에뮬레이터 탭 전 uiautomator dump 로 bounds 추출
· 리스트 항목 삭제는 인덱스가 아닌 객체·ID 기반
· 외부 API 응답은 도메인 검증 후 캐시 · 주+폴백 이중화
· 비동기 초기화 위젯은 준비 신호를 구독
# 7줄 — 전부 한 번씩 터진 것들
이 절이 길어지는 게 좋은 신호인지는 아직 모르겠습니다. 다만 짧아지지는 않더군요.
5. 코드 구조도 같은 기준으로 올라갑니다
승격되는 게 실수만은 아닙니다. 이 저장소에는 코드와 문서를 훑어 그래프로 만들어 두는 장치가 있습니다. 파일과 심볼을 노드로 놓고 참조 관계를 이어 둡니다. 지금 노드가 9,570개이고 커밋할 때마다 훅이 자동으로 갱신합니다.
이걸 만든 이유는 새 기능을 짜기 전에 물어보기 위해서입니다.
graph_query.py symbol <이름> # 이미 어디에 있나
graph_query.py neighbors <파일> # 고치면 어디가 흔들리나
# 같은 심볼이 2개 앱 이상 = core 승격 신호
여기에도 같은 숫자가 붙어 있습니다. 같은 심볼이 두 개 앱 이상에서 나오면 공용 패키지로 올릴 신호입니다. 한 앱에 있으면 그 앱 것이고, 두 앱에 있으면 공용입니다.
규모를 말씀드리면 저장소가 45GB인데 그중 42GB가 빌드 산출물입니다. 실제 코드와 문서는 그 나머지고, 거기서 뽑은 그래프가 7.6MB입니다. 조회는 언어 모델을 쓰지 않습니다. 파일을 읽어서 답하니 결정적이고 비용이 들지 않습니다. 그래서 짜기 전에 물어보는 게 부담이 아니라 습관이 될 수 있었습니다.
실수는 두 번 반복되면 규칙이 되고, 코드는 두 앱에 걸치면 공용으로 올라갑니다. 기준이 같은 게 우연은 아닙니다. 한 번은 우연이고 두 번은 패턴이라는 판단이 양쪽에 똑같이 들어간 것입니다.
6. 하루에 하나씩, 완성도를 지키면서
이제 시리즈 전체를 관통한 질문으로 돌아가겠습니다. 앱 공장 첫 글에서 던진 질문이 이것이었습니다.
하루에 하나씩 만들면서 완성도를 지킬 수 있을까?
그때는 앱 12개를 늘어놓고 시작했는데, 솔직히 답을 갖고 시작한 건 아니었습니다. 열세 편을 만들면서 찾은 답은 두 가지입니다.
첫째, 매번 잘 만드는 게 아니었습니다. 저는 매번 잘 만들지 못합니다. 그 대신 틀린 걸 두 번 틀리지 않는 구조를 만들었습니다. 오늘 말씀드린 승격이 그것이고, 앞선 글들의 기각 기본값·자기검증 금지·재측정 게이트도 전부 같은 성격입니다. 완성도를 매번 지키는 게 아니라 무너진 자리를 다시 안 밟는 것입니다. 잘하는 것과 다시 안 넘어지는 건 다른 일입니다.
둘째는 증거입니다. 이걸 제가 잘했다고 말하면 자평이 되니 외부 판정을 가져오겠습니다. 이 파이프라인으로 만든 앱을 스토어에 냈고, 신규 등록이 7개 업데이트가 3개였습니다. 그중 반려된 게 한 건도 없습니다. 심사는 제가 통과시킨 게 아니라 구글이 통과시킨 것입니다. 게이트가 옳았다는 증거로 제가 댈 수 있는 건 그것뿐입니다. 나머지는 전부 제 주장이니까요.
7. 정리
자동화의 품질은 설계가 만드는 게 아니라 승격 규칙이 만듭니다. 처음 설계가 좋아서가 아니라, 틀릴 때마다 그 자리에 규칙을 하나씩 박았기 때문에 지금 모양이 됐습니다.
열세 편 동안 봐 주셔서 감사합니다. 여기까지가 앱 공장 이야기입니다.
앱 공장 · 13화 (마지막)


답글 남기기