AI 개발팀 프로세스를 묻길래 검사기 23개를 보여줬다

쓰다보니 꼬였네요.

자정 가까운 시각에 오픈 채팅방에 내가 적은 문장이다.

밤 10시쯤 질문이 하나 올라왔다. 회사에서 AI 개발팀을 새로 만들었고 자기가 그 팀 팀장을 맡게 됐는데, 그동안 보안 업무만 해서 개발 프로세스를 팀에 적용하려니 노하우가 없다는 얘기였다. 표준화나 절차 같은 걸 어디서부터 잡아야 하냐고 물었다. 나는 답을 길게 쓸 생각이 없었고, 내가 이런 걸 알아볼 때 AI에게 던지는 지시문 형식을 그대로 복사해서 붙여줬다. 대화는 잘 붙었다. 다른 사람이 끼어들어 AI 조직은 사내 이해관계 충돌이 제일 많은 자리라 피곤할 거라고 거들었고, 나는 프로세스 4단계 얘기까지 이어갔고, 자정을 넘겨 고맙다는 답이 왔다.

ADR 얘기를 꺼내려다 표준화 얘기로 새서 스스로 끊은 거였다. 그날 밤엔 그냥 문장이 엉킨 줄 알았다. 다음 날 다른 이유로 게임 저장소를 열었다가, 왜 꼬였는지 알았다.

밝은 작업대에 두꺼운 매뉴얼이 덮인 채 놓여 있고 그 위 벽에 계측기가 여러 개 걸린 장면

내가 붙여준 건 지시문이 아니라 마지막 한 줄이었다

붙여준 형식은 이랬다.

너는 다년간 아키텍처 설계 및 코딩을 해온 시니어야.
나는 보안쪽 업무를 다년간 해왔어. 이번에 개발팀에 팀장역할을 맡게 되었어.
너의 역할은 심층검색과 분석을 통해 나에게 개발 프로세스 및
표준화 절차 가이드를 제공하는거야.
문서의 양식은 마크다운과 html 두 가지 양식으로 제공 받길 원해.
---
이런 형식으로 지시하려해 부족한 부분이 있어?
역할 목적 결과 또 뭐가 필요하지?

앞의 네 줄은 흔한 구성이다. 상대 역할, 내 배경, 시켜야 할 일, 받고 싶은 형식. 프롬프트 작성법 글 아무거나 읽으면 나온다.

내가 실제로 값을 보는 건 구분선 아래 두 줄이다. 지시를 실행시키기 전에, 지시문 자체를 같은 상대에게 먼저 검사받는 턴. 이걸 붙이면 첫 답이 문서가 아니라 되물음으로 온다. 회사 규모가 몇 명인지, 지금 팀이 쓰는 이슈 트래커가 뭔지, 기존 문서가 어디까지 있는지. 그 되물음에 답하고 나서 지시를 다시 던지면, 같은 모델이 완전히 다른 밀도의 결과를 낸다.

이 두 줄을 붙이게 된 건 안 붙였다가 크게 데인 적이 있어서다. 학습 관리 시스템을 열어놓고 "코드 재설계 해줘" 한 줄로 시작한 리팩토링이 초반 80%까지 빠르게 가다가 남은 20%에서 세 번 연속 같은 자리에서 무너졌다. 문서를 붙여도 다른 모델을 붙여도 똑같았고, 원인이 모델이 아니라 내가 인지한 설계 깊이였다는 건 네 번째에서야 알았다. 얕은 지시는 얕은 결과를 주는 게 아니라, 그럴듯한 결과를 줘서 한참 뒤에 발각된다.

그래서 실행 전에 한 턴을 태운다. 이 한 줄이 이 글 전체의 축소판이라는 건 나중에 알았다.

남은 건 네 단계뿐이었다

채팅방에서 내가 한 얘기의 골자는 이거였다. 개발 방법론은 계속 바뀌지만 안 바뀌는 게 있고, 그건 분석, 설계, 구현, 테스트 네 단계다.

이 네 개는 유행이 아니라 구조라서 벗어날 방법이 없다. 컴퓨터가 입력받고 처리하고 결과를 내는 것과 같은 층위의 얘기다. 입력 없이 처리하는 기계를 못 만들듯이, 뭘 만들지 알아내지 않고 어떻게 만들지 정할 수 없고, 정하지 않고 만들 수 없고, 만든 게 맞는지 안 보고 끝낼 수 없다. 순서를 겹치거나 잘게 돌리거나 한 사람이 네 개를 다 할 수는 있어도 단계 자체를 빼지는 못한다. 애자일이 폭포수를 이겼다고 할 때 없어진 건 단계가 아니라 단계 사이의 대기 시간이었다.

