์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ JavaScript ์—๋Ÿฌ ํ•ธ๋“ค๋ง๊ณผ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค: ์•ˆ์ •์ ์ธ ์ฝ”๋“œ๋ฅผ ์œ„ํ•œ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

๐Ÿš€ JavaScript ์—๋Ÿฌ ํ•ธ๋“ค๋ง๊ณผ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค: ์•ˆ์ •์ ์ธ ์ฝ”๋“œ๋ฅผ ์œ„ํ•œ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‚ด์•„๋‚จ๋Š” ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ „๋žต ๐Ÿ’ช

์•ˆ๋…•! ์˜ค๋Š˜์€ JavaScript ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๋ฐ˜๋“œ์‹œ ์•Œ์•„์•ผ ํ•  ์—๋Ÿฌ ํ•ธ๋“ค๋ง๊ณผ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•ด ๊นŠ์ด ์žˆ๊ฒŒ ํŒŒํ—ค์ณ๋ณผ ๊ฑฐ์•ผ. ๐Ÿ˜Š

์†”์งํžˆ ๋งํ•ด์„œ, ์ฝ”๋“œ๋ฅผ ์งœ๋‹ค ๋ณด๋ฉด ์—๋Ÿฌ๋Š” ํ”ผํ•  ์ˆ˜ ์—†๋Š” ์นœ๊ตฌ์•ผ. ์•„๋‹ˆ, ์–ด์ฉŒ๋ฉด ๊ฐ€์žฅ ์ž์ฃผ ๋งŒ๋‚˜๋Š” ๋™๋ฃŒ์ผ์ง€๋„ ๋ชฐ๋ผ! ๐Ÿค ํ•˜์ง€๋งŒ ์ด ์—๋Ÿฌ๋“ค์„ ์–ด๋–ป๊ฒŒ ๋‹ค๋ฃจ๋А๋ƒ์— ๋”ฐ๋ผ ์ดˆ๋ณด ๊ฐœ๋ฐœ์ž์™€ ์‹œ๋‹ˆ์–ด ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฐˆ๋ฆฌ๋Š” ๊ฑฐ์ง€.

์ด ๊ธ€์—์„œ๋Š” ๋‹จ์ˆœํžˆ try-catch๋งŒ ์“ฐ๋Š” ์ˆ˜์ค€์„ ๋„˜์–ด์„œ, ์‹ค์ „์—์„œ ๋ฐ”๋กœ ์จ๋จน์„ ์ˆ˜ ์žˆ๋Š” ํ”„๋กœ๋•์…˜ ๋ ˆ๋ฒจ์˜ ์—๋Ÿฌ ํ•ธ๋“ค๋ง ์ „๋žต์„ ํ•จ๊ป˜ ์•Œ์•„๋ณผ ๊ฑฐ์•ผ. ์žฌ๋Šฅ๋„ท์—์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜ ๋ฐฐ์šฐ๋Š” ๋ถ„๋“ค์—๊ฒŒ๋„ ์ •๋ง ์œ ์šฉํ•œ ๋‚ด์šฉ์ด ๋  ๊ฑฐ๋ผ ํ™•์‹ ํ•ด! ๐Ÿ’ก

! Error Handling try-catch-finally JavaScript Error Management

๐ŸŽฏ ์™œ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์ด ์ค‘์š”ํ• ๊นŒ?

๋ณธ๊ฒฉ์ ์œผ๋กœ ๋“ค์–ด๊ฐ€๊ธฐ ์ „์—, ์™œ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์ด ๊ทธ๋ ‡๊ฒŒ ์ค‘์š”ํ•œ์ง€๋ถ€ํ„ฐ ์งš๊ณ  ๋„˜์–ด๊ฐ€์ž. ๐Ÿค”

์ƒ์ƒํ•ด๋ด. ๋„ˆ๊ฐ€ ๋งŒ๋“  ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒฐ์ œ ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €๋Š”๋ฐ, ๊ฐ‘์ž๊ธฐ ํ™”๋ฉด์ด ํ•˜์–—๊ฒŒ ๋ณ€ํ•˜๋ฉด์„œ ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ๋œ๋‹ค๋ฉด? ์‚ฌ์šฉ์ž๋Š” ๋‹นํ™ฉํ•˜๊ณ , ๋„ˆ๋Š” ๋ฐค์ƒˆ ๋ฒ„๊ทธ๋ฅผ ์ฐพ์•„ ํ—ค๋งค๊ฒŒ ๋  ๊ฑฐ์•ผ. ๐Ÿ˜ฑ

์ข‹์€ ์—๋Ÿฌ ํ•ธ๋“ค๋ง์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ๋“ค์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ค˜:

โœ… ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ํ–ฅ์ƒ
์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ์•ฑ์ด ์™„์ „ํžˆ ๋ฉˆ์ถ”์ง€ ์•Š๊ณ , ์‚ฌ์šฉ์ž์—๊ฒŒ ์นœ์ ˆํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์–ด.

โœ… ๋””๋ฒ„๊น… ์‹œ๊ฐ„ ๋‹จ์ถ•
์–ด๋””์„œ, ์™œ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋Š”์ง€ ๋ช…ํ™•ํ•˜๊ฒŒ ๋กœ๊น…๋˜๋ฉด ๋ฌธ์ œ ํ•ด๊ฒฐ์ด ํ›จ์”ฌ ๋นจ๋ผ์ ธ.

โœ… ์‹œ์Šคํ…œ ์•ˆ์ •์„ฑ
์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์ƒํ™ฉ์—์„œ๋„ ์‹œ์Šคํ…œ์ด ์šฐ์•„ํ•˜๊ฒŒ ๋ณต๊ตฌ๋  ์ˆ˜ ์žˆ์–ด.

โœ… ๋น„์ฆˆ๋‹ˆ์Šค ์—ฐ์†์„ฑ
์ผ๋ถ€ ๊ธฐ๋Šฅ์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒจ๋„ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์€ ๊ณ„์† ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์ง€.

๐Ÿ“š 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