8년 동안 원했던 것, AI와 함께한 3개월의 개발기
source https://lalitm.com/post/building-syntaqlite-ai/
제 AI 경험과도 비슷한 경험이고 현실적인 얘기라 재밌게 읽었네요.
8년 동안, 나는 SQLite를 제대로 다룰 수 있는 고품질 devtools 세트를 원해 왔다. SQLite가 업계에서 차지하는 중요성을 생각하면, 이걸 위한 정말 좋은 개발자 경험에 아무도 제대로 투자하지 않았다는 사실이 늘 의아했다.
몇 주 전, 저녁 시간과 주말, 휴가를 쪼개 3개월 동안 약 250시간을 쏟아부은 끝에, 드디어 syntaqlite를 공개했다(GitHub). 오랫동안 품어 온 바람을 결국 실현한 셈이다. 그리고 이 일이 가능했던 가장 큰 이유는 AI 코딩 에이전트 덕분이라고 나는 생각한다.
물론 AI가 자기 프로젝트를 단번에 만들어줬다는 글도 넘쳐나고, 반대로 AI는 전부 허접한 결과물뿐이라고 잘라 말하는 글도 많다. 나는 좀 다른 방식으로 가보려고 한다. syntaqlite를 AI와 함께 만들면서 어디서 도움이 됐고, 어디서 오히려 해가 됐는지 내 경험을 차근차근 뜯어보겠다.
이 경험이 얼마나 일반화될 수 있는지도 스스로 판단할 수 있도록, 프로젝트 맥락과 내 배경도 함께 설명할 생각이다. 그리고 무언가를 주장할 때는 가능하면 프로젝트 저널, 코딩 기록, 커밋 히스토리 같은 증거를 붙이려고 한다.
왜 이걸 원했나
나는 Perfetto에서 일하면서, 성능 트레이스를 조회하기 위한 SQLite 기반 언어인 PerfettoSQL을 관리하고 있다. 기본적으로는 SQLite와 거의 같지만, 트레이스 쿼리 경험을 더 좋게 만들기 위한 몇 가지 확장이 들어가 있다. Google 내부에는 PerfettoSQL 코드가 약 10만 줄 정도 있고, 다양한 팀이 이걸 사용한다.
어떤 언어가 실제로 쓰이기 시작하면, 사용자들은 formatter나 linter, editor extension 같은 것도 자연스럽게 기대하게 된다. 나도 오픈소스 쪽 SQLite 도구를 좀 가져다 쓸 수 있지 않을까 기대했지만, 들여다볼수록 실망만 커졌다. 내가 찾은 것들은 신뢰성이 부족하거나, 속도가 부족하거나, PerfettoSQL에 맞게 확장하기엔 유연성이 부족했다. 처음부터 새로 만들 여지는 분명 있었지만, 그렇다고 "지금 우리가 가장 먼저 해야 할 일"은 아니었다. 그래서 마지못해 기존 도구를 써 왔고, 늘 더 나은 걸 바라는 상태였다.
한편으로는, 개인 시간에 해볼 수 있는 선택지도 있긴 했다. 나는 10대 때 오픈소스 프로젝트를 꽤 많이 만들었지만, 대학에 들어가고 나서는 더 이상 그만한 동기가 남아 있지 않다고 느끼면서 그런 활동이 자연스럽게 사라졌다. 유지보수자는 그냥 "코드를 던져놓고 무슨 일이 생기나 보는 사람"이 아니다. 버그를 분류하고, 크래시를 조사하고, 문서를 쓰고, 커뮤니티를 만들고, 무엇보다 프로젝트가 어느 방향으로 가야 하는지 잡아야 한다.
그래도 오픈소스의 감각, 그러니까 내가 원하는 걸 만들면서 동시에 다른 사람에게 도움이 될 수 있다는 그 감각은 한 번도 사라진 적이 없었다. SQLite devtools 프로젝트는 늘 내 머릿속에서 "언젠가 해보고 싶은 일"로 남아 있었다. 그런데 계속 미뤄 온 이유가 하나 더 있었다. 이 프로젝트는 어렵기도 하고, 지루하기도 한 경계선 위에 딱 놓여 있었기 때문이다.
왜 어렵고 지루한가
개인 시간을 들여 이 프로젝트를 할 거라면, Perfetto에만 도움이 되는 걸 만들고 싶진 않았다. 어떤 SQLite 사용자에게도 쓸 수 있는 걸 만들고 싶었다. 그러려면 SQL을 SQLite와 정확히 똑같이 파싱해야 한다.
언어 지향 devtool의 핵심은 parser다. parser는 소스 코드를 "parse tree"로 바꾸는 역할을 하고, 이 트리는 이후의 모든 기능이 기대는 중심 데이터 구조가 된다. parser가 정확하지 않으면 formatter나 linter도 결국 그 부정확함을 그대로 물려받게 된다. 내가 찾아본 많은 도구가 정확히 이 문제를 안고 있었다. SQLite 언어를 정밀하게 표현하기보다 대충 근사해서 처리하는 parser를 쓰고 있었던 것이다.
안타깝게도 SQLite는 다른 많은 언어와 달리, 어떻게 파싱해야 하는지 설명하는 공식 명세가 없다. parser를 위한 안정적인 API도 제공하지 않는다. 더 특이한 건, 구현상으로는 parse tree 자체를 아예 만들지도 않는다! 내가 보기엔 남은 현실적인 방법은 하나뿐이었다. SQLite 소스 코드에서 필요한 부분을 조심스럽게 추출해, 내가 원하는 parser를 만들 수 있게 거기에 맞춰 손보는 것이다.
문제는 그러려면 SQLite 소스 코드 깊숙이 들어가야 한다는 점이다. 이 코드는 이해하기 정말 까다롭다. 프로젝트 전체가 C로 쓰여 있고, 그것도 엄청나게 빽빽한 스타일로 작성되어 있다. 나도 virtual table API와 구현을 이해하는 데만 며칠을 쓴 적이 있다. parser 스택 전체를 파악하는 일은 그보다 훨씬 막막했다.
게다가 SQLite에는 언어 전체 표면적을 담아내는 규칙이 400개 넘게 있다. 이 각각의 "grammar rule"마다, 해당 문법이 parse tree의 어떤 노드로 매핑되는지 일일이 지정해야 한다. 엄청나게 반복적인 작업이다. 각 규칙은 주변 규칙과 비슷하면서도, 정의상 또 다를 수밖에 없다.
규칙만 문제가 아니다. 이게 맞게 동작하는지 확인할 테스트를 생각해 내고 작성해야 하고, 뭔가 틀어지면 디버깅해야 하고, 내가 실수한 부분 때문에 사람들이 올릴 버그를 분류하고 고쳐야 한다...
수년 동안 아이디어는 늘 여기서 멈췄다. 사이드 프로젝트로 하기엔 너무 어렵고, 동기를 오래 유지하기엔 너무 지루하고, 몇 달을 쏟았는데 결국 안 될 수도 있는 일에 투자하기엔 리스크가 너무 컸다.
어떻게 시작됐나
나는 2025년 초부터 코딩 에이전트(Aider, Roo Code, 그리고 7월부터는 Claude Code)를 써 왔다. 분명 도움이 되긴 했지만, "진지한 프로젝트를 맡겨도 되겠다"고 느낄 정도는 아니었다. 그런데 2025년 말쯤 되자 모델 품질이 꽤 크게 한 단계 올라간 느낌이 들었다. 동시에 Perfetto를 작업하면서, 믿을 만한 parser만 있었다면 너무 쉽게 풀렸을 문제들을 계속 만나고 있었다. 임시방편을 하나씩 더할 때마다 뒤에서 같은 생각이 맴돌았다. 이제는 진짜로 만들어볼 때가 된 걸까.
크리스마스 즈음 생각을 정리할 시간이 생겼고, AI를 가장 극단적으로 써보는 실험을 해보기로 했다. Claude Code 하나만, Max 플랜(월 200파운드)으로 붙여서 이걸 전부 vibe-coding으로 만들 수 있을까?
1월 대부분 동안 나는 반쯤은 기술 매니저처럼 움직였다. 설계 대부분과 구현 전부를 Claude에게 넘기고, 나는 계속 방향을 조정했다. 기능적으로는 꽤 그럴듯한 결과물이 나왔다. SQLite 소스에서 여러 Python 스크립트로 추출한 C 기반 parser, 그 위에 올라간 formatter, SQLite 언어와 PerfettoSQL 확장 둘 다에 대한 지원, 그리고 이 모든 걸 노출하는 웹 playground까지 만들었다.
하지만 1월 말에 코드베이스를 자세히 리뷰해 보니, 문제는 너무 분명했다. 코드는 완전히 스파게티였다. Python 기반 소스 추출 파이프라인의 큰 부분을 내가 이해하지 못했고, 함수들은 구조도 없이 여기저기 흩어져 있었고, 몇몇 파일은 수천 줄짜리 괴물이 되어 있었다. 말 그대로 엄청나게 깨지기 쉬운 상태였다. 당장 눈앞의 문제는 풀었지만, 내가 더 크게 그리고 있던 비전을 담아낼 수 있는 구조는 전혀 아니었고, Perfetto 도구 체인에 통합하는 건 더더욱 불가능했다. 그나마 다행이었던 건 접근 방식 자체는 가능하다는 걸 증명했고, 500개가 넘는 테스트를 만들어냈다는 점이었다. 그중 많은 테스트는 재사용할 수 있겠다고 느꼈다.
그래서 전부 버리고 처음부터 다시 시작하기로 했다. 그리고 코드베이스 대부분을 Rust로 옮기기로 했다. 상위 레벨 컴포넌트, 예를 들면 validator나 language server 같은 걸 만들려면 C는 너무 발목을 잡을 게 뻔했다. 보너스로, 추출 단계와 런타임을 C와 Python으로 나누지 않고 같은 언어로 묶을 수도 있었다.
더 중요한 변화는 프로젝트에서 내 역할을 완전히 바꿨다는 점이다. 모든 결정의 책임을 내가 직접 지기로 했고, AI는 훨씬 더 타이트한 프로세스 안에서 "엄청 강력한 자동완성"처럼 쓰기 시작했다. 설계를 먼저 분명히 잡고, 모든 변경을 꼼꼼히 리뷰하고, 문제를 발견하면 바로 고치고, linting이나 validation, 제법 본격적인 테스트 같은 발판을 미리 깔아 AI 출력물을 자동으로 검증했다.
핵심 기능은 2월 동안 형태를 갖췄고, 마지막 구간인 upstream 테스트 검증, editor extension, 패키징, 문서화까지 마치면서 3월 중순에 0.1 버전을 내놓을 수 있었다.
하지만 내 생각에 이 타임라인은 이 이야기에서 가장 덜 흥미로운 부분이다. 내가 정말 말하고 싶은 건, AI가 없었다면 아예 일어나지 않았을 일들과, 동시에 그걸 쓰는 과정에서 내가 치른 대가다.
AI가 있었기에 이 프로젝트는 존재하게 됐고, 지금 정도로 완성도 있게 나올 수 있었다
관성을 넘기
예전에도 쓴 적이 있는데, 소프트웨어 엔지니어로서 내 가장 큰 약점 중 하나는 새롭고 큰 프로젝트 앞에서 미루는 습관이다. 당시엔 몰랐지만, syntaqlite를 만드는 데 이보다 더 정확하게 들어맞는 설명도 없었다.
AI는 기술적인 판단에 대한 망설임, 내가 맞는 걸 만들고 있는지에 대한 불확실함, 시작 자체를 꺼리게 만드는 감각을 잠시 치워두게 해줬다. 아주 구체적인 문제를 눈앞에 놓아줬기 때문이다. "SQLite의 파싱이 어떻게 동작하는지 이해해야 해"가 아니라, "AI가 나한테 접근법 하나를 제안하게 만들고, 그걸 뜯어고쳐서 더 나은 걸 만들면 돼"가 된 것이다. 나는 머릿속에서 끝없이 설계를 굴리는 것보다, 만져볼 수 있는 프로토타입과 직접 볼 수 있는 코드를 두고 생각할 때 훨씬 잘 움직인다. AI는 내가 예전에는 상상도 못 했던 속도로 그 지점까지 가게 해줬다. 첫걸음만 떼고 나니, 그다음부터는 훨씬 쉬워졌다.
코드를 쏟아내는 속도
AI는 "코드를 쓰는 행위 자체"만 놓고 보면 나보다 잘했다. 전제는 그 코드가 충분히 명확해야 한다는 것이다. 문제를 "이 동작과 파라미터를 가진 함수를 써라" 또는 "이 인터페이스에 맞는 클래스를 만들어라" 정도로 쪼갤 수만 있다면, AI는 내가 직접 쓰는 것보다 더 빨리 만들어냈고, 결정적으로 미래의 독자에게 더 직관적으로 읽히는 스타일로 짜는 경우도 많았다. 내가 그냥 넘어갈 문서화도 해주고, 프로젝트 나머지 부분과 일관된 방식으로 코드를 배치하고, 작업 중인 언어의 이른바 "표준 방언(standard dialect)"에 맞춰 작성한다.
다만 이 "표준적임"은 양날의 검이다. 어떤 프로젝트에서든 코드 표준은 바로 예측 가능하고, 읽기 쉽고, 이상한 데가 없는 것이다. 하지만 모든 프로젝트에는 차별점이 되는 부분이 있다. 뻔하지 않은 무언가를 해내야 가치가 생기는 구간 말이다. syntaqlite에서는 그게 추출 파이프라인과 parser 아키텍처였다. 그런 부분에서는 AI의 "정규화하려는 본능"이 오히려 해로웠고, 그 영역은 내가 깊이 설계해야 했고 종종 그냥 직접 써버려야 했다.
그런데 반대로 보면, AI가 뻔한 코드를 빠르게 만드는 바로 그 속도 덕분에 refactoring에도 강했다. AI로 산업 규모처럼 코드를 쏟아내기 시작하면, 반드시 끊임없이 refactoring해야 한다. 안 그러면 바로 통제가 안 된다. 이건 vibe-coding 한 달 동안 얻은 가장 큰 교훈이었다. 나는 refactoring을 충분히 하지 않았고, 그 결과 코드베이스는 내가 더 이상 추론할 수 없는 물건이 되어 버렸고, 결국 전부 버릴 수밖에 없었다. 다시 쓰는 과정에서는 refactoring이 워크플로의 핵심이 됐다. AI가 큰 덩어리의 코드를 한 번 뽑아내고 나면, 나는 항상 한발 물러서서 "이거 못생겼나?"를 물었다. 어떤 때는 AI가 그걸 깔끔하게 정리해줬다. 또 어떤 때는 AI는 보지 못하지만 내가 볼 수 있는 큰 추상화가 있었고, 나는 방향만 제시하고 실행은 맡겼다. 취향이 있다면, 잘못된 접근법의 비용은 급격히 낮아진다. 빠르게 구조를 다시 잡을 수 있기 때문이다.
가르쳐주는 조수
AI를 쓴 여러 방식 중에서도, 연구와 학습 용도로 쓴 경우가 투입 시간 대비 가치가 가장 높았다.
나는 예전에도 interpreter와 parser를 다뤄본 적이 있지만, Wadler-Lindig pretty printing은 들어본 적도 없었다. formatter를 만들어야 했을 때 AI는 내가 이해할 수 있는 관점에서 구체적이고 바로 쓸 수 있는 설명을 해줬고, 더 공부할 수 있도록 논문까지 짚어줬다. 물론 결국 혼자서도 찾아냈을 수는 있다. 하지만 AI는 하루 이틀쯤 읽고 헤맬 일을, "근데 이건 왜 이렇게 되는 거지?"를 계속 물어볼 수 있는 밀도 높은 대화로 압축해 줬고, 덕분에 진짜로 이해할 수 있었다.
이건 내가 한 번도 제대로 해보지 않은 영역에도 그대로 적용됐다. 나는 C++와 Android 성능 분야에는 깊은 경험이 있지만, Rust tooling이나 editor extension API는 거의 손대본 적이 없었다. 그런데 AI와 함께하니 그건 큰 문제가 아니었다. 기초 원리는 비슷하고, 용어도 닮아 있고, 그 사이를 AI가 메워줬다. VS Code extension은 원래라면 API를 익히는 데만 하루 이틀이 걸렸을 테고, 그래야 겨우 시작할 수 있었을 거다. AI를 쓰니 한 시간 안에 돌아가는 extension이 나왔다.
며칠 동안 안 들여다본 프로젝트 부분을 다시 파악하는 데도 AI는 정말 유용했다. 내가 원하는 깊이를 조절할 수 있었기 때문이다. "이 컴포넌트 설명해줘"라고 하면 표면적인 리프레시를 받고, "자세한 선형 walkthrough를 해줘"라고 하면 더 깊이 들어가고, "이 repo에서 unsafe 사용을 감사해줘"라고 하면 문제 사냥 모드로 전환할 수 있었다. 컨텍스트 스위칭이 많으면 맥락은 금방 사라진다. AI는 필요할 때마다 그 맥락을 다시 불러오게 해줬다.
혼자였다면 못 만들었을 만큼
AI는 이 프로젝트를 존재하게 만든 것뿐 아니라, 이렇게까지 완성된 상태로 출시되게 만든 이유이기도 하다. 모든 오픈소스 프로젝트에는 중요하긴 하지만 절박하진 않은 긴 꼬리의 기능들이 있다. 이론상 어떻게 해야 하는지는 알지만, 핵심 작업이 더 급하다는 이유로 계속 뒤로 밀리는 것들 말이다. syntaqlite에도 그런 목록이 길었다. editor extension, Python 바인딩, WASM playground, 문서 사이트, 여러 생태계를 위한 패키징까지. AI 덕분에 이런 일들의 비용이 충분히 낮아져서, 오히려 건너뛰는 쪽이 더 이상한 선택처럼 느껴졌다.
그리고 UX에 쓸 정신적 여유도 생겼다. 구현에 에너지를 전부 써버리지 않아도 되니, 사용자가 처음 이 도구를 접할 때 어떤 느낌이어야 하는지를 생각할 수 있었다. 어떤 에러 메시지가 SQL을 고치는 데 실제로 도움이 될지, formatter 기본 출력은 어떤 모습이어야 할지, CLI 플래그는 직관적인지 같은 것들 말이다. 이런 게 바로 사람들이 한 번 써보고 마는 도구와 계속 쓰는 도구를 가르는 요소인데, AI는 내가 그런 부분까지 신경 쓸 여지를 만들어줬다. AI가 없었다면 훨씬 작은 무언가를 만들었을 것이고, 아마 editor extension도, docs site도 없었을 것이다. AI는 같은 프로젝트를 더 빨리 만들게 한 게 아니었다. 프로젝트가 무엇이 되는지 자체를 바꿨다.
AI가 치르게 한 비용
중독성
AI 코딩 도구를 쓰는 감각은 슬롯머신을 하는 것과 불편할 정도로 닮아 있다. 프롬프트를 보내고 기다리면, 뭔가 훌륭한 게 나오거나 완전히 쓸모없는 게 나온다. 나도 밤늦게까지 "프롬프트 하나만 더"를 해보고 싶어질 때가 많았고, 안 될 가능성이 높다는 걸 알면서도 그냥 어떤 결과가 나올지 보고 싶어서 계속 시도하곤 했다. 매몰비용의 오류도 작동했다. AI에 전혀 안 맞는 작업이라는 게 분명한데도, "이번엔 표현만 좀 바꿔보면 될지도"라고 스스로를 설득하면서 붙잡고 있었다.
피로가 쌓일수록 이 악순환은 더 심해졌다. 에너지가 있을 때는 정확하고 범위가 잘 잡힌 프롬프트를 쓸 수 있었고, 실제로 생산성도 좋았다. 그런데 피곤해지면 프롬프트가 흐려지고, 결과물도 나빠지고, 나는 다시 시도하고, 그 과정에서 더 지쳤다. 이런 경우엔 그냥 내가 직접 구현하는 게 더 빨랐을 가능성이 크지만, 그 루프에서 빠져나오기가 너무 어려웠다.
감각을 잃는 순간
프로젝트를 하면서 몇 번이나 코드베이스에 대한 내 정신적 모델을 잃어버렸다. 전체 아키텍처나 큰 그림이 아니라, 매일매일의 디테일 말이다. 뭐가 어디에 있는지, 어떤 함수가 어떤 함수를 호출하는지, 작은 결정들이 어떻게 쌓여서 작동하는 시스템이 되는지 같은 것들. 그렇게 되면 예상 못 한 문제가 튀어나오고, 나는 도대체 뭐가 어떻게 잘못되고 있는지 전혀 감을 못 잡는 상태에 빠지곤 했다. 그 느낌이 정말 싫었다.
더 깊은 문제는, 이런 단절이 결국 의사소통 붕괴로 이어진다는 점이었다. 지금 무슨 일이 벌어지고 있는지 머릿속 연결이 끊기면, 에이전트와 의미 있는 대화를 나누는 게 불가능해진다. 모든 대화가 점점 길고 장황해진다. 원래라면 "FooClass를 이렇게 바꿔"라고 말할 일을, "Bar를 하는 그 무언가를 이렇게 바꿔"라고 말하게 된다. 그러면 에이전트는 Bar가 뭔지, 그게 FooClass와 어떻게 대응되는지 스스로 추론해야 하고, 종종 틀린다. 코드를 이해하지 못하는 매니저가 개발자에게 공상적인 요구나 불가능한 요구를 한다고 엔지니어들이 늘 불평해 온 것과 정확히 같은 문제다. 다만 이번에는 내가 그 매니저가 되어버린 것이다.
이를 해결하기 위해 구현이 끝나자마자 코드를 바로 읽고, "내가 했다면 어디를 다르게 했을까?"를 적극적으로 따져보는 습관을 들였다.
물론 어떤 의미에서는, 이 얘기의 상당 부분은 몇 달 전에 내가 직접 쓴 코드에도 그대로 해당한다(AI 코드란 첫날부터 레거시 코드다라는 감각이 있는 이유도 그래서다). 다만 AI는 원래 손으로 타이핑하면서 생기는 근육 기억을 쌓지 않기 때문에, 그 드리프트가 훨씬 빨리 온다.
천천히 진행되는 부식
3개월 동안 조금씩 겪고 나서야 알아차린 문제들도 있었다.
AI를 쓰면 중요한 설계 결정을 자꾸 미루게 된다는 걸 느꼈다. refactoring 비용이 싸니까, 언제든 "나중에 정리하지 뭐"라고 할 수 있었다. 게다가 AI는 코드를 생성하던 그 산업 규모의 속도로 refactoring도 해낼 수 있었기 때문에, 미루는 비용이 낮게 느껴졌다. 하지만 실제로는 그렇지 않았다. 결정을 미루는 동안 코드베이스는 계속 헷갈리는 상태로 남아 있었고, 그건 내 사고력을 서서히 갉아먹었다. vibe-coding 한 달은 그 극단적인 사례였다. 문제 자체는 이해하고 있었지만, 어려운 설계 결정을 좀 더 일찍, 좀 더 단호하게 내렸다면 훨씬 빨리 올바른 아키텍처로 수렴할 수 있었을 것이다.
테스트도 비슷한 종류의 잘못된 안도감을 줬다. 테스트가 500개 넘는다는 사실은 든든하게 느껴졌고, AI 덕분에 테스트를 더 늘리는 것도 쉬웠다. 하지만 미래에 부딪힐 모든 edge case를 인간이나 AI가 다 예측할 수는 없다. vibe-coding 단계에서 몇 번이나 테스트 케이스를 떠올리다가, 어떤 컴포넌트의 설계 자체가 완전히 잘못됐고 전면 재작업이 필요하다는 걸 깨달은 적이 있다. 이건 내 불신이 커진 중요한 이유 중 하나였고, 결국 전부 버리고 처음부터 다시 가기로 한 결정에도 크게 작용했다.
결국 내가 배운 건, AI 시대에도 소프트웨어의 "기본 규칙"은 여전히 그대로라는 점이다. 탄탄한 토대, 즉 명확한 아키텍처와 잘 정의된 경계가 없으면, 나타나는 버그를 끝없이 쫓아다니는 상태에서 벗어날 수 없다.
시간 감각의 부재
계속 머릿속에 맴돈 것 중 하나는, AI가 시간의 흐름을 얼마나 이해하지 못하는가였다. AI는 어떤 시점의 코드베이스를 본다. 하지만 인간처럼 시간을 느끼지는 못한다. 나는 어떤 API를 실제로 써보면 어떤 느낌인지, 그 API가 몇 달 혹은 몇 년에 걸쳐 어떻게 변해왔는지, 왜 어떤 결정이 내려졌고 나중에 왜 뒤집혔는지 이야기할 수 있다.
이런 이해 부족이 낳는 자연스러운 문제는, 과거에 했던 실수를 또 반복하면서 교훈을 다시 배워야 하거나, 혹은 처음에는 잘 피했던 새로운 함정에 다시 빠지면서 장기적으로 더 느려진다는 점이다. 내 생각엔 이건 왜 뛰어난 시니어 엔지니어 한 명을 잃는 일이 팀에 그렇게 큰 타격이 되는지와 비슷한 문제다. 그런 사람들은 어디에도 적혀 있지 않은 역사와 맥락을 몸에 지니고 있고, 주변 사람들에게 방향 감각을 제공해준다.
이론적으로는 spec과 문서를 최신 상태로 유지해서 이런 맥락을 보존하려고 할 수 있다. 하지만 AI 이전에도 우리가 그걸 제대로 못 했던 데는 이유가 있다. 암묵적인 설계 결정을 빠짐없이 문서로 남기는 건 엄청나게 비싸고 시간이 많이 드는 일이다. AI가 초안을 써주는 데 도움을 줄 수는 있다. 하지만 정말 중요한 걸 정확히 담아냈는지는 자동으로 검증할 방법이 없기 때문에, 결국 사람이 직접 결과를 검토해야 한다. 그리고 그 역시 여전히 시간이 많이 든다.
여기에 컨텍스트 오염 문제도 있다. API A에 대한 설계 노트가 언제 API B에 반영될지 알 수 없다. 일관성은 코드베이스가 제대로 굴러가게 만드는 핵심 요소인데, 그러려면 지금 작업 중인 대상의 맥락만 필요한 게 아니라 비슷한 방식으로 설계된 다른 부분들에 대한 감각도 함께 있어야 한다. 그런데 무엇이 관련 있고 무엇이 아닌지 가르는 판단 자체가, 바로 조직 내 축척된 지식이 해주던 역할이다.
상대성
위 내용을 돌아보면, AI가 도움이 된 순간과 해가 된 순간의 패턴은 꽤 일관됐다.
내가 이미 깊이 이해하고 있는 문제를 다룰 때 AI는 정말 훌륭했다. 출력물을 즉시 검토할 수 있었고, 문제가 반영되기 전에 잡아낼 수 있었고, 혼자서는 도저히 못 냈을 속도로 움직일 수 있었다. parser rule 생성이 가장 분명한 예였다. 각 규칙이 무엇을 만들어내야 하는지 내가 정확히 알고 있었기 때문에, AI가 낸 결과를 1~2분 안에 검토하고 빠르게 반복할 수 있었다.
설명은 할 수 있지만 아직 완전히 알지는 못하는 문제를 다룰 때, AI는 괜찮았지만 더 많은 주의가 필요했다. formatter를 위해 Wadler-Lindig를 익힐 때가 그랬다. 내가 원하는 바를 말로 풀 수 있었고, 결과가 맞는 방향으로 가고 있는지 평가할 수 있었고, AI의 설명에서 배우기도 했다. 하지만 계속 적극적으로 개입해야 했고, AI가 내놓은 걸 그대로 받아들일 수는 없었다.
내가 뭘 원하는지조차 모르는 문제를 다룰 때, AI는 도움이 안 되거나 오히려 해로웠다. 프로젝트 아키텍처가 가장 명확한 사례였다. 초반 몇 주 동안 나는 AI가 제시하는 막다른 길을 따라가며 많은 시간을 보냈고, 그 순간엔 생산적으로 느껴졌지만 결국 검토를 견디지 못하는 설계들을 여기저기 탐색했다. 돌이켜보면, 애초에 AI를 루프에 넣지 않고 그냥 혼자 곰곰이 생각하는 편이 더 빨랐을지도 모르겠다는 생각이 든다.
하지만 전문성만으로도 충분하지는 않다. 내가 문제를 깊이 이해하고 있어도, 그 작업에 객관적으로 검증 가능한 정답이 없으면 AI는 여전히 힘들어했다. 구현에는 적어도 로컬 수준에서는 정답이 있다. 코드는 컴파일되고, 테스트는 통과하고, 출력은 요청한 것과 맞아야 한다. 설계는 그렇지 않다. 객체지향이 처음 부상한 지 수십 년이 지났는데도 우리는 아직도 그걸 두고 논쟁하고 있다.
구체적으로는, syntaqlite의 공개 API를 설계할 때 이 문제가 가장 선명하게 드러났다. 3월 초 며칠 동안 나는 API refactoring만 했고, 숙련된 엔지니어라면 본능적으로 피했을 어색한 문제들을 손으로 하나하나 바로잡아야 했다. 그런데 AI는 그걸 완전히 엉망으로 만들어놨다. "이 API는 쓰기 편한가", "이 API가 사용자의 실제 문제를 푸는 데 도움이 되는가" 같은 건 테스트나 객관적 지표로 판정할 수 없다. 그리고 바로 그래서 코딩 에이전트가 이 부분에서는 정말 형편없었다.
이 대목에서 나는 다시, 한때 푹 빠져 있던 물리학, 그중에서도 상대성이론을 떠올리게 된다. 작은 국소 영역만 보면 물리 법칙은 단순하고 뉴턴식으로 보인다. 하지만 시야를 넓히면 시공간은 국소적 그림만으로는 예측할 수 없는 방식으로 휘어진다. 코드도 똑같다. 함수나 클래스 수준에서는 대체로 분명한 정답이 있고, AI는 거기서 뛰어나다. 하지만 아키텍처는 그 모든 국소적인 조각들이 상호작용하면서 생기는 것이고, 국소적으로만 맞는 컴포넌트를 이어붙인다고 좋은 전역적 거동이 나오지는 않는다.
매 순간 내가 이 축들 중 어디에 서 있는지 아는 것, 그게 AI를 효과적으로 쓰는 핵심 역량이라고 나는 생각한다.
마무리
8년은 어떤 프로젝트를 머릿속에 품고 있기엔 긴 시간이다. 그런데 3개월 작업만에 이 SQLite 도구들이 실제로 존재하고, 제대로 돌아가는 모습을 보게 된 건 엄청난 성과다. 그리고 AI가 없었다면 이 결과는 없었을 거라는 걸 나도 아주 잘 알고 있다.
하지만 그 과정은 사람들이 흔히 올리는 것처럼 깔끔하고 직선적인 성공담이 아니었다. 나는 vibe-coding으로 한 달을 통째로 날렸고, 내가 실제로 이해하지 못하는 코드베이스를 관리하는 함정에 빠졌고, 그 대가로 전체 재작성까지 치러야 했다.
내 결론은 단순하다. AI는 구현 단계에서는 엄청난 force multiplier이지만, 설계를 대신하게 두면 위험하다. 구체적인 기술 질문에 대한 정답을 내놓는 데는 탁월하지만, 역사도 모르고, 취향도 없고, 사람이 실제로 그 API를 쓰면서 어떤 감정을 느낄지도 이해하지 못한다. 소프트웨어의 "영혼"까지 AI에 맡기면, 결국은 예전보다 훨씬 빠르게 벽에 부딪히게 될 뿐이다.
내가 다른 사람들에게서 더 많이 보고 싶은 것도 바로 이런 이야기다. 이 글에서 내가 해보려 한 것처럼, 이런 도구로 실제 소프트웨어를 만들면서 겪은 정직하고 구체적인 기록 말이다. 주말 장난감 프로젝트나 일회성 스크립트가 아니라, 실제 사용자와 부딪히고, 버그 리포트를 받고, 심지어 만드는 사람 본인의 생각까지 바뀌는 현실을 견뎌야 하는 그런 소프트웨어 말이다.
