[번역] console.log()를 멈추고 프로파일링을 시작하라: 진짜 디버깅을 위한 개발자 가이드
Soshy·
원문: Stop console.log() and start profiling: A developer’s guide to real debugging
React 개발을 막 시작했을 무렵, 나는 코드가 어떻게 동작하는지 파악하기 위해 console.log()에 지나치게 의존했다. 터미널은 "여기", "데이터 패치 완료", "렌더링 중…" 같은 메시지들로 가득 찼다. 처음에는 꽤 체계적인 방법처럼 느껴졌지만, 어느 순간 내가 인사이트가 아닌 출력값을 수집하고 있다는 걸 깨달았다. 그 깨달음이 나를 로깅에서 프로파일링으로 이끌었고, 바로 그 전환이 그냥 돌아가는 코드와 실제 환경에서 제대로 동작하는 코드를 구분 짓는다.
console.log()가 옳은 것처럼 느껴지는 이유 (그리고 왜 한계에 부딪히는가)
대부분의 입문자에게 console.log()는 처음으로 손에 익는 디버깅 도구다. "이 컴포넌트가 렌더링됐나?", "지금 state가 뭐지?" 같은 즉각적인 질문에 바로 답을 준다. 기능을 만들면서 짧은 호흡으로 React를 배울 때, 이 피드백 루프는 이해보다 속도에 최적화되어 있다.
문제는 UI가 조금이라도 복잡해지는 순간 드러난다. React의 렌더는 명령형으로 순서대로 실행되는 단계가 아니라, state에 대한 재계산이다. 컴포넌트 본문 안에서 로그를 찍으면 예상보다 훨씬 많이 실행되는데, 특히 개발 모드에서 Strict Mode가 활성화된 경우엔 더 심하다. 나 역시 React의 렌더링 모델을 제대로 이해하기 전에 로그를 잘못 읽어서, 버그가 없는 곳에 버그가 있다고 오판한 적이 있다.
간단한 예시를 보자.
import { useState } from "react";
function ProductList({ products }) {
const [filter, setFilter] = useState("");
const filteredProducts = products.filter((p) =>
p.name.toLowerCase().includes(filter.toLowerCase())
);
console.log("ProductList rendered");
console.log("Filtered products:", filteredProducts);
return (
<div>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="Search products"
/>
<ul>
{filteredProducts.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
</div>
);
}
키를 누를 때마다 리렌더링이 발생하고, 터미널은 로그로 넘쳐난다. 값은 보이지만, 성능에 대한 인사이트도, 불필요한 렌더링이 UI를 느리게 만들고 있는지에 대해서도 알 수 없다.
프로파일링이 보여주는 것
로깅이 스크린샷을 찍는 것이라면, 프로파일링은 영상을 직접 보는 것이다. 공식 React 문서에 소개된 React Profiler는 어떤 컴포넌트가 렌더되는지, 얼마나 자주 렌더되는지, 각 렌더에 시간이 얼마나 걸리는지를 기록한다. Chrome의 Performance 도구는 렌더 지속 시간, 빈도, JavaScript 실행과 레이아웃 사이의 상호작용까지 측정해 준다.
import { Profiler } from "react";
function ProductListWrapper({ products }) {
const onRenderCallback = (
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime,
interactions
) => {
console.log({ id, phase, actualDuration, baseDuration });
};
return (
<Profiler id="ProductList" onRender={onRenderCallback}>
<ProductList products={products} />
</Profiler>
);
}
이 콜백이 알려주는 정보는 다음과 같다.
actualDuration: 실제 렌더에 걸린 시간baseDuration: 메모이제이션 없이 걸렸을 시간phase: 마운트인지 업데이트인지
이 데이터를 활용하면 불필요한 리렌더나 비용이 큰 연산 같은 성능 병목을 정확히 짚어내고 효과적으로 최적화할 수 있다.
내가 실제로 겪은 실수 (그리고 해결 과정)
한번은 키보드 내비게이션을 지원하는 필터 리스트를 만든 적이 있다. 동작은 했지만, 타이핑할 때 눈에 띄는 입력 지연이 있었다. state 변화를 빠짐없이 로그로 찍어봤지만, 이상한 건 없어 보였다.
프로파일링은 다른 이야기를 해줬다. 키 입력마다 화면 밖 아이템을 포함한 전체 리스트가 리렌더되고 있었던 것이다. 원인은 미묘했다. 렌더할 때마다 파생 배열이 새로 만들어져 자식 컴포넌트에 props로 전달되고 있었다. 해당 연산을 메모이제이션하자 렌더 시간이 크게 줄었고, 키보드 인터랙션이 즉각적으로 느껴졌다.
대규모 리팩토링도 없었고, 추측도 없었다. 오직 근거만 있었다.
오늘부터 프로파일링을 시작하는 법
로그를 완전히 버릴 필요는 없다. 균형을 다시 잡으면 된다:
- 로그는 사실 확인에 — 이벤트 흐름이나 API 응답처럼 팩트를 검증할 때 쓴다.
- 프로파일링은 동작 탐색에 — 특히 렌더 빈도와 컴포넌트 경계를 파악할 때 쓴다.
- 유휴 상태가 아닌 인터랙션을 측정하라 — 사용자는 성능을 전체적인 흐름으로 체감한다.
- 한 번에 하나씩만 바꿔라 — 개선 효과가 우연이 아닌 원인으로 귀속되도록 해야한다.
"로그 하나만 더 추가하자"는 방식보다 훨씬 잘 확장되는 접근이다.
이것이 React를 대하는 감각을 어떻게 바꾸는가
개발 초기에 디버깅에서 가장 힘든 부분은 감정적 반응이다. UI가 제멋대로 굴면 마치 내 문제인 것처럼 느껴진다. 프로파일링은 문제를 외부화해서 그 충격을 덜어준다. 좌절을 데이터로 대체하고, React를 불투명한 시스템에서 관찰 가능한 규칙을 가진 시스템으로 바꿔놓는다. 그렇게 쌓이는 자신감은 어떤 단기적 해결책보다 빠르게 성장한다.
더 나은 디버깅이 더 나은 감각을 만든다
로깅에서 프로파일링으로 전환하는 건 모든 개발자의 여정에서 하나의 이정표다. 추측에서 앎으로 넘어가는 순간이다. console.log에 의존할 때 우리는 대개 버그의 증상만 치료하고 있다. 프로파일링은 병의 원인을 진단하게 해준다.
React를 막 시작했다면, 이 전환은 중요하다. 더 좋은 질문을 던지고, 더 작은 변화를 만들고, 직감보다 근거를 믿는 습관을 길러준다.
진짜 디버깅이란 줄을 더 많이 출력하는 게 아니다. 작업이 실제로 어디서 일어나는지 이해하는 것이다. 프로파일링은 사용자의 경험(특히 보조 기술에 의존하는 사용자들의 경험)과 자신의 학습 시간을 모두 존중하게 만든다.
결국, 디버깅은 사람이 하는 일이다. 도구는 스트레스를 더하는 게 아니라 줄여야 한다. 프로파일링부터 시작하라. 더 빠르게 배워라.