ChatGPT, GitHub Copilot, Cursor 및 그 형제 격인 도구들은 이제 프롬프트를 다 입력하기도 전에 React 컴포넌트를 툭 내뱉습니다. Next.js 라우트를 Supabase에 연결한다고요? 몇 초면 끝납니다. 꼬여버린 TypeScript 유틸리티를 리팩터링한다고요? 타입 가드(type guard)까지 포함된 세 가지 옵션을 바로 제안합니다. 현대적인 웹 스택에서 일하는 사람이라면, 이 경험은 거의 마법처럼 느껴질 수도 있습니다.
저는 이 도구들을 매일 사용합니다. 제 스택은 Next.js, TypeScript, Supabase이며, AI는 에디터 바로 옆에 자리 잡고 커스텀 훅을 스캐폴딩하거나, 데이터베이스 쿼리를 생성하거나, 지저분한 조건부 로직을 정리할 준비를 마친 상태입니다. 작은 단위의 작업에서 AI는 매우 빠른 주니어 개발자처럼 작동합니다. 문법을 완벽하게 숙지하고 있습니다. 제가 구글링해야 할 API 영역들을 기억하고 있죠. 보일러플레이트(boilerplate) 코드를 작성하는 데 지치지도 않습니다.
하지만 소프트웨어는 계속 고장 납니다. 앱은 점점 느려지는 것 같고, 고객 대시보드는 버벅거립니다. 엣지 케이스(edge case) 때문에 폼(form)이 충돌하기도 합니다. AI 덕분에 코딩이 훨씬 쉬워졌다면, 왜 소프트웨어를 사용하는 경험은 몇 년 전보다 더 나빠진 것처럼 느껴지는 걸까요?
정답은 구문(syntax)을 생성하는 것과 소프트웨어를 구축하는 것은 서로 다른 작업이라는 점입니다.
구문은 아키텍처가 아니다
AI는 토큰을 놀라울 정도로 잘 다룹니다. Supabase 실시간 채널을 리스닝하는 useEffect 훅을 작성해 달라고 하면, 컴파일 가능한 결과물을 내놓습니다. 타입이 지정되지 않은 JavaScript 파일을 엄격한 TypeScript로 변환하거나, 커피 한 잔을 마시기도 전에 Zod 검증이 포함된 폼 컴포넌트를 뚝딱 만들어낼 수도 있습니다.
AI가 할 수 없는 것은 여러분의 특정 애플리케이션의 윤곽을 이해하는 것입니다. 좋은 소프트웨어는 의도적인 상태 관리, 신중한 레이스 컨디션(race condition) 처리, 그리고 데이터가 실제로 존재하는 곳과 단순히 표시되는 곳을 구분하는 명확한 지도가 필요합니다. AI는 시스템이 아닌 당장 눈앞의 파일만 봅니다. 코드베이스를 하중을 견디는 벽이 있는 살아있는 구조물이 아니라, 평면적인 텍스트 복도처럼 취급합니다.
실제로 집에 살아본 적이 없는 건축가를 떠올려 보세요. 그들은 아름다운 평면도를 그릴 수 있고, 침실에 창문이 몇 개 있어야 하는지도 압니다. 하지만 2월에 어디서 파이프가 새는지, 여름철 무더위에 어느 복도를 사용할 수 없게 되는지는 모릅니다. 건물을 지탱하는 것은 바로 그런 '살아본 경험'에서 나오는 지식입니다. 코드도 마찬가지입니다.
두 가지 마찰 지점
엄격한 가드레일 없이 AI에게 더 큰 단위의 코드를 작성하게 하면, 똑같은 두 가지 문제가 반복해서 발생하는 것을 목격하게 됩니다.
첫째, AI는 여러분이 이미 구축해 놓은 디자인 패턴을 무시합니다. 팀에서 모든 데이터 페칭(fetching)을 전용 커스텀 훅 레이어로 분리했을 수도 있고, Supabase RLS 정책을 프론트엔드 헬퍼에 매핑하는 엄격한 컨벤션이 있을 수도 있습니다. AI는 상관하지 않습니다. 당장의 프롬프트를 해결할 수만 있다면, 버튼의 onClick 안에 생으로 supabase.from().select()를 던져버립니다. 코드는 돌아가고, 심지어 깔끔해 보이기까지 합니다. 하지만 이는 코드베이스 내에서 이질적인 존재(outlier)가 되며, 모든 이질적인 코드는 미래의 리팩터링 비용(tax)이 됩니다. 6개월 뒤, 누군가는 그 바늘을 찾아내 왜 존재하는지 이해하고, 다시 원래의 흐름에 맞게 조심스럽게 조정해야 합니다.
둘째, 단순함으로 충분한 상황에서도 복잡함을 추구합니다. 이 도구들은 추상 팩토리(abstract factory), 복잡한 리듀서 패턴, 다층 구조의 고차 컴포넌트(higher-order components)가 필요한 대규모 저장소들을 학습했습니다. 단순한 연락처 폼을 만들어 달라고 요청해도, 상태 머신(state machine), 컨텍스트 프로바이더(context provider), 그리고 세 개의 파일에 걸친 커스텀 훅 추상화 구조를 내놓을 수도 있습니다. 기술적으로 틀린 해결책은 아닙니다. 그저 무거울 뿐입니다. 불필요한 레이어가 추가될 때마다 인지적 부채(cognitive debt)가 쌓입니다. 작업을 건너뛴 것이 아니라, 이자를 붙여 뒤로 미룬 것입니다.
속도의 함정
여기에는 위험한 피드백 루프가 존재합니다. AI는 기능을 두 배 빠르게 만들게 해주지만, 인간의 주의력은 그만큼 확장되지 않습니다. 배포 시간을 절반으로 줄였다면, 코드 리뷰에는 두 배의 시간을 쓰고 있나요? 테스트는 더 많이 작성하고 있나요, 아니면 더 적게 작성하고 있나요?
실제로 생성된 코드는 권위 있어 보이기 때문에 믿어버리기가 매우 쉽습니다. 최신 문법을 사용하고, 주석은 딱 적절한 위치에 달려 있으며, 변수명은 전문적으로 들립니다. 하지만 그 매끄러운 겉모습 뒤에 미묘한 버그들이 숨어 있습니다. 세터(setter)를 누락한 훅의 의존성 배열, 기술적으로는 맞지만 처리하지 못한 null 상태를 허용하는 TypeScript 타입, 특정 스키마에서 소프트 삭제(soft-deleted)된 행을 고려하지 않은 Supabase 쿼리 같은 것들 말이죠. 배포 속도를 맞추기 위해 모든 줄을 읽는 대신 훑어보게 됩니다. 월요일에는 그 속도가 환상적으로 느껴지겠지만, 금요일의 디버깅 세션은 자정까지 이어질 것입니다.
진짜 비용
이 비용을 치르는 사람은 개발자가 아닙니다. 바로 최종 사용자입니다.
소프트웨어가 점점 더 무겁고 투박하게 느껴지는 이유는, 팀이 관리할 수 있는 속도보다 복잡성이 더 빠르게 증가하고 있기 때문입니다. 우리는 무적이라도 된 듯한 기분을 느끼게 해주는 도구들을 갖춘 채, 더 적은 인원으로 더 큰 애플리케이션을 만들고 있습니다. 개발자 한 명이 오후 내에 대시보드 전체의 뼈대를 잡을 수 있게 되자, 조직은 수요일까지 대시보드 세 개를 가져오기를 기대합니다. 주의 없는 확장은 취약한 시스템을 만들어냅니다. 상태(state)는 비대해지고, 번들 크기는 야금야금 커지며, 레이스 컨디션(race condition)은 급증합니다. 인터페이스는 현대적으로 보일지 모르지만, 사용자가 뒤로 가기 버튼을 누르면 초기화되어 버리거나, AI가 생성한 데이터 페칭(data fetch)의 워터폴(waterfall) 현상을 프로파일링할 시간이 없어서 하이드레이션(hydration)에 4초나 걸리기도 합니다.
기계를 위해 일하지 말고, 기계와 함께 일하세요
이 말이 AI를 에디터에서 쫓아내야 한다는 뜻은 아닙니다. 경계가 필요하다는 뜻입니다.
AI가 잘하는 일에 활용하세요. 반복적인 TypeScript 인터페이스, 상용구(boilerplate) 성격의 Supabase 쿼리, Jest 설정과 같은 지루한 작업들은 AI에게 맡기세요.
