AI 시대의 개발자는 검증을 설계한다 (2) 구조를 검증하는 Lint
아 저 컴포넌트에다 저런 조회 로직 추가하면 안되는데...
> ‘아 저 컴포넌트에다 저런 조회 로직 추가하면 안되는데…’
AI에게 구현을 맡겨놓고 잠깐 후 터미널로 돌아온 참이었습니다. 클로드 코드가 이제 막 무언가를 추가하고 있었습니다. 새로운 React Hook이었습니다. 로컬 저장소의 값을 가져오는 코드였습니다. 그런데 문제는 위치였습니다. UI만 담당해야할 얇은 컴포넌트에 추가하고 있었죠.
‘이 녀석 멈춰야겠다’ ESC를 누르려는 순간이었습니다.
갑자기 AI가 알아서 코드를 수정했습니다. 뷰 컴포넌트에서 저장소 로직을 빼내는 겁니다. 신기했습니다.
알고보니 AI가 Lint를 돌려보고, 피드백을 받아 고친 상황이었습니다. 제가 똑바로 하라고 피드백하기 전에, 이미 Lint가 피드백을 줘버린 겁니다.
도파민이 나오는 짜릿한 순간이었습니다.
그 순간에도 세션은 멈추지 않고 다음 커밋으로 흘러갔습니다.
프롬프트를 기깔나게 엔지니어링한 것도 아니었습니다. MCP를 설치한 것도 아니었습니다. 에이전틱 메모리를 강화한 것도 아니었습니다. 그저 Lint 규칙 45개를 줬을 뿐.
말로 지시해도 막을 수 없는 실수
AI가 멍청하다고 느껴질 때가 있습니다. ‘아니 이런 것도 못해?’ ‘똑바로 안해?’ 심한 욕까지 하는 분도 봤습니다.
이런 문제가 있으면 흔히 AGENTS.md / CLAUDE.md 를 잘 쓰라고 합니다. DON’T. 이렇게 하지마. DO. 이렇게 해. 크게 대문자로 쓰라고요.
처음엔 저도 AI가 실수할 때마다 그렇게 썼습니다. 너 마음대로 컬러 쓰지마. 디자인 시스템에 있는 토큰을 써.
이 방법은 Skill, 메모리 등 다양한 기법으로 발전했습니다. 그러나 결국은 LLM에게 자연어 지시를 주는 방법입니다. 자연어 지시는 아무리 해도 결국 어긋나는 부분이 있습니다.
하지 말아야할 것을 모두 일일이 나열할 수도 없습니다. LLM의 컨텍스트는 한계가 있기 때문에 까먹기도 합니다. 어느 순간에는 ‘좋은 지적입니다. 제가 실수했습니다. ~했어야했는데 잘못했네요’ 같은 사과를 볼 수밖에 없죠.
LLM이 언젠가 실수를 반복한다면, 여전히 저는 LLM이 쓴 코드에서 눈을 뗄 수가 없습니다.
구조를 결정적으로 잡을 수 있는 Lint
‘AI가 생성 속도를 폭발시켰다. 생성 결과가 우리의 의도와 일치하는지는 항상 확률적이다. AI가 짠 코드를 신뢰하고 검증하는 시간을 줄이기 위해 결정론적 검증이 필요하다.’ 라고 1편에서 말씀드렸습니다.
단위 테스트, 통합 테스트 같은 자동화된 테스트는 기능의 어긋남을 잡아냅니다. 타입 체크는 런타임에 발생할 수 있는 값의 오류를 잡아냅니다. 하지만 이 도구들은 ‘구조’의 어긋남은 잡지 못합니다. 코드의 일관성이나 컨벤션은 잡지 못합니다.
구조를 결정적으로 잡을 수 있는 가장 좋은 도구가 ‘Lint’입니다.
Lint를 모르는 개발자는 없을 겁니다. 하지만 Lint는 그냥 ‘맞춤법 검사기’라는 느낌이 듭니다.
> Lint가 뭐 그렇게 대단해? Lint로는 한계가 있지 않나?
저도 그렇게 생각했습니다. 하지만 AI가 코드를 짜는 시대가 되다보니, Lint를 더 끝까지 밀어붙일 수 있게 됐습니다. 생각보다 큰 효과를 낼 수 있더라고요. 오늘은 바로 그 얘기를 해보려고 합니다.
(저는 웹 프론트엔드 개발을 하고 있습니다. 그래서 예시가 대부분 Typescript와 React 입니다. 아무래도 구체적인 예시를 들 수밖에 없는 주제더라고요. 하지만 Lint는 스택을 가리지 않고 유용할 거라고 생각합니다. 독자분들은 각자 자신의 상황에 적절한 케이스를 상상해서 이해해주시리라 믿습니다)
원래 나에게 Lint는 그냥.. 맞춤법 검사기였다.
사실 저에게 Lint는 맞춤법 검사기 같은 거였습니다. 워드에서 글 쓰다보면 빨간 줄 쳐지는 거 있잖아요. 딱 그 수준으로 생각했죠.
예전에 팀에서 ‘다같이 Lint 워닝을 줄여보자’ 캠페인을 한 적이 있습니다. 솔직히 고백하자면 정말 귀찮았습니다. 경고가 떠도 코드가 안 돌아가는 건 아니니까요. 혹시나 괜히 고치다가 버그라도 나면 어떻게 하나. 고쳐야 하는 건 아는데 우선순위는 자꾸 밀렸습니다.
저에게 Lint는 그런 존재였습니다. 있으면 좋지만, 잘해봐야 크게 달라지는 건 없는 맞춤법 검사기.
그저 똑같은 코드 리뷰를 받기 싫어서
회사에서 일하면서 직접 Lint를 만들게 됐습니다. 똑같은 코드 리뷰를 주고받는 저를 발견한 게 계기였습니다.
저와 같이 일하는 창회님은 파일명, 핸들러 이름, 폴더 위치에 굉장히 깐깐한데요. 요리사로 치면 작은 칼은 여기, 큰 칼은 여기, 도마는 어디 놓을지 챙기는 사람이랄까요.
“이건 여기 말고 저 파일로 가야 할 것 같아요”, “이건 model이니까 파일명에 .model.ts가 붙어야죠” 같은 리뷰를 계속 받았습니다.
솔직히 저는 그렇게 꼼꼼한 사람이 못 됩니다. 그래서 생각했습니다.
애초에 AI가 이런 실수를 못 하게 막아버리면 어떨까?
마침 코드베이스의 규칙을 문서로 쓰고 있던 참이었습니다. 라우팅은 이렇게, API 요청은 이렇게, 버튼 이벤트 코드는 여기에… 이런 패턴을 정리한 문서였죠. 그 문서를 AI한테 주고, 이걸 결정적으로 강제하는 Lint로 만들어보라고 했습니다.
그때까진 별 대단한 기대는 없었습니다.
구조를 잡는 Lint
이런 Lint를 쌓아가다보니, 20개쯤 됐을 때 “어, 리뷰할 게 별로 없네?”라는 말을 듣기 시작했습니다.
30개를 넘어가자 AI가 자가 피드백을 하고 고치를 루프를 보게 됐습니다.
여기서 깨달았습니다. Lint는 아키텍처와 구조를 잡을 수 있다는 걸요.
구조를 잡는 Lint 예시
-
의존성 방향을 강제합니다. shared 레이어가 app을 import하지 못하게, 화면 컴포넌트가 상위 컨테이너를 참조하지 못하게 막죠. 컴파일은 멀쩡히 통과하는데 아키텍처가 조용히 무너지는 걸 잡아줍니다.
-
경계를 격리합니다. Feature끼리 서로의 내부를 직접 import하지 못하게, API 호출은 지정된 모듈 안에서만 하도록.
-
진입점을 강제합니다. 라우트 파일에는 re-export만 두고, 실제 코드는 못 넣게.
-
이름이 곧 레이어가 되게 합니다. 파일 확장자(.model, .api 같은)만 보고도 이게 어느 층에 속하는지 알 수 있죠.
-
containers 폴더 직속 파일은 반드시 이름이 Container로 끝나야 한다거나, 상수 파일(.const.ts)에는 함수 정의를 아예 못 넣게 하는 것까지.
이런 구조적인 규칙은 코드베이스의 뼈대 역할을 해서, 몇 개만 추가해도 체감이 큽니다. AI한테 새 파일이나 함수를 만들라고 시키면, 제가 예상한 바로 그 자리에 그 이름으로 만들어 놓습니다.
늘어날수록 강해지는 Lint
Lint를 쌓다 보면 이런 의문이 들 수 있습니다.
> “아무리 그래도 Lint는 단순하고 기계적인 케이스만 잡는 거 아냐?”
> “실수하는 경우가 수백 가진데 그걸 언제 하나하나 다 잡아?”
저도 원래 Lint라는 건 다 하나하나 독립적인 규칙이라고 생각했습니다. 여태까지 제가 본 Lint는 다 그랬거든요. A 케이스 하나 잡고, B 케이스 하나 잡고.
그런데 구조를 잡는 Lint는 쓰면 쓸수록 더 유용해졌습니다. 먼저 만든 Lint가 다음 Lint를 쉽게 만들어줬습니다. 독립적인 규칙은 그만큼 잡을 수 있는 패턴에 한계가 있지만, 그걸 조합해나갈 수 있다면 훨씬 강력해집니다.
원리는 이렇습니다. 레이어, 파일명, 폴더 구조, 함수명 같은 걸 먼저 강하게 잡아두면, 그 위에 더 어려운 규칙을 얹기가 쉬워집니다.
예를 들어볼게요.
React에는 데이터를 가져오는 useSuspenseQuery라는 함수가 있습니다. 이걸 쓸 때는 바깥을 Suspense라는 컴포넌트로 감싸줘야 합니다.
저는 먼저 규칙을 하나 걸었습니다. useSuspenseQuery를 쓰는 커스텀 훅은 반드시 이름이 …SuspenseQuery로 끝나야 한다고요. 솔직히 이것만 보면 “굳이 이렇게까지?” 싶은 주관적인 규칙입니다. useProductQuery, useUserQuery처럼 자유롭게 이름을 짓는 경우도 많습니다.
그런데 이 규칙을 걸어두면, 다음 규칙이 쉬워집니다. 이제 ‘-SuspenseQuery’로 끝나는 함수를 찾습니다. 그 바깥에 Suspense가 제대로 감싸져 있는지를 검사합니다. 이런 Lint를 또 만들 수 있거든요.
이름 규칙이라는 발판이 없었다면 “어떤 게 Suspense 훅인지” 알아내는 것부터가 까다로웠을 겁니다. 변수명, 함수명을 좀 과하다 싶을 만큼 규칙으로 잡아두면, 그 위에서 더 정교한 Lint를 거는 게 편해집니다.
더 구체적인 예시가 필요한 분들을 위해 예시 레포를 하나 만들어두었습니다. lint-enforced-architecture >>
(프로젝트의 의존성 그래프 자체를 분석하는 더 고-급 도구들도 있습니다. ArchUnit이나 dependency-cruiser 같은 것들인데요. 저도 처음엔 신나서 써보려다가, 원하는 구조 규칙 대부분이 그냥 ESLint로도 충분히 잡혀서 무거운 도구까지는 안 들였습니다. 써보신 분들 후기가 궁금하네요.)
그런데 이건 AI라서 되는 게 아닙니다. “Lint가 이 정도까지 되는 거였어?”를 제가 몰랐을 뿐이지, 고수분들은 AI가 나오기 한참 전부터 이렇게 쓰고 있었더라고요.
다만 AI 시대에 와서야 비로소 이걸 ‘안 할 이유’가 사라졌습니다. 비용은 내려가고, 가치는 올라갔거든요.
Lint 작성 비용 ↓ - AI는 패턴 매칭 코드를 잘 쓴다.
Lint는 본질적으로 입력과 출력이 명확한 알고리즘입니다. Lint는 추상 구문 트리로 코드 구조를 분석하고 정형화된 데이터로 만듭니다. 이런 정형 데이터에서 구문 분석, 셀렉터, 정규식으로 패턴 매칭을 하는 건 정답이 명확해서 LLM이 잘하는 영역이죠. 우리 잡고 싶은 안티 패턴을 굉장히 잘 캐치해냅니다.
저는 손으로 코딩하던 시절 정규식엔 젬병이었습니다. 지금 AI가 하는 패턴 매칭을 코딩해보라고 했다면 못했을 겁니다. 하지만 AI는 규칙만 명확하다면 패턴을 잡고 정교한 예외 처리를 하는데 뛰어납니다.
무엇보다 Lint가 패턴 매칭을 잘하는지는 아주 쉽고 싸게 검증할 수 있습니다. 코드베이스에 돌려보면 바로 답이 나옵니다. 잡아야 할 걸 잡는지, 안 잡아야 할 걸 잡는지. 틀린 출력을 쉽게 걸러낼 수 있습니다. 그걸 바탕으로 AI에게 고도화를 해달라고 하면 됩니다.
Lint 적용 비용↓ - 피드백이 있으면 순식간에 고친다.
Lint를 지키는 비용도 많이 줄어듭니다. 손코딩 시대에는 Lint가 에러를 뱉으면 직접 수정해야하는 게 정말 귀찮았습니다.
‘파일명이 400줄 넘으면 안된다’는 Lint가 있다고 해보죠. 코드를 살짝 수정하다가 401줄이 되었습니다. 그런데 그 순간에 이걸 리팩토링하고 파일 분리하자니, 파일도 추가해야하고 함수도 옮겨야 하고 매우 귀찮습니다. 1줄 넘었다고 엄청 문제가 있는 건 아닌데 말이죠.
사람은 이럴 때 스윽 회피해버립니다. Disable-lint 주석을 달아줍니다. 솔직히 저도 이렇게 많이 했었습니다.
그런데 이제 대부분의 코드는 AI가 씁니다. AI가 뭐 함수명 바꾸는거, 파일 옮기는 거 귀찮아하나요? 아닙니다.
오히려 AI는 구체적 피드백이 있으면 기계적인 수정은 엄청난 속도로 잘합니다. 이제 좀 과하다 싶은 Lint도 유용해집니다.
Lint의 가치↑ - AI는 패턴을 증폭한다
코드베이스의 일관성은 예전에도 중요했지만, 이제는 더 중요해졌습니다. AI가 패턴을 증폭하기 때문입니다. LLM은 본질적으로 “A, B, C 다음에 올 건?” 하는 빈칸 채우기로 작동합니다. 그래서 구체적인 예시를 잘 깔아주는 게 중요하죠. 흔히 few-shot이라고 부르는 게 이겁니다.
코딩 에이전트한테 진짜 예시 역할을 하는 건 우리가 프롬프트에 적어주는 예시가 아니라, 컨텍스트로 끌어다 읽는 주변 코드입니다. 즉 코드베이스의 일관성 자체가 암묵적인 예시 풀이 됩니다.
만약 코드베이스가 A1, B9, S7, L5 이런 식으로 제각각이라면, AI가 추론할 지배적인 패턴이 없습니다. 따라야 할 기준이 없으니 출력의 분산이 커지고, 매번 예상치 못한 결과로 우리를 놀래킵니다.
반대로 코드에 일정한 패턴이 있으면, AI는 별말 안 해도 그 패턴을 추론해서 똑같이 만듭니다. 경계가 깨끗하고 명시적이면 눈치껏 잘 지킵니다. 이곳엔 다른 스타일.. 저곳엔 예외 로직.. 이렇게 들쭉날쭉하면 AI도 들쭉날쭉한 코드를 뿌립니다.
Lint가 일관성을 강제하고 → 일관된 코드가 AI의 컨텍스트로 들어가고 → AI가 그 패턴을 증폭 재생산하고 → 코드베이스는 더 일관돼집니다. 코드베이스가 커질수록 새로 추가되는 코드가 기존 코드를 점점 더 닮아갑니다. Lint를 촘촘하게, 오래 지켜나가면 결국 코드베이스의 일관성 그 자체가 가장 강력한 Lint가 됩니다.
깨진 유리창 법칙이란 게 있습니다. 이미 하나 깨진 유리창이 있으면 무질서가 계속 늘어난다는 거죠. AI 시대엔 무질서도, 질서도 전보다 빠르게 증폭합니다. 그래서 첫 단추를 규칙으로 확실하게 강제하는 게 중요합니다.
Lint의 가치 ↑ - 이제 검증이 병목이다
AI가 코드 생성을 폭발시키면서 병목이 ‘생산’에서 ‘검증’으로 옮겨갔습니다.
사람·테스트·리뷰는 검증 한 번에 매번 비용이 듭니다. Lint는 다릅니다. 한 번 작성해두면 그 뒤로는 한계비용이 0에 수렴합니다. 매번, 빠짐없이, 같은 기준으로. 이런 결정론적 검증 기계는 검증의 부담을 확 줄여줍니다. 기준이 명확한 검증을 Lint한테 넘길수록, 사람은 정말 판단이 필요한 곳에 예산을 아껴 쓸 수 있습니다.
예전엔 ‘Lint 그거 경고 좀 뜬다고 뭐가 달라져?’ 였습니다. 생산이 병목일 땐 검증을 깎아주는 도구가 한가한 사치처럼 보였죠. 병목이 검증으로 옮겨가니, 도구는 그대로인데 가치가 재평가된 겁니다.
요약하자면 Lint의 가치는 올라갔습니다. 일관성을 만들어주고, 검증을 자동화해주니까요. Lint의 비용은 내려갔습니다. 작성이 쉬워졌고, 수정 적용도 빨라졌습니다.
이제 Lint를 다시 봐야 할 때가 왔습니다.
왜 주관적인 Lint를 만들어야하는가
> ‘그래? 그럼 Lint 좋다는 거 AI한테 검색해서 싹 가져오라고 한 다음 내 코드에 설치해야겠다.’
> ‘그럼 너 Lint 쓰고 있는 거 뭔데? 나도 좀 쓰자’
여기까지 듣다보면 이런 생각이 들 수 있습니다.
하지만 저는 여러분의 주관적인 기준이 담긴 Lint를 쌓아나가는 게 좋다고 생각합니다. 조금 천천히 가더라도요.
제가 Lint를 쓰면서 정말 좋았던 건, AI가 객관적으로 좋은 코드를 써줘서가 아니었습니다. AI가 내가 생각한 그 구조를 예측 가능하게 뱉어줬기 때문입니다. 그리고 그걸 예측 가능하게 만든 건 범용 안티패턴 Lint가 아니라, 내 취향을 규칙으로 좁힌 것들이었습니다.
주관적일수록 구체적이다
저는 좋은 코드가 완벽하게 객관적일 수 없다고 생각합니다. 좋은 코드는 본질적으로 주관적(opinionated)입니다.
물론 누구나 동의할 범용 Lint도 있죠. ’== 대신 ===를 쓴다’, ‘안 쓰는 변수는 선언하지 않는다’. 근데 대부분은 어느 정도 주관이 들어갑니다. ‘한 파일에 컴포넌트 하나만 둔다’ 같은 거요. (‘난 여러 개 모아두는 게 보기 좋은데?’)
여기서 중요한 게 있습니다. 주관적일수록 구체적입니다. 이유는 단순합니다. 모두를 만족시키려는 규칙은 추상적일 수밖에 없고, 누군가를 포기한 규칙만 구체적일 수 있거든요. 그리고 Lint라는 검증 기계의 힘은 바로 이 구체성에서 나옵니다.
예를 들어보죠. “함수는 한 가지 일만 해야 한다.” 누군가 리뷰에 이런 코멘트를 달았다고 칩시다. 뭘 고쳐야 할지 해석이 분분합니다. ‘한 가지 일’이 뭔데요? 기계로 검증하기 어렵습니다. 모두가 동의하는 원칙이라 그만큼 추상적입니다.
반대로 제가 좋아하는 책 <Five Lines of Code>엔 이런 규칙이 나옵니다. ‘함수에서 else는 쓰지 않는다’, ‘if는 함수 맨 위에 와야 한다.’ 아주 구체적이고 강제적이죠. 이건 ‘하나의 함수가 여러 일을 하는 패턴’을 막으려는 겁니다. if가 여기저기 박히는 순간, 이미 그 함수는 여러 일을 하고 있다는 뜻이니까요. 누군가는 ‘if 여러 개가 뭐가 어때서?‘라고 할 겁니다. 합의를 포기한 규칙이라 그렇습니다. 대신 기계가 판정할 수 있게 됐죠.
그리고 이 규칙이 가끔 틀린다는 게 오히려 핵심입니다. else 금지가 멀쩡한 코드를 잡아낼 때도 있어요. 근데 바로 그 지점이 ‘사람이 한번 들여다볼 곳’입니다.
주관적 규칙은 정답을 강제하는 도구가 아니라, 검토할 자리를 결정론적으로 찍어주는 도구입니다. <Five Lines of Code> 저자도 완벽한 규칙이라서가 아니라, 구체적이라서 리팩토링할 코드를 찾아냅니다.
‘그런 것까지 잡혀?’ 싶은 강한 규칙들을 보면, 대부분 주관과 취향이 듬뿍 들어가 있습니다. 제가 쌓은 것들도 그렇습니다.
-
데이터 가져오는 방식은 하나로 통일합니다. useQuery만 쓰고 useMutation은 금지. effect 안에서 fetch도 금지. 사실 useMutation은 멀쩡하게 잘 동작합니다. 객관적으로 보면 굳이 금지할 이유가 없을 수도 있어요. 근데 방식이 둘로 갈리는 것보다 하나로 좁히는 게 일관성에 낫다고 판단한 겁니다. 명백히 제 취향이 들어간 선택이죠.
-
함수 이름의 prefix로 역할을 강제합니다. 이벤트 핸들러는 handle로 시작, 데이터 훅은 Query로 끝, ref는 ref로 시작. 이름만 보고 뭐 하는 녀석인지 알도록요.
-
사이드 이펙트가 있는 함수는 항상 정해둔 loading 훅으로 감싸게 합니다. loading 처리를 빼먹지 않습니다.
-
Component 폴더 안에서 데이터 페칭 쿼리는 금지합니다. Component는 UI만, 데이터 페칭은 Container의 관심사니까요.
이런 취향 담긴 규칙을 쌓다 보면, AI가 ‘맞는 코드’를 쓰는 걸 넘어 ‘내가 쓴 것 같은 코드’를 쓰게 됩니다.
물론 Lint가 전부는 아닙니다. Lint로 모든 나쁜 구조를 잡을 순 없어요. 예외가 너무 많아 규칙으로 못 박는 경우도 있고, 저도 여전히 AI가 실수하는 걸 봅니다. 그래서 Skill을 발동시키고 코드 리뷰로 잡아내죠.
근데 한 번에 완벽한 Lint를 깔 필요는 없습니다. 예외가 하나 나올 때마다 기억해뒀다가, 그 하위의 구체적인 케이스를 하나씩 좁혀가면 됩니다. 취향을 한 줄씩 쌓아가는 겁니다.
실수를 회고하고 Lint로 쌓아간다
주관적인 나만의 Lint를 쌓는 데 가장 좋은 재료 창고가 하나 있습니다. AI와의 회고입니다.
앞에서 구체적이고 주관적인 규칙이 좋다고 했죠. 근데 막상 그런 규칙을 머리로 짜내려면 잘 안 떠오릅니다. 떠올려봐야 내가 이미 아는 안티패턴만 나오거든요. 회고는 다릅니다. 내 코드베이스에서 실제로 터진 실수를 재료로 주니까, 상상으로 만든 규칙이 아니라 현장에서 검증된 규칙이 나옵니다.
저는 AI랑 코딩하고 한 세션이 끝나면 회고를 시킵니다. 항상 놓친 게 있고, 제대로 못 한 게 있으니까요. AI는 세션 로그를 다 읽으면서 제가 고치라고 했던 것, 의도를 잘못 해석하고 움직였던 것을 찾아냅니다. 저는 이 실수들을 md 파일로 저장하게 해둡니다.
몇 주 뒤, 이 재료를 모아서 줍니다.
> ‘너가 최근 한 달간 했던 실수를 Lint로 결정적으로 막으려면 어떻게 해야 할지 제안해봐.’
그냥 일반론으로 규칙 만들라고 하면 뻔한 게 나옵니다. 근데 예측과 달랐던 생생한 실수가 이미 쌓여 있습니다. 원칙을 구체적인 Lint로 좁힐 실마리가 훨씬 많이 나옵니다.
회고가 참 알찬 이유가 여기 있습니다. 내가 이미 아는 실수뿐 아니라, 내가 몰라서 애초에 못 만들었을 규칙까지 발굴해주니까요.
예를 들죠. 모바일 앱에서 AI한테 일을 시켰다가, 앱 내에서 딥링크를 여는 메서드랑 외부 브라우저에서 링크를 여는 메서드를 헷갈려서 버그가 난 적이 있습니다. 사실 저도 그 차이를 잘 몰라서 못 잡아낸 경우였어요. 테스트하다가 알게 됐고, 다시 고쳤습니다.
이 실수 다시 하지 말라고 회고를 시켰죠. AI는 어떤 케이스에선 외부 브라우저를 열면 안 된다는 걸 알게 됐고, md 파일로 저장됐습니다. 몇 주 뒤 이 파일들을 모아 Lint를 만들어보라고 했더니, 정말 규칙을 만들어서 잘 잡아주더라고요.
Lint는 현시점 가장 저평가된 AI 하네스입니다
서두에 말씀드린 그 장면. AI가 제 피드백보다 먼저 자기 코드를 고쳤던 그 순간. 마법이 아니었습니다. 제가 그 자리에 Lint 45개를 쌓아둔 결과였을 뿐이죠.
AI가 멍청해서 자꾸 엉뚱한 코드를 뱉는 게 아닙니다. 명확한 피드백과 구조가 없어서 엉뚱한 코드를 뱉는 겁니다.
구조를 잡고 피드백을 주는 Lint를 쌓아가보세요.
Lint는 현 시점 가장 저평가된 AI 하네스입니다.
Lint 모음은 어느 때보다 싸게 만들 수 있게 됐습니다. 그런데 거기에 당신의 취향이 들어가야만 효과를 냅니다.
처음부터 모든 컨벤션을 잡으려고 하면 힘들 수 있습니다. 일단 파일명/폴더구조/함수명 컨벤션을 하나씩 잡아나가보세요. 뒤의 Lint가 쉬워집니다.
그리고 다음에 AI랑 코딩하다가 “아 그거 아닌데” 하고 고치라고 시키는 순간이 오면, 그걸 그냥 넘기지 말고 md 파일로 저장해두세요. 그리고 나중에 거기서 패턴을 뽑아내면 됩니다.
더 구체적인 예시가 필요하신 분들을 위해, Typescript 기준 레포지토리를 간단하게 만들어두었으니 보셔도 좋습니다. lint-enforced-architecture >>
> “구조는 Lint로 잡았는데, 버그 없이 제대로 동작하는지는 어떻게 알지?”
다음 편에서는 3편에서는 ‘기능을 검증하는 설계, 자동화 테스트’에 대해 얘기해보겠습니다.