그래서 방법론들이 실제로 무엇을 다루는지 보면 겹친다. 폭포수부터 애자일, 스크럼, 익스트림 프로그래밍, 도메인 주도 설계까지 이름을 쭉 놓고 보면 대부분 설계 단계 얘기다. 요구사항을 얼마나 큰 덩어리로 자를 거냐, 자른 걸 언제 설계하고 언제 코드로 넘길 거냐, 설계를 문서로 남길 거냐 코드로 남길 거냐. 다투는 지점이 거기에 몰려 있다.

AI가 오면서 그 자리의 값이 떨어졌다. 설계와 구현 사이의 거리가 사라져서다. 예전에는 설계를 정하는 비용과 구현을 되돌리는 비용이 둘 다 커서, 언제 얼마나 설계할지가 진짜 의사결정이었다. 지금은 문장으로 말하면 코드가 나오고, 마음에 안 들면 다시 말하면 다른 코드가 나온다. 설계 시점을 앞으로 당길지 뒤로 미룰지를 놓고 방법론을 고르는 일 자체가 예전만큼 안 중요해졌다.

이건 내 유니티 저장소에서 이미 겪은 일이다. 커밋 219개를 넉 달 뒤에 다시 봤는데, 4월 첫 주는 fix 도배였고, 4월 19일에 설계서 17개를 쓴 다음 소스를 통째로 지웠고, 5월엔 작업에 Step 1부터 53까지 번호를 붙였더니 fix가 3개로 줄었다. 그 넉 달 동안 내가 만든 건 게임이 아니라 일 시키는 순서였다. 그리고 그 순서에서 실제로 효과를 낸 건 설계서 17개가 아니라 번호였다. 번호는 설계가 아니라 확인 지점이다.

그래서 채팅방에서 마지막에 이렇게 적었다. 요즘 회사 트렌드도 4단계 각각의 결과를 검증하는 쪽으로 옮겨가고 있다고. 적을 때는 남 얘기하듯이 적었다.

그래서 저장소를 열어봤다

다음 날 아들과 만든 로블록스 게임 저장소를 열었다. 다른 작업을 하려던 참이었는데 앞의 대화가 걸려서 먼저 세어봤다.

Luau 소스 43개 파일에 27,592줄. 문서 11개에 105,021자. 커밋 17개, 8월 5일 새벽부터 8월 13일 새벽까지 8일치.

문서를 크기순으로 늘어놓으면 배포 가이드 42KB, 수익화 전략 22KB, 브랜딩 전략 19KB, 게임 설계서 12KB 순이다. 분석과 설계에 해당하는 문서는 충분히 두껍다. 구현 명세서는 없고, 테스트 명세서도 없다.

채팅방에서 내가 이렇게 적었었다. 요구사항 분석서, 설계서, 구현 명세서, 테스트 명세서를 만드는 게 좋겠지만 요즘은 잘 안 만든다고. 구글이나 애플 쪽은 이게 없으면 개발을 안 한다는 말도 덧붙였다. 그 문장을 쓸 때 나는 업계 얘기를 하고 있다고 생각했는데, 내 저장소가 정확히 그 상태였다. 앞의 둘은 두껍고 뒤의 둘은 없다.

그런데 뒤의 둘이 없다는 게 이 저장소에서 문제를 일으킨 방식은 내가 예상한 쪽이 아니었다.

설계서에 있던 기능이 코드에는 없었다

8월 9일 밤에 밸런스 패치 하나를 코드리뷰했다. 수치가 이상하다는 느낌이 들어서 열어본 건데, 나온 건 수치 문제가 아니었다.

지연 스폰이라는 게 있다. 층이 30개까지 늘어나면서 서버 시작할 때 NPC 300기를 다 세우면 안 되니까, 플레이어가 들어간 층만 세우도록 만든 기능이다. Config에 LazySpawn 값이 있었다. 그 옆에 왜 필요한지 설명하는 주석도 있었다. 설계 문서에도 적혀 있었다.

