에러 핸들링 설계 패턴: 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 공식 문서를 참고하세요.
무료 PDF: 5분 완성 Claude Code 치트시트
이메일 주소만 등록하시면 A4 한 장짜리 치트시트 PDF를 즉시 보내드립니다.
개인정보는 엄격하게 관리하며 스팸은 보내지 않습니다.
이 글을 작성한 사람
Masa
Claude Code를 적극 활용하는 엔지니어. 10개 언어, 2,000페이지 이상의 테크 미디어 claudecode-lab.com을 운영 중.
관련 글
Claude Code/Codex 안전 Agent Harness 설계: 권한, 검증, 롤백
Claude Code와 Codex를 안전하게 운영하기 위한 Agent Harness를 권한 정책, 실행 계획, 검증, 복구 계층으로 설계합니다.
Claude Code 서브에이전트 활용 패턴 10선
Claude Code의 서브에이전트 기능을 활용하는 10가지 실전 패턴. 병렬 처리, 전문화, 컨텍스트 분리로 개발 속도를 두 배로 만드는 방법.
Claude Code Agent SDK 입문 ― 자율 에이전트를 빠르게 구축하는 방법
Claude Code Agent SDK로 자율형 AI 에이전트를 구축하는 방법을 해설합니다. 설정부터 도구 정의, 멀티스텝 실행까지 실전 코드와 함께 소개합니다.