DevInsight

나중에 다시 보려고, AI로 정리해두는 기술 기록

Frontend
조회 3약 10분 읽기

AI가 만든 React를 의심해야 하는 순간

React Doctor는 AI agent와 개발자가 남긴 React 코드를 정적으로 훑어 state와 effect, 성능, 구조, 보안, 접근성 문제를 드러내는 도구다. lint를 넘어서 CI와 PR, agent workflow에 품질 게이트를 세우는 흐름을 다루기 좋은 주제다.

#React#React Doctor#Static Analysis#AI Coding#Code Review#CI#PR Review#Next.js

React에서 가장 위험한 순간은 화면이 멀쩡히 뜨고, 테스트도 통과하고, 심지어 lint 경고도 거의 없는데 어딘가 계속 찜찜할 때다. 버튼은 눌리고 데이터는 보인다. 그런데 페이지를 몇 번만 오가면 메모리가 조금씩 불어난다거나, 입력창이 특정 조건에서 갑자기 버벅인다거나, 네트워크 탭에 설명하기 어려운 중복 요청이 쌓인다. 이런 종류의 불안은 대개 문법이 아니라 구조에서 온다. 특히 자동 생성 도구가 작성한 React 코드에서는 이 불안이 훨씬 자주 나타난다.

자동화된 코드 작성이 React 생산성을 밀어 올린 것은 분명하다. 반복적인 컴포넌트 뼈대, form wiring, API 연결, CRUD 화면 구성은 예전보다 훨씬 빨라졌다. 문제는 React가 겉으로 보기보다 훨씬 규율이 많은 런타임이라는 점이다. JSX를 그럴듯하게 조합하는 능력과, React의 state와 effect 모델을 정확히 이해하는 능력은 다른 층위의 문제다. 코드를 빠르게 늘어놓는 쪽은 이미 충분히 강해졌지만, 그 코드가 렌더 사이클과 동기화 규칙을 존중하는지는 별개의 검증이 필요해졌다.

바로 그 지점에서 정적 분석 도구의 역할이 달라진다. 예전의 lint가 주로 문법적 일관성과 몇 가지 금지 패턴을 감시했다면, 최근의 React 분석 도구는 “이 코드는 왜 나중에 문제를 일으킬 가능성이 높은가”를 더 깊게 본다. state를 어디에 두었는지, effect가 정말 side effect인지 아니면 계산을 잘못 숨긴 것인지, props와 memoization이 실제로 성능에 도움이 되는지, 접근성과 보안이 컴포넌트 경계에서 새지 않는지까지 함께 본다. React Doctor 같은 이름이 붙는 이유도 거기에 있다. 단순한 스타일 검사기가 아니라, 겉으로 건강해 보이는 UI의 생체 신호를 재는 쪽에 가깝다.

그럴듯한 React가 자주 틀리는 이유

자동 생성 도구가 만든 React가 특히 의심스러운 이유는, 이 코드가 흔히 “결과 중심”으로 조립되기 때문이다. 화면에 무엇이 보여야 하는지는 잘 맞춘다. 하지만 React는 결과물보다 과정이 중요한 프레임워크다. 어떤 값이 source of truth인지, 어떤 연산이 render 중에 안전한지, 어떤 작업이 effect로 밀려야 하는지, 어떤 참조가 매 렌더마다 바뀌어도 괜찮은지 같은 규칙이 전체 안정성을 만든다.

예를 들어 검색창 하나를 붙이는 일은 쉬워 보인다. 자동 생성된 코드에서는 입력값, 필터링된 목록, 서버 요청 상태, 정렬 옵션, 선택된 항목, URL query 동기화가 모두 각자 state로 늘어나는 경우가 많다. 처음에는 잘 동작한다. 그러나 state가 많아질수록 불변식은 약해진다. 같은 사실을 두 군데 이상 저장하기 시작하고, effect는 그 틈을 메우기 위해 계속 추가된다. 결국 “A가 바뀌면 B를 맞춘다”, “B가 바뀌면 C를 갱신한다”, “C가 바뀌면 URL을 수정한다” 같은 체인이 생긴다. 이때부터 React 코드는 데이터를 표현하는 코드가 아니라, 어긋난 상태를 봉합하는 코드가 된다.