그런데 Npc.start는 옛날 코드 그대로 서버 시작에 30층을 전부 세우고 있었다. 지연 스폰을 만든 이유 그 자체가 안 돌고 있었던 거다. 게다가 층을 식별하는 자리에 문자열 키 대신 순서 번호를 넘기고 있어서, 배율 계산이 층을 못 찾고 전 층이 배율 없는 1층 수치로 섰다. 플레이어가 입장하면 그 층을 제대로 한 벌 더 세우니까 NPC가 중복으로 깔렸다.

같은 커밋에서 하나 더 나왔다. 유휴 층을 정리하는 deactivateStage 함수는 정의만 있고 호출부가 0개였다. 아무도 안 부르는 함수가 파일 안에 멀쩡히 앉아 있었고, Luau는 그걸 경고하지 않는다.

보상 배율도 어긋나 있었다. 주석에는 30분 플레이하면 술식 하나를 살 수 있다고 적혀 있었고 술식 가격은 8,000젬이다. 실제로 계산해보니 30분에 81,664젬이 들어왔다. 10배 차이다. 체력은 1.51배만 올려두고 보상은 18.4배를 먹였는데, 분당 소득으로 환산해본 적이 한 번도 없었기 때문에 아무도 몰랐다. 나도 몰랐고, 그 값을 쓴 쪽도 몰랐다.

그날 커밋 메시지에 내가 적은 문장이 이거다.

Config 에 값이 있다고 기능이 있는 건 아니다. LazySpawn·IdleDespawn 은 값도 주석도 있었지만 읽는 코드가 0줄이었다 → 새 Config 항목에는 검사기 한 줄을 같이.

설계 산출물은 전부 있었다. 문서에도 있고, Config에도 있고, 주석에도 있었다. 코드만 없었다. 빌드는 통과했고 게임은 떴고 화면상 아무 차이가 없었다.

테스트 명세서가 없는 것보다 이쪽이 더 위험하다. 없으면 없는 줄 안다. 있는데 안 도는 건 있는 줄 안다.

실패가 소리를 안 낸다

그 뒤로 검사기 소스를 다시 읽어봤다. check_refs.py, 1,422줄짜리 파이썬 스크립트다. 여기서 "조용히"라는 단어가 9번 나온다.

  • 뽑기는 확률표가 깨져도 조용히 동작해버린다
  • 드롭 표 키에 오타가 나면 조용히 "드롭 없음"이 된다
  • 클라 사전에 이펙트 이름이 없으면 조용히 기본 연출로 떨어진다
  • 유료 확률형 정책 API를 안 지켜도 화면상 아무 차이가 없어 눈치채지 못한다
  • 결계막은 서버와 클라 양쪽이 다 있어야 하고, 한쪽만 있으면 조용히 죽는다

전부 내가 겪고 나서 적은 문장들이다. AI가 쓴 코드가 실패하는 방식이 대체로 이렇다. 컴파일 에러는 안 난다. 문법 검사 43파일 0건, 빌드 27,635줄 성공, 여기까지 다 통과한다. 통과하면서 아무것도 안 하는 코드가 남는다.

남의 저장소에서도 같은 걸 봤다. AI 에이전트가 만들었다는 브라우저를 클론해서 직접 세어봤는데 300만 줄 중 112만 줄이 vendor 디렉터리였고, 라이브러리는 1분 35초 만에 빌드되는데 cargo test는 컴파일조차 되지 않았다. 같은 파일에 mod tests가 두 번 선언돼 있었다. 빌드가 통과한다는 사실이 아무것도 보증하지 않는 지점이 거기다.

검증이 옮겨간 자리가 여기다. 기계가 잡을 수 있는 실패는 이미 기계가 잡고 있다. 남은 건 소리 없이 통과하는 것들이다.

증상 세 개가 버그 하나였다

8월 13일 새벽에 증상 세 개가 한꺼번에 보고됐다. 주간 순위표가 "불러오는 중"에서 멈추고, 제령장에 들어갈 수 없고, 1대1 매칭이 안 됐다. 서로 무관한 시스템 세 개다.

씨앗은 MapBuilder.lua 1956줄의 한 줄이었다. 문 번호가 들어가야 할 자리에 stage.floorColor를 넘겼다. Color3 값은 산술 연산이 안 되니까 런타임 에러가 났다.

증폭은 그 다음이다. MapBuilder.build()가 서버 진입점의 평범한 한 줄이라 스크립트가 거기서 그냥 끝났다. 뒤에 줄줄이 서 있던 MatchService.start_loop(), LeaderboardService.start(), NpcService.start()가 전부 미실행 상태로 남았다. 증상 세 개는 그 결과였다.

