소리의 법칙: 코드가 속삭이지 말고 외쳐야 하는 이유

발행: (2026년 1월 14일 오후 08:04 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

우리는 모두 그런 경험을 해봤습니다. 금요일 오후 4시, 의미를 알 수 없는 스택 트레이스를 바라보고 있습니다.
오류는 전형적인 JavaScript 치명적 오류: Cannot read properties of null (reading ‘email’).
줄 번호를 클릭하면 사용자 프로필을 렌더링하려는 React 컴포넌트를 가리킵니다. 코드는 정상적으로 보이며 user 객체가 있어야 합니다. 왜 null일까요?

데이터 흐름을 추적해 보면, 서비스를 호출하고 API 유틸리티를 호출하는 헬퍼 함수를 발견합니다. 다섯 층 깊이, 문제의 원인은 getUser라는 함수였습니다. 데이터베이스 오류가 발생했지만, 아무에게도 알리지 않고 조용히 null을 반환해 애플리케이션이 계속 실행되도록 했습니다.

이것을 “Passing the Buck.” 라고 합니다. 증상(크래시)은 원인(데이터베이스 실패)으로부터 몇 분, 여러 파일 떨어진 곳에서 나타나며, 가장 디버깅하기 어려운 유형의 버그입니다.

침묵의 실패

주니어 엔지니어들은 애플리케이션이 크래시 나는 것을 두려워해 오류를 잡아두고, 문제를 무시한 채 null, false, -1 같은 “안전한” 값을 반환합니다. 이는 애플리케이션을 Zombie Mode—반쯤 죽은 상태로 계속 실행하게 합니다.

null을 조용히 반환하면 오류 처리를 호출자에게 떠넘기는 것입니다. 함수를 사용하는 모든 위치에서 null을 확인해야 합니다. 한 명이라도 개발자가 이를 잊으면 나중에 앱이 크래시하고 원인을 알 수 없습니다.

시니어 엔지니어들은 다른 규칙을 따릅니다: 함수가 이름이 약속하는 일을 할 수 없으면 즉시 오류를 발생시켜야 합니다. 이것이 The Law of Loudness입니다.

getUser(id)라는 함수를 호출하면, 나는 사용자를 기대합니다. null을 기대하지 않죠. 데이터베이스가 불타고 있어서 사용자를 제공할 수 없다면, 빈 상자를 건네며 웃지 말고, 소리쳐 주세요.

우리는 Fail Fast를 원합니다. 예외를 사용해 데이터 무결성을 보호하세요; 데이터가 잘못되었다면 즉시 생산 라인을 멈추세요.

전과 후: 라우드니스 법 적용하기

BEFORE – “안전한” 무음 실패

// BEFORE: The "Safe" Silent Failure
function getUser(id) {
  // If the DB is down, we hide it.
  if (!db.isConnected()) {
    console.log("DB is down...");
    return null; // <--- Passing the buck
  }

  const user = db.find(id);

  // If user isn't found, we return null again.
  // Is it null because they don't exist?
  // Or because the DB is broken?
  // The caller will never know.
  return user || null;
}

// THE RESULT:
// The caller code assumes it got a user.
const user = getUser(50);
// CRASH happens here, 10 lines later.
console.log(user.profile.email);

AFTER – 라우드니스 법

// AFTER: The Law of Loudness
function getUser(id) {
  // 1. Guard Clause for System Failure
  if (!db.isConnected()) {
    // Stop execution immediately. We know EXACTLY what broke.
    throw new DatabaseConnectionError('Cannot fetch user: DB is down');
  }

  const user = db.find(id);

  // 2. Guard Clause for Data Integrity
  if (!user) {
    // Differentiate between "System Broken" and "Not Found"
    throw new NotFoundError(`User with ID ${id} does not exist`);
  }

  return user;
}

// THE RESULT:
// The crash happens INSIDE getUser.
// The stack trace points exactly to the line that failed.
// We know if it was a DB error or a missing user instantly.

크게 외치는 것의 장점

  • Debugging Speed – 앱이 충돌하면 스택 트레이스가 바로 getUser를 가리킵니다. 데이터베이스가 다운됐는지 사용자가 없는지 즉시 알 수 있어 UI 컴포넌트에서 null 값을 찾는 데 걸리는 시간을 몇 시간씩 절약할 수 있습니다.
  • Trust – 호출자는 반복적인 if (user !== null) 검사를 할 필요가 없습니다. 호출이 끝난 뒤 실행이 계속된다면 유효한 사용자가 있다는 뜻입니다.
  • Data Integrity – “Zombies”(반쯤 만들어진 데이터 객체)가 상태 관리에 떠다니며 이상한 버그를 일으키는 것을 방지합니다(예: 정의되지 않은 ID로 프로필을 저장하는 경우).

콘솔에 빨간 텍스트가 보인다고 두려워하지 마세요. 빨간 텍스트는 솔직합니다. 조용한 null은 거짓이며, 실제로 집이 불타고 있을 때 시스템에 모든 것이 정상이라고 말해줍니다.

규칙

  1. 예상되는 오류 (예: 존재하지 않을 수 있는 제품을 검색) – null이 유효한 도메인 상태인 경우에만 특수화된 Result 타입 또는 null을 반환합니다.
  2. 예상치 못한 오류 (네트워크 장애, 잘못된 구성, 필수 데이터 누락) – throw합니다. 강력히.

결론

컴파일러를 만족시키기 위해 코드를 작성하는 것을 멈추세요. 소리의 법칙을 받아들이고, 빠르게 실패하며, 오류가 들리게 하세요.

이 글은 제 핸드북, “The Professional Junior: Writing Code that Matters.” 의 발췌입니다. 쓰여지지 않은 엔지니어링 규칙에 대한 전술적인 현장 가이드입니다.

👉 전체 핸드북 받기

Back to Blog

관련 글

더 보기 »

개발자? 아니면 그냥 Toolor?

번역할 텍스트를 제공해 주시겠어요? 현재는 이미지 링크만 있어 내용을 확인할 수 없습니다. 텍스트를 복사해서 알려주시면 한국어로 번역해 드리겠습니다.