이 문제는 사람이 급하게 작성한 코드에서도 발생하지만, 자동 생성 도구가 만든 코드에서는 더 전형적으로 드러난다. 가장 쉬운 해법은 대개 state를 더 추가하는 것이기 때문이다. 화면이 꼬이면 derived value를 계산하기보다 새 state를 만든다. re-render가 많으면 근본 원인을 보지 않고 memo를 덧댄다. 서버 응답 타이밍이 꼬이면 effect 안에 guard를 넣는다. 이런 코드는 즉시 실패하지 않는다. 대신 유지보수 시점에서 비용을 폭발시킨다.

effect가 많아질수록 로직은 덜 보인다

React의 effect는 원래 외부 세계와 동기화하는 장치다. DOM API, subscription, timer, network, 브라우저 이벤트, imperative 라이브러리 통합처럼 render 바깥의 세계와 연결될 때 쓰는 것이 자연스럽다. 그런데 자동 생성된 코드에서는 effect가 비즈니스 로직의 하수구처럼 쓰이기 쉽다. 계산도 effect에서, 파생 상태 갱신도 effect에서, validation도 effect에서, 심지어 단순한 값 합성도 effect에서 처리한다.

문제는 effect가 많아질수록 코드의 시간축이 흐려진다는 데 있다. render는 선언적이지만 effect는 시간 의존적이다. 언제 실행되는지, 이전 클린업이 어떻게 동작하는지, dependency array가 빠짐없이 맞는지, 경쟁 상태는 없는지 생각해야 한다. 이건 한 줄 한 줄의 정답보다 흐름의 정합성이 중요하다. 그래서 겉보기에는 합리적인 effect도 운영 환경에서만 이상 신호를 낸다.

대표적인 징후는 이렇다. 특정 페이지에서만 API 호출이 두 번씩 나간다. 입력 중 debounce를 걸었는데도 간헐적으로 오래된 결과가 화면에 남는다. 모달을 열고 닫을 때 포커스가 튄다. 탭 전환 뒤 이전 subscription이 해제되지 않아 로그가 중복된다. 이런 현상은 개별 버그처럼 보여도, 실제로는 effect 설계가 불안정하다는 공통 원인을 가진다. 정적 분석 도구가 가치 있는 이유는 이 버그들이 재현되기 전 단계에서 구조적 냄새를 찾아낸다는 데 있다.

lint를 통과해도 불안한 코드가 있다

많은 팀이 “ESLint는 이미 돌고 있는데 무엇이 더 필요한가”라는 질문을 한다. 이 질문은 절반만 맞다. 일반적인 lint는 여전히 필수다. 하지만 React의 실패는 점점 더 문법이 아니라 해석의 영역에서 발생한다. Hook 규칙 위반처럼 명백한 오류는 기존 도구도 잘 잡는다. 반대로 정말 비용이 큰 문제는 대부분 “법적으로는 합법이지만 구조적으로는 위험한 코드”다.

예를 들어 불필요한 state duplication, render 중 계산해도 되는 값을 effect와 state로 우회하는 패턴, 컴포넌트 경계를 넘나드는 과도한 prop drilling, 분리할 필요가 없는 memoization, key 안정성 문제, stale closure 가능성, framework별 잘못된 data fetching 습관 같은 것들은 프로젝트가 커질수록 치명적이다. 이런 이슈는 코드를 한 줄씩 보는 리뷰로는 놓치기 쉽고, 일반 lint로는 지나치기 쉽다. React 특화 정적 분석이 필요한 이유는 바로 이 중간지대 때문이다.

더 흥미로운 부분은, 이런 도구가 품질 관문을 개인의 감각에서 팀의 시스템으로 옮긴다는 점이다. “이 코드는 왠지 찜찜하다”는 느낌은 숙련자에게는 유효하지만, 협업에서는 불안정한 기준이다. 반면 state/effect/performance/architecture/accessibility/security 같은 관점으로 문제를 표준화하면, 리뷰는 취향 싸움이 아니라 위험도 판단으로 바뀐다. 이 변화는 특히 Pull Request가 빠르게 쌓이는 팀에서 중요하다.

PR 화면에 나타나는 작은 경고가 조직을 바꾼다

정적 분석 도구를 로컬에서 한 번 돌려보는 것과, PR과 CI 안에 편입하는 것은 완전히 다른 일이다. 로컬 실행은 학습 도구에 가깝고, CI 통합은 운영 장치에 가깝다. 전자는 “내 코드에 무슨 문제가 있지?”를 묻고, 후자는 “이 문제를 팀이 어떤 시점에 멈출 것인가?”를 결정한다.