오타는 16분 만에 고쳤다. 그런데 그때 고친 건 씨앗뿐이고 증폭 경로는 그대로 있었다. 다음 오타가 나면 똑같이 서버 전체가 멈춘다. 그날 새벽에 v0.7.2를 하나 더 올려서 그쪽을 고쳤다. 부팅 단계를 하나씩 xpcall로 감싸는 Util.boot을 만들고, 서버 진입점의 init 21단계와 start 16단계, 클라이언트 컨트롤러 11개, 맵 구획 12개를 전부 끊어놨다. 클라이언트 쪽을 끊으면서 커밋 메시지에 "여기가 더 아프다"고 적었는데, 진행도 UI가 죽으면 그 아래 입력 처리가 안 돌아서 조작 자체가 안 된다. 사용자한테는 그냥 게임이 안 켜지는 걸로 보인다.

pcall이 아니라 xpcall을 쓴 이유가 있다. 부팅은 한 번뿐이라 재현이 비싸고, 그러면 "실패했다"는 메시지보다 "어느 줄에서"가 훨씬 중요하다. 스택이 필요하다.

분석 단계의 검증이라는 게 이거다. 증상 개수와 버그 개수가 같다고 가정하지 않는 것. 저 새벽에 순위표 담당, 매칭 담당, 제령장 담당을 각각 붙였으면 셋 다 자기 코드에서 아무것도 못 찾았을 거다.

하나씩 보면 전부 정상이었다

며칠 앞선 8월 8일에는 실기 보고가 하나 들어왔다. 2층에서 캐릭터가 공처럼 튕긴다는 거였다.

2층에는 적이 8기 있다. 중급 5기에 하급 3기. 한 대 맞을 때마다 넉백 14가 걸리고, 적 공격 쿨타임은 1.6초에서 1.9초 사이다. 8기가 다 붙으면 초당 4번에서 5번 밀린다.

넉백 14는 정상값이다. 쿨타임 1.6초도 정상이다. 적 8기도 설계대로다. 어느 값 하나 잘못된 게 없고 합만 비정상이었다. 그래서 수치를 깎는 대신 누적을 깎았다. 2.2초 창 안에서 넉백이 겹치면 0.62의 거듭제곱으로 줄어들게 하고 하한을 0.2로 뒀다.

같은 구조가 다음 패치에서 또 나왔다. 적의 어그로 반경이 42에서 58인데 층 반경이 50에서 64다. 두 값이 비슷하니까 플레이어가 층 가운데 서면 그 층 전체가 한꺼번에 달려든다. 9층에서는 초당 170의 피해가 들어오고 체력 100이면 0.6초에 죽는다. 평타마다 0.4초씩 경직이 걸리니 초당 6번을 맞으면 경직이 끊기지 않는다. 화면상으로는 캐릭터가 못 움직이고 그대로 죽는다.

여기서도 개별 수치는 다 정상 범위였다. 그래서 동시 공격자 수에 상한 4를 걸었다. 4기가 2.1초 쿨로 30.1씩 때리면 초당 57이다. 그런데 이 60이라는 숫자가 어디서 나왔냐면, 난이도 배율 주석에 이미 "3~4기 동시, 약 60dps"라고 적혀 있었다. 설계할 때 전제한 값이 있었는데 코드는 그걸 강제하지 않고 있었다.

설계 문서에 적힌 가정이 코드에서 실제로 강제되고 있느냐. 이걸 아무도 안 보면 문서는 명세가 아니라 소원이 된다.

그래서 검사기가 그만큼 늘었다

check_refs.py가 8일 만에 1,422줄이 됐다. 검사 항목은 23종이다. 스킬 id가 구현체에 있는지, 이펙트 종류에 핸들러가 있는지, 소환 확률 합이 정확히 100.000%인지, 광장에서 시설이 서로를 가리지 않는지, 플레이 화폐와 결제 화폐가 갈려 있는지, 부팅이 격리돼 있는지.

이건 유닛 테스트가 아니다. 게임 코드에는 테스트가 한 줄도 없다. 로블록스 런타임 없이 돌릴 수 없어서다. 그래서 검사기는 소스를 텍스트로 읽어 "여기 있는 이름이 저기에도 있는가"를 대조한다. 원시적이지만 앞의 조용한 실패들은 대부분 이름이 한쪽에만 있는 형태로 나타난다.

