Skip to main content

Command Palette

Search for a command to run...

타입스크립트를 사용한 에러 처리와 방어적 프로그래밍

Published
타입스크립트를 사용한 에러 처리와 방어적 프로그래밍
J

Code Tinker. Interested in user interface software development. Trying to think functionally.

원문: Alex Khomenko, "Error Handling and Defensive Programming with Typescript"

에러는 소프트웨어 개발과 떼려야 뗄 수 없는 부분이며 유효하지 않은 사용자 입력이나 서버 장애 또는 코드 내 버그 등 예기치 못한 상황에서 발생합니다. 에러 처리는 매끄러운 사용자 경험을 유지하고 애플리케이션 충돌을 방지하기 위해 문제를 예측하고 탐지하며 해결하는 과정을 말합니다. 방어적 프로그래밍은 이를 보완하기 위한 접근 방식으로, 잠재적인 오용과 예기치 못한 상황을 대비하는 견고한 코드를 작성하여 에러를 줄이는 것을 목표로 합니다.

자바스크립트의 슈퍼셋인 타입스크립트는 정적 타이핑과 추가 기능들을 통해 개발자가 런타임 대신 컴파일 타임에 에러를 발견하도록 하여 상당한 인기를 얻었습니다. 이는 코드를 더욱 신뢰성 있게 만들 뿐만 아니라 개발 주기에서 잠재적인 문제를 조기에 표시하여 개발자 생산성을 높여줍니다.

이 글에서는 효과적인 에러 처리와 방어적 프로그래밍을 위해 타입스크립트의 타입 시스템과 컴파일러 옵션 활용법을 알아봅니다. 타입스크립트의 일반적인 에러 유형과 예외 처리 메커니즘을 살펴보고 회복력이 더 높은 코드를 작성하기 위한 실용적인 기법을 제공할 것입니다. 이 글을 끝까지 읽는다면 에러를 예측하고 예방하는 전략을 익혀 안전하고 신뢰성 있으며 유지보수하기 좋은 애플리케이션 코드를 만들 수 있을 것입니다.

타입스크립트 에러 이해하기

타입스크립트에서 발생하는 에러의 본질을 파악하는 것은 효과적인 에러 처리를 구현하기 위한 기본입니다. 타입스크립트에서 에러는 크게 컴파일 타임 에러와 런타임 에러 두 가지로 분류됩니다.

  • 컴파일 타임 에러는 코드가 타입스크립트 컴파일러에 의해 타입스크립트에서 자바스크립트로 변환될 때 발생합니다. 여기에는 구문 에러, 타입 에러 및 코드를 실행하기 전에 탐지될 수 있는 기타 문제가 포함됩니다.

  • 런타임 에러는 컴파일 과정에서 벗어나 자바스크립트 코드가 실행될 때에만 발생하는 에러입니다. 논리 에러, 예상치 못한 사용자 입력 또는 컴파일러가 예측하지 못한 외부 시스템 장애 등이 원인이 될 수 있습니다.

타입스크립트의 엄격한 타입 시스템은 넓은 범위의 에러 유형을 컴파일 타임에 발견하도록 설계되었습니다. 타입스크립트 개발자들이 마주하는 몇몇 일반적인 에러는 다음과 같습니다.

  • 타입 에러 - 값이 예상하는 타입과 일치하지 않는 경우

  • null과 undefined 에러 - null이 아닌 값으로 정의되거나 null이 아닌 값을 포함해야 하는 객체가 실제로는 그렇지 않은 경우

  • 유효범위 에러 - 변수나 함수가 접근 가능한 유효범위 밖에서 사용된 경우

  • 구문 에러 - 작성된 코드가 언어의 원칙을 준수하지 않는 경우

타입스크립트에서의 방어적 프로그래밍 기법

타입스크립트에서의 방어적 프로그래밍이란 단순히 에러에 반응하는 것이 아니라 에러를 사전에 예방하는 코드를 작성하는 것입니다. 발생 가능한 문제를 예측하고 이러한 문제가 실제로 일어나는 걸 방지하도록 코딩하는 것이죠. 견고하고 에러 발생 가능성이 적은 애플리케이션을 만드는 데 도움이 되는, 타입스크립트에 특화된 몇 가지 방어적 프로그래밍 기법을 소개합니다.

