
꿀팁 — 이것만 알면 시킬 수 있다
에이전트에게 코드를 수정 시킬 때 가장 큰 문제는, 하나의 파일에 함수가 20개 들어 있는 것이다. 함수 하나가 필요해서 파일을 열었는데 19개의 불필요한 함수가 딸려온다. 이것이 에이전트의 성능을 30~85% 떨어뜨린다.
해결법은 세 마디다. Go, TypeScript, Python 전부 지원한다.
에이전트에게: “가장 긴 파일 찾아서 함수별로 분리해. 파일명은 함수명과 같게. 기존 테스트 전부 통과해야 해.”
에이전트에게: “filefunc validate 돌려서 위반 0으로 만들어. 기존 테스트 전부 통과해야 해.”
에이전트에게: “tsma next 반복해서 모든 함수에 테스트 추가해. 미커버 분기가 나오면 그 분기를 커버하는 테스트도 추가해. All functions complete 나올 때까지.”
코드를 읽지 못해도 이 세 마디면 된다. 판단은 도구가 하고, 실행은 에이전트가 한다. 결정만 내리면 된다.
“파일이 너무 많아지지 않나?” — 파일은 3~14배 늘어난다. 하지만 에이전트는 디렉토리를 열지 않는다. 검색한다. 파일이 500개든 1,000개든 검색 명령어 한 번이면 끝이다.
“테스트가 없는 레거시 코드는?” — tsma next를 반복하면 된다. 에이전트가 중간에 죽어도 진행은 보존된다. 새 에이전트가 tsma next를 치면 이어간다. 527개 함수에서 검증 완료.
찍먹 체험
1강 앱을 Claude Code로 열고:
“이 프로젝트에서 가장 긴 파일이 뭐야? 그 파일에 함수가 몇 개 들어있어?”
대부분 하나의 파일에 여러 함수가 몰려 있을 것이다. 이제 시킨다:
“그 파일의 각 함수를 별도 파일로 분리해. 파일명은 함수명과 같게.”
분리가 끝나면 이렇게 시킨다:
“할 일 완료 처리 함수를 찾아서 설명해.”
분리 전이었으면 에이전트가 긴 파일 전체를 읽어야 했다. 분리 후에는 complete_todo.go 하나만 열면 된다. 에이전트의 탐색 비용이 줄어드는 것을 직접 본다. 이것이 “파일 하나에 개념 하나"의 효과다.
왜 이렇게 시켜야 하는가
도입: 사람의 사무실에 로봇을 넣지 마라
7강까지 우리는 드리프트를 막고(Hurl), 결정과 구현을 분리하고(yongol), 래칫으로 진행을 강제하고(Ratchet Pattern), 아첨 편향을 역이용하는(IFEval) 방법을 배웠다.
이 모든 것을 적용해도 한 가지가 남는다. 코드 자체의 구조.
에이전트에게 “이 함수를 수정해"라고 시키면 에이전트는 무엇을 하는가? 파일을 찾고, 파일을 열고, 내용을 읽고, 수정한다. 이 과정에서 에이전트의 탐색 단위는 파일이다.
그런데 파일 하나에 함수가 20개 있으면 어떻게 되는가?
함수 하나가 필요해서 파일을 열었는데 19개의 불필요한 함수가 딸려온다. 이것이 컨텍스트 오염이다.
CrossError 타입 하나가 필요해서 read
→ 19개의 불필요한 함수가 딸려옴
→ 컨텍스트 오염
사람이 일하는 사무실에 로봇을 넣으면 안 된다. 로봇이 일할 수 있는 공장을 만들어야 한다. 코드도 마찬가지다.
SWE가 읽기 좋은 코드 ≠ 에이전트가 조작하기 좋은 코드
SWE(소프트웨어 엔지니어)는 코드를 읽을 때 파일을 스크롤하면서 맥락을 잡는다. 2,000줄짜리 파일도 경험이 있으면 “이 부분은 건드리면 안 된다"는 직감이 작동한다.
에이전트는 그런 직감이 없다.
| SWE | AI 에이전트 | |
|---|---|---|
| 탐색 방식 | 디렉토리 트리를 눈으로 훑는다 | grep으로 검색한다 |
| 파일 열기 | IDE에서 스크롤 | read file — 전체 로딩 |
| 맥락 판단 | 직감 + 경험 | 컨텍스트에 있는 것만 안다 |
| 불필요한 코드 | 무시한다 | 컨텍스트 예산을 소모한다 |
| 결정적 차이 | 2,000줄을 봐도 필요한 부분만 본다 | 2,000줄을 전부 처리한다 |
연구가 이것을 확인한다.
연구에 의하면 불필요한 정보가 섞이면 AI 성능이 30~85% 떨어진다.1
컨텍스트가 짧을수록 좋다. 이것은 직관이 아니라 실험 결과다. 그러면 코드를 구조적으로 쪼개서 필요한 것만 넣으면 된다. 문제는 도구가 없었다는 것이다.
filefunc가 그 빈자리를 채운다. Go, TypeScript, Python 프로젝트를 모두 지원한다. 1강에서 어떤 언어로 앱을 만들었든 적용할 수 있다.
filefunc — 파일 하나에 개념 하나
filefunc의 핵심 원칙은 단 하나다.
파일 하나에 개념 하나. 파일명 = 개념명.
func이든 type이든 interface든 const 묶음이든 동일하다. 이 원칙 하나에서 모든 룰이 파생된다.
# filefunc 없이
read utils.go → func 20개, 19개 불필요. 컨텍스트 오염.
# filefunc
read check_one_file_one_func.go → func 1개. 정확히 필요한 것.
Hono 프레임워크(star 23k+)에서 실증했다. 186개 파일을 626개로 쪼갰다. 테스트 4,419개, 한 개도 안 깨졌다. 파일이 3.4배 늘었지만 로직은 한 줄도 안 바뀌었다. 순수 구조 리팩토링이다.
필요한 5-10개를 집는 것보다, 불필요한 290개를 안 여는 게 더 중요하다.
프로그램은 세 가지뿐이다
filefunc가 파일을 쪼개는 기준은 임의적이지 않다. 어떤 프로그램이든 세 가지 동작의 조합으로 만들 수 있다:
- 순차 — 위에서 아래로 실행한다
- 분기 — 조건에 따라 갈림길을 택한다
- 반복 — 같은 동작을 여러 번 한다
이것은 1966년에 수학적으로 증명되었다(Bohm-Jacopini 정리). 이름은 몰라도 된다. 중요한 것은, filefunc가 하나의 함수에 이 세 가지 중 하나의 흐름만 갖도록 강제한다는 것이다.
이 코드를 읽을 줄 몰라도 된다. 중요한 건 //ff:what 뒤에 이 함수가 뭘 하는지 한 줄로 적혀 있다는 것이다:
//ff:func feature=validate type=rule control=sequence
//ff:what F1: validates one func per file
func CheckOneFileOneFunc(gf *model.GoFile) []model.Violation {
| control | 의미 | 중첩 제한 |
|---|---|---|
sequence | 순차 실행 | 2단계 |
selection | 분기 (switch) | 2단계 |
iteration | 반복 (loop) | 2~3단계 |
왜 중첩을 제한하는가? 코드 안에 조건 안에 반복 안에 조건이 들어가면 깊이 3이다. 사람도 읽기 어렵고 에이전트도 헷갈린다. 깊이 2로 제한하면 각 함수가 단순해지고, 에이전트가 수정할 때 부작용이 줄어든다.
Hono의 Node.search는 깊이 6이었다. filefunc으로 리팩토링한 후 깊이 2. 각 조각은 한 가지 제어 흐름만 가진다. 전체 알고리즘은 동일하다.
22개 검증 룰 — 당신이 외울 필요 없다
filefunc의 룰은 22개다. 많아 보이지만 당신이 이 룰을 외울 필요는 없다. filefunc validate를 돌리면 도구가 위반을 전부 찾아서 알려주고, 에이전트가 고친다. 당신은 “위반 0으로 만들어"라고 시키면 끝이다.
아래 표는 “이런 것들을 검사한다"는 전체 그림을 보여주기 위한 것이다. 훑어보고 넘어가면 된다.
파일 구조 룰:
| 룰 | 위반 시 |
|---|---|
| 파일 하나에 func 하나 (파일명 = 함수명) | ERROR |
| 파일 하나에 type 하나 (파일명 = 타입명) | ERROR |
| 메서드: 1 file 1 method | ERROR |
_test.go는 복수 func 허용 | 예외 |
코드 품질 룰:
| 룰 | 위반 시 |
|---|---|
| 중첩 깊이: 순차=2, 분기=2, 반복=2~3 | ERROR |
| func 최대 1,000줄 | ERROR |
| func 권고: sequence/iteration 100줄, selection 300줄 | WARNING |
filefunc validate를 실행하면 위반이 전부 나열된다. 에이전트에게 이 목록을 주면 while ERROR > 0: fix 루프로 수렴한다.
코드북과 어노테이션 — 에이전트의 지도
파일을 쪼개면 끝이 아니다. 에이전트가 “어떤 파일을 열어야 하는가"를 빠르게 판단할 수 있어야 한다. 코드북과 어노테이션이 이것을 해결한다.
코드북 (codebook.yaml):
required:
feature: [validate, annotate, chain, parse, codebook, report, cli]
type: [command, rule, parser, walker, model, formatter, loader, util]
optional:
pattern: [error-collection, file-visitor, rule-registry]
level: [error, warning, info]
코드북은 프로젝트의 어휘 목록이다. AI 에이전트의 지도. 이 파일도 당신이 직접 만들 필요 없다 — “에이전트에게: ‘이 프로젝트의 codebook.yaml을 만들어’“라고 시키면 된다. 코드북이 있으면 에이전트가 탐색 없이 정확한 파일을 바로 찾는다.
어노테이션:
역시 코드 자체는 몰라도 된다. 핵심은 어노테이션 한 줄이 함수의 역할을 요약한다는 것이다:
//ff:func feature=validate type=rule control=sequence
//ff:what F1: validates one func per file
//ff:why Primary citizen is AI agent. 1 file 1 concept prevents context pollution.
func CheckOneFileOneFunc(gf *model.GoFile) []model.Violation {
| 어노테이션 | 내용 |
|---|---|
//ff:func | func 파일의 feature, type, control 등 메타 |
//ff:what | 1줄 설명 — 이 함수가 뭘 하는가 |
//ff:why | 왜 이렇게 만들었는가 — 사용자의 결정 |
//ff:checked | LLM 검증 서명 (자동 생성) |
이 어노테이션들이 검색 인덱스 역할을 한다. 벡터 임베딩이나 RAG 같은 무거운 인프라 없이, grep 한 번으로 정밀한 파일 목록이 나온다.
코드북 + 어노테이션이 만드는 차이
filefunc + 코드북이 있으면, 에이전트가 300개 파일 중 필요한 5개만 정확히 찾는다. 나머지 295개는 열지도 않는다.
기존 방식은? 에이전트가 파일을 하나씩 열어보면서 “이건 관련 있나?” 추측한다. 열었더니 함수 20개, 대부분 불필요하다. 탐색에 드는 시간이 실제 작업 시간보다 길다.
코드북이 있으면? 에이전트가 코드북을 보고 즉시 검색 쿼리를 구성한다. 파일 하나에 개념 하나이므로, 열어본 파일은 전부 유효한 컨텍스트다. 30개를 read해도 전부 유효하면 30개가 문제가 아니다. 1개를 read했는데 30개 분량이 딸려오는 게 문제다.
tsma — 레거시 코드의 회귀 방어선
코드를 에이전트가 읽을 수 있게 만들었다(filefunc). 이제 에이전트가 수정해도 되는지 알 수 있게 만들어야 한다. 테스트가 없는 함수를 수정하면 뭐가 깨지는지 아무도 모른다.
10만 줄짜리 레거시 코드를 물려받았다고 해보자. 테스트가 없다. 리팩토링하고 싶지만 건드리면 뭐가 깨질지 모른다. 테스트를 짜려면 코드를 이해해야 하고, 코드를 이해하려면 문서가 있어야 하는데 문서도 없다.
아무도 안 건드린다. 더 썩는다.
Fortune 500 IT 예산의 60~80%가 이 교착 상태에 묶여 있다.
LLM이 테스트를 대신 짜줄 수 있다면? 문제는 세 가지다:
- 어디부터 시작해야 하는지 모른다. 함수가 527개일 때 1번부터 순서대로? 가장 중요한 것부터? 기준이 없다.
- 테스트의 질을 검증할 수 없다. LLM이 작성한 테스트가 pass했다. 진짜 동작을 검증하는가, 빈껍데기인가?
- 피드백이 없으면 60~70%에서 멈춘다. 어떤 분기가 빠졌는지 알려줘야 나머지를 채운다.
tsma가 이 세 가지를 전부 해결한다.
tsma next — 명령어 하나
에이전트가 알아야 할 명령어는 하나다.
$ tsma next
이 명령어 하나가 전체 루프를 구동한다:
$ tsma next # 테스트가 없는 다음 함수를 보여준다
→ 테스트를 작성한다
$ tsma next # 새 테스트를 감지하고, 실행하고, 커버리지를 측정한다
→ 100%? PASS, 다음 함수로
→ <100%? 미커버 분기를 라인 번호와 함께 알려준다
$ tsma next # 수정된 테스트를 재측정한다
→ 개선되든 아니든, DONE으로 표시하고 다음으로
“All functions complete!“가 나올 때까지 반복한다.
피드백이 LLM의 테스트를 극적으로 바꾼다
tsma의 핵심 가치는 인덱싱도, 커버리지 측정도 아니다. 미커버 분기를 라인 번호로 정확히 알려주는 것이다.
피드백 없이:
"ListContracts 함수의 테스트를 작성해줘"
→ LLM이 happy path만 테스트
→ 커버리지 60~70%
피드백과 함께:
"ListContracts 함수의 테스트를 작성해줘"
→ 커버리지 65% (11/17)
→ UNCOVERED:
line 41 — if params.Status != nil
line 44 — if params.BuildingId != nil
line 70 — if err != nil (CountSummary)
→ LLM이 정확히 그 분기를 커버하는 테스트 추가
→ 커버리지 100%
같은 LLM이다. 차이는 피드백의 유무뿐이다. 라인 번호 세 줄이 60%와 100%를 가른다.
6강에서 배운 Ratchet Pattern이 여기서 실현된다. tsma가 피드백을 주고, LLM이 수정하고, tsma가 다시 측정하고. 이것이 Symbolic Feedback Loop다.
527개 함수에서 검증했다
실제 프로젝트(527개 함수)에 tsma를 적용한 결과:
| 결과 | 수 | 비율 |
|---|---|---|
| PASS (100% 분기 커버리지) | 246 | 46.7% |
| DONE (best-effort) | 281 | 53.3% |
| TODO (미처리) | 0 | 0% |
246개 함수가 분기 커버리지 100%에 도달했다. 나머지 281개는 100%에 도달하지 못했지만, 가능한 범위까지 테스트가 작성되었다.
왜 100%에 못 도달하는 함수가 있는가? 테스트하기 어려운 구조로 짜여 있는 함수가 있다. 원래 코드가 테스트를 고려하지 않고 만들어졌기 때문이다. 이것은 tsma의 한계가 아니라 코드의 테스트 가능성을 반영한다.
에이전트가 죽어도 진행은 보존된다
에이전트는 반드시 뻗는다. 토큰 한도, 네트워크 에러, 세션 끊김. 527개 함수를 한 세션에 다 처리할 수 없다.
tsma는 진행 상태를 .tsma/session.json에 영속 저장한다.
$ tsma status
527 functions
PASS: 246 (46.7%)
DONE: 281 (53.3%)
TODO: 0 (0.0%)
에이전트가 200번째 함수에서 죽으면? 새 에이전트가 tsma next를 치면 201번째부터 이어간다. session.json이 체크포인트다. 여러 에이전트가 교대로 작업해도 충돌이 없다. 함수 단위로 원자적이다.
세션은 캐시이고 소스 파일이 source of truth다. 테스트 파일을 삭제하면 session.json에 PASS로 기록되어 있더라도 해당 함수는 TODO로 되돌아간다. 세션이 현실과 괴리되지 않는다.
filefunc + tsma + whyso: 세 도구의 결합
filefunc과 tsma는 독립적이지만, 결합하면 시너지가 생긴다. 여기에 whyso까지 더해지면 에이전트 친화적 코드베이스의 세 축이 완성된다.
filefunc → tsma 연결:
filefunc이 파일 하나에 함수 하나를 강제하므로, tsma의 함수 인덱싱이 파일 인덱싱과 동치가 된다. 함수 단위 테스트 = 파일 단위 테스트. 추적 비용 제로.
filefunc → whyso 연결:
func = file이므로 함수 단위 변경 이력이 파일 단위로 정확히 떨어진다.
whyso history check_ssac_openapi.go # CheckSSaCOpenAPI 함수의 변경 이력
한 파일에 함수가 여러 개 있으면 어느 함수가 바뀐 건지 diff를 뒤져야 한다. filefunc면 파일 변경 = 함수 변경. 추적 비용 제로.
whyso가 보여주는 coupling 통계도 filefunc 덕에 정밀해진다:
whyso coupling check_ssac_openapi.go
같은 요청에 함께 수정된 함수:
check_response_fields.go 8회
check_err_status.go 5회
types.go 4회
명시적 관계가 없는데 coupling 통계에서 자꾸 나오면 숨은 의존성 신호다.
Agent Operable Codebase의 4가지 조건
지금까지 배운 것을 정리하면, 에이전트가 안정적으로 작업할 수 있는 코드베이스의 조건은 네 가지다:
| 조건 | 도구 | 효과 |
|---|---|---|
| 1. 파일 하나에 개념 하나 | filefunc | 컨텍스트 오염 차단 |
| 2. 모든 함수에 테스트 | tsma | 수정 후 회귀 감지 |
| 3. 심볼릭 참조로 연결 | operationId (yongol) | 레이어 간 추적 |
| 4. 암묵적 커플링 제거 | whyso coupling | 숨은 의존성 검출 |
이 네 조건을 갖춘 코드베이스에서 에이전트는:
- grep 한 번으로 정확한 파일을 찾고
- read 한 번에 필요한 것만 읽고
- 수정 후 테스트로 회귀를 감지하고
- 기능 하나의 전체 범위를 operationId로 파악한다
8강의 핵심 메시지: 기차를 더 빠르게 만들지 마라. 선로를 깔아라.
더 큰 모델, 더 똑똑한 에이전트가 아니라 — 에이전트가 일할 수 있는 구조가 먼저다.
실습 (filefunc + tsma)
준비물: 프로젝트 (Go, TypeScript, Python 중 아무거나 — 1강에서 만든 앱이면 된다), filefunc, tsma (에이전트에게 설치 시킨다). filefunc과 tsma 모두 Go, TypeScript, Python을 지원한다.
목표: 기존 프로젝트를 agent-operable codebase로 전환한다.
단계 1 — filefunc validate 적용
# 현재 프로젝트에서 filefunc validate 실행
filefunc validate
# 위반 목록 확인
# 에이전트에게 위반 수정 지시
"filefunc validate 결과에서 ERROR를 전부 수정해.
기존 테스트가 깨지면 안 돼."
단계 2 — 위반 함수 분리
- 파일 하나에 함수 여러 개 있는 경우 → 각 함수를 독립 파일로 분리
- 파일명을 함수명과 일치시킨다
- 에이전트에게 “기존 코드가 깨지지 않게 분리해"라고 시키면 된다
단계 3 — tsma로 커버리지 측정
# tsma 초기화 및 상태 확인
tsma status
# 에이전트에게 테스트 작성 지시
"tsma next 반복해서 테스트 추가해.
미커버 분기가 나오면 그 분기를 커버하는 테스트도 추가해.
All functions complete 나올 때까지."
단계 4 — 결과 확인
- filefunc validate: 0 errors
- tsma status: TODO 0
- 기존 테스트: 전부 통과
확인할 것:
- 위반 0이 되기까지 에이전트가 몇 라운드 돌았는가?
- filefunc 적용 전후로 파일 수와 평균 LOC가 어떻게 바뀌었는가?
- tsma가 피드백을 주지 않았을 때와 줬을 때 커버리지 차이가 얼마인가?
연관 글
Reins Engineering 전체 강의
| 강 | 제목 |
|---|---|
| 제 1강 | AI에게 시키는 법 |
| 제 2강 | AI를 못 믿는 법 |
| 제 3강 | 깨지지 않는 앱 |
| 제 4강 | 결정을 코드 밖으로 |
| 제 5강 | 고삐 있는 AI |
| 제 6강 | 통과하면 잠근다 |
| 제 7강 | 아첨을 뒤집는 법 |
| 제 8강 | 에이전트의 공장 |
| 제 9강 | 코드 너머의 자동화 |
| 제 10강 | 데이터의 법 |
근거 자료 출처
- Stanford, “Lost in the Middle: How Language Models Use Long Contexts” (2024) — 관련 정보가 컨텍스트 중간에 묻히면 30%+ 성능 하락
- Amazon, “Context Length Alone Hurts LLM Performance” (2025) — 불필요한 토큰이 공백이어도 13.9~85% 성능 하락
- Bohm-Jacopini 정리 (1966) — 모든 프로그램은 순차, 분기, 반복 세 가지 제어 구조의 조합으로 표현 가능
- Hono 프레임워크 실증 — 186개 파일 → 626개 파일 분리, 테스트 4,419개 전부 통과
- tsma 527개 함수 실증 — PASS 246개(46.7%), DONE 281개(53.3%), TODO 0개
- Fortune 500 IT 예산 통계 — 레거시 유지보수에 60~80% 묶임
Stanford “Lost in the Middle” (2024): 관련 정보가 컨텍스트 중간에 묻히면 30%+ 하락. Amazon “Context Length Alone Hurts LLM Performance” (2025): 불필요한 토큰이 공백이어도 13.9~85% 하락. ↩︎