React 품질 이슈는 대개 merge 이후에 비용이 커진다. 한 사람이 만든 잘못된 effect 패턴이 다른 파일에 복제되고, 임시로 추가한 memo가 팀의 기본 습관처럼 굳어지고, 페이지 단위의 비대한 client component가 새 기능의 기본 템플릿이 된다. 이때 필요한 것은 단순한 지적이 아니라 초기에 제동을 거는 관문이다. PR에 인라인 annotation이 붙고, 변경된 파일 맥락에서 왜 문제가 되는지가 드러나고, 일정 수준 이상의 건강 점수를 기준선으로 삼을 수 있다면 리뷰의 질이 안정된다.

중요한 것은 이 관문이 엄격함만으로 성공하지 않는다는 사실이다. 처음부터 모든 규칙을 실패로 처리하면 반발이 생긴다. 특히 이미 부채가 많은 서비스는 더 그렇다. 좋은 도입은 대개 현 상태를 측정하는 것에서 시작한다. 지금 점수가 100점이 아니라는 사실보다, 어떤 범주의 문제가 반복되는지가 더 중요하다. effect 남용이 많은지, dead code가 누적되는지, 접근성 기본기가 약한지, Next.js 경계에서 server와 client가 뒤섞이는지 같은 패턴을 읽어야 한다. 품질 게이트는 벌점 시스템이 아니라 학습 곡선을 만드는 장치여야 오래 간다.

자동 생성 React에서 유독 자주 보이는 냄새

자동 생성 도구가 만든 React를 의심해야 하는 순간은 몇 가지가 있다. 첫째, state가 지나치게 많을 때다. 하나의 사용자 의도를 표현하는 데 여러 state가 동원되고, 그 관계를 effect가 관리한다면 이미 설계가 흔들리고 있을 가능성이 높다. 둘째, dependency array가 길고 복잡한 effect가 많을 때다. 이는 대개 effect가 너무 많은 책임을 떠안고 있다는 신호다. 셋째, 성능 최적화 흔적이 이른 시점부터 지나치게 많이 보일 때다. useMemo, useCallback, memo가 논리보다 먼저 등장하는 코드는 종종 원인 치료보다 증상 완화에 가깝다.

넷째, 프레임워크 경계가 흐릴 때다. Next.js라면 server component로 둘 수 있는 로직이 불필요하게 client로 내려와 있거나, URL state와 local state가 이중화되어 있거나, 데이터 fetching과 UI state가 한 컴포넌트 안에서 엉켜 있는 경우가 흔하다. 다섯째, 접근성과 보안이 마지막 장식처럼 붙어 있을 때다. aria-* 속성을 몇 개 덧댔다고 접근성이 해결되는 것은 아니고, dangerouslySetInnerHTML 주변에 sanitize 한 줄을 넣었다고 안전성이 자동 보장되는 것도 아니다. React 특화 분석이 이런 영역까지 보는 이유는, 프레임워크 사용법이 결국 사용자 경험과 공격 표면까지 연결되기 때문이다.

점수보다 중요한 것은 어떤 문제를 반복해서 만드는가

정적 분석 도구가 점수 체계를 제공하면 사람들은 쉽게 숫자에 끌린다. 82점인지 91점인지, 지난주보다 올랐는지 떨어졌는지, 배포 기준선을 몇 점으로 잡을지 논의하게 된다. 숫자는 분명 유용하다. 하지만 진짜 가치는 분류에 있다. 어떤 조직은 correctness보다 architecture 카테고리에서 더 많이 무너지고, 어떤 조직은 accessibility 기본기가 계속 발목을 잡는다. 어떤 프론트엔드 팀은 성능보다 dead code와 구조적 중복 때문에 속도가 떨어진다.

그래서 점수는 대시보드일 뿐 진단서는 아니다. 좋은 팀은 총점보다 반복되는 병목을 본다. 예컨대 state/effect 관련 경고가 계속 쌓인다면, 이는 개인의 실수보다 팀의 사고방식 문제일 수 있다. “동기화해야 할 값이 보이면 일단 state로 둔다”는 습관이 자리 잡았다는 뜻이다. architecture 경고가 많다면 컴포넌트 설계를 데이터 흐름보다 파일 분리 관점에서만 다루고 있을 가능성이 있다. dead code가 계속 늘어난다면 실험 기능을 정리하지 않는 운영 리듬의 문제일 수 있다. 도구는 경고를 보여주지만, 해석은 결국 팀의 일이다.