입력 검증과 타입 가드

가장 중요한 방어적 프로그래밍 방법 중 하나는 입력을 프로그램에서 사용하기 전에 검증하는 것입니다. 타입스크립트의 타입 시스템은 변수와 매개변수가 예상되는 타입임을 확인하여 입력된 값이 올바른지 검증하는 과정에 큰 도움을 줍니다. 그러나 사용자 입력, API 응답 또는 파일 내용과 같이 타입스크립트가 컴파일 타임에 확인할 수 없는 외부 입력을 다룰 때는 데이터를 런타임에 검증하는 것이 필수입니다.

function processInput(input: any) {
  if (typeof input === "string") {
    // 올바른 입력이므로 계속 진행합니다.
  } else {
    // 올바르지 않은 입력이므로 에러를 던지거나 알맞게 처리합니다.
  }
}

타입 가드는 입력 검증을 위한 타입스크립트의 강력한 기능입니다. 어떤 값이 특정 타입과 일치하는 지를 확인합니다.

function isNumber(value: unknown): value is number {
  return typeof value === "number";
}

함수와 메서드 전제 조건

전제 조건이란 이후 로직을 진행하기 전 입력과 시스템 상태가 유효함을 검증하기 위해 함수나 메서드 시작 시에 수행되는 검사입니다. 예를 들어 이런 거죠.

function divide(dividend: number, divisor: number): number {
  if (divisor === 0) {
    throw new Error("Cannot divide by zero.");
  }
  return dividend / divisor;
}

이러한 검사는 올바르지 않은 인자나 부적절한 시스템 상태로 인한 에러를 방지하여 함수가 주어진 입력에 대해 안전하게 동작하도록 만듭니다. 타입스크립트의 타입 시스템은 이러한 전제 조건을 강제하여 어떤 입력이 예상되고 함수가 어떤 반환값을 보장하는지 명확히 합니다.

readonly를 활용한 불변 자료 구조

불변성은 예측 가능한 코드 작성을 돕는 원칙입니다. 객체를 생성한 후 수정할 수 없다면 예기치 못한 변경과 관련된 모든 에러를 예방할 수 있습니다. 타입스크립트는 readonly 키워드로 불변성을 지원합니다.