검사기 주석에 "여기서 막는 사고"라고 적어둔 자리가 세 군데 있다. 실제로 났던 사고를 적어놓은 거다.

실제로 겪은 사고: 순위판이 스폰→주구상점 시선 위에 서서 상점이 안 보였다.

층이 2개일 때는 눈으로 확인할 수 있었다. 30개가 되면 못 한다.

이 검사가 없으면 다음 세션에서 "매출원이 적다" 며 저주석 팩을 다시 만든다

마지막 게 제일 오래 걸려서 이해했다. 저 검사기가 막는 대상은 버그가 아니라 다음 세션의 AI다. 결제 화폐와 플레이 화폐를 분리한 이유는 대화 중에 합의한 건데, 그 대화는 세션이 끝나면 사라진다. 다음 세션에서 매출을 늘리라고 시키면 가장 자연스러운 답이 결제 화폐 팩을 늘리는 거다. 검사기는 그 자연스러운 답을 실패로 만들어서 합의를 코드 밖에 붙들어둔다. 문서로 적어두면 안 읽고, 검사기로 적어두면 안 읽어도 걸린다. 같은 이유로 규칙 문서를 MCP 도구로 만든 적도 있는데, 그쪽은 읽게 만드는 장치고 검사기는 안 읽어도 걸리는 장치다.

검사기 자체가 틀린 적도 있다. 소환 카탈로그 검사가 파일 전체를 뒤지는 방식이라, 소환은 플레이 화폐 전용이라는 설계 근거를 주석으로 적는 것조차 오류로 잡았다. 근거를 남길 수 없는 검사는 언젠가 주석과 함께 지워진다.

검사기를 추가할 때 붙인 습관이 하나 있다. 고의로 틀린 코드를 넣어보고 진짜 잡히는지 확인한 다음에 커밋한다. 배선 검사 7종을 만들었을 때 고의 오류 7종을 전부 넣어봤고, 부팅 격리 검사 4종도 네 가지 다 넣어봤다. 검증 도구를 검증하지 않으면 통과했다는 사실이 아무 의미가 없다. 통과 표시를 만드는 쪽과 통과를 판정하는 쪽이 같으면 그건 검문소가 아니다.

검사 하나를 더 들면, 매치가 끝난 뒤 팀 번호를 안 지우면 광장에 돌아온 사람들이 같은 팀으로 묶인 채라 서로 때릴 수 없게 된다. 에러도 로그도 없고, 누군가 친구를 때려보고 "왜 안 맞지" 할 때까지 아무도 모른다. 명세서에 문장으로 적어두면 다음 사람이 안 읽으면 그만이지만, 검사기에 넣으면 매치 상태를 손대는 순간 걸린다.

마지막 칸은 아직 비어 있다

커밋 17개 중 9개에 "실기 검증은 없다"는 문장이 들어 있다. 표현만 조금씩 다르다. 실기 플레이는 미검증, 실기 재확인은 아직.

기계가 보는 데는 여기까지다. Luau 문법 43파일 0건, 교차 참조 23종 통과, 빌드 27,635줄 성공, XML 유효.

사람이 봐야 하는 칸은 따로 있다. F5를 눌러서 대기하는 저주의 그림이 의도한 대로인지, 보상을 고친 뒤 체감 소득이 어떤지, 폰에서 점프가 진짜 되는지. 폰 점프는 실제로 한 번 크게 데인 자리다. PC에서는 멀쩡한데 폰에서 처음부터 끝까지 한 번도 안 뛰어졌다. 로블록스 기본 컨트롤 모듈이 매 프레임 humanoid.Jump에 값을 대입하고 있어서 우리가 켠 값이 물리 단계에 도달하기 전에 덮이고 있었다. 조건문이 아니라 대입문이라 무조건 덮인다. 이건 어떤 정적 검사로도 못 잡는다. 폰을 손에 들고 점프 버튼을 눌러봐야 안다.

검사기가 늘어날수록 이 경계가 오히려 선명해진다. 자동화가 사람 몫을 줄여주는 게 아니라, 자동화가 못 하는 칸이 어디인지를 정확히 남긴다. 9개 커밋에 같은 문장이 반복되는 건 그 칸을 매번 비워둔 채로 커밋했다는 뜻이고, 그게 지금 이 저장소의 가장 큰 부채다.