Next.js 시대에 더 까다로워진 판단

React 품질 검사가 최근 더 중요해진 배경에는 Next.js 같은 메타 프레임워크의 확장이 있다. 현대 React는 단순히 컴포넌트를 잘 작성하는 문제를 넘어, 서버와 클라이언트 경계를 언제 나눌지, 어떤 상태를 URL과 공유할지, 어떤 연산을 서버에서 끝낼지, 어떤 부분을 hydration에 맡길지까지 판단해야 한다. 자동 생성된 코드는 이 경계를 자주 뭉갠다. 일단 돌아가게 만드는 데는 성공하지만, 비용 구조는 나빠진다.

가장 흔한 실수는 client component를 너무 넓게 잡는 것이다. 편의상 상위에서 use client를 붙여버리면, 그 아래 subtree 전체가 클라이언트 책임으로 넘어간다. 그러면 번들 크기, hydration 비용, data flow 설계, 캐시 전략이 한꺼번에 바뀐다. 또 다른 실수는 서버에서 결정할 수 있는 값을 클라이언트 state로 다시 보관하는 것이다. 이중 진실은 React를 어렵게 만드는 가장 오래된 문제인데, 자동 생성 코드는 이를 매우 쉽게 재생산한다. 정적 분석 도구가 framework-aware해야 하는 이유가 여기에 있다. React 자체만 보아서는 놓치는 문제가 실제 앱에서는 더 크기 때문이다.

잘 잡는 도구가 좋은 도구는 아니다

여기서 한 번 더 생각해야 할 점이 있다. 많이 잡아내는 도구가 곧 좋은 도구는 아니다. 경고가 많다는 사실만으로는 아무 의미가 없다. 오히려 팀을 지치게 할 수 있다. 좋은 React 분석 도구는 세 가지 균형을 맞춰야 한다. 규칙은 충분히 날카로워야 하지만, 설명은 실전적이어야 하고, 통합 방식은 자연스러워야 한다.

설명이 실전적이어야 한다는 것은 “왜 이 패턴이 위험한가”가 단순 교과서 문장으로 끝나지 않아야 한다는 뜻이다. stale closure는 정의를 외우는 것으로 해결되지 않는다. 어떤 상황에서 오래된 값이 붙잡히고, 왜 UI에서는 드물게 보이지만 운영에서 크게 터지는지까지 연결돼야 한다. 통합 방식이 자연스러워야 한다는 것은 리뷰어가 새로운 대시보드를 하나 더 열어야 한다는 뜻이 아니어야 한다. PR, CI, 로컬 실행이라는 익숙한 흐름 안에 붙어야 팀이 받아들인다. 그리고 가장 중요한 조건은, 규칙이 현대 React 문법과 프레임워크 현실을 따라가야 한다는 점이다. 과거의 베스트 프랙티스를 절대 기준처럼 들이대는 분석은 오히려 해롭다.

사람이 해야 할 리뷰가 오히려 선명해진다

정적 분석이 강해질수록 사람의 리뷰가 덜 필요해질 것 같지만 실제로는 반대다. 기계가 반복 가능한 냄새를 먼저 걷어내면, 사람은 더 높은 수준의 질문에 집중할 수 있다. 이 state는 왜 존재하는가. 이 컴포넌트 경계는 사용자 행동 모델과 맞는가. 이 상호작용은 네트워크 지연에서 어떤 경험을 만드는가. 이 화면은 앞으로 세 기능이 더 붙어도 버틸 구조인가. 이런 질문은 여전히 자동화하기 어렵다. 하지만 저수준 패턴 검출이 정리되면, 그 질문이 훨씬 또렷해진다.

오히려 자동 생성 시대에는 사람의 리뷰가 더 개념적이어야 한다. 문법 오류나 자잘한 Hook 실수를 잡는 데 시간을 쓰는 순간, 정말 중요한 설계 토론이 뒤로 밀린다. 좋은 정적 분석 도구는 사람을 대체하는 것이 아니라, 사람의 판단력을 값싼 경고 소음에서 보호한다. 이건 품질 문제이면서 동시에 조직 설계 문제다.

운영 환경에서 먼저 보이는 신호들

