Communitygithub.com

오류 처리

TypeScript, Python, Go에서 안정적인 오류 처리를 위한 패턴입니다. 타입 기반 오류, 오류 경계, 재시도, 서킷 브레이커 및 사용자 대상 오류 메시지를 다룹니다. TypeScript, Python, Go에서 오류 타입, 재시도, 서킷 브레이커 또는 사용자 대상 실패 메시지를 설계할 때 사용하세요.

오류 처리란 무엇인가요?

이 스킬은 프로덕션 애플리케이션에서 일관되고 견고한 오류 처리를 위한 포괄적인 패턴을 제공합니다. TypeScript, Python, Go 세 가지 주요 언어를 지원하며, 각 언어에 맞는 구체적인 구현 예제를 포함합니다. 핵심 원칙으로는 빠르고 명확한 실패, 타입 기반 오류 사용, 사용자 메시지와 개발자 메시지 분리, 오류 삼키기 금지, 오류를 API 계약의 일부로 간주하는 것을 강조합니다. 주요 패턴으로는 도메인별 오류 클래스 계층 구조, Result 패턴(오류를 던지지 않는 스타일), API 오류 핸들러(Next.js, Express, FastAPI), React 오류 경계(Error Boundary), 센티널 오류 및 오류 래핑(Go), 지수 백오프를 사용한 재시도 로직, 사용자 친화적인 오류 메시지 매핑을 다룹니다. 또한, 코드 병합 전 오류 처리가 제대로 되었는지 확인할 수 있는 체크리스트도 제공합니다. 이 스킬은 오류 타입 설계, 재시도 로직 추가, API 엔드포인트 검토, 사용자 대상 오류 메시지 구현, 계단식 실패 진단 등 다양한 상황에서 활성화하여 사용할 수 있습니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/error-handling

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

오류 처리 패턴

프로덕션 애플리케이션을 위한 일관되고 견고한 오류 처리 패턴입니다.

활성화 시점

  • 새 모듈이나 서비스를 위한 오류 타입 또는 예외 계층 구조를 설계할 때
  • 신뢰할 수 없는 외부 의존성에 재시도 로직이나 서킷 브레이커를 추가할 때
  • API 엔드포인트에서 누락된 오류 처리를 검토할 때
  • 사용자 대상 오류 메시지와 피드백을 구현할 때
  • 계단식 실패나 오류를 조용히 삼키는 문제를 디버깅할 때

핵심 원칙

  1. 빠르고 명확하게 실패하라 — 오류가 발생한 경계에서 표면화하고 숨기지 마십시오.
  2. 문자열 메시지보다 타입 기반 오류를 사용하라 — 오류는 구조를 가진 일급 값입니다.
  3. 사용자 메시지 ≠ 개발자 메시지 — 사용자에게는 친숙한 텍스트를, 서버 측에는 전체 컨텍스트를 로깅하십시오.
  4. 절대 오류를 조용히 삼키지 마라 — 모든 catch 블록은 처리, 재전파, 또는 로깅 중 하나를 수행해야 합니다.
  5. 오류는 API 계약의 일부다 — 클라이언트가 수신할 수 있는 모든 오류 코드를 문서화하십시오.

TypeScript / JavaScript

타입 기반 오류 클래스

// 도메인에 맞는 오류 계층 구조를 정의하세요.
export class AppError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly statusCode: number = 500,
    public readonly details?: unknown,
  ) {
    super(message)
    this.name = this.constructor.name
    // 트랜스파일된 ES5 JavaScript에서 올바른 프로토타입 체인을 유지합니다.
    // 내장 Error 클래스를 확장할 때 `instanceof` 검사(예: `error instanceof NotFoundError`)가
    // 올바르게 작동하도록 하기 위해 필요합니다.
    Object.setPrototypeOf(this, new.target.prototype)
  }
}

export class NotFoundError extends AppError {
  constructor(resource: string, id: string) {
    super(`${resource} not found: ${id}`, 'NOT_FOUND', 404)
  }
}

export class ValidationError extends AppError {
  constructor(message: string, details: { field: string; message: string }[]) {
    super(message, 'VALIDATION_ERROR', 422, details)
  }
}

export class UnauthorizedError extends AppError {
  constructor(reason = 'Authentication required') {
    super(reason, 'UNAUTHORIZED', 401)
  }
}

export class RateLimitError extends AppError {
  constructor(public readonly retryAfterMs: number) {
    super('Rate limit exceeded', 'RATE_LIMITED', 429)
  }
}