AI는 네 단계를 한 턴 안에서 한다

요즘 AI에게 일 시키는 걸 보면 이 네 단계가 안 보인다. 없어진 건 아니다. 구조라서 뺄 수가 없다. 한 턴 안으로 접혀 들어간 것뿐이다.

"제령장을 30층으로 늘려줘" 한 줄을 던지면 그 안에서 네 개가 다 돈다. 뭐가 필요한지 추리고, 층 구조와 배율을 정하고, Luau를 쓰고, 문법이 맞는지 스스로 확인한다. 몇 분 뒤에 결과가 나오고, 그 사이 네 번의 판단은 대화 로그에 흩어지거나 아예 안 남는다. 사람은 마지막 산출물만 본다. 빌드가 되나, 화면이 뜨나. 나도 그랬다.

앞에서 본 세 가지가 각각 다른 단계에서 틀린 거였는데, 마지막 산출물만 봐서는 그게 구분이 안 됐다.

지연 스폰은 설계가 맞고 구현이 없었다. 지연 스폰이 필요하다는 판단은 정확했고, Config에 값을 쓰고 주석에 이유까지 적었다. 그걸 읽는 코드만 안 썼다. 나는 값과 주석과 문서를 보고 설계가 됐다고 통과시켰는데, 그건 설계를 검증한 게 아니라 설계 산출물이 존재한다는 걸 확인한 거였다. 보상 배율은 분석이 아예 없었다. 분당 소득이 얼마여야 하는지를 아무도 안 물었다. 목표는 주석에 적혀 있었는데 그 목표와 실제 값을 대조하는 일이 통째로 빠졌다. 넉백은 값 하나하나가 설계대로였고 합만 틀렸다. 상호작용을 보는 자리가 없었다.

AI가 못한 게 아니다. 네 단계를 다 하기는 했다. 사람이 낄 자리가 없었을 뿐이다. 한 턴 안에서 네 개가 연달아 돌면 그 사이에 손을 넣을 틈이 물리적으로 없다.

맨 앞에 붙여준 지시문의 마지막 두 줄이 하는 일이 이거다. AI가 네 단계를 시작하기 전에 분석만 사람 쪽으로 한 번 끌어온다. 되물음이 오는 동안은 내가 분석을 하고 있는 거고, 그 턴이 끝나야 나머지 셋이 돈다. 한 줄로 접힌 걸 두 조각으로 벌린 것뿐인데도 결과가 달라진다.

다시 답한다면

그 팀장에게 다시 답한다면 방법론 이름은 하나도 안 꺼낼 것 같다. 대신 채팅방에서 반쯤 하다 만 얘기를 제대로 하겠다. 우리 팀 개발 주기를 어디서 끊을 거냐를 먼저 정하고, 그 주기 안에 네 단계를 늘어놓고, 각 단계마다 무엇을 둘지를 하나씩 계획하는 것. 코딩 규칙은 어느 단계에 붙고, 설계 문서는 어느 단계의 산출물이고, CI는 어디서 돌고, 리뷰는 언제 들어가는지. 이게 표준화의 실체다. 방법론을 고르는 일과는 다르다. 방법론은 남이 만든 완제품을 통째로 가져오는 거고, 이건 우리 단계에 우리 물건을 하나씩 놓는 일이다.

단계는 고정이라 고민할 게 없고, 그 안에 무엇을 놓을지가 회사마다 갈린다. 채팅방에서 큰 줄기는 어디나 같고 세부는 회사마다 다르다고 적었는데, 뒤집으면 남의 세부를 베껴올 수 없다는 말이다. 네 칸짜리 빈 표는 어디나 같고 채우는 건 각자 해야 한다.

여기에 한 칸을 더 붙이자는 게 지난 8일 동안 배운 거다. 각 단계에 무엇을 둘지 옆에, 무엇이 통과하면 이 단계가 끝난 거냐를 같이 적는다. 그래야 자산을 고르는 기준이 생긴다. 문서가 무엇을 통과시키기 위한 건지 옆 칸에 적혀 있으면 두꺼워지지 않는다.

내 저장소를 그 표에 넣어봤다.

분석 단계에는 증상 보고와 원인 기록을 뒀다. 통과 조건은 증상 개수와 원인 개수를 따로 세었느냐인데, 8월 13일에 셋을 하나로 합치면서 생긴 기준이다.