React 구조 문제가 운영에서 어떻게 보이느냐를 아는 것도 중요하다. 품질 이슈는 종종 프론트엔드 내부에서만 감지되지 않는다. API 비용 상승, 캐시 효율 저하, 브라우저 성능 민원 증가, 세션 길이 감소, 특정 퍼널에서의 이탈 증가는 모두 UI 구조 불안정과 연결될 수 있다. 예를 들어 중복 fetch는 단순한 프론트엔드 버그가 아니라 백엔드 비용 문제로 번진다. 과도한 client boundary는 hydration 지연과 상호작용 지연으로 이어진다. 접근성 결함은 법적 리스크나 SEO 손실로 나타날 수 있다.

그래서 React 정적 분석은 프론트엔드 팀의 취향 도구가 아니라 운영 지표의 선행 신호로 봐야 한다. 오류 추적 시스템에 문제가 잡힌 뒤 대응하는 것은 이미 늦다. 정적 분석은 “아직 사고는 없지만 이 설계는 사고 친다”는 조용한 경고를 준다. 팀이 성숙할수록 이런 조용한 경고를 더 진지하게 받아들인다.

도입의 핵심은 통제보다 학습이다

새로운 품질 도구를 붙일 때 실패하는 팀은 대개 두 부류다. 하나는 처음부터 너무 강하게 막아 개발 흐름을 깨뜨리는 경우고, 다른 하나는 리포트만 쌓아두고 아무런 운영 루프를 만들지 않는 경우다. 전자는 반발을 사고, 후자는 망각된다. 중간 지점이 필요하다.

처음에는 PR 차단보다 가시화가 우선이다. 어떤 종류의 경고가 자주 나오는지, 어느 레이어에서 반복되는지, 특정 팀이나 기능군에 편향되는지 본다. 그다음 정말 비용이 큰 규칙부터 게이트로 승격한다. 예컨대 correctness와 security는 빠르게 강제하고, architecture나 performance는 추세 관리부터 시작할 수 있다. 이런 식의 단계적 도입은 “도구가 시키니까 고친다”가 아니라 “왜 이 문제를 지금 막아야 하는가”를 조직이 납득하게 만든다.

짧은 예시는 이런 식이다.

name: react-quality on: pull_request: jobs: doctor: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npx react-doctor@latest

설정 자체는 중요하지 않다. 더 중요한 것은 이 결과를 어떤 태도로 읽느냐다. 경고를 “또 하나의 귀찮은 봇 댓글”로 취급하면 실패한다. 반대로 리뷰 문화와 연결하면 도구는 꽤 빠르게 습관을 바꾼다.

의심은 비관이 아니라 성숙함이다

자동 생성 도구가 쓴 React를 의심하자는 말은, 자동화를 거부하자는 뜻이 아니다. 오히려 반대다. 자동화를 진지하게 쓰려면, 그 산출물을 검증하는 더 정교한 눈이 필요하다는 뜻이다. 코드 작성 비용이 급격히 내려간 시대에는 작성보다 선별이 중요해진다. 무엇을 받아들이고, 무엇을 다시 묻고, 무엇을 구조적으로 막을지 결정하는 능력이 팀의 경쟁력이 된다.

React는 자유로운 것처럼 보이지만 실제로는 꽤 엄격한 시스템이다. 잘못된 state 하나, 어색한 effect 하나, 필요 없는 client boundary 하나가 나중에 성능, 유지보수성, 접근성, 심지어 보안까지 흔든다. 자동 생성 도구는 이 엄격함을 자주 가볍게 넘긴다. 그래서 지금 필요한 것은 더 많은 코드가 아니라, 더 좋은 의심이다.

좋은 의심은 감정이 아니라 체계다. React Doctor 같은 도구가 흥미로운 이유는 바로 거기에 있다. “왠지 불안하다”를 “이 패턴은 이런 이유로 위험하다”로 번역해 준다. 그 번역이 로컬 실행에서 끝나지 않고 PR과 CI, 팀의 리뷰 흐름 안으로 들어올 때, 비로소 품질은 개인기에서 시스템으로 넘어간다. 그리고 그 순간부터 React 코드는 조금 덜 화려해질지 몰라도, 훨씬 더 오래 버틴다.

댓글

댓글을 읽어오는 중입니다.

같이 읽으면 좋은 글

방금 읽은 주제와 이어지는 글을 골랐습니다.

Frontend 전체 보기

이전 글

에이전트가 서로 일하는 방식이 제품 경쟁력을 바꾸는 순간