class Point {
  readonly x: number;
  readonly y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

const p = new Point(1, 2);
p.x = 3; // 에러: 'x'는 읽기 전용 프로퍼티이므로 'x'에 할당할 수 없습니다.

readonly를 사용하면 클래스나 인터페이스의 특정 프로퍼티가 부주의하게 변경되지 않도록 하여 부작용이 줄어들고 시간이 지나도 유지보수가 용이해 집니다.

방어적 복사와 Partial 타입

복잡한 자료구조로 작업할 때는 예상치 못한 변경을 방지하기 위해 객체의 방어적 복사본을 만드는 것이 유용한 경우가 많습니다. 타입스크립트의 Partial 타입을 사용하면 주어진 타입과 동일한 구조를 가지지만 모든 프로퍼티를 선택적으로 설정하는 새로운 타입을 만들 수 있습니다.

interface User {
  id: string;
  name: string;
  age: number;
}

function updateUser(user: User, updates: Partial<User>): User {
  return { ...user, ...updates };
}

Partial을 사용하여 원본 객체가 수정되지 않으며 갱신이 안전하고 통제된 방식으로 적용됩니다. 이는 상태 관리와 데이터 변환 시에 특히 유용합니다.

본래 방어적 프로그래밍은 신중하고 사려 깊게 코드를 작성하는 것입니다. 타입스크립트의 타입 안전성을 활용하여 필요한 곳에 런타임 검사를 구현하고, 가능한 모든 곳에 불변 구조를 준수하여 개발자는 일반적인 에러에 저항하며 견고한 소프트웨어 공학 관행에 맞는 강력한 방어적 코드를 만들 수 있습니다.

null이 될 수 있는 타입과 null 안전성 처리하기

nullundefined 값 처리는 자바스크립트와 타입스크립트에서 에러가 일어나는 주된 이유입니다. 다른 언어에서 "null 포인터 예외"로도 알려져 있는 null 참조 에러는 프로그램이 null 또는 undefined 값을 가지는 프로퍼티나 메서드에 접근을 시도할 때 발생합니다. 타입스크립트는 null이 될 수 있는 타입을 관리하고 null 안전성을 보장하는 몇 가지 기능을 제공합니다.

옵셔널 체이닝과 nullish 병합 이해하기

타입스크립트의 옵셔널 체이닝(?.)과 nullish 병합(??) 연산자는 잠재적으로 null 또는 undefined인 값을 장황하고 반복적인 검사 없이 다루는 간결한 방법을 제공합니다.

interface User {
  id: string;
  profile?: {
    name: string;
    age?: number;
  };
}

// 옵셔널 체이닝
function getUserName(user: User): string | undefined {
  return user.profile?.name;
}

const user: User = { id: "1", profile: { name: "Alice" } };
console.log(getUserName(user)); // "Alice"
console.log(getUserName({ id: "2" })); // undefined

// Nullish 병합
function getUserAge(user: User): number {
  return user.profile?.age ?? -1;
}

console.log(getUserAge(user)); // -1
console.log(getUserAge({ id: "3", profile: { name: "Bob", age: 25 } })); // 25

런타임 null 검사용 타입 가드

타입 가드는 데이터 타입 검사에만 사용되지 않으며, 특히 제네릭 타입과 함께 사용하면 어떤 값이 null이 아님을 검사할 수 있어 개발자가 런타임에 null 검사를 세밀하게 제어할 수 있게 합니다. 이는 외부 데이터 소스나 API와 작업할 때 특히 유용합니다.

function isNonNull<T>(value: T | null | undefined): value is T {
  return value !== null && typeof value !== "undefined";
}

function getValueOrFallback(
  value: string | null | undefined,
  fallback: string,
): string {
  return isNonNull(value) ? value : fallback;
}

console.log(getValueOrFallback(null, "default string")); // "default string"

null이 될 수 있는 타입을 위한 타입 별칭과 유니언 타입

null이 될 수 있는 타입에 대한 별칭을 만드는 것은 null 또는 undefined가 될 수 있는 타입들을 다루기 쉽게 만들어 코드 가독성과 유지보수성을 개선할 수 있습니다.

type MaybeString = string | null | undefined;

function logMessage(message: MaybeString) {
  if (isNonNull(message)) {
    console.log(message);
  } else {
    console.log("No message to display.");
  }
}

이런 기법들을 통해 타입스크립트 개발자는 nullundefined와 관련된 함정에 대항하는 효과적인 방어책을 마련할 수 있습니다. null 안전성 기능과 모범 사례를 수용하여 null 참조 에러가 발생하는 빈도를 대폭 낮추고 애플리케이션의 전반적인 신뢰성을 향상시킬 수 있습니다.

고급 에러 처리 패턴

타입스크립트는 개발자가 코드를 더욱 견고하고 유지보수하기 용이하게 만들 수 있는 고급 에러 처리 패턴을 사용할 수 있도록 해줍니다. 이러한 패턴들은 타입스크립트의 정적 타이핑 및 고급 타입 추론 성능을 활용하여 에러 처리 로직을 위한 컴파일 타임 검사를 제공합니다.

에러 처리에 유니언 타입 사용하기

타입스크립트의 유니언 타입은 여러 타입 중 하나에 해당하는 값을 나타내는 데 사용할 수 있습니다. 예를 들어 다음과 같이 결과나 에러를 반환하는 함수를 작성할 때 유용합니다.

type SuccessResponse = { success: true; value: number };
type ErrorResponse = { success: false; error: string };

function divide(
  dividend: number,
  divisor: number,
): SuccessResponse | ErrorResponse {
  if (divisor === 0) {
    return { success: false, error: "Cannot divide by zero." };
  }
  return { success: true, value: dividend / divisor };
}

success 프로퍼티를 검사하여 호출자가 각 경우를 적절하게 처리할 수 있으며 타입스크립트의 타입 시스템이 각 경우에 대해 올바른 필드에 접근하고 있음을 보장합니다.

에러 상태를 위한 구별된 유니언 구현하기

태그된 유니언이라고도 알려진 구별된 유니언은 서로 다른 경우를 더욱 명확히 처리할 수 있는 유니언 타입 패턴의 확장판입니다. 구별된 유니언 내 각 타입은 보통 type 또는 kind라고 불리는 싱글톤 프로퍼티를 공통으로 가집니다. 이 프로퍼티는 런타임 타입 가드 구현을 간단하게 만들어 더욱 읽기 쉽고 안전한 타입 구분을 가능하게 합니다.

type Result =
  | { kind: "success"; value: number }
  | { kind: "failure"; error: Error };

function getResult(): Result {
  // 성공하거나 실패할 수 있는 로직
}

const result = getResult();
switch (result.kind) {
  case "success":
    console.log(`The result is ${result.value}`);
    break;
  case "failure":
    console.error(`An error occurred: ${result.error.message}`);
    break;
}

에러 경계와 전파

중첩된 컴포넌트 또는 레이어로 이루어진 복잡한 애플리케이션에서는 에러 경계를 구현하는 것이 유용합니다. 에러 경계란 하위 컴포넌트에서 발생한 에러를 잡아내고 상위 레벨로 전파되어 영향을 미치는 것을 막는 구조입니다. 이런 패턴은 사용자 인터페이스 프레임워크에서 특히 유용하지만 좀 더 일반적인 상황에서도 사용할 수 있습니다.

class ErrorBoundary {
  try<T>(func: () => T): T | null {
    try {
      return func();
    } catch (e) {
      // 에러를 처리하고 전파되는 것을 방지합니다.
      this.handleError(e);
      return null;
    }
  }