Result 패턴 (throw 없는 스타일)

오류가 예상되고 흔한 작업(파싱, 외부 호출)의 경우:

type Result<T, E = AppError> =
  | { ok: true; value: T }
  | { ok: false; error: E }

function ok<T>(value: T): Result<T> {
  return { ok: true, value }
}

function err<E>(error: E): Result<never, E> {
  return { ok: false, error }
}

// 사용법
async function fetchUser(id: string): Promise<Result<User>> {
  try {
    const user = await db.users.findUnique({ where: { id } })
    if (!user) return err(new NotFoundError('User', id))
    return ok(user)
  } catch (e) {
    return err(new AppError('Database error', 'DB_ERROR'))
  }
}

const result = await fetchUser('abc-123')
if (!result.ok) {
  // TypeScript는 여기서 result.error 타입을 압니다.
  logger.error('Failed to fetch user', { error: result.error })
  return
}
// TypeScript는 여기서 result.value 타입을 압니다.
console.log(result.value.email)

API 오류 핸들러 (Next.js / Express)

import { NextRequest, NextResponse } from 'next/server'

function handleApiError(error: unknown): NextResponse {
  // 알려진 애플리케이션 오류
  if (error instanceof AppError) {
    return NextResponse.json(
      {
        error: {
          code: error.code,
          message: error.message,
          ...(error.details ? { details: error.details } : {}),
        },
      },
      { status: error.statusCode },
    )
  }

  // Zod 유효성 검사 오류
  if (error instanceof z.ZodError) {
    return NextResponse.json(
      {
        error: {
          code: 'VALIDATION_ERROR',
          message: 'Request validation failed',
          details: error.issues.map(i => ({
            field: i.path.join('.'),
            message: i.message,
          })),
        },
      },
      { status: 422 },
    )
  }

  // 예상치 못한 오류 — 세부 정보를 로깅하고 일반 메시지 반환
  console.error('Unexpected error:', error)
  return NextResponse.json(
    { error: { code: 'INTERNAL_ERROR', message: 'An unexpected error occurred' } },
    { status: 500 },
  )
}

export async function POST(req: NextRequest) {
  try {
    // ... 핸들러 로직
  } catch (error) {
    return handleApiError(error)
  }
}

React 오류 경계 (Error Boundary)

import { Component, ErrorInfo, ReactNode } from 'react'

interface Props {
  fallback: ReactNode
  onError?: (error: Error, info: ErrorInfo) => void
  children: ReactNode
}

interface State {
  hasError: boolean
  error: Error | null
}

export class ErrorBoundary extends Component<Props, State> {
  state: State = { hasError: false, error: null }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error }
  }

  componentDidCatch(error: Error, info: ErrorInfo) {
    this.props.onError?.(error, info)
    console.error('Unhandled React error:', error, info)
  }

  render() {
    if (this.state.hasError) return this.props.fallback
    return this.props.children
  }
}

// 사용법
<ErrorBoundary fallback={<p>Something went wrong. Please refresh.</p>}>
  <MyComponent />
</ErrorBoundary>

Python

사용자 정의 예외 계층 구조

class AppError(Exception):
    """Base application error."""
    def __init__(self, message: str, code: str, status_code: int = 500):
        super().__init__(message)
        self.code = code
        self.status_code = status_code

class NotFoundError(AppError):
    def __init__(self, resource: str, id: str):
        super().__init__(f"{resource} not found: {id}", "NOT_FOUND", 404)

class ValidationError(AppError):
    def __init__(self, message: str, details: list[dict] | None = None):
        super().__init__(message, "VALIDATION_ERROR", 422)
        self.details = details or []

FastAPI 글로벌 예외 핸들러

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(AppError)
async def app_error_handler(request: Request, exc: AppError) -> JSONResponse:
    return JSONResponse(
        status_code=exc.status_code,
        content={"error": {"code": exc.code, "message": str(exc)}},
    )

@app.exception_handler(Exception)
async def generic_error_handler(request: Request, exc: Exception) -> JSONResponse:
    # 전체 세부 정보를 로깅하고 일반 메시지 반환
    logger.exception("Unexpected error", exc_info=exc)
    return JSONResponse(
        status_code=500,
        content={"error": {"code": "INTERNAL_ERROR", "message": "An unexpected error occurred"}},
    )

Go

센티널 오류 및 오류 래핑

package domain

import "errors"

