RootHunting

주니어 개발자들을 위한 패턴 언어 - 잡초의 잎만 자르지 않고 뿌리까지 뽑아내는 문제 해결법

The Story 1: The Null Check Plaster (Programming)

두 명의 개발자, 민수와 하나가 NullPointerException이 발생하는 버그를 처리하고 있다.

민수의 해결책 (The Quick Plaster): 민수는 에러 로그를 보고 즉시 반응했다. "아, user.Address가 null이라서 터졌네." 그는 코드를 수정했다.

if (user.Address != null) {
    print(user.Address.City);
}

"해결했다!" 하지만 일주일 뒤, 다른 화면에서 똑같은 에러가 발생했다. 민수는 또 if 문을 추가했다. 한 달 뒤, 시스템 곳곳에는 if (user.Address != null) 코드가 50군데나 복사되어 있었고, 코드는 지저분해졌다.

하나의 해결책 (The Root Hunt): 하나는 에러를 보고 잠시 멈췄다. "잠깐, user.Address는 가입할 때 필수 항목인데 왜 null이 되었지?" 그녀는 코드를 역추적했다.

  1. "왜 null이지?" -> 회원가입 API를 확인해보니 Address를 저장하는 로직이 빠져 있었다.

  2. "왜 빠졌지?" -> 최근에 추가된 '간편 가입' 기능에서 주소 입력을 건너뛰게 되어 있었다.

  3. "그럼 어떻게 해야 하지?" -> Address를 Nullable로 바꾸는 게 아니라, 간편 가입 시 기본값을 넣거나 저장 로직을 보강해야 한다.

하나는 원인이 발생한 지점을 수정했다. 단 한 곳만 고쳤고, 50군데의 null 체크는 필요 없어졌다. 버그는 다시는 돌아오지 않았다.

The Story 2: The Mold on the Wallpaper (Ordinary Life)

민수와 하나는 새로 이사한 집의 거실 벽지에 핀 '곰팡이'를 발견했다.

민수의 해결책 (The Paint Patch): 민수는 보기에 흉한 곰팡이를 빨리 없애고 싶어 한다. 그는 흰색 페인트를 사 와서 곰팡이 위에 덧칠했다. "이제 깨끗하네!" 하지만 며칠 뒤, 페인트 위로 다시 검은 곰팡이가 피어올랐다. 민수는 다시 페인트를 칠했지만, 곰팡이는 점점 더 넓게 번져나갔다.

하나의 해결책 (The Leak Hunt): 하나는 곰팡이를 닦아내기 전에 원인을 찾기로 했다. 그녀는 벽지를 조심스럽게 뜯어내고 벽 안쪽을 살폈다. 결국 벽 뒤에 숨겨진 낡은 배관에서 미세하게 물이 새고 있음을 발견했다. 하나는 배관공을 불러 물이 새는 곳을 완전히 수리했다. 벽은 다시는 축축해지지 않았고, 곰팡이도 영원히 사라졌다. 근본 원인(누수)을 잡지 않으면 겉모습(페인트)은 아무런 의미가 없음을 하나는 알고 있었다.

Context

DetectiveWork를 통해 버그가 발생하는 지점(현장)을 찾아냈다. 이제 이것을 고쳐야 한다.

일상적인 상황:

당신은 잡초를 뽑지 않고, 잡초 위에 페인트를 칠해서 안 보이게 만들고 있다.

Problem

증상(Symptom)만 치료하면, 원인(Root Cause)은 살아남아 시스템의 더 깊은 곳을 망가뜨린다.

Solution

문제가 발생한 지점에서 멈추지 말고, "왜?"를 5번 물어 가장 깊은 원인을 찾아 제거하라.

근본 원인을 해결하면 코드는 오히려 더 단순해진다.

Principle 1: The 5 Whys (5번의 왜)

도요타 생산 방식(Toyota Production System)의 핵심 기법이다.

  1. 왜 서버가 죽었지? -> 메모리가 부족해서.

  2. 왜 부족하지? -> 특정 API가 메모리를 해제하지 않아서.

  3. 왜 해제하지 않지? -> DB 연결을 닫는 코드가 예외 처리에 빠져 있어서.

  4. 왜 예외 처리에 빠져 있지? -> try-finally 블록을 안 써서.

  5. 해결책: 메모리를 늘리는 게 아니라(증상 치료), try-with-resources 구문을 사용한다(근본 치료).

Principle 2: Fix the Process, Not Just the Code (코드 너머의 프로세스 수정)

버그가 발생했다는 것은, 그 버그가 만들어질 수밖에 없었던 **환경**이 있다는 뜻이다.

Principle 3: Simplify, Don't Complicate (더하는 게 아니라 빼는 것)

근본 원인을 찾으면 대개 코드를 **삭제**하게 된다.

Real Examples

Example 1: The Z-Index War

화면에서 팝업창이 다른 요소 뒤에 가려지는 버그. Symptom Fix (민수):

.popup { z-index: 999999; } /* 일단 제일 높게! */

-> 나중에 더 중요한 알림창이 나오면 z-index: 1000000을 써야 한다.

Root Fix (하나): "왜 가려졌지?" -> "부모 요소에 position: relative가 없어서 Stacking Context가 새로 안 생겼구나." -> HTML 구조와 CSS Context를 정리해서 z-index: 10으로도 충분하게 만듦.

Example 2: The Reboot Script

서버가 일주일에 한 번씩 느려진다. Symptom Fix (민수): 매주 일요일 새벽에 서버를 재부팅하는 스크립트를 걸어둔다. (영원히 고통받음)

Root Fix (하나): 프로파일링 도구를 돌려본다. -> 로그 파일 핸들러가 닫히지 않고 계속 쌓이는 것을 발견. -> 로그 라이브러리 설정 수정. -> 재부팅 필요 없음.

Common Pitfalls

"Blaming the Person" (사람 탓하기)

"김 대리가 실수를 했네."라고 결론짓는 것은 가장 게으른 Root Hunting이다.

Stopping at the Code (코드에서 멈추기)

코드를 고치는 것에서 끝내지 마라.

"Too deep takes too long" (너무 깊게 파면 오래 걸려요)

급한 불을 꺼야 할 때도 있다. 그럴 때는 **증상 치료(Hotfix)**를 먼저 배포하고, 반드시 티켓을 만들어 **근본 치료(Refactoring)**를 후속 작업으로 잡아야 한다.

Connection to Other Patterns

Signs of Success

The Ultimate Insight

버그는 곰팡이와 같다. 습기(근본 원인)를 제거하지 않으면 계속 피어난다.

벽지를 덧바르는 것(Patch)은 곰팡이를 잠시 가릴 뿐입니다. 환기를 시키고 누수를 잡아야 합니다. 최고의 개발자는 코드를 많이 작성하는 사람이 아니라, 문제의 근원을 제거하여 **작성할 필요가 없는 코드**를 만드는 사람입니다.


CategoryPatternLanguage CategoryProgramming CategoryDebugging CategoryProblemSolving CategoryRefactoring

RootHunting (last edited 2025-12-30 10:00:29 by 정수)