Ver3.0 ๐ JavaScript ์๋ฌ ํธ๋ค๋ง๊ณผ ์์ธ ์ฒ๋ฆฌ ๋ฒ ์คํธ ํ๋ํฐ์ค: ์์ ์ ์ธ ์ฝ๋๋ฅผ ์ํ ์๋ฒฝ ๊ฐ์ด๋

๐ JavaScript ์๋ฌ ํธ๋ค๋ง๊ณผ ์์ธ ์ฒ๋ฆฌ ๋ฒ ์คํธ ํ๋ํฐ์ค: ์์ ์ ์ธ ์ฝ๋๋ฅผ ์ํ ์๋ฒฝ ๊ฐ์ด๋
ํ๋ก๋์ ํ๊ฒฝ์์ ์ด์๋จ๋ ์๋ฌ ์ฒ๋ฆฌ ์ ๋ต ๐ช
์๋
! ์ค๋์ JavaScript ๊ฐ๋ฐ์๋ผ๋ฉด ๋ฐ๋์ ์์์ผ ํ ์๋ฌ ํธ๋ค๋ง๊ณผ ์์ธ ์ฒ๋ฆฌ์ ๋ํด ๊น์ด ์๊ฒ ํํค์ณ๋ณผ ๊ฑฐ์ผ. ๐
์์งํ ๋งํด์, ์ฝ๋๋ฅผ ์ง๋ค ๋ณด๋ฉด ์๋ฌ๋ ํผํ ์ ์๋ ์น๊ตฌ์ผ. ์๋, ์ด์ฉ๋ฉด ๊ฐ์ฅ ์์ฃผ ๋ง๋๋ ๋๋ฃ์ผ์ง๋ ๋ชฐ๋ผ! ๐ค ํ์ง๋ง ์ด ์๋ฌ๋ค์ ์ด๋ป๊ฒ ๋ค๋ฃจ๋๋์ ๋ฐ๋ผ ์ด๋ณด ๊ฐ๋ฐ์์ ์๋์ด ๊ฐ๋ฐ์๊ฐ ๊ฐ๋ฆฌ๋ ๊ฑฐ์ง.
์ด ๊ธ์์๋ ๋จ์ํ try-catch๋ง ์ฐ๋ ์์ค์ ๋์ด์, ์ค์ ์์ ๋ฐ๋ก ์จ๋จน์ ์ ์๋ ํ๋ก๋์
๋ ๋ฒจ์ ์๋ฌ ํธ๋ค๋ง ์ ๋ต์ ํจ๊ป ์์๋ณผ ๊ฑฐ์ผ. ์ฌ๋ฅ๋ท์์ ํ๋ก๊ทธ๋๋ฐ ์ฌ๋ฅ์ ๊ณต์ ํ๊ฑฐ๋ ๋ฐฐ์ฐ๋ ๋ถ๋ค์๊ฒ๋ ์ ๋ง ์ ์ฉํ ๋ด์ฉ์ด ๋ ๊ฑฐ๋ผ ํ์ ํด! ๐ก
๐ฏ ์ ์๋ฌ ํธ๋ค๋ง์ด ์ค์ํ ๊น?
๋ณธ๊ฒฉ์ ์ผ๋ก ๋ค์ด๊ฐ๊ธฐ ์ ์, ์ ์๋ฌ ํธ๋ค๋ง์ด ๊ทธ๋ ๊ฒ ์ค์ํ์ง๋ถํฐ ์ง๊ณ ๋์ด๊ฐ์. ๐ค
์์ํด๋ด. ๋๊ฐ ๋ง๋ ์น ์ ํ๋ฆฌ์ผ์ด์
์์ ์ฌ์ฉ์๊ฐ ๊ฒฐ์ ๋ฒํผ์ ๋๋ ๋๋ฐ, ๊ฐ์๊ธฐ ํ๋ฉด์ด ํ์๊ฒ ๋ณํ๋ฉด์ ์๋ฌด๊ฒ๋ ์ ๋๋ค๋ฉด? ์ฌ์ฉ์๋ ๋นํฉํ๊ณ , ๋๋ ๋ฐค์ ๋ฒ๊ทธ๋ฅผ ์ฐพ์ ํค๋งค๊ฒ ๋ ๊ฑฐ์ผ. ๐ฑ
์ข์ ์๋ฌ ํธ๋ค๋ง์ ๋ค์๊ณผ ๊ฐ์ ๊ฒ๋ค์ ๊ฐ๋ฅํ๊ฒ ํด์ค:
โ
์ฌ์ฉ์ ๊ฒฝํ ํฅ์
์๋ฌ๊ฐ ๋ฐ์ํด๋ ์ฑ์ด ์์ ํ ๋ฉ์ถ์ง ์๊ณ , ์ฌ์ฉ์์๊ฒ ์น์ ํ ๋ฉ์์ง๋ฅผ ๋ณด์ฌ์ค ์ ์์ด.
โ
๋๋ฒ๊น
์๊ฐ ๋จ์ถ
์ด๋์, ์ ์๋ฌ๊ฐ ๋ฌ๋์ง ๋ช
ํํ๊ฒ ๋ก๊น
๋๋ฉด ๋ฌธ์ ํด๊ฒฐ์ด ํจ์ฌ ๋นจ๋ผ์ ธ.
โ
์์คํ
์์ ์ฑ
์์์น ๋ชปํ ์ํฉ์์๋ ์์คํ
์ด ์ฐ์ํ๊ฒ ๋ณต๊ตฌ๋ ์ ์์ด.
โ
๋น์ฆ๋์ค ์ฐ์์ฑ
์ผ๋ถ ๊ธฐ๋ฅ์ ๋ฌธ์ ๊ฐ ์๊ฒจ๋ ํต์ฌ ๊ธฐ๋ฅ์ ๊ณ์ ์๋ํ ์ ์์ง.
๐ JavaScript ์๋ฌ์ ์ข ๋ฅ ์ดํดํ๊ธฐ
์๋ฌ๋ฅผ ์ ๋ค๋ฃจ๋ ค๋ฉด ๋จผ์ ์ด๋ค ์ข ๋ฅ์ ์๋ฌ๋ค์ด ์๋์ง ์์์ผ ํด. JavaScript์๋ ํฌ๊ฒ 7๊ฐ์ง ๊ธฐ๋ณธ ์๋ฌ ํ์ ์ด ์์ด. ํ๋์ฉ ์ดํด๋ณด์! ๐
| ์๋ฌ ํ์ | ์ค๋ช | ๋ฐ์ ์ํฉ |
|---|---|---|
| Error | ๊ธฐ๋ณธ ์๋ฌ ๊ฐ์ฒด | ๋ชจ๋ ์๋ฌ์ ๋ถ๋ชจ ํด๋์ค |
| SyntaxError | ๋ฌธ๋ฒ ์ค๋ฅ | ์ฝ๋ ํ์ฑ ๋จ๊ณ์์ ๋ฐ์ |
| ReferenceError | ์ฐธ์กฐ ์ค๋ฅ | ์กด์ฌํ์ง ์๋ ๋ณ์ ์ฐธ์กฐ |
| TypeError | ํ์ ์ค๋ฅ | ์๋ชป๋ ํ์ ์ฌ์ฉ |
| RangeError | ๋ฒ์ ์ค๋ฅ | ์ ํจ ๋ฒ์๋ฅผ ๋ฒ์ด๋ ๊ฐ |
| URIError | URI ์ฒ๋ฆฌ ์ค๋ฅ | URI ํจ์ ์๋ชป ์ฌ์ฉ |
| EvalError | eval() ์ค๋ฅ | eval() ํจ์ ๊ด๋ จ (๊ฑฐ์ ์ฌ์ฉ ์ ํจ) |
๐ก ์ค์ ํ:
์ค๋ฌด์์ ๊ฐ์ฅ ์์ฃผ ๋ง์ฃผ์น๋ ์๋ฌ๋ TypeError์ ReferenceError์ผ. ํนํ undefined๋ null์ ์ ๊ทผํ๋ ค๊ณ ํ ๋ ๋ฐ์ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ง. ์ด๋ฐ ์๋ฌ๋ค์ ๋ฏธ๋ฆฌ ๋ฐฉ์ดํ๋ ์ฝ๋๋ฅผ ์ง๋ ์ต๊ด์ ๋ค์ด๋ฉด ์ข์!
๊ฐ ์๋ฌ ํ์ ์ ์ค์ ์ฝ๋๋ก ํ์ธํด๋ณด์:
// SyntaxError - ๋ฌธ๋ฒ์ด ํ๋ ธ์ ๋
// const x = ; // ์ด๋ ๊ฒ ์ฐ๋ฉด SyntaxError!
// ReferenceError - ์ ์ธ๋์ง ์์ ๋ณ์ ์ฌ์ฉ
try {
console.log(nonExistentVariable);
} catch (error) {
console.log(error.name); // "ReferenceError"
console.log(error.message); // "nonExistentVariable is not defined"
}
// TypeError - ์๋ชป๋ ํ์
์ฌ์ฉ
try {
const num = 123;
num.toUpperCase(); // ์ซ์์๋ toUpperCase ๋ฉ์๋๊ฐ ์์ด!
} catch (error) {
console.log(error.name); // "TypeError"
}
// RangeError - ๋ฒ์๋ฅผ ๋ฒ์ด๋ ๊ฐ
try {
const arr = new Array(-1); // ๋ฐฐ์ด ๊ธธ์ด๋ ์์๊ฐ ๋ ์ ์์ด!
} catch (error) {
console.log(error.name); // "RangeError"
}
// URIError - URI ์ธ์ฝ๋ฉ/๋์ฝ๋ฉ ์คํจ
try {
decodeURIComponent('%'); // ์๋ชป๋ URI ํ์
} catch (error) {
console.log(error.name); // "URIError"
}
๐ก๏ธ Try-Catch-Finally: ๊ธฐ๋ณธ ์ค์ ๊ธฐ๋ณธ
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์๋ฌ๋ฅผ ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์! ๊ฐ์ฅ ๊ธฐ๋ณธ์ด ๋๋ ๊ฑด ๋ฐ๋ก try-catch-finally ๊ตฌ๋ฌธ์ด์ผ. ๐
์ด ๊ตฌ์กฐ๋ ์ ๋ง ์ง๊ด์ ์ด์ผ. "์๋ํด๋ด(try), ์ ๋๋ฉด ์ก์(catch), ๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ ์ด๊ฑธ ํด(finally)"๋ผ๋ ๋ป์ด๊ฑฐ๋ !
๐จ ๊ธฐ๋ณธ ๊ตฌ์กฐ:
try {
// ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์๋ ์ฝ๋
// ์ฌ๊ธฐ์ ๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด ์ฆ์ catch ๋ธ๋ก์ผ๋ก ์ด๋
} catch (error) {
// ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋ ์คํ๋๋ ์ฝ๋
// error ๊ฐ์ฒด๋ฅผ ํตํด ์๋ฌ ์ ๋ณด์ ์ ๊ทผ ๊ฐ๋ฅ
} finally {
// ์๋ฌ ๋ฐ์ ์ฌ๋ถ์ ๊ด๊ณ์์ด ํญ์ ์คํ๋๋ ์ฝ๋
// ์ฃผ๋ก ๋ฆฌ์์ค ์ ๋ฆฌ(cleanup) ์์
์ ์ฌ์ฉ
}
์ค์ ์์ ๋ฅผ ํ๋ ๋ณผ๊น? JSON ํ์ฑ์ ์๋ฌ๊ฐ ์์ฃผ ๋ฐ์ํ๋ ๋ํ์ ์ธ ์ผ์ด์ค์ผ:
function parseUserData(jsonString) {
try {
const userData = JSON.parse(jsonString);
console.log('โ
ํ์ฑ ์ฑ๊ณต:', userData);
return userData;
} catch (error) {
console.error('โ JSON ํ์ฑ ์คํจ:', error.message);
// ๊ธฐ๋ณธ๊ฐ ๋ฐํ์ผ๋ก ์ฑ์ด ๊ณ์ ๋์ํ๋๋ก ํจ
return { name: 'Unknown', age: 0 };
} finally {
console.log('๐ ํ์ฑ ์๋ ์๋ฃ');
}
}
// ํ
์คํธ
parseUserData('{"name":"์ฒ ์","age":25}'); // ์ ์ ์๋
parseUserData('์๋ชป๋ JSON'); // ์๋ฌ ์ฒ๋ฆฌ๋จ
๐ฏ Finally ๋ธ๋ก ํ์ฉ ํ:
finally ๋ธ๋ก์ ์ ํ์ฌํญ์ด์ง๋ง, ๋ค์๊ณผ ๊ฐ์ ์ํฉ์์ ์ ๋ง ์ ์ฉํด:
ํ์ผ ๋ซ๊ธฐ
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ํด์
๋ก๋ฉ ์คํผ๋ ์จ๊ธฐ๊ธฐ
ํ์ด๋จธ ์ ๋ฆฌ
๋ฉ๋ชจ๋ฆฌ ํด์
์๋ฌ๊ฐ ๋๋ ์ ๋๋ ๋ฐ๋์ ์คํ๋์ด์ผ ํ๋ ์ ๋ฆฌ ์์
์ด ์๋ค๋ฉด finally๋ฅผ ์ฌ์ฉํ์!
์ข ๋ ๋ณต์กํ ์ค์ ์์ ๋ฅผ ๋ณด์. API ํธ์ถ ์๋๋ฆฌ์ค์ผ:
async function fetchUserProfile(userId) {
let loadingElement = document.getElementById('loading');
try {
// ๋ก๋ฉ ํ์ ์์
loadingElement.style.display = 'block';
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('โ
์ฌ์ฉ์ ๋ฐ์ดํฐ:', data);
return data;
} catch (error) {
console.error('โ ํ๋กํ ๋ก๋ฉ ์คํจ:', error.message);
// ์ฌ์ฉ์์๊ฒ ์น์ ํ ์๋ฌ ๋ฉ์์ง ํ์
showErrorMessage('ํ๋กํ์ ๋ถ๋ฌ์ค๋๋ฐ ์คํจํ์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.');
// ์๋ฌ ๋ก๊น
์๋น์ค๋ก ์ ์ก (์: Sentry, LogRocket)
logErrorToService(error);
return null;
} finally {
// ์ฑ๊ณต์ด๋ ์คํจ๋ ๋ก๋ฉ ํ์๋ ๋ฐ๋์ ์จ๊น
loadingElement.style.display = 'none';
console.log('๐ API ํธ์ถ ์๋ฃ');
}
}
function showErrorMessage(message) {
// ์ค์ ๋ก๋ UI์ ์๋ฌ ๋ฉ์์ง๋ฅผ ํ์ํ๋ ๋ก์ง
alert(message);
}
function logErrorToService(error) {
// ์ค์ ๋ก๋ ์๋ฌ ๋ก๊น
์๋น์ค๋ก ์ ์ก
console.log('๐ ์๋ฌ ๋ก๊น
:', error);
}
โ ๏ธ ์ฃผ์์ฌํญ:
1. Catch ๋ธ๋ก์ ๋น์๋์ง ๋ง!
catch (error) {} ์ด๋ ๊ฒ ๋น catch ๋ธ๋ก์ ์ ๋ ๊ธ๋ฌผ! ์๋ฌ๋ฅผ ์ผ์ผ๋ฒ๋ฆฌ๋ฉด ๋๋ฒ๊น
์ด ๋ถ๊ฐ๋ฅํด์ ธ.
2. ๋๋ฌด ๋์ ๋ฒ์๋ฅผ Try๋ก ๊ฐ์ธ์ง ๋ง!
์ ์ฒด ํจ์๋ฅผ try๋ก ๊ฐ์ธ๋ฉด ์ด๋์ ์๋ฌ๊ฐ ๋ฌ๋์ง ํ์
ํ๊ธฐ ์ด๋ ค์. ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ์๋ ํน์ ๋ถ๋ถ๋ง ๊ฐ์ธ์.
3. ์๋ฌ๋ฅผ ์ฌ๋ฐ์์ํฌ ๋๋ ์๋ณธ ์๋ฌ ์ ๋ณด๋ฅผ ์ ์งํด!
throw error ๋๋ throw new Error('๋ฉ์์ง', { cause: error }) ํํ๋ก ์ฌ์ฉํ์.
๐ญ ์ปค์คํ ์๋ฌ ํด๋์ค ๋ง๋ค๊ธฐ
๊ธฐ๋ณธ ์๋ฌ ํ์
๋ง์ผ๋ก๋ ๋ถ์กฑํ ๋๊ฐ ์์ด. ํนํ ๋๊ท๋ชจ ์ ํ๋ฆฌ์ผ์ด์
์์๋ ์ปค์คํ
์๋ฌ ํด๋์ค๋ฅผ ๋ง๋ค์ด์ ์ฌ์ฉํ๋ ๊ฒ ํจ์ฌ ํจ์จ์ ์ด์ผ! ๐จ
์๋๊ณ ? ์๋ฌ๋ฅผ ๋ ์ธ๋ฐํ๊ฒ ๋ถ๋ฅํ๊ณ , ๊ฐ ์๋ฌ ํ์
์ ๋ง๋ ์ฒ๋ฆฌ๋ฅผ ํ ์ ์๊ฑฐ๋ . ๋ง์น ์ท์ ์ข
๋ฅ๋ณ๋ก ์ ๋ฆฌํ๋ ๊ฒ์ฒ๋ผ ๋ง์ด์ผ! ๐๐
// ๊ธฐ๋ณธ ์ปค์คํ
์๋ฌ ํด๋์ค
class AppError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.name = this.constructor.name;
this.statusCode = statusCode;
this.isOperational = true; // ์์ ๊ฐ๋ฅํ ์๋ฌ์ธ์ง ํ์
// ์คํ ํธ๋ ์ด์ค์์ ์์ฑ์ ํธ์ถ ์ ๊ฑฐ
Error.captureStackTrace(this, this.constructor);
}
}
// ํน์ ์ํฉ์ ์ํ ์ปค์คํ
์๋ฌ๋ค
class ValidationError extends AppError {
constructor(message) {
super(message, 400);
this.name = 'ValidationError';
}
}
class AuthenticationError extends AppError {
constructor(message = '์ธ์ฆ์ด ํ์ํฉ๋๋ค') {
super(message, 401);
this.name = 'AuthenticationError';
}
}
class NotFoundError extends AppError {
constructor(resource = '๋ฆฌ์์ค') {
super(`${resource}๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค`, 404);
this.name = 'NotFoundError';
}
}
class DatabaseError extends AppError {
constructor(message, originalError) {
super(message, 500);
this.name = 'DatabaseError';
this.originalError = originalError;
}
}
์ด์ ์ด ์ปค์คํ ์๋ฌ๋ค์ ์ค์ ๋ก ์ฌ์ฉํด๋ณด์:
// ์ฌ์ฉ์ ๋ฑ๋ก ํจ์ ์์
function registerUser(userData) {
try {
// 1. ์
๋ ฅ ๊ฒ์ฆ
if (!userData.email || !userData.email.includes('@')) {
throw new ValidationError('์ฌ๋ฐ๋ฅธ ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์');
}
if (!userData.password || userData.password.length < 8) {
throw new ValidationError('๋น๋ฐ๋ฒํธ๋ ์ต์ 8์ ์ด์์ด์ด์ผ ํฉ๋๋ค');
}
// 2. ์ค๋ณต ์ฒดํฌ
const existingUser = checkUserExists(userData.email);
if (existingUser) {
throw new ValidationError('์ด๋ฏธ ๋ฑ๋ก๋ ์ด๋ฉ์ผ์
๋๋ค');
}
// 3. ์ฌ์ฉ์ ์์ฑ
const newUser = createUser(userData);
return newUser;
} catch (error) {
// ์๋ฌ ํ์
์ ๋ฐ๋ผ ๋ค๋ฅธ ์ฒ๋ฆฌ
if (error instanceof ValidationError) {
console.log('๐ ์
๋ ฅ ๊ฒ์ฆ ์คํจ:', error.message);
// UI์ ์ฌ์ฉ์ ์นํ์ ์ธ ๋ฉ์์ง ํ์
showValidationError(error.message);
} else if (error instanceof DatabaseError) {
console.error('๐พ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๋ฅ:', error.message);
// ๊ด๋ฆฌ์์๊ฒ ์๋ฆผ
notifyAdmin(error);
} else {
console.error('โ ์์์น ๋ชปํ ์ค๋ฅ:', error);
// ์ผ๋ฐ์ ์ธ ์๋ฌ ์ฒ๋ฆฌ
showGenericError();
}
throw error; // ์์๋ก ์๋ฌ ์ ํ
}
}
// ์๋ฌ ํธ๋ค๋ฌ ํจ์
function handleError(error) {
// ์ด์ ์๋ฌ (์์ ๊ฐ๋ฅํ ์๋ฌ)
if (error.isOperational) {
return {
status: error.statusCode,
message: error.message
};
}
// ํ๋ก๊ทธ๋๋ฐ ์๋ฌ (๋ฒ๊ทธ)
console.error('๐ ํ๋ก๊ทธ๋๋ฐ ์๋ฌ:', error);
return {
status: 500,
message: '์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค'
};
}
๐ก ์ปค์คํ
์๋ฌ ํด๋์ค์ ์ฅ์ :
๋ช
ํํ ์๋ ์๋ฌ ์ด๋ฆ๋ง ๋ด๋ ๋ฌด์จ ๋ฌธ์ ์ธ์ง ์ ์ ์์ด
์ผ๊ด๋ ์ฒ๋ฆฌ ๊ฐ์ ํ์
์ ์๋ฌ๋ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌ ๊ฐ๋ฅ
์ถ๊ฐ ์ ๋ณด statusCode, ์๋ณธ ์๋ฌ ๋ฑ ํ์ํ ์ ๋ณด๋ฅผ ๋ด์ ์ ์์ด
ํ์
์ฒดํฌ instanceof๋ก ์๋ฌ ํ์
์ ์ฝ๊ฒ ํ์ธ ๊ฐ๋ฅ
์ ์ง๋ณด์ ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง์ ์ค์ํํ๊ณ ๊ด๋ฆฌํ๊ธฐ ์ฌ์์ ธ
์ค์ ์์ ์์ฃผ ์ฌ์ฉํ๋ ํจํด์ ํ๋ ๋ ๋ณด์. API ์๋ต ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๋ ์ปค์คํ ์๋ฌ์ผ:
class ApiError extends AppError {
constructor(message, statusCode, endpoint, method = 'GET') {
super(message, statusCode);
this.name = 'ApiError';
this.endpoint = endpoint;
this.method = method;
this.timestamp = new Date().toISOString();
}
toJSON() {
return {
name: this.name,
message: this.message,
statusCode: this.statusCode,
endpoint: this.endpoint,
method: this.method,
timestamp: this.timestamp
};
}
}
// API ํธ์ถ ๋ํผ ํจ์
async function apiCall(endpoint, options = {}) {
try {
const response = await fetch(endpoint, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new ApiError(
errorData.message || 'API ์์ฒญ ์คํจ',
response.status,
endpoint,
options.method || 'GET'
);
}
return await response.json();
} catch (error) {
if (error instanceof ApiError) {
// API ์๋ฌ๋ ๊ทธ๋๋ก ์ ํ
throw error;
}
// ๋คํธ์ํฌ ์๋ฌ ๋ฑ ๋ค๋ฅธ ์๋ฌ๋ ApiError๋ก ๋ณํ
throw new ApiError(
'๋คํธ์ํฌ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค',
0,
endpoint,
options.method || 'GET'
);
}
}
// ์ฌ์ฉ ์์
async function loadUserData(userId) {
try {
const userData = await apiCall(`/api/users/${userId}`);
console.log('โ
์ฌ์ฉ์ ๋ฐ์ดํฐ ๋ก๋ ์ฑ๊ณต:', userData);
return userData;
} catch (error) {
if (error instanceof ApiError) {
console.error('๐ API ์๋ฌ:', error.toJSON());
// ์ํ ์ฝ๋์ ๋ฐ๋ฅธ ์ฒ๋ฆฌ
switch (error.statusCode) {
case 401:
redirectToLogin();
break;
case 404:
showNotFoundMessage();
break;
case 500:
showServerErrorMessage();
break;
default:
showGenericError();
}
}
}
}
๐ ๋น๋๊ธฐ ์ฝ๋์ ์๋ฌ ํธ๋ค๋ง
์, ์ด์ ์ง์ง ์ค์ํ ๋ถ๋ถ์ด์ผ! ํ๋ JavaScript ๊ฐ๋ฐ์์ ๋น๋๊ธฐ ์ฝ๋๋ ํผํ ์ ์๋ ์กด์ฌ๊ฑฐ๋ . ๊ทธ๋ฐ๋ฐ ๋น๋๊ธฐ ์ฝ๋์ ์๋ฌ ํธ๋ค๋ง์ ๋๊ธฐ ์ฝ๋์๋ ์ข ๋ฌ๋ผ. ๐ข
Promise, async/await, ์ฝ๋ฐฑ... ๊ฐ๊ฐ์ ๋ฐฉ์๋ง๋ค ์๋ฌ๋ฅผ ๋ค๋ฃจ๋ ๋ฐฉ๋ฒ์ด ๋ค๋ฅด๋๊น ํ๋์ฉ ์ ๋ณตํด๋ณด์! ๐ช
1๏ธโฃ Promise ์๋ฌ ํธ๋ค๋ง
// Promise์ ๊ธฐ๋ณธ ์๋ฌ ํธ๋ค๋ง
function fetchData(url) {
return fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('โ
๋ฐ์ดํฐ ๋ก๋ ์ฑ๊ณต:', data);
return data;
})
.catch(error => {
console.error('โ ๋ฐ์ดํฐ ๋ก๋ ์คํจ:', error.message);
// ์๋ฌ๋ฅผ ์ฒ๋ฆฌํ๊ณ ๊ธฐ๋ณธ๊ฐ ๋ฐํ
return { default: true };
})
.finally(() => {
console.log('๐ ์์ฒญ ์๋ฃ');
});
}
// Promise.all ์๋ฌ ํธ๋ค๋ง
async function loadMultipleResources() {
const urls = [
'/api/users',
'/api/posts',
'/api/comments'
];
try {
// ํ๋๋ผ๋ ์คํจํ๋ฉด ์ ์ฒด ์คํจ
const results = await Promise.all(
urls.map(url => fetch(url).then(r => r.json()))
);
console.log('โ
๋ชจ๋ ๋ฆฌ์์ค ๋ก๋ ์ฑ๊ณต:', results);
return results;
} catch (error) {
console.error('โ ๋ฆฌ์์ค ๋ก๋ ์คํจ:', error);
throw error;
}
}
// Promise.allSettled - ๋ ์์ ํ ๋ฐฉ๋ฒ
async function loadMultipleResourcesSafely() {
const urls = [
'/api/users',
'/api/posts',
'/api/comments'
];
const results = await Promise.allSettled(
urls.map(url => fetch(url).then(r => r.json()))
);
// ๊ฐ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ๋ณ์ ์ผ๋ก ์ฒ๋ฆฌ
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`โ
${urls[index]} ์ฑ๊ณต:`, result.value);
} else {
console.error(`โ ${urls[index]} ์คํจ:`, result.reason);
}
});
// ์ฑ๊ณตํ ๊ฒ๋ค๋ง ํํฐ๋ง
const successfulResults = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);
return successfulResults;
}
๐ฏ Promise.all vs Promise.allSettled:
Promise.all: ํ๋๋ผ๋ ์คํจํ๋ฉด ์ฆ์ reject. ๋ชจ๋ ์์ฒญ์ด ์ฑ๊ณตํด์ผ ํ ๋ ์ฌ์ฉ.
Promise.allSettled: ๋ชจ๋ Promise๊ฐ ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆผ. ์ผ๋ถ ์คํจํด๋ ๊ด์ฐฎ์ ๋ ์ฌ์ฉ.
Promise.race: ๊ฐ์ฅ ๋จผ์ ์๋ฃ๋๋ ๊ฒ๋ง ์ฌ์ฉ. ํ์์์ ๊ตฌํ์ ์ ์ฉ.
Promise.any: ํ๋๋ผ๋ ์ฑ๊ณตํ๋ฉด OK. ์ฌ๋ฌ ๋์ ์ค ํ๋๋ง ์ฑ๊ณตํ๋ฉด ๋ ๋ ์ฌ์ฉ.
2๏ธโฃ Async/Await ์๋ฌ ํธ๋ค๋ง
Async/await๋ ๋น๋๊ธฐ ์ฝ๋๋ฅผ ๋๊ธฐ ์ฝ๋์ฒ๋ผ ์์ฑํ ์ ์๊ฒ ํด์ฃผ๋ ๋ฉ์ง ๋ฌธ๋ฒ์ด์ผ. ์๋ฌ ํธ๋ค๋ง๋ try-catch๋ก ๊น๋ํ๊ฒ ํ ์ ์์ง! ๐
// ๊ธฐ๋ณธ async/await ์๋ฌ ํธ๋ค๋ง
async function getUserData(userId) {
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค: ${userId}`);
}
const userData = await response.json();
return userData;
} catch (error) {
console.error('โ ์ฌ์ฉ์ ๋ฐ์ดํฐ ๋ก๋ ์คํจ:', error.message);
throw error; // ์์๋ก ์ ํ
}
}
// ์ฌ๋ฌ ๋น๋๊ธฐ ์์
์ ์๋ฌ ํธ๋ค๋ง
async function processUserOrder(userId, orderId) {
try {
// 1๋จ๊ณ: ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
const user = await getUserData(userId);
console.log('โ
์ฌ์ฉ์ ์ ๋ณด:', user);
// 2๋จ๊ณ: ์ฃผ๋ฌธ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
const order = await getOrderData(orderId);
console.log('โ
์ฃผ๋ฌธ ์ ๋ณด:', order);
// 3๋จ๊ณ: ๊ฒฐ์ ์ฒ๋ฆฌ
const payment = await processPayment(user, order);
console.log('โ
๊ฒฐ์ ์๋ฃ:', payment);
// 4๋จ๊ณ: ์ด๋ฉ์ผ ๋ฐ์ก
await sendConfirmationEmail(user.email, order);
console.log('โ
ํ์ธ ์ด๋ฉ์ผ ๋ฐ์ก ์๋ฃ');
return { success: true, orderId, payment };
} catch (error) {
// ์ด๋ ๋จ๊ณ์์ ์คํจํ๋์ง ๋ก๊น
console.error('โ ์ฃผ๋ฌธ ์ฒ๋ฆฌ ์คํจ:', error.message);
// ์คํจํ ์์
์ ๋ฐ๋ผ ๋กค๋ฐฑ ์ฒ๋ฆฌ
await rollbackOrder(orderId);
throw new Error(`์ฃผ๋ฌธ ์ฒ๋ฆฌ ์ค ์ค๋ฅ ๋ฐ์: ${error.message}`);
}
}
๐จ ์๋ฌ ํธ๋ค๋ง ํจํด: ๋ํผ ํจ์
๋ฐ๋ณต์ ์ธ try-catch๋ฅผ ์ค์ด๊ธฐ ์ํ ์ ์ฉํ ํจํด์ด์ผ:
// ์๋ฌ ํธ๋ค๋ง ๋ํผ ํจ์
function asyncHandler(fn) {
return async function(...args) {
try {
return await fn(...args);
} catch (error) {
console.error('โ ์๋ฌ ๋ฐ์:', error.message);
// ์๋ฌ ๋ก๊น
์๋น์ค๋ก ์ ์ก
logErrorToService(error);
throw error;
}
};
}
// ์ฌ์ฉ ์์
const safeGetUserData = asyncHandler(async (userId) => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error('์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค');
return await response.json();
});
// ์ด์ try-catch ์์ด ์ฌ์ฉ ๊ฐ๋ฅ
const userData = await safeGetUserData(123);
3๏ธโฃ ํ์์์๊ณผ ์ฌ์๋ ๋ก์ง
๋คํธ์ํฌ ์์ฒญ์ ์ธ์ ๋ ์คํจํ ์ ์์ด. ๊ทธ๋์ ํ์์์๊ณผ ์ฌ์๋ ๋ก์ง์ ๊ตฌํํ๋ ๊ฒ ์ค์ํด! โฑ๏ธ
// ํ์์์ ๊ตฌํ
function fetchWithTimeout(url, timeout = 5000) {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('์์ฒญ ์๊ฐ ์ด๊ณผ')), timeout)
)
]);
}
// ์ฌ์๋ ๋ก์ง ๊ตฌํ
async function fetchWithRetry(url, options = {}) {
const maxRetries = options.maxRetries || 3;
const retryDelay = options.retryDelay || 1000;
for (let i = 0; i < maxRetries; i++) {
try {
console.log(`๐ ์๋ ${i + 1}/${maxRetries}`);
const response = await fetchWithTimeout(url, options.timeout);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
console.log('โ
์์ฒญ ์ฑ๊ณต!');
return await response.json();
} catch (error) {
console.error(`โ ์๋ ${i + 1} ์คํจ:`, error.message);
// ๋ง์ง๋ง ์๋์๋ค๋ฉด ์๋ฌ ๋์ง๊ธฐ
if (i === maxRetries - 1) {
throw new Error(`${maxRetries}๋ฒ ์๋ ํ ์คํจ: ${error.message}`);
}
// ๋ค์ ์๋ ์ ๋๊ธฐ (์ง์ ๋ฐฑ์คํ)
const delay = retryDelay * Math.pow(2, i);
console.log(`โณ ${delay}ms ํ ์ฌ์๋...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// ์ฌ์ฉ ์์
async function loadCriticalData() {
try {
const data = await fetchWithRetry('/api/critical-data', {
maxRetries: 3,
retryDelay: 1000,
timeout: 5000
});
console.log('โ
์ค์ ๋ฐ์ดํฐ ๋ก๋ ์๋ฃ:', data);
return data;
} catch (error) {
console.error('โ ์ค์ ๋ฐ์ดํฐ ๋ก๋ ์ต์ข
์คํจ:', error.message);
// ํด๋ฐฑ ๋ฐ์ดํฐ ์ฌ์ฉ
return getDefaultData();
}
}
โ ๏ธ ๋น๋๊ธฐ ์๋ฌ ํธ๋ค๋ง ์ฃผ์์ฌํญ:
1. Unhandled Promise Rejection ๋ฐฉ์ง
๋ชจ๋ Promise์๋ ๋ฐ๋์ .catch()๋ try-catch๋ฅผ ๋ถ์ฌ์ผ ํด!
2. Event Loop ๋ธ๋กํน ์ฃผ์
๋๋ฌด ๋ง์ ์ฌ์๋๋ ๊ธด ํ์์์์ ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ์ผ์ผํฌ ์ ์์ด.
3. ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง
ํ์์์์ด๋ ์ธํฐ๋ฒ์ ๋ฐ๋์ ์ ๋ฆฌํด์ค์ผ ํด. clearTimeout(), clearInterval() ์์ง ๋ง!
4. ์๋ฌ ์ ํ ์ฒด์ธ
async ํจ์์์ ์๋ฌ๋ฅผ catchํ๊ณ ๋ค์ throwํ์ง ์์ผ๋ฉด, ์์ ํธ์ถ์๋ ์๋ฌ๋ฅผ ์ ์ ์์ด.
๐ช ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ ๊ตฌํํ๊ธฐ
์๋ฌด๋ฆฌ ์กฐ์ฌํด๋ ๋์น๋ ์๋ฌ๊ฐ ์์ ์ ์์ด. ๊ทธ๋์ ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ๋ฅผ ๊ตฌํํ๋ ๊ฒ ์ค์ํด! ์ด๊ฑด ๋ง์ง๋ง ์์ ๋ง ๊ฐ์ ๊ฑฐ์ผ. ๐ฅ
๋ธ๋ผ์ฐ์ ์ Node.js ํ๊ฒฝ์์ ๊ฐ๊ฐ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ์์๋ณด์!
๐ ๋ธ๋ผ์ฐ์ ํ๊ฒฝ
// 1. ์ผ๋ฐ JavaScript ์๋ฌ ์บ์น
window.addEventListener('error', (event) => {
console.error('๐จ ์ ์ญ ์๋ฌ ๋ฐ์:', {
message: event.message,
filename: event.filename,
lineno: event.lineno,
colno: event.colno,
error: event.error
});
// ์๋ฌ ๋ก๊น
์๋น์ค๋ก ์ ์ก
logErrorToService({
type: 'JavaScript Error',
message: event.message,
stack: event.error?.stack,
url: window.location.href,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString()
});
// ์ฌ์ฉ์์๊ฒ ์น์ ํ ๋ฉ์์ง ํ์
showErrorNotification('์ผ์์ ์ธ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํด์ฃผ์ธ์.');
// ๊ธฐ๋ณธ ์๋ฌ ์ฒ๋ฆฌ ๋ฐฉ์ง (์ ํ์ฌํญ)
// event.preventDefault();
});
// 2. Promise rejection ์บ์น
window.addEventListener('unhandledrejection', (event) => {
console.error('๐จ ์ฒ๋ฆฌ๋์ง ์์ Promise rejection:', {
reason: event.reason,
promise: event.promise
});
// ์๋ฌ ๋ก๊น
logErrorToService({
type: 'Unhandled Promise Rejection',
reason: event.reason?.message || event.reason,
stack: event.reason?.stack,
url: window.location.href,
timestamp: new Date().toISOString()
});
// ๊ธฐ๋ณธ ๋์ ๋ฐฉ์ง
event.preventDefault();
});
// 3. ๋ฆฌ์์ค ๋ก๋ฉ ์๋ฌ ์บ์น
window.addEventListener('error', (event) => {
if (event.target !== window) {
console.error('๐จ ๋ฆฌ์์ค ๋ก๋ฉ ์คํจ:', {
tagName: event.target.tagName,
src: event.target.src || event.target.href
});
// ์ด๋ฏธ์ง ๋ก๋ฉ ์คํจ ์ ๋์ฒด ์ด๋ฏธ์ง ํ์
if (event.target.tagName === 'IMG') {
event.target.src = '/images/placeholder.png';
}
}
}, true); // capture phase์์ ์บ์น
๐ก ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ ํ์ฉ ํ:
์๋ฌ ๋ก๊น
Sentry, LogRocket, Bugsnag ๊ฐ์ ์๋น์ค๋ก ์ ์ก
์ฌ์ฉ์ ์๋ฆผ ์น์ ํ ์๋ฌ ๋ฉ์์ง ํ์
์๋ ๋ณต๊ตฌ ๊ฐ๋ฅํ ๊ฒฝ์ฐ ์๋์ผ๋ก ์ฌ์๋
๋ถ์ ์๋ฌ ํจํด ๋ถ์์ผ๋ก ๋ฒ๊ทธ ์ฐ์ ์์ ๊ฒฐ์
์๋ฆผ ์ฌ๊ฐํ ์๋ฌ ๋ฐ์ ์ ๊ฐ๋ฐํ์ ์ฆ์ ์๋ฆผ
๐ฅ๏ธ Node.js ํ๊ฒฝ
// Node.js ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ
process.on('uncaughtException', (error, origin) => {
console.error('๐จ ์ฒ๋ฆฌ๋์ง ์์ ์์ธ:', {
error: error.message,
stack: error.stack,
origin: origin
});
// ์๋ฌ ๋ก๊น
logErrorToFile(error);
// ํ๋ก์ธ์ค ์ข
๋ฃ (์์ ํ๊ฒ)
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('๐จ ์ฒ๋ฆฌ๋์ง ์์ Promise rejection:', {
reason: reason,
promise: promise
});
// ์๋ฌ ๋ก๊น
logErrorToFile(reason);
});
// Express.js ์๋ฌ ํธ๋ค๋ง ๋ฏธ๋ค์จ์ด
function errorHandler(err, req, res, next) {
console.error('๐จ Express ์๋ฌ:', {
message: err.message,
stack: err.stack,
url: req.url,
method: req.method,
ip: req.ip
});
// ์๋ฌ ๋ก๊น
logErrorToService({
type: 'Express Error',
message: err.message,
stack: err.stack,
url: req.url,
method: req.method,
headers: req.headers,
body: req.body,
timestamp: new Date().toISOString()
});
// ํด๋ผ์ด์ธํธ์ ์๋ต
if (err.isOperational) {
// ์ด์ ์๋ฌ (์์ ๊ฐ๋ฅํ ์๋ฌ)
res.status(err.statusCode || 500).json({
status: 'error',
message: err.message
});
} else {
// ํ๋ก๊ทธ๋๋ฐ ์๋ฌ (๋ฒ๊ทธ)
res.status(500).json({
status: 'error',
message: '์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค'
});
}
}
// Express ์ฑ์ ์๋ฌ ํธ๋ค๋ฌ ๋ฑ๋ก
app.use(errorHandler);
์ค์ ์์ ์ฌ์ฉํ๋ ํตํฉ ์๋ฌ ํธ๋ค๋ง ์์คํ ์ ๊ตฌ์ถํด๋ณด์:
// ํตํฉ ์๋ฌ ํธ๋ค๋ง ์์คํ
class ErrorHandler {
constructor() {
this.errorLog = [];
this.maxLogSize = 100;
this.setupHandlers();
}
setupHandlers() {
// ๋ธ๋ผ์ฐ์ ํ๊ฒฝ
if (typeof window !== 'undefined') {
window.addEventListener('error', this.handleError.bind(this));
window.addEventListener('unhandledrejection', this.handleRejection.bind(this));
}
// Node.js ํ๊ฒฝ
if (typeof process !== 'undefined') {
process.on('uncaughtException', this.handleError.bind(this));
process.on('unhandledRejection', this.handleRejection.bind(this));
}
}
handleError(event) {
const errorInfo = {
type: 'error',
message: event.message || event.error?.message,
stack: event.error?.stack,
timestamp: new Date().toISOString(),
url: typeof window !== 'undefined' ? window.location.href : 'N/A',
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : 'N/A'
};
this.logError(errorInfo);
this.notifyUser(errorInfo);
this.sendToService(errorInfo);
}
handleRejection(event) {
const errorInfo = {
type: 'unhandledRejection',
reason: event.reason?.message || event.reason,
stack: event.reason?.stack,
timestamp: new Date().toISOString(),
url: typeof window !== 'undefined' ? window.location.href : 'N/A'
};
this.logError(errorInfo);
this.sendToService(errorInfo);
event.preventDefault();
}
logError(errorInfo) {
console.error('๐จ ์๋ฌ ๋ฐ์:', errorInfo);
this.errorLog.push(errorInfo);
// ๋ก๊ทธ ํฌ๊ธฐ ์ ํ
if (this.errorLog.length > this.maxLogSize) {
this.errorLog.shift();
}
// ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ์ ์ฅ (๋ธ๋ผ์ฐ์ )
if (typeof localStorage !== 'undefined') {
try {
localStorage.setItem('errorLog', JSON.stringify(this.errorLog));
} catch (e) {
console.error('๋ก์ปฌ ์คํ ๋ฆฌ์ง ์ ์ฅ ์คํจ:', e);
}
}
}
notifyUser(errorInfo) {
// ์ฌ์ฉ์์๊ฒ ์น์ ํ ๋ฉ์์ง ํ์
if (typeof window !== 'undefined') {
const message = this.getUserFriendlyMessage(errorInfo);
// ์ค์ ๋ก๋ ํ ์คํธ ๋ฉ์์ง๋ ๋ชจ๋ฌ์ ์ฌ์ฉ
console.log('๐ข ์ฌ์ฉ์ ์๋ฆผ:', message);
}
}
getUserFriendlyMessage(errorInfo) {
// ์๋ฌ ํ์
์ ๋ฐ๋ผ ๋ค๋ฅธ ๋ฉ์์ง
if (errorInfo.message?.includes('network')) {
return '๋คํธ์ํฌ ์ฐ๊ฒฐ์ ํ์ธํด์ฃผ์ธ์.';
}
if (errorInfo.message?.includes('timeout')) {
return '์์ฒญ ์๊ฐ์ด ์ด๊ณผ๋์์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.';
}
return '์ผ์์ ์ธ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.';
}
async sendToService(errorInfo) {
// ์๋ฌ ๋ก๊น
์๋น์ค๋ก ์ ์ก
try {
await fetch('/api/log-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(errorInfo)
});
} catch (e) {
console.error('์๋ฌ ๋ก๊น
์๋น์ค ์ ์ก ์คํจ:', e);
}
}
getErrorLog() {
return this.errorLog;
}
clearErrorLog() {
this.errorLog = [];
if (typeof localStorage !== 'undefined') {
localStorage.removeItem('errorLog');
}
}
}
// ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ ์ธ์คํด์ค ์์ฑ
const globalErrorHandler = new ErrorHandler();
// ์ฌ์ฉ ์์
console.log('๐ ์๋ฌ ๋ก๊ทธ:', globalErrorHandler.getErrorLog());
๐ ์๋ฌ ๋ก๊น ๊ณผ ๋ชจ๋ํฐ๋ง
์๋ฌ๋ฅผ ์ก๋ ๊ฒ๋ ์ค์ํ์ง๋ง, ๊ทธ ์๋ฌ๋ค์ ๊ธฐ๋กํ๊ณ ๋ถ์ํ๋ ๊ฒ๋ ์์ฒญ ์ค์ํด! ๐
ํ๋ก๋์
ํ๊ฒฝ์์๋ ์ฌ์ฉ์๊ฐ ๊ฒช๋ ๋ชจ๋ ์๋ฌ๋ฅผ ์ถ์ ํ๊ณ ๋ถ์ํด์ผ ํด. ๊ทธ๋์ผ ๋ฒ๊ทธ๋ฅผ ๋น ๋ฅด๊ฒ ์ฐพ์์ ๊ณ ์น ์ ์๊ฑฐ๋ ! ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์๋ ์์ ์ ์ธ ์๋น์ค๋ฅผ ์ํด ์ด๋ฐ ์์คํ
์ด ํ์์ ์ด์ผ.
๐ ๏ธ ์๋ฌ ๋ก๊น ์์คํ ๊ตฌ์ถ
// ๊ณ ๊ธ ์๋ฌ ๋ก๊ฑฐ ํด๋์ค
class ErrorLogger {
constructor(config = {}) {
this.config = {
endpoint: config.endpoint || '/api/logs',
batchSize: config.batchSize || 10,
flushInterval: config.flushInterval || 30000, // 30์ด
enableConsole: config.enableConsole !== false,
environment: config.environment || 'production'
};
this.errorQueue = [];
this.startAutoFlush();
}
// ์๋ฌ ๋ก๊น
๋ฉ์ธ ๋ฉ์๋
log(error, context = {}) {
const errorData = this.formatError(error, context);
if (this.config.enableConsole) {
console.error('๐ด Error logged:', errorData);
}
this.errorQueue.push(errorData);
// ๋ฐฐ์น ํฌ๊ธฐ์ ๋๋ฌํ๋ฉด ์ฆ์ ์ ์ก
if (this.errorQueue.length >= this.config.batchSize) {
this.flush();
}
}
// ์๋ฌ ํฌ๋งทํ
formatError(error, context) {
return {
// ๊ธฐ๋ณธ ์๋ฌ ์ ๋ณด
message: error.message || String(error),
name: error.name || 'Error',
stack: error.stack,
// ์ปจํ
์คํธ ์ ๋ณด
context: {
...context,
url: typeof window !== 'undefined' ? window.location.href : undefined,
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
timestamp: new Date().toISOString(),
environment: this.config.environment
},
// ์ฌ์ฉ์ ์ ๋ณด (์๋ค๋ฉด)
user: this.getUserInfo(),
// ์์คํ
์ ๋ณด
system: this.getSystemInfo(),
// ์ถ๊ฐ ๋ฉํ๋ฐ์ดํฐ
metadata: {
errorId: this.generateErrorId(),
severity: this.calculateSeverity(error),
tags: this.extractTags(error, context)
}
};
}
// ์ฌ์ฉ์ ์ ๋ณด ์์ง
getUserInfo() {
// ์ค์ ๋ก๋ ์ธ์ฆ ์์คํ
์์ ๊ฐ์ ธ์ด
return {
id: localStorage.getItem('userId'),
sessionId: this.getSessionId()
};
}
// ์์คํ
์ ๋ณด ์์ง
getSystemInfo() {
if (typeof window === 'undefined') return {};
return {
screen: {
width: window.screen.width,
height: window.screen.height
},
viewport: {
width: window.innerWidth,
height: window.innerHeight
},
memory: performance.memory ? {
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize
} : undefined
};
}
// ์๋ฌ ์ฌ๊ฐ๋ ๊ณ์ฐ
calculateSeverity(error) {
if (error.name === 'TypeError' || error.name === 'ReferenceError') {
return 'high';
}
if (error.message?.includes('network') || error.message?.includes('timeout')) {
return 'medium';
}
return 'low';
}
// ํ๊ทธ ์ถ์ถ
extractTags(error, context) {
const tags = [];
if (error.name) tags.push(error.name);
if (context.component) tags.push(`component:${context.component}`);
if (context.action) tags.push(`action:${context.action}`);
return tags;
}
// ๊ณ ์ ์๋ฌ ID ์์ฑ
generateErrorId() {
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
// ์ธ์
ID ๊ฐ์ ธ์ค๊ธฐ/์์ฑ
getSessionId() {
let sessionId = sessionStorage.getItem('sessionId');
if (!sessionId) {
sessionId = this.generateErrorId();
sessionStorage.setItem('sessionId', sessionId);
}
return sessionId;
}
// ์๋ฒ๋ก ์ ์ก
async flush() {
if (this.errorQueue.length === 0) return;
const errors = [...this.errorQueue];
this.errorQueue = [];
try {
const response = await fetch(this.config.endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ errors })
});
if (!response.ok) {
console.error('์๋ฌ ๋ก๊ทธ ์ ์ก ์คํจ:', response.status);
// ์คํจํ ์๋ฌ๋ค์ ๋ค์ ํ์ ์ถ๊ฐ
this.errorQueue.unshift(...errors);
}
} catch (error) {
console.error('์๋ฌ ๋ก๊ทธ ์ ์ก ์ค ์ค๋ฅ:', error);
// ์คํจํ ์๋ฌ๋ค์ ๋ค์ ํ์ ์ถ๊ฐ
this.errorQueue.unshift(...errors);
}
}
// ์๋ ํ๋ฌ์ ์์
startAutoFlush() {
this.flushTimer = setInterval(() => {
this.flush();
}, this.config.flushInterval);
}
// ์ ๋ฆฌ
destroy() {
if (this.flushTimer) {
clearInterval(this.flushTimer);
}
this.flush(); // ๋จ์ ์๋ฌ๋ค ์ ์ก
}
}
// ์ ์ญ ๋ก๊ฑฐ ์ธ์คํด์ค
const errorLogger = new ErrorLogger({
endpoint: '/api/error-logs',
batchSize: 5,
flushInterval: 20000,
environment: process.env.NODE_ENV || 'development'
});
// ์ฌ์ฉ ์์
try {
// ์ํํ ์์
riskyOperation();
} catch (error) {
errorLogger.log(error, {
component: 'UserProfile',
action: 'loadData',
userId: 123
});
}
๐ฏ ์ธ๊ธฐ ์๋ฌ ๋ชจ๋ํฐ๋ง ์๋น์ค:
Sentry ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ์๋ฌ ํธ๋ํน ์๋น์ค. ์ค์๊ฐ ์๋ฆผ, ์คํ ํธ๋ ์ด์ค, ์ฌ์ฉ์ ์ปจํ
์คํธ ์ ๊ณต
LogRocket ์ธ์
๋ฆฌํ๋ ์ด ๊ธฐ๋ฅ์ผ๋ก ์๋ฌ ๋ฐ์ ์ ์ฌ์ฉ์์ ํ๋์ ์ฌํ ๊ฐ๋ฅ
Bugsnag ๋ชจ๋ฐ์ผ ์ฑ๊ณผ ์น ๋ชจ๋ ์ง์. ์์ ์ฑ ์ ์ ์ ๊ณต
Rollbar ์ค์๊ฐ ์๋ฌ ๊ทธ๋ฃนํ๊ณผ ์ฐ์ ์์ ์ง์
New Relic APM๊ณผ ํตํฉ๋ ์ข
ํฉ ๋ชจ๋ํฐ๋ง ์๋ฃจ์
๐ ์๋ฌ ๋ถ์ ๋์๋ณด๋
์๋ฌ ๋ฐ์ดํฐ๋ฅผ ์์งํ๋ค๋ฉด, ์ด์ ๋ถ์ํด์ผ ํด! ๊ฐ๋จํ ์๋ฌ ๋ถ์ ์์คํ ์ ๋ง๋ค์ด๋ณด์:
// ์๋ฌ ๋ถ์ ํด๋์ค
class ErrorAnalytics {
constructor() {
this.errors = [];
this.loadFromStorage();
}
// ์๋ฌ ์ถ๊ฐ
addError(error) {
this.errors.push({
...error,
timestamp: new Date().toISOString()
});
this.saveToStorage();
}
// ์๋ฌ ํต๊ณ
getStatistics() {
return {
total: this.errors.length,
byType: this.groupByType(),
bySeverity: this.groupBySeverity(),
byTime: this.groupByTime(),
topErrors: this.getTopErrors(5),
errorRate: this.calculateErrorRate()
};
}
// ํ์
๋ณ ๊ทธ๋ฃนํ
groupByType() {
const grouped = {};
this.errors.forEach(error => {
const type = error.name || 'Unknown';
grouped[type] = (grouped[type] || 0) + 1;
});
return grouped;
}
// ์ฌ๊ฐ๋๋ณ ๊ทธ๋ฃนํ
groupBySeverity() {
const grouped = { high: 0, medium: 0, low: 0 };
this.errors.forEach(error => {
const severity = error.metadata?.severity || 'low';
grouped[severity]++;
});
return grouped;
}
// ์๊ฐ๋๋ณ ๊ทธ๋ฃนํ
groupByTime() {
const now = Date.now();
const oneHour = 60 * 60 * 1000;
return {
lastHour: this.errors.filter(e =>
now - new Date(e.timestamp).getTime() < oneHour
).length,
last24Hours: this.errors.filter(e =>
now - new Date(e.timestamp).getTime() < 24 * oneHour
).length,
last7Days: this.errors.filter(e =>
now - new Date(e.timestamp).getTime() < 7 * 24 * oneHour
).length
};
}
// ๊ฐ์ฅ ๋ง์ด ๋ฐ์ํ ์๋ฌ
getTopErrors(limit = 5) {
const errorCounts = {};
this.errors.forEach(error => {
const key = `${error.name}: ${error.message}`;
if (!errorCounts[key]) {
errorCounts[key] = {
error: error,
count: 0
};
}
errorCounts[key].count++;
});
return Object.values(errorCounts)
.sort((a, b) => b.count - a.count)
.slice(0, limit);
}
// ์๋ฌ ๋ฐ์๋ฅ ๊ณ์ฐ
calculateErrorRate() {
const timeWindows = this.groupByTime();
return {
perHour: timeWindows.lastHour,
perDay: timeWindows.last24Hours / 24,
perWeek: timeWindows.last7Days / 7
};
}
// ๋ก์ปฌ ์คํ ๋ฆฌ์ง์ ์ ์ฅ
saveToStorage() {
try {
localStorage.setItem('errorAnalytics', JSON.stringify(this.errors));
} catch (e) {
console.error('์๋ฌ ๋ถ์ ๋ฐ์ดํฐ ์ ์ฅ ์คํจ:', e);
}
}
// ๋ก์ปฌ ์คํ ๋ฆฌ์ง์์ ๋ก๋
loadFromStorage() {
try {
const data = localStorage.getItem('errorAnalytics');
if (data) {
this.errors = JSON.parse(data);
}
} catch (e) {
console.error('์๋ฌ ๋ถ์ ๋ฐ์ดํฐ ๋ก๋ ์คํจ:', e);
}
}
// ๋ฆฌํฌํธ ์์ฑ
generateReport() {
const stats = this.getStatistics();
console.log('๐ ์๋ฌ ๋ถ์ ๋ฆฌํฌํธ');
console.log('='.repeat(50));
console.log(`์ด ์๋ฌ ์: ${stats.total}`);
console.log('\nํ์
๋ณ ๋ถํฌ:');
Object.entries(stats.byType).forEach(([type, count]) => {
console.log(` ${type}: ${count}ํ`);
});
console.log('\n์ฌ๊ฐ๋๋ณ ๋ถํฌ:');
Object.entries(stats.bySeverity).forEach(([severity, count]) => {
console.log(` ${severity}: ${count}ํ`);
});
console.log('\n์๊ฐ๋๋ณ ๋ฐ์:');
console.log(` ์ต๊ทผ 1์๊ฐ: ${stats.byTime.lastHour}ํ`);
console.log(` ์ต๊ทผ 24์๊ฐ: ${stats.byTime.last24Hours}ํ`);
console.log(` ์ต๊ทผ 7์ผ: ${stats.byTime.last7Days}ํ`);
console.log('\n๊ฐ์ฅ ๋ง์ด ๋ฐ์ํ ์๋ฌ:');
stats.topErrors.forEach((item, index) => {
console.log(` ${index + 1}. ${item.error.name}: ${item.error.message} (${item.count}ํ)`);
});
console.log('='.repeat(50));
return stats;
}
// ๋ฐ์ดํฐ ์ด๊ธฐํ
clear() {
this.errors = [];
localStorage.removeItem('errorAnalytics');
}
}
// ์ ์ญ ๋ถ์ ์ธ์คํด์ค
const errorAnalytics = new ErrorAnalytics();
// ์ฌ์ฉ ์์
errorAnalytics.generateReport();
๐จ ์ฌ์ฉ์ ์นํ์ ์ธ ์๋ฌ ๋ฉ์์ง
๊ธฐ์ ์ ์ผ๋ก ์๋ฒฝํ ์๋ฌ ํธ๋ค๋ง๋ ์ค์ํ์ง๋ง, ์ฌ์ฉ์์๊ฒ ์ด๋ป๊ฒ ๋ณด์ฌ์ฃผ๋๋๋ ์์ฒญ ์ค์ํด! ๐
"Uncaught TypeError: Cannot read property 'name' of undefined" ๊ฐ์ ๋ฉ์์ง๋ฅผ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ฃผ๋ฉด ์ ๋๊ฒ ์ง? ์น์ ํ๊ณ ์ดํดํ๊ธฐ ์ฌ์ด ๋ฉ์์ง๋ก ๋ฐ๊ฟ์ผ ํด!
// ์๋ฌ ๋ฉ์์ง ๋ณํ๊ธฐ
class ErrorMessageTranslator {
constructor() {
this.messageMap = {
// ๋คํธ์ํฌ ๊ด๋ จ
'Failed to fetch': '์๋ฒ์ ์ฐ๊ฒฐํ ์ ์์ต๋๋ค. ์ธํฐ๋ท ์ฐ๊ฒฐ์ ํ์ธํด์ฃผ์ธ์.',
'Network request failed': '๋คํธ์ํฌ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.',
'timeout': '์์ฒญ ์๊ฐ์ด ์ด๊ณผ๋์์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.',
// ์ธ์ฆ ๊ด๋ จ
'Unauthorized': '๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค.',
'Forbidden': '์ ๊ทผ ๊ถํ์ด ์์ต๋๋ค.',
'Invalid token': '๋ก๊ทธ์ธ ์ ๋ณด๊ฐ ๋ง๋ฃ๋์์ต๋๋ค. ๋ค์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.',
// ๋ฐ์ดํฐ ๊ด๋ จ
'Not found': '์์ฒญํ์ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.',
'Invalid input': '์
๋ ฅํ์ ์ ๋ณด๊ฐ ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค.',
'Validation failed': '์
๋ ฅ ํ์์ ํ์ธํด์ฃผ์ธ์.',
// ์๋ฒ ๊ด๋ จ
'Internal server error': '์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.',
'Service unavailable': '์๋น์ค๊ฐ ์ผ์์ ์ผ๋ก ์ด์ฉ ๋ถ๊ฐ๋ฅํฉ๋๋ค.',
// ๊ธฐํ
'Unknown error': '์ ์ ์๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.'
};
this.actionMap = {
'Failed to fetch': '์๋ก๊ณ ์นจํ๊ฑฐ๋ ์ธํฐ๋ท ์ฐ๊ฒฐ์ ํ์ธํด์ฃผ์ธ์.',
'Unauthorized': '๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.',
'Not found': '์ด์ ํ์ด์ง๋ก ๋์๊ฐ๊ฑฐ๋ ํ์ผ๋ก ์ด๋ํด์ฃผ์ธ์.',
'timeout': 'ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํ๊ฑฐ๋ ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.'
};
}
// ์๋ฌ๋ฅผ ์ฌ์ฉ์ ์นํ์ ๋ฉ์์ง๋ก ๋ณํ
translate(error) {
const errorMessage = error.message || String(error);
// ์ ํํ ์ผ์นํ๋ ๋ฉ์์ง ์ฐพ๊ธฐ
if (this.messageMap[errorMessage]) {
return {
title: '์ค๋ฅ ๋ฐ์',
message: this.messageMap[errorMessage],
action: this.actionMap[errorMessage] || '๋ค์ ์๋ํด์ฃผ์ธ์.',
severity: this.getSeverity(error)
};
}
// ๋ถ๋ถ ์ผ์นํ๋ ๋ฉ์์ง ์ฐพ๊ธฐ
for (const [key, value] of Object.entries(this.messageMap)) {
if (errorMessage.toLowerCase().includes(key.toLowerCase())) {
return {
title: '์ค๋ฅ ๋ฐ์',
message: value,
action: this.actionMap[key] || '๋ค์ ์๋ํด์ฃผ์ธ์.',
severity: this.getSeverity(error)
};
}
}
// ๊ธฐ๋ณธ ๋ฉ์์ง
return {
title: '์ค๋ฅ ๋ฐ์',
message: '์ผ์์ ์ธ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.',
action: 'ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํ๊ฑฐ๋ ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.',
severity: 'medium'
};
}
// ์๋ฌ ์ฌ๊ฐ๋ ํ๋จ
getSeverity(error) {
const message = error.message?.toLowerCase() || '';
if (message.includes('server') || message.includes('database')) {
return 'high';
}
if (message.includes('network') || message.includes('timeout')) {
return 'medium';
}
return 'low';
}
// ์ปค์คํ
๋ฉ์์ง ์ถ๊ฐ
addCustomMessage(errorKey, userMessage, action) {
this.messageMap[errorKey] = userMessage;
if (action) {
this.actionMap[errorKey] = action;
}
}
}
// ์๋ฌ UI ํ์ ํด๋์ค
class ErrorUI {
constructor() {
this.translator = new ErrorMessageTranslator();
this.setupStyles();
}
// ์คํ์ผ ์ค์
setupStyles() {
if (typeof document === 'undefined') return;
const style = document.createElement('style');
style.textContent = `
.error-notification {
position: fixed;
top: 20px;
right: 20px;
max-width: 400px;
padding: 20px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
z-index: 10000;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.error-notification.severity-high {
border-left: 4px solid #ef5350;
}
.error-notification.severity-medium {
border-left: 4px solid #ffa726;
}
.error-notification.severity-low {
border-left: 4px solid #42a5f5;
}
.error-notification-title {
font-weight: bold;
margin-bottom: 8px;
color: #333;
}
.error-notification-message {
color: #666;
margin-bottom: 12px;
line-height: 1.5;
}
.error-notification-action {
color: #999;
font-size: 0.9em;
}
.error-notification-close {
position: absolute;
top: 10px;
right: 10px;
background: none;
border: none;
font-size: 20px;
cursor: pointer;
color: #999;
}
`;
document.head.appendChild(style);
}
// ์๋ฌ ์๋ฆผ ํ์
show(error, duration = 5000) {
if (typeof document === 'undefined') return;
const translated = this.translator.translate(error);
const notification = document.createElement('div');
notification.className = `error-notification severity-${translated.severity}`;
notification.innerHTML = `
<button class="error-notification-close">ร</button>
<div class="error-notification-title">${translated.title}</div>
<div class="error-notification-message">${translated.message}</div>
<div class="error-notification-action">${translated.action}</div>
`;
document.body.appendChild(notification);
// ๋ซ๊ธฐ ๋ฒํผ ์ด๋ฒคํธ
const closeBtn = notification.querySelector('.error-notification-close');
closeBtn.addEventListener('click', () => {
this.hide(notification);
});
// ์๋ ๋ซ๊ธฐ
if (duration > 0) {
setTimeout(() => {
this.hide(notification);
}, duration);
}
return notification;
}
// ์๋ฆผ ์จ๊ธฐ๊ธฐ
hide(notification) {
notification.style.animation = 'slideIn 0.3s ease-out reverse';
setTimeout(() => {
notification.remove();
}, 300);
}
}
// ์ ์ญ ์ธ์คํด์ค
const errorUI = new ErrorUI();
// ์ฌ์ฉ ์์
try {
throw new Error('Failed to fetch');
} catch (error) {
errorUI.show(error);
}
๐จ ์ฌ์ฉ์ ์นํ์ ์๋ฌ ๋ฉ์์ง ์์ฑ ํ:
๋ช
ํํ๊ฒ ๋ฌด์จ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋์ง ๊ฐ๋จ๋ช
๋ฃํ๊ฒ
์น์ ํ๊ฒ ๋น๋ํ๋ ํค์ด ์๋ ๋์์ ์ฃผ๋ ํค์ผ๋ก
ํด๊ฒฐ์ฑ
์ ์ ์ฌ์ฉ์๊ฐ ๋ฌด์์ ํด์ผ ํ๋์ง ์๋ ค์ฃผ๊ธฐ
์ ๋ฌธ ์ฉ์ด ํผํ๊ธฐ ๊ธฐ์ ์ ์ธ ์ฉ์ด ๋์ ์ผ์ ์ธ์ด ์ฌ์ฉ
๊ธ์ ์ ์ผ๋ก "์คํจํ์ต๋๋ค" ๋์ "๋ค์ ์๋ํด์ฃผ์ธ์"
์ด๋ชจ์ง ํ์ฉ ์ ์ ํ ์ด๋ชจ์ง๋ก ์น๊ทผ๊ฐ ๋ํ๊ธฐ (๊ณผํ์ง ์๊ฒ!)
โจ ์ค์ ์์ : ํผ ๊ฒ์ฆ ์๋ฌ ๋ฉ์์ง
// ํผ ๊ฒ์ฆ ์๋ฌ ๋ฉ์์ง ํฌํผ
const formErrorMessages = {
email: {
required: '์ด๋ฉ์ผ ์ฃผ์๋ฅผ ์
๋ ฅํด์ฃผ์ธ์. ๐ง',
invalid: '์ฌ๋ฐ๋ฅธ ์ด๋ฉ์ผ ํ์์ด ์๋๋๋ค. (์: user@example.com)',
duplicate: '์ด๋ฏธ ์ฌ์ฉ ์ค์ธ ์ด๋ฉ์ผ์
๋๋ค. ๋ค๋ฅธ ์ด๋ฉ์ผ์ ์ฌ์ฉํด์ฃผ์ธ์.'
},
password: {
required: '๋น๋ฐ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์. ๐',
tooShort: '๋น๋ฐ๋ฒํธ๋ ์ต์ 8์ ์ด์์ด์ด์ผ ํฉ๋๋ค.',
tooWeak: '๋ ๊ฐ๋ ฅํ ๋น๋ฐ๋ฒํธ๋ฅผ ์ฌ์ฉํด์ฃผ์ธ์. (๋์๋ฌธ์, ์ซ์, ํน์๋ฌธ์ ํฌํจ)',
mismatch: '๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค. ๋ค์ ํ์ธํด์ฃผ์ธ์.'
},
phone: {
required: '์ ํ๋ฒํธ๋ฅผ ์
๋ ฅํด์ฃผ์ธ์. ๐ฑ',
invalid: '์ฌ๋ฐ๋ฅธ ์ ํ๋ฒํธ ํ์์ด ์๋๋๋ค. (์: 010-1234-5678)'
},
name: {
required: '์ด๋ฆ์ ์
๋ ฅํด์ฃผ์ธ์. ๐ค',
tooShort: '์ด๋ฆ์ ์ต์ 2์ ์ด์์ด์ด์ผ ํฉ๋๋ค.',
invalid: '์ด๋ฆ์๋ ํน์๋ฌธ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.'
}
};
function showFormError(field, errorType) {
const message = formErrorMessages[field]?.[errorType] || '์
๋ ฅ ๋ด์ฉ์ ํ์ธํด์ฃผ์ธ์.';
errorUI.show(new Error(message), 3000);
}
๐ง ์ค์ ์๋ฌ ํธ๋ค๋ง ํจํด
์ด์ ์ค์ ์์ ๋ฐ๋ก ์จ๋จน์ ์ ์๋ ์๋ฌ ํธ๋ค๋ง ํจํด๋ค์ ์์๋ณด์! ์ด ํจํด๋ค์ ์ค์ ํ๋ก์ ํธ์์ ๊ฒ์ฆ๋ ๊ฒ๋ค์ด์ผ. ๐ผ
1๏ธโฃ Result ํจํด (Rust ์คํ์ผ)
์๋ฌ๋ฅผ ์์ธ๋ก ๋์ง๋ ๋์ , ๊ฒฐ๊ณผ ๊ฐ์ฒด๋ก ๋ฐํํ๋ ํจํด์ด์ผ. ํจ์ํ ํ๋ก๊ทธ๋๋ฐ์์ ๋ง์ด ์ฌ์ฉ๋ผ:
// Result ํด๋์ค
class Result {
constructor(success, value, error) {
this.success = success;
this.value = value;
this.error = error;
}
static ok(value) {
return new Result(true, value, null);
}
static err(error) {
return new Result(false, null, error);
}
isOk() {
return this.success;
}
isErr() {
return !this.success;
}
unwrap() {
if (this.isErr()) {
throw this.error;
}
return this.value;
}
unwrapOr(defaultValue) {
return this.isOk() ? this.value : defaultValue;
}
map(fn) {
if (this.isErr()) return this;
try {
return Result.ok(fn(this.value));
} catch (error) {
return Result.err(error);
}
}
mapErr(fn) {
if (this.isOk()) return this;
return Result.err(fn(this.error));
}
}
// ์ฌ์ฉ ์์
function divide(a, b) {
if (b === 0) {
return Result.err(new Error('0์ผ๋ก ๋๋ ์ ์์ต๋๋ค'));
}
return Result.ok(a / b);
}
function calculateAverage(numbers) {
if (numbers.length === 0) {
return Result.err(new Error('๋น ๋ฐฐ์ด์
๋๋ค'));
}
const sum = numbers.reduce((acc, n) => acc + n, 0);
return divide(sum, numbers.length);
}
// ์ฒด์ด๋ ์ฌ์ฉ
const result = calculateAverage([10, 20, 30])
.map(avg => avg.toFixed(2))
.map(str => `ํ๊ท : ${str}`)
.mapErr(err => `๊ณ์ฐ ์คํจ: ${err.message}`);
if (result.isOk()) {
console.log('โ
', result.value);
} else {
console.error('โ', result.error);
}
// ๊ธฐ๋ณธ๊ฐ ์ฌ์ฉ
const average = calculateAverage([]).unwrapOr(0);
console.log('ํ๊ท :', average);
2๏ธโฃ Either ํจํด (ํจ์ํ)
// Either ํด๋์ค (Left = ์๋ฌ, Right = ์ฑ๊ณต)
class Either {
constructor(left, right) {
this.left = left;
this.right = right;
}
static left(value) {
return new Either(value, null);
}
static right(value) {
return new Either(null, value);
}
isLeft() {
return this.left !== null;
}
isRight() {
return this.right !== null;
}
map(fn) {
return this.isRight()
? Either.right(fn(this.right))
: this;
}
flatMap(fn) {
return this.isRight()
? fn(this.right)
: this;
}
fold(leftFn, rightFn) {
return this.isLeft()
? leftFn(this.left)
: rightFn(this.right);
}
}
// ์ฌ์ฉ ์์
function parseJSON(jsonString) {
try {
return Either.right(JSON.parse(jsonString));
} catch (error) {
return Either.left(error.message);
}
}
function validateUser(user) {
if (!user.email) {
return Either.left('์ด๋ฉ์ผ์ด ํ์ํฉ๋๋ค');
}
if (!user.name) {
return Either.left('์ด๋ฆ์ด ํ์ํฉ๋๋ค');
}
return Either.right(user);
}
// ์ฒด์ด๋
const result = parseJSON('{"email":"test@test.com","name":"์ฒ ์"}')
.flatMap(validateUser)
.map(user => ({ ...user, validated: true }))
.fold(
error => `โ ์๋ฌ: ${error}`,
user => `โ
์ฑ๊ณต: ${user.name}`
);
console.log(result);
3๏ธโฃ Circuit Breaker ํจํด
์ธ๋ถ ์๋น์ค ํธ์ถ์ด ๊ณ์ ์คํจํ ๋, ์ผ์ ์๊ฐ ๋์ ํธ์ถ์ ์ฐจ๋จํ๋ ํจํด์ด์ผ. ์์คํ ๊ณผ๋ถํ๋ฅผ ๋ฐฉ์งํ ์ ์์ด! โก
// Circuit Breaker ํด๋์ค
class CircuitBreaker {
constructor(options = {}) {
this.failureThreshold = options.failureThreshold || 5;
this.resetTimeout = options.resetTimeout || 60000; // 1๋ถ
this.monitoringPeriod = options.monitoringPeriod || 10000; // 10์ด
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.failureCount = 0;
this.lastFailureTime = null;
this.successCount = 0;
this.stats = {
totalCalls: 0,
successfulCalls: 0,
failedCalls: 0,
rejectedCalls: 0
};
}
async execute(fn) {
this.stats.totalCalls++;
// OPEN ์ํ: ํธ์ถ ์ฐจ๋จ
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime >= this.resetTimeout) {
console.log('๐ Circuit Breaker: HALF_OPEN ์ํ๋ก ์ ํ');
this.state = 'HALF_OPEN';
this.successCount = 0;
} else {
this.stats.rejectedCalls++;
throw new Error('Circuit Breaker๊ฐ OPEN ์ํ์
๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.');
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.stats.successfulCalls++;
this.failureCount = 0;
if (this.state === 'HALF_OPEN') {
this.successCount++;
if (this.successCount >= 2) {
console.log('โ
Circuit Breaker: CLOSED ์ํ๋ก ๋ณต๊ตฌ');
this.state = 'CLOSED';
this.successCount = 0;
}
}
}
onFailure() {
this.stats.failedCalls++;
this.failureCount++;
this.lastFailureTime = Date.now();
if (this.failureCount >= this.failureThreshold) {
console.log('๐จ Circuit Breaker: OPEN ์ํ๋ก ์ ํ');
this.state = 'OPEN';
}
}
getState() {
return this.state;
}
getStats() {
return {
...this.stats,
state: this.state,
failureCount: this.failureCount,
successRate: this.stats.totalCalls > 0
? (this.stats.successfulCalls / this.stats.totalCalls * 100).toFixed(2) + '%'
: '0%'
};
}
reset() {
this.state = 'CLOSED';
this.failureCount = 0;
this.successCount = 0;
this.lastFailureTime = null;
}
}
// ์ฌ์ฉ ์์
const apiCircuitBreaker = new CircuitBreaker({
failureThreshold: 3,
resetTimeout: 30000,
monitoringPeriod: 10000
});
async function callExternalAPI(endpoint) {
return apiCircuitBreaker.execute(async () => {
const response = await fetch(endpoint);
if (!response.ok) {
throw new Error(`API ํธ์ถ ์คํจ: ${response.status}`);
}
return await response.json();
});
}
// ํ
์คํธ
async function testCircuitBreaker() {
for (let i = 0; i < 10; i++) {
try {
const data = await callExternalAPI('/api/unstable-endpoint');
console.log(`โ
ํธ์ถ ${i + 1} ์ฑ๊ณต:`, data);
} catch (error) {
console.error(`โ ํธ์ถ ${i + 1} ์คํจ:`, error.message);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
console.log('๐ Circuit Breaker ํต๊ณ:', apiCircuitBreaker.getStats());
}
๐ก Circuit Breaker ํจํด์ ์ฅ์ :
์์คํ
๋ณดํธ ์ฐ์ ์ฅ์ ๋ฐฉ์ง
๋น ๋ฅธ ์คํจ ๋ถํ์ํ ๋๊ธฐ ์๊ฐ ์ ๊ฑฐ
์๋ ๋ณต๊ตฌ ์๋น์ค๊ฐ ์ ์ํ๋๋ฉด ์๋์ผ๋ก ์ฌ๊ฐ
๋ชจ๋ํฐ๋ง ์์คํ
๊ฑด๊ฐ ์ํ ์ถ์ ๊ฐ๋ฅ
์ฌ์ฉ์ ๊ฒฝํ ์ฆ๊ฐ์ ์ธ ์๋ฌ ์๋ต์ผ๋ก UX ๊ฐ์
4๏ธโฃ Retry ํจํด (์ง์ ๋ฐฑ์คํ)
// Retry ํฌํผ ํจ์
async function retryWithBackoff(fn, options = {}) {
const maxRetries = options.maxRetries || 3;
const initialDelay = options.initialDelay || 1000;
const maxDelay = options.maxDelay || 30000;
const backoffMultiplier = options.backoffMultiplier || 2;
const shouldRetry = options.shouldRetry || (() => true);
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
console.log(`๐ ์๋ ${attempt + 1}/${maxRetries + 1}`);
return await fn();
} catch (error) {
lastError = error;
// ์ฌ์๋ ์ฌ๋ถ ํ์ธ
if (!shouldRetry(error) || attempt === maxRetries) {
throw error;
}
// ์ง์ ๋ฐฑ์คํ ๊ณ์ฐ
const delay = Math.min(
initialDelay * Math.pow(backoffMultiplier, attempt),
maxDelay
);
// ์งํฐ ์ถ๊ฐ (๋๋ค์ฑ)
const jitter = delay * 0.1 * Math.random();
const totalDelay = delay + jitter;
console.log(`โณ ${Math.round(totalDelay)}ms ํ ์ฌ์๋...`);
await new Promise(resolve => setTimeout(resolve, totalDelay));
}
}
throw lastError;
}
// ์ฌ์ฉ ์์
async function fetchUserData(userId) {
return retryWithBackoff(
async () => {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
},
{
maxRetries: 3,
initialDelay: 1000,
maxDelay: 10000,
backoffMultiplier: 2,
shouldRetry: (error) => {
// 5xx ์๋ฌ๋ง ์ฌ์๋
return error.message.includes('500') ||
error.message.includes('502') ||
error.message.includes('503');
}
}
);
}
// ๊ณ ๊ธ Retry ํด๋์ค
class RetryManager {
constructor() {
this.retryStrategies = new Map();
}
// ์ฌ์๋ ์ ๋ต ๋ฑ๋ก
registerStrategy(name, strategy) {
this.retryStrategies.set(name, strategy);
}
// ์ ๋ต ์คํ
async execute(strategyName, fn) {
const strategy = this.retryStrategies.get(strategyName);
if (!strategy) {
throw new Error(`์ฌ์๋ ์ ๋ต์ ์ฐพ์ ์ ์์ต๋๋ค: ${strategyName}`);
}
return retryWithBackoff(fn, strategy);
}
}
// ์ ์ญ Retry Manager
const retryManager = new RetryManager();
// ์ ๋ต ๋ฑ๋ก
retryManager.registerStrategy('api-call', {
maxRetries: 3,
initialDelay: 1000,
backoffMultiplier: 2,
shouldRetry: (error) => error.message.includes('5')
});
retryManager.registerStrategy('critical-operation', {
maxRetries: 5,
initialDelay: 500,
backoffMultiplier: 1.5,
shouldRetry: () => true
});
// ์ฌ์ฉ
const data = await retryManager.execute('api-call', async () => {
return await fetch('/api/data').then(r => r.json());
});
๐ฏ ํ๋ ์์ํฌ๋ณ ์๋ฌ ํธ๋ค๋ง
๊ฐ ํ๋ ์์ํฌ๋ง๋ค ์๋ฌ ํธ๋ค๋ง ๋ฐฉ์์ด ์กฐ๊ธ์ฉ ๋ฌ๋ผ. ์ฃผ์ ํ๋ ์์ํฌ๋ค์ ๋ฒ ์คํธ ํ๋ํฐ์ค๋ฅผ ์์๋ณด์! ๐
โ๏ธ React ์๋ฌ ํธ๋ค๋ง
// Error Boundary ์ปดํฌ๋ํธ
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null
};
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('๐จ React Error Boundary:', error, errorInfo);
this.setState({
error: error,
errorInfo: errorInfo
});
// ์๋ฌ ๋ก๊น
์๋น์ค๋ก ์ ์ก
logErrorToService({
type: 'React Component Error',
message: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack,
timestamp: new Date().toISOString()
});
}
render() {
if (this.state.hasError) {
return (
<div style={{
padding: '40px',
textAlign: 'center',
background: '#fff3e0',
borderRadius: '12px',
margin: '20px'
}}>
<h2>๐ข ์! ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค</h2>
<p>ํ์ด์ง๋ฅผ ์๋ก๊ณ ์นจํ๊ฑฐ๋ ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.</p>
<button
onClick={() => window.location.reload()}
style={{
padding: '10px 20px',
background: '#6c9bd1',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
marginTop: '20px'
}}
>
์๋ก๊ณ ์นจ
</button>
{process.env.NODE_ENV === 'development' && (
<details style={{ marginTop: '20px', textAlign: 'left' }}>
<summary>์๋ฌ ์์ธ ์ ๋ณด</summary>
<pre style={{
background: '#f5f5f5',
padding: '15px',
borderRadius: '6px',
overflow: 'auto'
}}>
{this.state.error && this.state.error.toString()}
{this.state.errorInfo && this.state.errorInfo.componentStack}
</pre>
</details>
)}
</div>
);
}
return this.props.children;
}
}
// ์ฌ์ฉ ์์
function App() {
return (
<ErrorBoundary>
<Header />
<ErrorBoundary>
<MainContent />
</ErrorBoundary>
<ErrorBoundary>
<Sidebar />
</ErrorBoundary>
<Footer />
</ErrorBoundary>
);
}
// React Query ์๋ฌ ํธ๋ค๋ง
import { useQuery } from 'react-query';
function UserProfile({ userId }) {
const { data, error, isLoading, isError } = useQuery(
['user', userId],
() => fetchUser(userId),
{
retry: 3,
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
onError: (error) => {
console.error('์ฌ์ฉ์ ๋ฐ์ดํฐ ๋ก๋ ์คํจ:', error);
errorLogger.log(error, { component: 'UserProfile', userId });
}
}
);
if (isLoading) return <div>๋ก๋ฉ ์ค...</div>;
if (isError) {
return (
<div className="error-message">
<p>์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ฌ ์ ์์ต๋๋ค.</p>
<p>{error.message}</p>
</div>
);
}
return <div>{data.name}</div>;
}
๐ Vue.js ์๋ฌ ํธ๋ค๋ง
// Vue ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ
const app = createApp(App);
app.config.errorHandler = (err, instance, info) => {
console.error('๐จ Vue Error:', {
error: err,
component: instance?.$options.name,
info: info
});
// ์๋ฌ ๋ก๊น
errorLogger.log(err, {
framework: 'Vue',
component: instance?.$options.name,
lifecycle: info
});
};
// ์ปดํฌ๋ํธ ๋ ๋ฒจ ์๋ฌ ํธ๋ค๋ง
export default {
name: 'UserProfile',
data() {
return {
user: null,
error: null,
loading: false
};
},
async created() {
await this.loadUser();
},
methods: {
async loadUser() {
this.loading = true;
this.error = null;
try {
this.user = await fetchUser(this.userId);
} catch (error) {
console.error('์ฌ์ฉ์ ๋ก๋ ์คํจ:', error);
this.error = error.message;
errorLogger.log(error, {
component: 'UserProfile',
userId: this.userId
});
} finally {
this.loading = false;
}
}
},
errorCaptured(err, instance, info) {
console.error('์์ ์ปดํฌ๋ํธ ์๋ฌ:', err);
this.error = 'ํ์ ์ปดํฌ๋ํธ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.';
return false; // ์๋ฌ ์ ํ ์ค๋จ
}
};
๐ ฐ๏ธ Angular ์๋ฌ ํธ๋ค๋ง
// ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ
import { ErrorHandler, Injectable } from '@angular/core';
@Injectable()
export class GlobalErrorHandler implements ErrorHandler {
handleError(error: Error) {
console.error('๐จ Angular Error:', error);
// ์๋ฌ ๋ก๊น
errorLogger.log(error, {
framework: 'Angular',
url: window.location.href
});
// ์ฌ์ฉ์์๊ฒ ์๋ฆผ
errorUI.show(error);
}
}
// ๋ชจ๋์ ๋ฑ๋ก
@NgModule({
providers: [
{ provide: ErrorHandler, useClass: GlobalErrorHandler }
]
})
export class AppModule { }
// HTTP ์ธํฐ์
ํฐ๋ก API ์๋ฌ ์ฒ๋ฆฌ
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
// ํด๋ผ์ด์ธํธ ์๋ฌ
errorMessage = `ํด๋ผ์ด์ธํธ ์ค๋ฅ: ${error.error.message}`;
} else {
// ์๋ฒ ์๋ฌ
errorMessage = `์๋ฒ ์ค๋ฅ: ${error.status} - ${error.message}`;
}
console.error(errorMessage);
errorLogger.log(new Error(errorMessage), {
type: 'HTTP Error',
status: error.status,
url: error.url
});
return throwError(() => new Error(errorMessage));
})
);
}
}
๐ฏ ํ๋ ์์ํฌ ์๋ฌ ํธ๋ค๋ง ๊ณตํต ์์น:
๊ณ์ธตํ ์ ์ญ, ์ปดํฌ๋ํธ, ํจ์ ๋ ๋ฒจ๋ก ์๋ฌ ํธ๋ค๋ง ๊ณ์ธต ๊ตฌ์ฑ
๋ช
ํํ ์ฑ
์ ๊ฐ ๋ ๋ฒจ์์ ์ฒ๋ฆฌํ ์๋ฌ์ ์ ํํ ์๋ฌ ๊ตฌ๋ถ
์ฌ์ฉ์ ์ค์ฌ ๊ธฐ์ ์ ์๋ฌ๋ฅผ ์ฌ์ฉ์ ์นํ์ ๋ฉ์์ง๋ก ๋ณํ
๋ก๊น
๋ชจ๋ ์๋ฌ๋ฅผ ์ ์ ํ ๋ก๊น
ํ์ฌ ๋๋ฒ๊น
์ง์
๋ณต๊ตฌ ์ ๋ต ๊ฐ๋ฅํ ๊ฒฝ์ฐ ์๋ ๋ณต๊ตฌ ๋ฉ์ปค๋์ฆ ๊ตฌํ
๐งช ์๋ฌ ํธ๋ค๋ง ํ ์คํธ
์๋ฌ ํธ๋ค๋ง ์ฝ๋๋ ํ ์คํธ๊ฐ ํ์ํด! ๐ ์ ๋๋ก ์๋ํ๋์ง ํ์ธํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์.
// Jest๋ฅผ ์ฌ์ฉํ ์๋ฌ ํธ๋ค๋ง ํ
์คํธ
describe('์๋ฌ ํธ๋ค๋ง ํ
์คํธ', () => {
// 1. ์๋ฌ๊ฐ ์ ๋๋ก ๋์ ธ์ง๋์ง ํ
์คํธ
test('0์ผ๋ก ๋๋๋ฉด ์๋ฌ๋ฅผ ๋์ ธ์ผ ํจ', () => {
function divide(a, b) {
if (b === 0) {
throw new Error('0์ผ๋ก ๋๋ ์ ์์ต๋๋ค');
}
return a / b;
}
expect(() => divide(10, 0)).toThrow('0์ผ๋ก ๋๋ ์ ์์ต๋๋ค');
expect(() => divide(10, 0)).toThrow(Error);
});
// 2. ๋น๋๊ธฐ ์๋ฌ ํ
์คํธ
test('API ํธ์ถ ์คํจ ์ ์๋ฌ๋ฅผ ๋์ ธ์ผ ํจ', async () => {
async function fetchData() {
const response = await fetch('/api/data');
if (!response.ok) {
throw new Error('API ํธ์ถ ์คํจ');
}
return await response.json();
}
// fetch๋ฅผ ๋ชจํน
global.fetch = jest.fn(() =>
Promise.resolve({
ok: false,
status: 500
})
);
await expect(fetchData()).rejects.toThrow('API ํธ์ถ ์คํจ');
});
// 3. ์ปค์คํ
์๋ฌ ํ
์คํธ
test('ValidationError๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์์ฑ๋์ด์ผ ํจ', () => {
const error = new ValidationError('์ด๋ฉ์ผ์ด ์ ํจํ์ง ์์ต๋๋ค');
expect(error).toBeInstanceOf(ValidationError);
expect(error).toBeInstanceOf(AppError);
expect(error).toBeInstanceOf(Error);
expect(error.name).toBe('ValidationError');
expect(error.statusCode).toBe(400);
expect(error.isOperational).toBe(true);
});
// 4. ์๋ฌ ํธ๋ค๋ฌ ํ
์คํธ
test('์๋ฌ ํธ๋ค๋ฌ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํด์ผ ํจ', () => {
const mockLogger = jest.fn();
const errorHandler = new ErrorHandler();
errorHandler.logError = mockLogger;
const error = new Error('ํ
์คํธ ์๋ฌ');
errorHandler.handleError(error);
expect(mockLogger).toHaveBeenCalledWith(
expect.objectContaining({
message: 'ํ
์คํธ ์๋ฌ'
})
);
});
// 5. Retry ๋ก์ง ํ
์คํธ
test('3๋ฒ ์ฌ์๋ ํ ์คํจํด์ผ ํจ', async () => {
let attemptCount = 0;
async function unreliableFunction() {
attemptCount++;
throw new Error('ํญ์ ์คํจ');
}
await expect(
retryWithBackoff(unreliableFunction, { maxRetries: 2 })
).rejects.toThrow('ํญ์ ์คํจ');
expect(attemptCount).toBe(3); // ์ด๊ธฐ ์๋ + 2๋ฒ ์ฌ์๋
});
// 6. Circuit Breaker ํ
์คํธ
test('Circuit Breaker๊ฐ OPEN ์ํ๊ฐ ๋์ด์ผ ํจ', async () => {
const breaker = new CircuitBreaker({ failureThreshold: 3 });
// 3๋ฒ ์คํจ์ํค๊ธฐ
for (let i = 0; i < 3; i++) {
try {
await breaker.execute(async () => {
throw new Error('์คํจ');
});
} catch (e) {
// ์๋ฌ ๋ฌด์
}
}
expect(breaker.getState()).toBe('OPEN');
// OPEN ์ํ์์๋ ํธ์ถ์ด ๊ฑฐ๋ถ๋์ด์ผ ํจ
await expect(
breaker.execute(async () => 'success')
).rejects.toThrow('Circuit Breaker๊ฐ OPEN ์ํ์
๋๋ค');
});
});
// React ์ปดํฌ๋ํธ ์๋ฌ ํ
์คํธ
import { render, screen } from '@testing-library/react';
describe('ErrorBoundary ํ
์คํธ', () => {
test('์๋ฌ ๋ฐ์ ์ ํด๋ฐฑ UI๋ฅผ ํ์ํด์ผ ํจ', () => {
const ThrowError = () => {
throw new Error('ํ
์คํธ ์๋ฌ');
};
// ์ฝ์ ์๋ฌ ์์ ์ต์
const spy = jest.spyOn(console, 'error').mockImplementation(() => {});
render(
<ErrorBoundary>
<ThrowError />
</ErrorBoundary>
);
expect(screen.getByText(/๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค/i)).toBeInTheDocument();
spy.mockRestore();
});
});
๐งช ์๋ฌ ํธ๋ค๋ง ํ
์คํธ ์ฒดํฌ๋ฆฌ์คํธ:
โ
์์๋ ์๋ฌ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๋์ ธ์ง๋๊ฐ?
โ
์๋ฌ ๋ฉ์์ง๊ฐ ๋ช
ํํ๊ณ ์ ์ฉํ๊ฐ?
โ
์ปค์คํ
์๋ฌ ํด๋์ค๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์๋ํ๋๊ฐ?
โ
๋น๋๊ธฐ ์๋ฌ๊ฐ ์ ๋๋ก ์ฒ๋ฆฌ๋๋๊ฐ?
โ
์ฌ์๋ ๋ก์ง์ด ์์๋๋ก ์๋ํ๋๊ฐ?
โ
Circuit Breaker๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋๊ฐ?
โ
์๋ฌ ๋ก๊น
์ด ์ ๋๋ก ๋๋๊ฐ?
โ
์ฌ์ฉ์์๊ฒ ์ ์ ํ ์๋ฌ ๋ฉ์์ง๊ฐ ํ์๋๋๊ฐ?
โ
์๋ฌ ๋ฐ์ ํ ์์คํ
์ด ๋ณต๊ตฌ ๊ฐ๋ฅํ๊ฐ?
โ
์ฃ์ง ์ผ์ด์ค๊ฐ ๋ชจ๋ ์ฒ๋ฆฌ๋๋๊ฐ?
๐ ์๋ฌ ํธ๋ค๋ง ๋ฒ ์คํธ ํ๋ํฐ์ค ์์ฝ
์, ์ด์ ์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ด์ฉ์ ์ ๋ฆฌํด๋ณด์! ์ค์ ์์ ๋ฐ๋ก ์ ์ฉํ ์ ์๋ ํต์ฌ ์์น๋ค์ด์ผ. ๐
๐ฏ ํต์ฌ ์์น 10๊ฐ์ง
1๏ธโฃ ์๋ฌ๋ฅผ ๋ฌด์ํ์ง ๋ง๋ผ
๋น catch ๋ธ๋ก์ ์ ๋ ๊ธ๋ฌผ! ์ต์ํ ๋ก๊น
์ด๋ผ๋ ํด์ผ ํด.
2๏ธโฃ ๋ช
ํํ ์๋ฌ ๋ฉ์์ง๋ฅผ ์์ฑํ๋ผ
"์๋ฌ ๋ฐ์" ๋์ "์ฌ์ฉ์ ID 123์ ์ฐพ์ ์ ์์ต๋๋ค" ์ฒ๋ผ ๊ตฌ์ฒด์ ์ผ๋ก.
3๏ธโฃ ์๋ฌ๋ฅผ ์ ์ ํ ๋ ๋ฒจ์์ ์ฒ๋ฆฌํ๋ผ
๋ชจ๋ ์๋ฌ๋ฅผ ์ต์์์์ ์ฒ๋ฆฌํ์ง ๋ง๊ณ , ๊ฐ ๋ ๋ฒจ์์ ์ ์ ํ ์ฒ๋ฆฌ.
4๏ธโฃ ์ปค์คํ
์๋ฌ ํด๋์ค๋ฅผ ํ์ฉํ๋ผ
์๋ฌ ํ์
์ ์ธ๋ถํํ๋ฉด ์ฒ๋ฆฌ๊ฐ ํจ์ฌ ์ฌ์์ ธ.
5๏ธโฃ ๋น๋๊ธฐ ์๋ฌ๋ฅผ ๋์น์ง ๋ง๋ผ
Promise rejection์ ๋ฐ๋์ ์ฒ๋ฆฌํด์ผ ํด. unhandledrejection ์ด๋ฒคํธ ํ์ฉ!
6๏ธโฃ ์ฌ์ฉ์ ์นํ์ ์ธ ๋ฉ์์ง๋ฅผ ๋ณด์ฌ์ค๋ผ
๊ธฐ์ ์ฉ์ด ๋์ ์ผ์ ์ธ์ด๋ก, ํด๊ฒฐ ๋ฐฉ๋ฒ๋ ํจ๊ป ์ ์.
7๏ธโฃ ์๋ฌ๋ฅผ ๋ก๊น
ํ๊ณ ๋ชจ๋ํฐ๋งํ๋ผ
ํ๋ก๋์
ํ๊ฒฝ์ ์๋ฌ๋ ๋ฐ๋์ ์ถ์ ํด์ผ ํด.
8๏ธโฃ ์ฌ์๋์ ํด๋ฐฑ ์ ๋ต์ ๊ตฌํํ๋ผ
์ผ์์ ์ธ ์๋ฌ๋ ์ฌ์๋, ์๊ตฌ์ ์ธ ์๋ฌ๋ ํด๋ฐฑ ์ฒ๋ฆฌ.
9๏ธโฃ ์๋ฌ ํธ๋ค๋ง๋ ํ
์คํธํ๋ผ
์๋ฌ ์๋๋ฆฌ์ค๋ ํ
์คํธ ์ผ์ด์ค์ ํฌํจ์์ผ์ผ ํด.
๐ ๋ฌธ์ํํ๋ผ
์ด๋ค ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์๋์ง, ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋์ง ๋ฌธ์๋ก ๋จ๊ฒจ.
๐ผ ์ค์ ์ฒดํฌ๋ฆฌ์คํธ:
๊ฐ๋ฐ ๋จ๊ณ
โก ๋ชจ๋ ๋น๋๊ธฐ ํจ์์ ์๋ฌ ํธ๋ค๋ง ์ถ๊ฐ
โก ์ปค์คํ
์๋ฌ ํด๋์ค ์ ์
โก ์ ์ญ ์๋ฌ ํธ๋ค๋ฌ ๊ตฌํ
โก ์๋ฌ ๋ก๊น
์์คํ
๊ตฌ์ถ
ํ
์คํธ ๋จ๊ณ
โก ์๋ฌ ์๋๋ฆฌ์ค ํ
์คํธ ์์ฑ
โก ์ฃ์ง ์ผ์ด์ค ํ์ธ
โก ์๋ฌ ๋ฉ์์ง ๊ฒ์ฆ
โก ๋ณต๊ตฌ ๋ก์ง ํ
์คํธ
๋ฐฐํฌ ์
โก ์๋ฌ ๋ชจ๋ํฐ๋ง ๋๊ตฌ ์ฐ๋ (Sentry ๋ฑ)
โก ํ๋ก๋์
์๋ฌ ์๋ฆผ ์ค์
โก ์๋ฌ ๋์๋ณด๋ ๊ตฌ์ฑ
โก ์๋ฌ ๋์ ๋งค๋ด์ผ ์์ฑ
์ด์ ๋จ๊ณ
โก ์๋ฌ ๋ก๊ทธ ์ ๊ธฐ ๊ฒํ
โก ์๋ฌ ํจํด ๋ถ์
โก ์ฐ์ ์์ ๋์ ์๋ฌ๋ถํฐ ์์
โก ์๋ฌ ํธ๋ค๋ง ๊ฐ์ ์ง์
๐ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ ๋ง ๊ธด ์ฌ์ ์ด์์ง? ๐
ํ์ง๋ง ์ด์ ๋๋ JavaScript ์๋ฌ ํธ๋ค๋ง์ ๋ฌ์ธ์ด ๋์์ด! ๐
์๋ฌ ํธ๋ค๋ง์ ๋จ์ํ ์ฝ๋๊ฐ ๊นจ์ง์ง ์๊ฒ ํ๋ ๊ฒ ์ด์์ ์๋ฏธ๊ฐ ์์ด. ์ฌ์ฉ์ ๊ฒฝํ์ ์งํค๊ณ , ์์คํ
์ ์์ ์ฑ์ ๋ณด์ฅํ๋ฉฐ, ๊ฐ๋ฐ์์ ์ถ์ ์ง์ ๋์ด๋ ์ค์ํ ๊ธฐ์ ์ด์ง. ๐ช
์ฌ๋ฅ๋ท์์ ํ๋ก๊ทธ๋๋ฐ ์ฌ๋ฅ์ ๊ณต์ ํ๊ฑฐ๋ ๋ฐฐ์ธ ๋๋, ์ด๋ฐ ์๋ฌ ํธ๋ค๋ง ์ง์์ ์ ๋ง ์ค์ํด. ์์ ์ ์ธ ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฅ๋ ฅ์ ๋ชจ๋ ๊ฐ๋ฐ์๊ฐ ๊ฐ์ถฐ์ผ ํ ํต์ฌ ์ญ๋์ด๋๊น! ๐
๊ธฐ์ตํด! ์ข์ ๊ฐ๋ฐ์๋ ์๋ฌ๊ฐ ๋ฐ์ํ์ง ์๋ ์ฝ๋๋ฅผ ์ง๋ ๊ฒ ์๋๋ผ, ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋ ์ฐ์ํ๊ฒ ์ฒ๋ฆฌํ๋ ์ฝ๋๋ฅผ ์ง๋ ๊ฑฐ์ผ. ๐จ
์ด ๊ธ์์ ๋ฐฐ์ด ๋ด์ฉ๋ค์ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํด๋ณด๊ณ , ๊ณ์ ๊ฐ์ ํด๋๊ฐ์. ์๋ฌ ํธ๋ค๋ง์ ํ ๋ฒ์ ์๋ฒฝํ๊ฒ ํ ์ ์์ด. ๊ฒฝํ์ ์์ผ๋ฉด์ ์ ์ ๋ ๋์์ง๋ ๊ฑฐ์ผ! ๐
ํน์ ๋งํ๋ ๋ถ๋ถ์ด ์๊ฑฐ๋ ๋ ๊ถ๊ธํ ๊ฒ ์๋ค๋ฉด, ์ฌ๋ฅ๋ท ์ปค๋ฎค๋ํฐ์์ ๋ค๋ฅธ ๊ฐ๋ฐ์๋ค๊ณผ ์ํตํด๋ณด๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ํจ๊ป ์ฑ์ฅํ๋ ๊ฑฐ์ง! ๐ค
์, ์ด์ ๋๊ฐ์ ๋ฉ์ง ์ฝ๋๋ฅผ ์์ฑํด๋ด! ์๋ฌ๊ฐ ๋๋ ๋นํฉํ์ง ๋ง๊ณ , ์ค๋ ๋ฐฐ์ด ๋ด์ฉ์ ๋ ์ฌ๋ฆฌ๋ฉด์ ์ฐจ๊ทผ์ฐจ๊ทผ ํด๊ฒฐํด๋๊ฐ๋ฉด ๋ผ. ํ์ดํ
! ๐โจ
๐ฏ ํต์ฌ ๊ธฐ์ตํ๊ธฐ
์๋ฌ๋ ์ ์ด ์๋๋ผ ์น๊ตฌ์ผ!
์ ๋๋ก ๋ค๋ฃจ๋ฉด ๋ ์์ ์ ์ด๊ณ ์ฌ์ฉ์ ์นํ์ ์ธ
์ ํ๋ฆฌ์ผ์ด์
์ ๋ง๋ค ์ ์์ด! ๐ช
๊ด๋ จ ํค์๋
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.

๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.