| name | error-handling |
| description | Enforce proper error handling patterns. Use when writing async code, API calls, or user-facing features. Covers try-catch, error boundaries, graceful degradation, and user feedback. |
| allowed-tools | Read, Glob, Grep, Edit, Write, Bash |
| license | MIT |
| metadata | {"author":"antigravity-team","version":"1.0"} |
Error Handling Patterns
์ ์ ํ ์๋ฌ ์ฒ๋ฆฌ ํจํด์ ๊ฐ์ ํ๋ ์คํฌ์
๋๋ค.
Core Principle
"์๋ฌ๋ ์จ๊ธฐ์ง ์๊ณ , ์ ์ ํ ์ฒ๋ฆฌํ๊ณ , ์ฌ์ฉ์์๊ฒ ์๋ฆฐ๋ค."
"Fail gracefully, recover when possible."
Rules
| ๊ท์น | ์ํ | ์ค๋ช
|
|---|
| ๋น catch ๋ธ๋ก ๊ธ์ง | ๐ด ํ์ | ์ต์ ๋ก๊น
ํ์ |
| ์ฌ์ฉ์ ์นํ์ ๋ฉ์์ง | ๐ด ํ์ | ๊ธฐ์ ์ ์๋ฌ ๋ฉ์์ง ๋
ธ์ถ ๊ธ์ง |
| Error Boundary ์ฌ์ฉ | ๐ด ํ์ (React) | ์ปดํฌ๋ํธ ์๋ฌ ๊ฒฉ๋ฆฌ |
| Graceful Degradation | ๐ก ๊ถ์ฅ | ๋ถ๋ถ ์คํจ ์ ๋์ ์ ๊ณต |
๊ธฐ๋ณธ ํจํด
Try-Catch ์ฌ๋ฐ๋ฅธ ์ฌ์ฉ
try {
await fetchData();
} catch (e) {
}
try {
await fetchData();
} catch (e) {
console.log('์๋ฌ ๋ฐ์');
}
try {
await fetchData();
} catch (error) {
console.error('fetchData failed:', error);
errorTracker.capture(error);
showToast('๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋๋ฐ ์คํจํ์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.');
return fallbackData;
}
์๋ฌ ํ์
๊ตฌ๋ถ
async function fetchUser(id: string) {
try {
const response = await api.get(`/users/${id}`);
return response.data;
} catch (error) {
if (error instanceof NetworkError) {
showToast('๋คํธ์ํฌ ์ฐ๊ฒฐ์ ํ์ธํด์ฃผ์ธ์.');
return null;
}
if (error instanceof NotFoundError) {
showToast('์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.');
return null;
}
if (error instanceof AuthError) {
router.push('/login');
return null;
}
console.error('Unexpected error:', error);
errorTracker.capture(error);
showToast('์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.');
return null;
}
}
์ปค์คํ
์๋ฌ ํด๋์ค
export class AppError extends Error {
constructor(
message: string,
public code: string,
public statusCode?: number,
public isOperational: boolean = true
) {
super(message);
this.name = 'AppError';
}
}
export class ValidationError extends AppError {
constructor(message: string, public field?: string) {
super(message, 'VALIDATION_ERROR', 400);
this.name = 'ValidationError';
}
}
export class NetworkError extends AppError {
constructor(message: string = '๋คํธ์ํฌ ์ฐ๊ฒฐ์ ํ์ธํด์ฃผ์ธ์') {
super(message, , );
. = ;
}
}
{
() {
(, , );
. = ;
}
}
React Error Boundary
๊ธฐ๋ณธ Error Boundary
import { Component, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
}
interface State {
hasError: boolean;
error?: Error;
}
export class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.(, error, errorInfo);
..?.(error, errorInfo);
errorTracker.(error, { : errorInfo });
}
() {
(..) {
.. || ;
}
..;
}
}
() {
(
);
}
Error Boundary ์ฌ์ฉ
function App() {
return (
<ErrorBoundary fallback={<FullPageError />}>
<Router>
<Routes />
</Router>
</ErrorBoundary>
);
}
function Dashboard() {
return (
<div>
<Header />
<ErrorBoundary fallback={<ChartError />}>
<Chart data={data} />
</ErrorBoundary>
<ErrorBoundary fallback={<TableError />}>
<DataTable data={data} />
</ErrorBoundary>
</div>
);
}
Async ์๋ฌ ์ฒ๋ฆฌ
Promise ์๋ฌ
fetchData().then(data => setData(data));
fetchData()
.then(data => setData(data))
.catch(error => {
console.error('Failed to fetch:', error);
setError(error);
});
async function loadData() {
try {
const data = await fetchData();
setData(data);
} catch (error) {
console.error('Failed to fetch:', error);
setError(error);
}
}
์ฌ๋ฌ Promise ์ฒ๋ฆฌ
const [users, posts] = await Promise.all([
fetchUsers(),
fetchPosts(),
]);
const results = await Promise.allSettled([
fetchUsers(),
fetchPosts(),
]);
const users = results[0].status === 'fulfilled' ? results[0].value : [];
const posts = results[1].status === 'fulfilled' ? results[1].value : [];
results
.filter((r): r is PromiseRejectedResult => r.status === 'rejected')
.forEach(r => console.error('Failed:', r.reason));
Graceful Degradation
๊ธฐ๋ฅ ์ ํ ํจํด
async function getRecommendations(userId: string) {
try {
return await fetchPersonalizedRecommendations(userId);
} catch (error) {
console.warn('Personalized recommendations failed:', error);
try {
return await fetchPopularContent();
} catch (error) {
console.warn('Popular content failed:', error);
return getCachedDefaultRecommendations();
}
}
}
UI ๋์ ์ ๊ณต
function UserAvatar({ userId }: { userId: string }) {
const [imageError, setImageError] = useState(false);
const user = useUser(userId);
if (imageError || !user?.avatarUrl) {
return (
<div className="avatar-placeholder">
{user?.name?.charAt(0) || '?'}
</div>
);
}
return (
<img
src={user.avatarUrl}
alt={user.name}
onError={() => setImageError(true)}
/>
);
}
์ฌ์ฉ์ ์นํ์ ๋ฉ์์ง
๋ฉ์์ง ๋งคํ
const errorMessages: Record<string, string> = {
NETWORK_ERROR: '๋คํธ์ํฌ ์ฐ๊ฒฐ์ ํ์ธํด์ฃผ์ธ์.',
UNAUTHORIZED: '๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค.',
FORBIDDEN: '์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค.',
NOT_FOUND: '์์ฒญํ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.',
VALIDATION_ERROR: '์
๋ ฅ ์ ๋ณด๋ฅผ ํ์ธํด์ฃผ์ธ์.',
RATE_LIMIT: '์์ฒญ์ด ๋๋ฌด ๋ง์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.',
SERVER_ERROR: '์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.',
DEFAULT: '์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.',
};
function getUserFriendlyMessage(error: unknown): string {
if (error instanceof AppError) {
return errorMessages[error.code] || errorMessages.DEFAULT;
}
return errorMessages.DEFAULT;
}
๐ด ๊ธ์ง: ๊ธฐ์ ์ ๋ฉ์์ง ๋
ธ์ถ
showToast(error.message);
showToast(error.stack);
showToast(getUserFriendlyMessage(error));
๋ก๊น
์ ๋ต
export const logger = {
error: (message: string, error: unknown, context?: object) => {
if (process.env.NODE_ENV === 'development') {
console.error(message, error, context);
}
errorTracker.captureException(error, {
tags: { message },
extra: context,
});
},
warn: (message: string, context?: object) => {
console.warn(message, context);
},
};
Checklist
์ฝ๋ ์์ฑ ์
React ์ปดํฌ๋ํธ
API ํธ์ถ
References