세미콜론 논쟁은 그만, 진짜 문제가 터지고 있다
솔직히 말하면, 저도 초반에는 PR 리뷰할 때 들여쓰기, 변수명, 주석 스타일 같은 것들 엄청 잡았습니다. 리뷰 코멘트가 30개씩 달리고, 작성자는 방어적이 되고, 정작 런타임에서 터질 버그는 슬쩍 넘어가는 구조. 지금 돌이켜보면 진짜 중요한 걸 놓치고 있었던 거죠.
그런데 지금은 그 문제가 개인 습관 수준을 넘어섰습니다. AI 코딩 도구가 PR 생성 비용을 바닥으로 떨어뜨리면서, 리뷰어 한 명이 하루에 감당해야 하는 PR 개수가 폭발적으로 늘어났거든요. geeknews에서 다룬 'PR의 환상' 아티클이 정확히 짚은 부분입니다. 생성은 빨라졌는데, 검토는 여전히 사람의 시간과 집중력에 의존한다는 비대칭이 극한까지 증폭됐다는 거.
PR이 원래 하던 일이 뭐였더라
여기서 한 발짝 물러나야 해요. PR이 원래 무엇을 위한 장치였는지를 다시 생각해보면, 단순히 "코드 문법 맞나요, 테스트 통과했나요"를 확인하는 절차가 아니었습니다. 작성자가 "나는 왜 이렇게 만들었는지 설명할 수 있다"를 암묵적으로 약속하는 구조였고, 리뷰어는 코드 뒤의 설계 의도와 판단까지 검토하는 역할이었죠. 머지 버튼은 '코드 승인'이 아니라 '팀이 이 변경의 책임을 함께 지겠다는 사회적 합의'였던 겁니다.
AI가 만든 코드는 테스트를 통과하고 컴파일도 됩니다. 그런데 왜 그렇게 작성됐는지, 어떤 제약 조건 하에서 그 판단이 내려졌는지는 비어 있는 경우가 많아요. 시간이 지나 장애가 발생하면 팀은 버그를 고치는 게 아니라, 의도 없는 코드를 추측하며 해석하는 상황에 놓이게 됩니다. 일반적인 버그보다 이게 훨씬 무서운 이유입니다.
그래서 React PR, 어디를 봐야 하나
dev.to에 올라온 "Code Review for TypeScript React: What to Look For"는 1,000개 이상의 React PR을 리뷰한 경험에서 나온 우선순위 피라미드를 제시합니다. 핵심은 간단해요. 들여쓰기 같은 스타일 문제에 에너지를 쏟기 전에, 실제로 프로덕션을 터뜨릴 수 있는 것들을 먼저 보라는 거죠.
최우선(Must Fix) 으로 봐야 할 것은 타입 안전성 위반, 로직 버그, 보안 취약점, 성능 이슈입니다. 그다음이 아키텍처 설계, 접근성, 에러 핸들링 같은 중간 우선순위고, 변수명이나 코드 정리 같은 건 맨 마지막이에요. 세미콜론 논쟁은 아예 목록에 없고요.
타입 안전성: any는 그냥 패스하면 안 됩니다
any 하나가 TypeScript를 쓰는 이유 전체를 무력화합니다. function handleSubmit(data: any) 같은 코드가 PR에 있으면 무조건 잡아야 해요. 컴파일 시점에 잡힐 버그가 런타임 폭탄으로 돌아오는 거니까요.
더 까다로운 건 as 타입 단언입니다. const user = (await response.json()) as User 이런 코드, 서버가 다른 shape을 반환하면 그냥 뚫립니다. Zod 같은 런타임 검증 라이브러리로 실제로 파싱하는 패턴이 훨씬 안전해요. null/undefined 체크 누락도 마찬가지입니다. user.name을 바로 접근하기 전에 if (!user) 분기가 있는지, 혹은 옵셔널 체이닝 user?.name이 쓰였는지 반드시 확인해야 합니다.
React Hooks: 규칙 위반은 조용히 터집니다
Hooks 관련 버그는 특히 악질인 게, 코드가 '돌아가는 것처럼 보이다가' 특정 조건에서만 터지거든요. PR 리뷰에서 반드시 확인할 것들이 있습니다.
첫째, 조건부 Hook 호출. if (condition) { useFetch(...) } 이런 패턴은 Hooks의 기본 규칙 위반입니다. ESLint react-hooks/rules-of-hooks가 잡아주긴 하는데, 그 설정이 켜져 있는지도 확인해야 해요.
둘째, useEffect 의존성 배열 누락. useEffect(() => { fetchUser(userId) }, []) 이렇게 userId를 빠뜨리면 userId가 바뀌어도 fetch가 안 날아갑니다. stale closure 버그의 클래식. eslint-disable 주석이 있다면 그 의도가 명시적으로 설명되어 있어야 해요.
셋째, 불필요한 리렌더링. 인라인 객체나 함수를 props로 넘기면 React.memo가 있어도 매번 새 참조가 생겨서 리렌더링이 터집니다. useCallback, useMemo, 또는 컴포넌트 외부로 상수를 빼는 패턴이 필요해요.
넷째, 상태를 props에서 파생시키는 패턴. useState(initialCount)로 초기화하고 initialCount가 바뀌어도 상태가 안 바뀌는 구조, 생각보다 자주 봅니다. props를 직접 쓰거나 key로 리셋하거나, 정말 필요하면 useEffect로 동기화하는 방식이 맞아요.
AI 코드가 만드는 새로운 리뷰 과제
AI가 생성한 React 코드는 위에 나열한 패턴 오류를 상당히 잘 피해냅니다. 훈련 데이터에 베스트 프랙티스가 잔뜩 들어가 있으니까요. 문제는 'PR의 환상' 기사가 지적한 지점, 바로 설명 가능성의 공백입니다.
useMemo로 감싸진 복잡한 필터링 로직이 있다고 해봐요. 코드 자체는 문법적으로 맞고, 타입도 맞고, 테스트도 통과합니다. 그런데 왜 이 의존성 배열이 이렇게 구성되었는지, 왜 이 시점에 메모이제이션이 필요하다고 판단했는지, 어떤 성능 측정 결과를 근거로 이 최적화를 선택했는지—그게 비어 있습니다. 6개월 후에 이 코드를 건드려야 하는 사람은 추측하며 읽어야 해요.
그래서 AI 생성 코드가 포함된 PR을 리뷰할 때는 질문의 방향이 달라져야 합니다. "이 코드가 돌아가나?"에서 "이 코드를 작성자가 설명할 수 있나?" 로요.
실전 리뷰 체크리스트: 이 순서로 보세요
Figma 시안을 px 단위로 확인하듯, PR도 순서와 기준이 있어야 합니다. 제가 실제로 쓰는 흐름입니다.
- 타입 안전성:
any사용, 근거 없는as단언, null 체크 누락, 타입 내로잉 오류 - Hooks 규칙: 조건부/루프 내 Hook, 의존성 배열 누락, eslint-disable 근거 명시 여부
- 상태 관리: 직접 mutation, props에서 파생된 state 동기화 문제
- 성능: 렌더링 내 고비용 연산 (useMemo 없는 filter/sort/map), 인라인 객체/함수로 인한 불필요한 리렌더
- 설명 가능성: PR description에 "왜 이렇게 바꿨는지"가 있는가, 복잡한 로직에 주석이 있는가, AI 생성 코드라면 작성자가 검토했다는 근거가 있는가
스타일, 변수명, 주석 위치는 맨 마지막. 자동화(ESLint, Prettier, Husky)에 맡길 수 있는 건 그냥 맡기세요.
게이트를 버릴 게 아니라, 다시 정의해야 한다
xz Utils 백도어 사태가 보여준 것처럼, PR 게이트는 AI 이전에도 이미 취약했습니다. OpenClaw 사례는 개인 신뢰 모델로 설계된 게이트가 글로벌 규모의 기여 속도를 감당 못 할 때 무슨 일이 생기는지를 압축해서 보여줬고요. GitHub가 2026년 2월에 PR 비활성화 옵션을 추가한 것도, 단순 기능 추가가 아니라 "PR이 항상 선이다"라는 전제를 플랫폼 스스로 내려놓는 신호로 읽어야 합니다.
PR을 없애자는 게 아닙니다. 게이트의 조건을 바꿔야 한다는 거예요. 컴파일 성공과 테스트 통과는 필요조건이지 충분조건이 아닙니다. 이 변경의 이유를 추적할 수 있는가, 설계 판단의 흔적이 남아 있는가, 작성자가 이 코드를 소유하고 있는가—이 질문이 PR 게이트의 새로운 최소 조건이 되어야 합니다.
이유를 추적할 수 없는 PR은 팀이 소유하는 지적 자산이 아니라, 팀을 지배할 기술 부채입니다. AI가 코드를 빠르게 쏟아낼수록, 리뷰어가 이 질문을 더 날카롭게 던져야 하는 이유가 거기 있어요.