버그는 AI가 모른다, 이유는 개발자가 안다
Cursor나 GitHub Copilot으로 코드를 생성하다 보면 묘한 버그를 마주하는 순간이 온다. 코드 자체는 그럴듯하다. 문법도 맞고, 패턴도 익숙하다. 그런데 화면이 기대한 대로 동작하지 않는다. 이 순간 AI에게 다시 물어봐도 비슷한 코드가 돌아올 뿐이다. 왜냐하면 AI는 증상을 보지 못하고, 원리를 이해하지 못하기 때문이다.
최근 velog에 올라온 'React는 왜 setState를 비동기로 처리할까?' 포스트는 이 문제를 아주 정직하게 건드린다. setCount(1)을 호출한 직후 console.log(count)를 찍으면 왜 0이 출력되는가. 많은 개발자가 한 번쯤 겪는 이 혼란은, 사실 React의 설계 철학 전체가 압축된 순간이다.
setState는 '변경 명령'이 아니라 '변경 요청'이다
핵심을 한 줄로 정리하면 이렇다. setState는 상태를 즉시 바꾸는 함수가 아니라, React에게 상태 변경을 요청하는 함수다. React는 이 요청을 받아 일단 대기열에 등록하고, 이벤트 핸들러 실행이 끝난 뒤 수집된 변경들을 한 번에 처리한다. 그래서 같은 함수 스코프 안에서 state를 읽으면 여전히 이전 값이 보인다.
이 설계의 이유는 성능이다. 하나의 이벤트 핸들러 안에서 setCount, setName, setAge를 연달아 호출한다고 상상해보자. 만약 각 호출마다 즉시 렌더링이 발생한다면 세 번의 불필요한 재계산이 일어난다. React는 이를 막기 위해 변경 요청을 모아두고 적절한 시점에 단 한 번만 렌더링한다. React 18의 Automatic Batching은 이 원칙을 이벤트 핸들러 밖—setTimeout, Promise 콜백—까지 확장한 것이다.
이 원리를 모르면 setCount(count + 1)을 세 번 연달아 호출했을 때 왜 1만 증가하는지 설명할 수 없다. 세 호출 모두 동일한 렌더링 시점의 count를 참조하기 때문에 결국 setCount(1)을 세 번 요청한 것과 다르지 않다. 해법은 함수형 업데이트—setCount(prev => prev + 1)—이고, 이 패턴이 존재하는 이유 역시 위의 비동기 처리 구조에서 비롯된다.
React Native로 3개월에 게임 두 개, 그리고 숨어 있던 렌더링 문제
dev.to에 올라온 솔로 개발자 Ri의 경험담은 다른 각도에서 같은 교훈을 건넨다. 그는 Unity를 배우는 대신 이미 알고 있던 React Native로 iOS 게임 두 편을 3개월 만에 출시했다. 빠른 프로토타이핑, 익숙한 도구, 빠른 검증—전형적인 린 접근이다.
그런데 그가 예상치 못했다고 고백한 지점이 흥미롭다. 수백 개의 브릭 셀을 부드럽게 렌더링하는 것이 생각보다 훨씬 어려웠다는 것이다. FlatList 튜닝, React.memo, 불필요한 리렌더 제거—결국 React 렌더링 사이클을 제대로 이해하지 않고서는 게임이 버벅거릴 수밖에 없었다. React Native는 게임 엔진이 아니지만, 렌더링 원리를 이해하면 게임 엔진처럼 쓸 수 있다는 것을 그는 몸으로 증명했다.
AI 생성 코드의 사각지대, 바로 이 지점이다
두 이야기가 교차하는 지점이 바로 지금 우리가 주목해야 할 곳이다. AI 코딩 도구는 패턴을 잘 안다. useState 훅을 어떻게 선언하는지, 컴포넌트를 어떻게 분리하는지, FlatList에 keyExtractor를 어떻게 넘기는지—이런 구문 수준의 지식은 AI가 개발자보다 빠르고 정확하게 뱉어낸다.
하지만 AI는 왜 그렇게 동작하는지를 맥락에 맞게 추론하지 못한다. 특정 컴포넌트에서 상태 업데이트가 연속으로 일어날 때 배칭이 기대대로 작동하는지, 함수형 업데이트가 필요한 시점은 언제인지, 특정 리스트가 유독 느린 이유가 렌더링 사이클 어디에 있는지—이것은 증상을 보고 원리로 거슬러 올라가는 추론이다. 지금의 AI는 이 방향으로는 아직 약하다.
결국 AI가 만들어준 코드를 프로덕션에서 믿으려면, 개발자가 그 코드의 동작 경계를 스스로 그릴 수 있어야 한다. setState가 비동기라는 사실을 모르는 채 AI가 생성한 상태 관리 코드를 그대로 붙이면, 디버깅은 AI가 아니라 개발자 몫이 된다.
원리 이해는 줄어드는 게 아니라, 더 중요해진다
AI 도구가 코드 생성 속도를 끌어올릴수록, 역설적으로 렌더링 원리를 이해하는 개발자의 가치는 높아진다. 코드를 빠르게 쓰는 능력은 평준화되지만, 코드가 왜 그렇게 동작하는지를 설명하고 문제를 구조적으로 판단하는 능력은 평준화되지 않는다.
프로토타입을 빠르게 검증하고 싶다면 AI를 쓰면 된다. 하지만 그 프로토타입이 실제 사용자를 만나는 순간—성능이 튀거나, 상태가 꼬이거나, 렌더링이 기대와 다르게 동작할 때—그 자리를 메우는 것은 React 동작 원리를 체화한 개발자다. Ri가 React Native로 게임을 완성할 수 있었던 것도, 결국 렌더링 최적화를 직접 파고들었기 때문이다.
AI 코딩 도구 시대에 공부해야 할 것이 줄어든다고 생각한다면, 그건 절반만 맞다. 구문과 패턴은 AI에게 넘겨도 된다. 하지만 setState가 비동기인 이유, 배칭이 렌더링을 어떻게 최적화하는지, 함수형 업데이트가 왜 필요한지—이 질문들에 대한 답은 여전히 개발자의 머릿속에 있어야 한다.