  private handleError(error: Error) {
    // 에러를 기록하고 가능하면 복구합니다.
  }
}

const boundary = new ErrorBoundary();
boundary.try(() => {
  // 에러를 던질 수도 있는 연산
});

타입스크립트 개발자는 이러한 고급 에러 처리 패턴을 이용해 서로 다른 에러 조건을 우아하게 처리하는 코드를 작성할 수 있고 애플리케이션의 신뢰성을 더욱 높일 수 있으며 에러 처리 코드를 이해하기 쉽고 유지보수하기 좋도록 만들 수 있습니다. 이러한 패턴들은 타입스크립트가 제공하는 강력한 타입 시스템을 최대한 활용하여 에러가 런타임에 올바르게 처리되었을 뿐만 아니라 코드베이스에 명확하고 명시적으로 표현되도록 합니다.

결론

타입스크립트의 견고한 타입 시스템은 이전의 자바스크립트 프로젝트가 달성하기 어려웠던 수준의 신뢰성과 유지보수성을 가져다줍니다. 개발자는 에러 처리와 방어적 프로그래밍을 위한 타입스크립트 기능을 이해하고 활용하여 여러 일반적인 유형의 런타임 에러에 대해 회복력 있는 코드를 작성할 수 있습니다.

타입 가드를 통한 입력 검증, readonly를 활용한 불변성 강제, Partial 타입 사용, 구별된 유니언을 통한 상태 관리, 안전한 타입 디자인 패턴 구현과 같은 방어적 프로그래밍 기법으로 애플리케이션을 견고하고 확장 가능하게 만들 수 있습니다.

옵셔널 체이닝, nullish 병합, 엄격한 null 검사, null이 될 수 없음을 의미하는 단언문과 런타임 null 검사를 통해 null이 될 수 있는 타입을 관리하는 것은 세상에서 가장 두렵고도 흔한 버그의 원천 중 하나인 null 참조 에러를 방지하게 해줍니다.

정리하자면 타입스크립트의 정적 타이핑 기능과 세심한 에러 처리 및 방어적 프로그래밍 기법을 조합하면 고품질 소프트웨어를 만들 수 있는 강력한 도구를 얻게 됩니다. 이러한 기능과 패턴을 활용하여 개발자는 기능적이면서도 회복력 있고 유지보수하기 쉬운 소프트웨어를 만들 수 있습니다. 나아가 더 즐거운 개발 경험을 주고 최종 사용자에게 더 높은 만족도를 제공할 것입니다.

참고문헌 및 자료

More from this blog

나의 오픈 소스 시작 이야기

원문: TkDoDo, “My Open Source Origin Story“ 가끔씩 제가 받는 질문이 하나 있는데, 바로 오픈 소스와 리액트 쿼리(React Query)를 어떻게 시작하게 되었는지입니다. 저의 기본 원칙은 어떤 질문을 세 번 받으면 더 이상 답변할 필요가 없도록 질문에 대해 글로 쓴다는 것입니다. 하지만 이 질문은 주로 직접 만났을 때 받는 질문이라 글로 작성할 생각을 한 적이 없었습니다. 최근에 오프라인 컨퍼런스에 더 많이 참...

Jul 30, 2025
나의 오픈 소스 시작 이야기

이더넷이란?

원문: baeldung, “What Is Ethernet?“ 1. 소개 이 튜토리얼에서는 이더넷(Ethernet)과 이를 통해 이루어지는 데이터 전송에 대해 알아보겠습니다. 2. 이더넷이란? 이더넷은 근거리 통신망(LAN) 또는 광역 네트워크(WAN) 내에서 장치들이 데이터를 주고받고 통신하기 쉽게 만들어 주는 널리 사용되는 기술입니다. 컴퓨터, 프린터, 서버는 물론 스마트 홈 기기까지도 이더넷으로 연결됩니다. 가정이나 사무실처럼 제한된 공간...

Jul 20, 2025
이더넷이란?

포스트 개발자 시대

원문: Josh W. Comeau, "The Post-Developer Era" 2년 전 2023년 3월, "프런트엔드 개발의 종말"이라는 제목의 블로그 글을 발행했습니다. 이는 OpenAI가 GPT-4 쇼케이스를 발표한 직후였고, 당시 업계 분위기는 머지않아 인간 소프트웨어 개발자는 필요 없어지고 앞으로는 소프트웨어 개발을 AI가 전담하게 될 것이라는 전망이 지배적이었습니다. 저는 이런 주장에 회의적이었고 그 블로그 글에서 소프트웨어 개발...

Jul 10, 2025
포스트 개발자 시대

널리 사용되는 네트워크 프로토콜

원문: Subham Datta, "Popular Network Protocols" 1. 개요 이 튜토리얼에서는 가장 널리 사용되고 인기 있는 네트워크 프로토콜들을 소개합니다. 2. 네트워크 프로토콜 소개 의사소통과 정보 교환은 현대 사회에서 가장 중요하고 강력한 역량입니다. 컴퓨터 네트워킹이란 여러 대의 컴퓨터와 장치를 케이블이나 위성을 통해 서로 연결하여, 거리와 상관없이 정보·자원·데이터베이스 등을 공유할 수 있게 하는 것을 말합니다. 네...

Jun 20, 2025
널리 사용되는 네트워크 프로토콜

커맨드 라인에 편해지는 법

원문: Julia Evans, "What helps people get comfortable on the command line?" 가끔 커맨드 라인을 써야 하는 친구들과 이야기하다 보면 많은 이들이 여전히 터미널을 두려워하고 있다는 걸 느낍니다. 그럴 때마다 어떤 조언을 할지 잘 모르겠더라고요. 저는 워낙 오래전부터 터미널을 써왔기 때문이죠. 그래서 Mastodon에 이렇게 물어봤습니다. 최근 1~3년 사이에 터미널 공포(?)를 극복한 분...

Jun 10, 2025
커맨드 라인에 편해지는 법
C

CodeSnap

84 posts

한국어로 전달하는 웹 개발 번역 매거진