// 타입 검사를 위한 센티널 오류
var (
    ErrNotFound    = errors.New("not found")
    ErrUnauthorized = errors.New("unauthorized")
    ErrConflict     = errors.New("conflict")
)

// 컨텍스트와 함께 오류를 래핑하세요 — 원래 오류를 절대 잃지 마십시오.
func (r *UserRepository) FindByID(ctx context.Context, id string) (*User, error) {
    user, err := r.db.QueryRow(ctx, "SELECT * FROM users WHERE id = $1", id)
    if errors.Is(err, sql.ErrNoRows) {
        return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
    }
    if err != nil {
        return nil, fmt.Errorf("querying user %s: %w", id, err)
    }
    return user, nil
}

// 핸들러 수준에서 래핑을 풀어 응답을 결정합니다.
func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
    user, err := h.service.GetUser(r.Context(), chi.URLParam(r, "id"))
    if err != nil {
        switch {
        case errors.Is(err, domain.ErrNotFound):
            writeError(w, http.StatusNotFound, "not_found", err.Error())
        case errors.Is(err, domain.ErrUnauthorized):
            writeError(w, http.StatusForbidden, "forbidden", "Access denied")
        default:
            slog.Error("unexpected error", "err", err)
            writeError(w, http.StatusInternalServerError, "internal_error", "An unexpected error occurred")
        }
        return
    }
    writeJSON(w, http.StatusOK, user)
}

지수 백오프를 사용한 재시도

interface RetryOptions {
  maxAttempts?: number
  baseDelayMs?: number
  maxDelayMs?: number
  retryIf?: (error: unknown) => boolean
}

async function withRetry<T>(
  fn: () => Promise<T>,
  options: RetryOptions = {},
): Promise<T> {
  const {
    maxAttempts = 3,
    baseDelayMs = 500,
    maxDelayMs = 10_000,
    retryIf = () => true,
  } = options

  let lastError: unknown

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn()
    } catch (error) {
      lastError = error
      if (attempt === maxAttempts || !retryIf(error)) throw error

      const jitter = Math.random() * baseDelayMs
      const delay = Math.min(baseDelayMs * 2 ** (attempt - 1) + jitter, maxDelayMs)
      await new Promise(resolve => setTimeout(resolve, delay))
    }
  }

  throw lastError
}

// 사용법: 일시적인 네트워크 오류는 재시도하지만, 4xx 오류는 재시도하지 않습니다.
const data = await withRetry(() => fetch('/api/data').then(r => r.json()), {
  maxAttempts: 3,
  retryIf: (error) => !(error instanceof AppError && error.statusCode < 500),
})

사용자 대상 오류 메시지

오류 코드를 사람이 읽을 수 있는 메시지로 매핑하세요. 기술적인 세부 정보는 사용자에게 표시되는 텍스트에서 제외하십시오.

const USER_ERROR_MESSAGES: Record<string, string> = {
  NOT_FOUND: 'The requested item could not be found.',
  UNAUTHORIZED: 'Please sign in to continue.',
  FORBIDDEN: "You don't have permission to do that.",
  VALIDATION_ERROR: 'Please check your input and try again.',
  RATE_LIMITED: 'Too many requests. Please wait a moment and try again.',
  INTERNAL_ERROR: 'Something went wrong on our end. Please try again later.',
}

export function getUserMessage(code: string): string {
  return USER_ERROR_MESSAGES[code] ?? USER_ERROR_MESSAGES.INTERNAL_ERROR
}

오류 처리 체크리스트

오류 처리와 관련된 코드를 병합하기 전에 다음을 확인하세요:

  • 모든 catch 블록이 처리, 재전파, 또는 로깅 중 하나를 수행합니다. — 조용히 삼키지 않습니다.
  • API 오류가 표준 형식 { error: { code, message } }을 따릅니다.
  • 사용자 대상 메시지에 스택 추적이나 내부 세부 정보가 포함되지 않습니다.
  • 전체 오류 컨텍스트가 서버 측에 로깅됩니다.
  • 사용자 정의 오류 클래스가 code 필드를 가진 기본 AppError를 확장합니다.
  • 비동기 함수가 오류를 호출자에게 표면화합니다. — 폴백 없이 fire-and-forget 패턴을 사용하지 않습니다.
  • 재시도 로직이 재시도 가능한 오류(4xx 클라이언트 오류 제외)에 대해서만 재시도합니다.
  • React 컴포넌트가 렌더링 오류를 처리하기 위해 ErrorBoundary로 래핑됩니다.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

관련 스킬