에러 핸들링 설계 패턴: Claude Code 실전 가이드
커스텀 에러 클래스, Result 타입, 글로벌 에러 핸들러, React Error Boundary까지 Claude Code로 타입 안전한 에러 처리 체계를 구축하는 방법.
Claude Code로 에러 핸들링 설계 개선하기
적절한 에러 핸들링은 애플리케이션의 견고함과 직결됩니다. Claude Code를 활용하면 타입 안전하고 일관된 에러 처리 패턴을 효율적으로 구현할 수 있습니다.
커스텀 에러 클래스 설계
> 앱 전체에서 사용할 커스텀 에러 클래스를 설계해줘.
> HTTP 상태 코드 매핑, 에러 코드, 사용자용 메시지까지 포함해서.
// errors/base.ts
export abstract class AppError extends Error {
abstract readonly statusCode: number;
abstract readonly code: string;
readonly isOperational: boolean;
constructor(message: string, isOperational = true) {
super(message);
this.name = this.constructor.name;
this.isOperational = isOperational;
Error.captureStackTrace(this, this.constructor);
}
toJSON() {
return {
error: {
code: this.code,
message: this.message,
...(process.env.NODE_ENV === "development" && { stack: this.stack }),
},
};
}
}
// errors/http.ts
export class NotFoundError extends AppError {
readonly statusCode = 404;
readonly code = "NOT_FOUND";
constructor(resource: string, id?: string) {
super(id ? `${resource}(${id})을(를) 찾을 수 없습니다` : `${resource}을(를) 찾을 수 없습니다`);
}
}
export class ValidationError extends AppError {
readonly statusCode = 400;
readonly code = "VALIDATION_ERROR";
readonly fields: Record<string, string[]>;
constructor(fields: Record<string, string[]>) {
super("유효성 검증 오류가 있습니다");
this.fields = fields;
}
toJSON() {
return {
error: {
code: this.code,
message: this.message,
fields: this.fields,
},
};
}
}
export class UnauthorizedError extends AppError {
readonly statusCode = 401;
readonly code = "UNAUTHORIZED";
constructor(message = "Authentication required") {
super(message);
}
}
export class ForbiddenError extends AppError {
readonly statusCode = 403;
readonly code = "FORBIDDEN";
constructor(message = "권한이 없습니다") {
super(message);
}
}
Result 타입 패턴
예외를 쓰지 않고 타입 안전하게 에러를 다루는 Result 타입 패턴입니다.
> Result 타입 패턴을 구현해줘.
> 성공과 실패를 타입 레벨에서 구분할 수 있도록.
// types/result.ts
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
function ok<T>(data: T): Result<T, never> {
return { success: true, data };
}
function err<E>(error: E): Result<never, E> {
return { success: false, error };
}
// 사용 예시
async function findUser(id: string): Promise<Result<User, NotFoundError>> {
const user = await db.user.findUnique({ where: { id } });
if (!user) {
return err(new NotFoundError("User", id));
}
return ok(user);
}
// 호출 측
const result = await findUser("123");
if (result.success) {
console.log(result.data.name); // 타입 안전하게 접근
} else {
console.log(result.error.message); // 에러 타입도 타입 안전
}
글로벌 에러 핸들러
Express용 글로벌 에러 핸들러 미들웨어를 구현해 봅시다.
> Express 글로벌 에러 핸들러 미들웨어를 만들어줘.
> AppError는 정해진 응답으로 변환, 예상치 못한 에러는 500으로.
> 프로덕션 환경에서는 스택 트레이스 숨김.
import { Request, Response, NextFunction } from "express";
import { AppError } from "../errors/base";
export function globalErrorHandler(
err: Error,
req: Request,
res: Response,
_next: NextFunction
) {
// 로그 출력
if (err instanceof AppError && err.isOperational) {
console.warn(`[${err.code}] ${err.message}`);
} else {
console.error("Unexpected error:", err);
}
// AppError 인 경우 정의된 응답을 반환
if (err instanceof AppError) {
return res.status(err.statusCode).json(err.toJSON());
}
// 예상치 못한 에러
res.status(500).json({
error: {
code: "INTERNAL_ERROR",
message: process.env.NODE_ENV === "production"
? "서버 오류가 발생했습니다"
: err.message,
},
});
}
비동기 에러 캐치
> Express async route handler의 에러를
> 자동으로 캐치하는 래퍼를 만들어줘.
import { Request, Response, NextFunction, RequestHandler } from "express";
function asyncHandler(
fn: (req: Request, res: Response, next: NextFunction) => Promise<void>
): RequestHandler {
return (req, res, next) => {
fn(req, res, next).catch(next);
};
}
// 사용 예시: try-catch가 불필요
router.get("/users/:id", asyncHandler(async (req, res) => {
const user = await userService.findById(req.params.id);
if (!user) {
throw new NotFoundError("User", req.params.id);
}
res.json({ data: user });
}));
프론트엔드 Error Boundary
> React Error Boundary를 만들어줘.
> 에러 종류에 따라 다른 UI를 표시하도록.
import { Component, ReactNode } from "react";
interface Props {
children: ReactNode;
fallback?: (error: Error, retry: () => void) => ReactNode;
}
interface State {
error: Error | null;
}
class ErrorBoundary extends Component<Props, State> {
state: State = { error: null };
static getDerivedStateFromError(error: Error) {
return { error };
}
handleRetry = () => {
this.setState({ error: null });
};
render() {
if (this.state.error) {
if (this.props.fallback) {
return this.props.fallback(this.state.error, this.handleRetry);
}
return (
<div className="p-8 text-center">
<h2 className="text-xl font-bold text-red-600">오류가 발생했습니다</h2>
<p className="mt-2 text-gray-600">{this.state.error.message}</p>
<button
onClick={this.handleRetry}
className="mt-4 rounded bg-blue-500 px-4 py-2 text-white"
>
다시 시도
</button>
</div>
);
}
return this.props.children;
}
}
디버깅과 에러 조사에 대한 구체적 방법은 디버깅 테크닉 완전 가이드, TypeScript에서의 타입 설계는 TypeScript 개발 활용법, 보안 관점의 에러 정보 처리는 보안 감사 자동화도 함께 참고해 보세요.
정리
일관된 에러 처리 설계는 애플리케이션의 견고함과 유지보수성을 크게 끌어올려 줍니다. Claude Code를 활용하면 커스텀 에러 클래스, Result 타입, 글로벌 핸들러까지 효율적으로 구현할 수 있습니다.
에러 핸들링 모범 사례는 Node.js 공식 가이드, Claude Code에 대해서는 Anthropic 공식 문서를 참고하세요.
Claude Code 워크플로우를 한 단계 업그레이드하세요
지금 바로 Claude Code에 복사해 쓸 수 있는 검증된 프롬프트 템플릿 50선.
이 글을 작성한 사람
Masa
Claude Code를 적극 활용하는 엔지니어. 10개 언어, 2,000페이지 이상의 테크 미디어 claudecode-lab.com을 운영 중.
관련 글
Claude Code Agent SDK 입문 ― 자율 에이전트를 빠르게 구축하는 방법
Claude Code Agent SDK로 자율형 AI 에이전트를 구축하는 방법을 해설합니다. 설정부터 도구 정의, 멀티스텝 실행까지 실전 코드와 함께 소개합니다.
Claude Code 컨텍스트 관리 테크닉 완전 가이드
Claude Code의 컨텍스트 윈도우를 최대한 활용하는 실전 테크닉을 해설합니다. 토큰 절약, 대화 분할, CLAUDE.md 활용법까지 소개합니다.
Claude Code MCP Server 설정 및 실전 활용 가이드
Claude Code의 MCP Server 기능을 종합적으로 소개합니다. 외부 도구 연결, 서버 설정, 실전 통합 사례까지 한 번에 알아보세요.