설계 단계에는 문서 11개 105,021자가 있다. 통과 조건은 문서에 적은 가정이 코드 어디서 강제되는지 가리킬 수 있느냐다. 동시 공격자 4는 실제로 상한이 됐는데 나머지는 안 세어봤다.

구현 단계에는 코딩 규칙 197줄과 검사기 23종을 뒀다. 통과 조건은 검사기 통과와 빌드 성공이고 실제로 통과한다. 다만 아무도 안 시켜서 돌지 않는다.

테스트 단계에는 아무것도 없다. 통과 조건은 F5 를 눌러본 사람이 있느냐인데, 커밋 17개 중 9개가 비어 있다.

세 번째 줄이 이 표를 그리고 나서야 보였다. 검사기 23종을 1,422줄까지 키워놓고 그걸 거는 자리를 안 만들었다. .github 디렉터리가 없고, git 훅도 0개다. 내가 기억날 때 손으로 check_refs.py를 돌리는 게 전부다. 커밋 메시지에 "교차 참조 23종 OK"라고 적혀 있는 건 그날 내가 돌렸다는 뜻이지 매번 돈다는 뜻이 아니다.

각 단계에 필요한 걸 하나씩 계획하는 일이 먼저였다. 8월 5일에 이 표를 그렸으면 CI 칸이 빈 걸 첫날 봤을 거고, 검사기를 만들 때 같이 걸었을 거다. 나는 순서를 거꾸로 갔다. 사고가 나서 검사를 만들고, 또 사고가 나서 추가하고, 그게 스물세 개가 돼서야 표를 그렸다. 표가 먼저였으면 검사기는 더 적었을 거고 CI는 있었을 거다.

빈칸이 보이는 게 이 표의 값이다. 문서를 아무리 두껍게 써도 빈칸은 안 보인다. 오히려 두꺼울수록 다 한 것처럼 보인다. 105,021자를 써놓고 CI가 0인 걸 8일 동안 몰랐다.

통과 조건은 연차로 반박할 수 없다

ADR이 앉는 자리도 거기다. 결정 기록은 설계 산출물처럼 보이지만 실제로는 검증 기록에 가깝다. 나중에 "이거 왜 이렇게 됐지"를 물었을 때 대조할 수 있는 물건이기 때문이다. 내가 채팅방에서 표준화 얘기를 하려다 ADR로 샌 게 아니라, 표준화를 물어보면 검증 얘기가 나오는 게 맞았던 거다. 그때는 그 연결을 문장으로 못 만들어서 꼬였다고 적었다.

그 채팅방에서 다른 분이 거들었던 말이 계속 남는다. AI 조직은 사내에서 이해관계 충돌이 제일 많이 일어나는 자리라 꽤 피곤할 거라고 했다. 맞는 말이고, 그래서 더 검증 쪽이라고 생각한다. 프로세스를 새로 들고 들어가는 팀장이 제일 약한 자리는 "이건 이렇게 하는 게 맞습니다"라고 말해야 하는 순간이고, 개발 관행을 놓고는 상대가 연차로 밀고 들어올 수 있다. 그런데 통과 조건은 판단이 아니라 상태다. 스크립트가 빨간 줄을 뱉었느냐, F5를 눌러본 사람이 있느냐. 이건 연차로 반박이 안 된다.

사내에 표준 기획서나 요구사항 정의서가 있는지부터 파악하라는 조언은 그대로 유효하다. 있으면 그게 분석 단계 칸에 이미 들어가 있는 물건이고, 없으면 그 칸이 비어 있는 거다. 다만 양식을 새로 만드는 데 시간을 쓰기 전에 표부터 그리는 게 순서다. 칸을 그려놓고 이미 있는 걸 집어넣어 보면 새로 만들어야 할 게 생각보다 적다.

나는 그 표를 8일 늦게 그렸다. 그 사이에 문서 105,021자와 파이썬 1,422줄이 쌓였고, 둘 다 필요하긴 했지만 순서가 반대였다. 그리고 지금 이 글을 쓰면서 알게 된 건, 그 1,422줄조차 아무도 안 시키면 안 돈다는 거다. 다음 커밋에 할 일이 하나 생겼다.

댓글

이 블로그의 인기 게시물

아이들은 우리 게임에 들어오지 못한다

AI가 썼다는 브라우저 300만 줄에서 112만 줄은 남의 코드였다

AI가 준 선택지, 초5 아들은 어떻게 골랐을까?