์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿ“š ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๋ฌธ์„œํ™”์™€ TSDoc์œผ๋กœ API ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑํ•˜๊ธฐ

๐Ÿ“š ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๋ฌธ์„œํ™”์™€ TSDoc์œผ๋กœ API ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑํ•˜๊ธฐ

์ฝ”๋“œ์— ์ƒ๋ช…์„ ๋ถˆ์–ด๋„ฃ๋Š” ๋ฌธ์„œํ™”์˜ ๋งˆ๋ฒ• โœจ

๐ŸŽฏ ์™œ ๋ฌธ์„œํ™”๊ฐ€ ์ค‘์š”ํ• ๊นŒ?

์•ˆ๋…•! ์˜ค๋Š˜์€ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๊ผญ ์•Œ์•„์•ผ ํ•  ๋ฌธ์„œํ™”์™€ TSDoc์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณผ๊ฒŒ. ํ˜น์‹œ 6๊ฐœ์›” ์ „์— ์ž‘์„ฑํ•œ ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ๋ดค๋Š”๋ฐ "์ด๊ฒŒ ๋ญ์ง€?" ํ•˜๋ฉด์„œ ๋‹นํ™ฉํ•œ ๊ฒฝํ—˜ ์žˆ์–ด? ๐Ÿ˜… ์•„๋‹ˆ๋ฉด ํŒ€์›์ด ์ž‘์„ฑํ•œ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋Š”๋ฐ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ญ˜ ์˜๋ฏธํ•˜๋Š”์ง€ ๋ชฐ๋ผ์„œ ์ง์ ‘ ๋ฌผ์–ด๋ณธ ์ ์€? ์ด๋Ÿฐ ์ƒํ™ฉ๋“ค์ด ๋ฐ”๋กœ ๋ฌธ์„œํ™”๊ฐ€ ํ•„์š”ํ•œ ์ด์œ ์•ผ. ์ข‹์€ ๋ฌธ์„œํ™”๋Š” ์ฝ”๋“œ์˜ ์ดํ•ด๋„๋ฅผ ๋†’์ด๊ณ , ํ˜‘์—…์„ ์›ํ™œํ•˜๊ฒŒ ๋งŒ๋“ค๋ฉฐ, ์œ ์ง€๋ณด์ˆ˜ ๋น„์šฉ์„ ํฌ๊ฒŒ ์ค„์—ฌ์ค˜. ํŠนํžˆ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์—์„œ๋Š” TSDoc์ด๋ผ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ ์ฃผ์„์„ ์ž‘์„ฑํ•˜๋ฉด ์ž๋™์œผ๋กœ ๋ฉ‹์ง„ API ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿš€
๋ฌธ์„œํ™”์˜ ํ•ต์‹ฌ ๊ฐ€์น˜ ๐Ÿ’ก ์ฝ”๋“œ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ: ํ•จ์ˆ˜๋‚˜ ํด๋ž˜์Šค์˜ ๋ชฉ์ ์„ ๋ช…ํ™•ํžˆ ์ „๋‹ฌ
๐Ÿค ํ˜‘์—… ํšจ์œจ์„ฑ: ํŒ€์›๋“ค์ด ์ฝ”๋“œ๋ฅผ ๋น ๋ฅด๊ฒŒ ์ดํ•ดํ•˜๊ณ  ์‚ฌ์šฉ
โฐ ์‹œ๊ฐ„ ์ ˆ์•ฝ: ๋ฐ˜๋ณต์ ์ธ ์งˆ๋ฌธ๊ณผ ์„ค๋ช… ์‹œ๊ฐ„ ๊ฐ์†Œ
๐Ÿ”ง ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด: ๋ฏธ๋ž˜์˜ ๋‚˜์™€ ํŒ€์›์„ ์œ„ํ•œ ๋ฐฐ๋ ค
์žฌ๋Šฅ๋„ท(https://www.jaenung.net)์—์„œ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ด€๋ จ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋“ค๋„ ์ž์‹ ์˜ ์ฝ”๋“œ๋ฅผ ์ž˜ ๋ฌธ์„œํ™”ํ•˜๋ฉด ์˜๋ขฐ์ธ์—๊ฒŒ ๋” ์ „๋ฌธ์ ์ธ ์ธ์ƒ์„ ์ค„ ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ API๋‚˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ๊ฒฝ์šฐ๋ผ๋ฉด ๋ฌธ์„œํ™”๋Š” ์„ ํƒ์ด ์•„๋‹ˆ๋ผ ํ•„์ˆ˜์•ผ! ๐Ÿ“–
๐Ÿ“ TSDoc ์ž‘์„ฑ โš™๏ธ ์ž๋™ ์ƒ์„ฑ ๐Ÿ“š API ๋ฌธ์„œ ๋ฌธ์„œํ™” ์ž๋™ํ™” ํ”„๋กœ์„ธ์Šค

๐Ÿ“ TSDoc์ด๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

TSDoc์€ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ์œ„ํ•œ ํ‘œ์ค€ํ™”๋œ ๋ฌธ์„œ ์ฃผ์„ ํ˜•์‹์ด์•ผ. ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์˜ JSDoc์—์„œ ์˜๊ฐ์„ ๋ฐ›์•˜์ง€๋งŒ, ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ํŠน์„ฑ์— ๋งž๊ฒŒ ์ตœ์ ํ™”๋˜์–ด ์žˆ์–ด. ๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ๊ฐ€ ์ฃผ๋„ํ•˜์—ฌ ๊ฐœ๋ฐœํ–ˆ๊ณ , ํ˜„์žฌ๋Š” ์˜คํ”ˆ์†Œ์Šค ์ปค๋ฎค๋‹ˆํ‹ฐ์™€ ํ•จ๊ป˜ ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์ง€. ๐ŸŽจ TSDoc์˜ ๊ฐ€์žฅ ํฐ ์žฅ์ ์€ ์ผ๊ด€์„ฑ์ด์•ผ. ์—ฌ๋Ÿฌ ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฐ์ž ๋‹ค๋ฅธ ์Šคํƒ€์ผ๋กœ ์ฃผ์„์„ ์ž‘์„ฑํ•˜๋ฉด ํ˜ผ๋ž€์Šค๋Ÿฝ์ž–์•„? TSDoc์€ ๋ช…ํ™•ํ•œ ๊ทœ์น™์„ ์ œ๊ณตํ•ด์„œ ๋ˆ„๊ฐ€ ์ž‘์„ฑํ•˜๋“  ๊ฐ™์€ ํ˜•์‹์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜.

๐Ÿ” TSDoc์˜ ํ•ต์‹ฌ ํŠน์ง•

1. ํ‘œ์ค€ํ™”๋œ ํƒœ๊ทธ ์‹œ์Šคํ…œ TSDoc์€ @param, @returns, @example ๊ฐ™์€ ํ‘œ์ค€ ํƒœ๊ทธ๋ฅผ ์ œ๊ณตํ•ด. ์ด ํƒœ๊ทธ๋“ค์€ ํŠน์ • ์ •๋ณด๋ฅผ ๊ตฌ์กฐํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ์ง€.

2. ๋งˆํฌ๋‹ค์šด ์ง€์›

์ฃผ์„ ์•ˆ์—์„œ ๋งˆํฌ๋‹ค์šด ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ๋งํฌ, ์ฝ”๋“œ ๋ธ”๋ก, ๋ฆฌ์ŠคํŠธ ๋“ฑ์„ ์ž์œ ๋กญ๊ฒŒ ํ™œ์šฉํ•ด์„œ ๋” ํ’๋ถ€ํ•œ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์ง€. โœ๏ธ

3. ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ํ†ตํ•ฉ

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ํƒ€์ž… ์‹œ์Šคํ…œ๊ณผ ์™„๋ฒฝํ•˜๊ฒŒ ํ†ตํ•ฉ๋ผ. IDE์—์„œ ์ž๋™์™„์„ฑ์ด๋‚˜ ํ˜ธ๋ฒ„ ํˆดํŒ์œผ๋กœ ๋ฌธ์„œ๋ฅผ ๋ฐ”๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด.

๐Ÿ’ป ๊ธฐ๋ณธ TSDoc ๋ฌธ๋ฒ•

TSDoc ์ฃผ์„์€ /**๋กœ ์‹œ์ž‘ํ•ด์„œ */๋กœ ๋๋‚˜. ์ผ๋ฐ˜ ์ฃผ์„๊ณผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด ๋ณ„ํ‘œ๊ฐ€ ๋‘ ๊ฐœ์ธ ๊ฑฐ์•ผ. ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์ œ๋ฅผ ๋ณผ๊นŒ?
/**
 * ๋‘ ์ˆซ์ž๋ฅผ ๋”ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
 * 
 * @param a - ์ฒซ ๋ฒˆ์งธ ์ˆซ์ž
 * @param b - ๋‘ ๋ฒˆ์งธ ์ˆซ์ž
 * @returns ๋‘ ์ˆซ์ž์˜ ํ•ฉ
 * 
 * @example
 * ```typescript
 * const result = add(5, 3);
 * console.log(result); // 8
 * ```
 */
function add(a: number, b: number): number {
  return a + b;
}
๋ณด๋‹ค์‹œํ”ผ ๊ตฌ์กฐ๊ฐ€ ๋ช…ํ™•ํ•˜์ง€? ํ•จ์ˆ˜์˜ ๋ชฉ์ , ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค๋ช…, ๋ฐ˜ํ™˜๊ฐ’, ๊ทธ๋ฆฌ๊ณ  ์‚ฌ์šฉ ์˜ˆ์ œ๊นŒ์ง€ ํ•œ๋ˆˆ์— ๋“ค์–ด์™€. ๐Ÿ‘€
๐Ÿ’ก ํŒ: TSDoc ์ฃผ์„์„ ์ž‘์„ฑํ•  ๋•Œ๋Š” ์ฒซ ์ค„์— ๊ฐ„๋‹จํ•œ ์š”์•ฝ์„ ์ž‘์„ฑํ•˜๊ณ , ๋นˆ ์ค„ ๋‹ค์Œ์— ์ƒ์„ธ ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒŒ ์ข‹์•„. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด IDE์˜ ํˆดํŒ์—์„œ ์š”์•ฝ๋งŒ ๋จผ์ € ๋ณด์—ฌ์ฃผ๊ณ , ํ•„์š”ํ•  ๋•Œ ์ „์ฒด ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ .

๐Ÿท๏ธ ์ฃผ์š” TSDoc ํƒœ๊ทธ ์™„๋ฒฝ ๊ฐ€์ด๋“œ

TSDoc์—๋Š” ๋‹ค์–‘ํ•œ ํƒœ๊ทธ๊ฐ€ ์žˆ์–ด. ๊ฐ ํƒœ๊ทธ๋Š” ํŠน์ • ๋ชฉ์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ๋ฌธ์„œ์˜ ํ’ˆ์งˆ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋ผ. ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ํƒœ๊ทธ๋“ค์„ ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด์ž! ๐ŸŽฏ

๐Ÿ“Œ ๊ธฐ๋ณธ ํƒœ๊ทธ

ํƒœ๊ทธ ์šฉ๋„ ์˜ˆ์‹œ
@param ํ•จ์ˆ˜ ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค๋ช… @param userId - ์‚ฌ์šฉ์ž ๊ณ ์œ  ID
@returns ๋ฐ˜ํ™˜๊ฐ’ ์„ค๋ช… @returns ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ์ฒด
@throws ๋ฐœ์ƒ ๊ฐ€๋Šฅํ•œ ์˜ˆ์™ธ @throws {Error} ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์„ ๋•Œ
@example ์‚ฌ์šฉ ์˜ˆ์ œ ์ฝ”๋“œ ์ƒ˜ํ”Œ ์ œ๊ณต
@remarks ์ถ”๊ฐ€ ์„ค๋ช… ์ฃผ์˜์‚ฌํ•ญ์ด๋‚˜ ์ƒ์„ธ ์ •๋ณด

๐Ÿ”ง @param ํƒœ๊ทธ ์ƒ์„ธ ํ™œ์šฉ

@param ํƒœ๊ทธ๋Š” ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ํƒœ๊ทธ ์ค‘ ํ•˜๋‚˜์•ผ. ํ•จ์ˆ˜์˜ ๊ฐ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๋Š”์ง€ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•ด์ค˜์•ผ ํ•ด.
/**
 * ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @param userId - ์—…๋ฐ์ดํŠธํ•  ์‚ฌ์šฉ์ž์˜ ๊ณ ์œ  ์‹๋ณ„์ž
 * @param data - ์—…๋ฐ์ดํŠธํ•  ๋ฐ์ดํ„ฐ ๊ฐ์ฒด
 * @param data.name - ์‚ฌ์šฉ์ž ์ด๋ฆ„ (์„ ํƒ์‚ฌํ•ญ)
 * @param data.email - ์ด๋ฉ”์ผ ์ฃผ์†Œ (์„ ํƒ์‚ฌํ•ญ)
 * @param options - ์ถ”๊ฐ€ ์˜ต์…˜
 * @param options.validateEmail - ์ด๋ฉ”์ผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์—ฌ๋ถ€ (๊ธฐ๋ณธ๊ฐ’: true)
 */
function updateUser(
  userId: string,
  data: { name?: string; email?: string },
  options?: { validateEmail?: boolean }
): void {
  // ๊ตฌํ˜„ ์ฝ”๋“œ
}
๊ฐ์ฒด ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์†์„ฑ๊นŒ์ง€ ๋ฌธ์„œํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒŒ ํฌ์ธํŠธ์•ผ! ๐ŸŽช ์  ํ‘œ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•ด์„œ ์ค‘์ฒฉ๋œ ์†์„ฑ๋„ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์–ด.

๐Ÿ“ค @returns ํƒœ๊ทธ ํ™œ์šฉ

๋ฐ˜ํ™˜๊ฐ’์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…ํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•ด. ๋‹จ์ˆœํžˆ ํƒ€์ž…๋งŒ ์•Œ๋ ค์ฃผ๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์–ด๋–ค ์ƒํ™ฉ์—์„œ ์–ด๋–ค ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š”์ง€ ์„ค๋ช…ํ•ด์ค˜์•ผ ํ•ด.
/**
 * ์‚ฌ์šฉ์ž๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @param query - ๊ฒ€์ƒ‰ ์ฟผ๋ฆฌ
 * @returns ๊ฒ€์ƒ‰๋œ ์‚ฌ์šฉ์ž ๋ฐฐ์—ด. ๊ฒฐ๊ณผ๊ฐ€ ์—†์œผ๋ฉด ๋นˆ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
 *          ๊ฐ ์‚ฌ์šฉ์ž ๊ฐ์ฒด๋Š” id, name, email ์†์„ฑ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
 */
async function searchUsers(query: string): Promise<User[]> {
  // ๊ตฌํ˜„ ์ฝ”๋“œ
}

โš ๏ธ @throws ํƒœ๊ทธ๋กœ ์˜ˆ์™ธ ๋ฌธ์„œํ™”

ํ•จ์ˆ˜๊ฐ€ ์–ด๋–ค ์ƒํ™ฉ์—์„œ ์—๋Ÿฌ๋ฅผ ๋˜์ง€๋Š”์ง€ ์•Œ๋ ค์ฃผ๋Š” ๊ฑด ์ •๋ง ์ค‘์š”ํ•ด. ์ด๊ฑธ ๋ชจ๋ฅด๋ฉด ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์ œ๋Œ€๋กœ ํ•  ์ˆ˜ ์—†๊ฑฐ๋“ .
/**
 * ํŒŒ์ผ์„ ์ฝ์–ด์„œ JSON์œผ๋กœ ํŒŒ์‹ฑํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @param filePath - ์ฝ์„ ํŒŒ์ผ ๊ฒฝ๋กœ
 * @returns ํŒŒ์‹ฑ๋œ JSON ๊ฐ์ฒด
 * @throws {FileNotFoundError} ํŒŒ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์„ ๋•Œ
 * @throws {ParseError} JSON ํŒŒ์‹ฑ์— ์‹คํŒจํ–ˆ์„ ๋•Œ
 * @throws {PermissionError} ํŒŒ์ผ ์ฝ๊ธฐ ๊ถŒํ•œ์ด ์—†์„ ๋•Œ
 */
function readJsonFile(filePath: string): object {
  // ๊ตฌํ˜„ ์ฝ”๋“œ
}
โš ๏ธ ์ฃผ์˜: @throws ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์‹ค์ œ๋กœ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ์˜ˆ์™ธ๋งŒ ๋ฌธ์„œํ™”ํ•ด์•ผ ํ•ด. ๊ณผ๋„ํ•˜๊ฒŒ ๋งŽ์€ ์˜ˆ์™ธ๋ฅผ ๋‚˜์—ดํ•˜๋ฉด ์˜คํžˆ๋ ค ํ˜ผ๋ž€์Šค๋Ÿฌ์šธ ์ˆ˜ ์žˆ์–ด.

๐Ÿ“š @example ํƒœ๊ทธ๋กœ ์‚ฌ์šฉ๋ฒ• ๋ณด์—ฌ์ฃผ๊ธฐ

์˜ˆ์ œ๋Š” ์ฒœ ๋งˆ๋”” ๋ง๋ณด๋‹ค ๋‚ซ์ง€! ๐Ÿ’Ž ์‹ค์ œ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋ณด์—ฌ์ฃผ๋ฉด ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๋“ค์ด ํ›จ์”ฌ ์‰ฝ๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์–ด.
/**
 * ๋ฐฐ์—ด์—์„œ ์ค‘๋ณต์„ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @param array - ์ค‘๋ณต์„ ์ œ๊ฑฐํ•  ๋ฐฐ์—ด
 * @returns ์ค‘๋ณต์ด ์ œ๊ฑฐ๋œ ์ƒˆ ๋ฐฐ์—ด
 * 
 * @example
 * ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•:
 * ```typescript
 * const numbers = [1, 2, 2, 3, 3, 3, 4];
 * const unique = removeDuplicates(numbers);
 * console.log(unique); // [1, 2, 3, 4]
 * ```
 * 
 * @example
 * ๊ฐ์ฒด ๋ฐฐ์—ด์—์„œ ์‚ฌ์šฉ:
 * ```typescript
 * const users = [
 *   { id: 1, name: 'Alice' },
 *   { id: 1, name: 'Alice' },
 *   { id: 2, name: 'Bob' }
 * ];
 * const uniqueUsers = removeDuplicates(users);
 * // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
 * ```
 */
function removeDuplicates<T>(array: T[]): T[] {
  return Array.from(new Set(array));
}
์—ฌ๋Ÿฌ ๊ฐœ์˜ @example ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋‹ค์–‘ํ•œ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์–ด. ๊ฐ ์˜ˆ์ œ์—๋Š” ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋” ์ข‹์•„!

๐ŸŽจ ๊ณ ๊ธ‰ TSDoc ๊ธฐ๋ฒ•

๊ธฐ๋ณธ ํƒœ๊ทธ๋“ค์„ ์ตํ˜”๋‹ค๋ฉด, ์ด์ œ ์ข€ ๋” ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•๋“ค์„ ์•Œ์•„๋ณผ ์ฐจ๋ก€์•ผ. ์ด๋Ÿฐ ๊ธฐ๋ฒ•๋“ค์„ ํ™œ์šฉํ•˜๋ฉด ์ •๋ง ์ „๋ฌธ์ ์ธ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด! ๐Ÿš€

๐Ÿ”— @see ํƒœ๊ทธ๋กœ ๊ด€๋ จ ํ•ญ๋ชฉ ์—ฐ๊ฒฐ

์ฝ”๋“œ๋ฒ ์ด์Šค์—์„œ ๊ด€๋ จ๋œ ํ•จ์ˆ˜๋‚˜ ํด๋ž˜์Šค๋ฅผ ์ฐธ์กฐํ•  ๋•Œ @see ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฌธ์„œ๋ฅผ ์ฝ๋Š” ์‚ฌ๋žŒ์ด ๊ด€๋ จ ์ •๋ณด๋ฅผ ์‰ฝ๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ์–ด.
/**
 * ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @param userData - ์ƒ์„ฑํ•  ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ
 * @returns ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ๊ฐ์ฒด
 * 
 * @see {@link updateUser} - ์‚ฌ์šฉ์ž ์ •๋ณด ์—…๋ฐ์ดํŠธ
 * @see {@link deleteUser} - ์‚ฌ์šฉ์ž ์‚ญ์ œ
 * @see https://docs.example.com/user-management - ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ๊ฐ€์ด๋“œ
 */
function createUser(userData: UserData): User {
  // ๊ตฌํ˜„ ์ฝ”๋“œ
}

๐Ÿ“ฆ @typeParam์œผ๋กœ ์ œ๋„ค๋ฆญ ํƒ€์ž… ์„ค๋ช…

์ œ๋„ค๋ฆญ์„ ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜๋‚˜ ํด๋ž˜์Šค์—์„œ๋Š” @typeParam ํƒœ๊ทธ๋กœ ํƒ€์ž… ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์–ด.
/**
 * ๋ฐฐ์—ด์˜ ๊ฐ ์š”์†Œ๋ฅผ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @typeParam T - ์ž…๋ ฅ ๋ฐฐ์—ด์˜ ์š”์†Œ ํƒ€์ž…
 * @typeParam U - ์ถœ๋ ฅ ๋ฐฐ์—ด์˜ ์š”์†Œ ํƒ€์ž…
 * @param array - ๋ณ€ํ™˜ํ•  ๋ฐฐ์—ด
 * @param mapper - ๊ฐ ์š”์†Œ๋ฅผ ๋ณ€ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜
 * @returns ๋ณ€ํ™˜๋œ ์ƒˆ ๋ฐฐ์—ด
 * 
 * @example
 * ```typescript
 * const numbers = [1, 2, 3];
 * const strings = map(numbers, n => n.toString());
 * // ['1', '2', '3']
 * ```
 */
function map<T, U>(array: T[], mapper: (item: T) => U): U[] {
  return array.map(mapper);
}

๐Ÿท๏ธ @deprecated๋กœ ์‚ฌ์šฉ ์ค‘๋‹จ ํ‘œ์‹œ

API๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ ๋” ๋‚˜์€ ๋Œ€์•ˆ์ด ์ƒ๊ฒผ์„ ๋•Œ, ๊ธฐ์กด ํ•จ์ˆ˜๋ฅผ ๋ฐ”๋กœ ์‚ญ์ œํ•˜๋ฉด ์•ˆ ๋˜์ง€. ๋Œ€์‹  @deprecated ํƒœ๊ทธ๋กœ ํ‘œ์‹œํ•˜๊ณ  ๋Œ€์•ˆ์„ ์•ˆ๋‚ดํ•ด์ค˜์•ผ ํ•ด. โš ๏ธ
/**
 * ์‚ฌ์šฉ์ž ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
 * 
 * @deprecated ์ด ํ•จ์ˆ˜๋Š” v2.0์—์„œ ์ œ๊ฑฐ๋  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.
 *             ๋Œ€์‹  {@link getUsersPaginated}๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.
 * 
 * @returns ๋ชจ๋“  ์‚ฌ์šฉ์ž ๋ฐฐ์—ด
 */
function getUsers(): User[] {
  // ๊ตฌํ˜„ ์ฝ”๋“œ
}

/**
 * ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ์ง€์›ํ•˜๋Š” ์‚ฌ์šฉ์ž ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
 * 
 * @param page - ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ (1๋ถ€ํ„ฐ ์‹œ์ž‘)
 * @param limit - ํŽ˜์ด์ง€๋‹น ํ•ญ๋ชฉ ์ˆ˜
 * @returns ํŽ˜์ด์ง€๋„ค์ด์…˜๋œ ์‚ฌ์šฉ์ž ๋ชฉ๋ก
 */
function getUsersPaginated(page: number, limit: number): PaginatedUsers {
  // ๊ตฌํ˜„ ์ฝ”๋“œ
}

๐ŸŽญ @internal๋กœ ๋‚ด๋ถ€ API ํ‘œ์‹œ

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฐœ๋ฐœํ•  ๋•Œ, ์™ธ๋ถ€์— ๊ณต๊ฐœํ•˜์ง€ ์•Š์„ ๋‚ด๋ถ€ ํ•จ์ˆ˜๋‚˜ ํด๋ž˜์Šค๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์–ด. @internal ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋Ÿฐ ํ•ญ๋ชฉ๋“ค์„ ๊ณต๊ฐœ ๋ฌธ์„œ์—์„œ ์ œ์™ธํ•  ์ˆ˜ ์žˆ์–ด.
/**
 * @internal
 * ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ํ—ฌํผ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
 * ์™ธ๋ถ€์—์„œ ์ง์ ‘ ํ˜ธ์ถœํ•˜์ง€ ๋งˆ์„ธ์š”.
 */
function _internalHelper(): void {
  // ๊ตฌํ˜„ ์ฝ”๋“œ
}
TSDoc ํƒœ๊ทธ ์กฐํ•ฉ์˜ ํž˜ ์—ฌ๋Ÿฌ ํƒœ๊ทธ๋ฅผ ์กฐํ•ฉํ•ด์„œ ์‚ฌ์šฉํ•˜๋ฉด ์ •๋ง ํ’๋ถ€ํ•œ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. ์˜ˆ๋ฅผ ๋“ค์–ด:

โœ… @param + @throws: ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฒ€์ฆ ์‹คํŒจ ์‹œ ์˜ˆ์™ธ
โœ… @example + @see: ์˜ˆ์ œ์™€ ๊ด€๋ จ ๋ฌธ์„œ ๋งํฌ
โœ… @deprecated + @see: ๋Œ€์ฒด ํ•จ์ˆ˜ ์•ˆ๋‚ด
โœ… @remarks + @example: ์ƒ์„ธ ์„ค๋ช…๊ณผ ์‹ค์šฉ ์˜ˆ์ œ

๐Ÿ“ ๋งˆํฌ๋‹ค์šด ํ™œ์šฉํ•˜๊ธฐ

TSDoc ์ฃผ์„ ์•ˆ์—์„œ ๋งˆํฌ๋‹ค์šด์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑด ์ •๋ง ํฐ ์žฅ์ ์ด์•ผ. ํ…์ŠคํŠธ๋ฅผ ๋” ์ฝ๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฑฐ๋“ !
/**
 * ๋ณต์žกํ•œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
 * 
 * @remarks
 * ์ด ํ•จ์ˆ˜๋Š” ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค:
 * 
 * 1. **๊ฒ€์ฆ**: ์ž…๋ ฅ ๋ฐ์ดํ„ฐ์˜ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
 * 2. **๋ณ€ํ™˜**: ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ด๋ถ€ ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜
 * 3. **์ฒ˜๋ฆฌ**: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ ์šฉ
 * 4. **๋ฐ˜ํ™˜**: ๊ฒฐ๊ณผ ๋ฐ์ดํ„ฐ ๋ฐ˜ํ™˜
 * 
 * > **์ค‘์š”**: ์ด ํ•จ์ˆ˜๋Š” ๋น„๋™๊ธฐ๋กœ ๋™์ž‘ํ•˜๋ฏ€๋กœ ๋ฐ˜๋“œ์‹œ await๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
 * 
 * ์„ฑ๋Šฅ ๊ณ ๋ ค์‚ฌํ•ญ:
 * - ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ(>10MB)๋Š” ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์„ธ์š”
 * - ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋ฉด `processDataParallel()`์„ ์‚ฌ์šฉํ•˜์„ธ์š”
 * 
 * @param data - ์ฒ˜๋ฆฌํ•  ๋ฐ์ดํ„ฐ
 * @returns ์ฒ˜๋ฆฌ๋œ ๊ฒฐ๊ณผ
 */
async function processData(data: RawData): Promise<ProcessedData> {
  // ๊ตฌํ˜„ ์ฝ”๋“œ
}
๋ฆฌ์ŠคํŠธ, ๊ฐ•์กฐ, ์ธ์šฉ๊ตฌ ๋“ฑ์„ ์ž์œ ๋กญ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ธด ์„ค๋ช…๋„ ๊ตฌ์กฐํ™”๋˜์–ด์„œ ์ฝ๊ธฐ ํŽธํ•ด์ ธ! ๐Ÿ“–

๐Ÿ› ๏ธ API ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑ ๋„๊ตฌ๋“ค

TSDoc ์ฃผ์„์„ ์—ด์‹ฌํžˆ ์ž‘์„ฑํ–ˆ๋‹ค๋ฉด, ์ด์ œ ์ด๊ฑธ ๋ฉ‹์ง„ ์›น ๋ฌธ์„œ๋กœ ๋ณ€ํ™˜ํ•  ์ฐจ๋ก€์•ผ! ์—ฌ๋Ÿฌ ๋„๊ตฌ๋“ค์ด ์žˆ๋Š”๋ฐ, ๊ฐ๊ฐ ํŠน์ง•์ด ๋‹ฌ๋ผ. ํ”„๋กœ์ ํŠธ์— ๋งž๋Š” ๋„๊ตฌ๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด. ๐Ÿ”จ

๐Ÿ“˜ TypeDoc - ๊ฐ€์žฅ ์ธ๊ธฐ ์žˆ๋Š” ์„ ํƒ

TypeDoc์€ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ํ”„๋กœ์ ํŠธ๋ฅผ ์œ„ํ•œ ๋ฌธ์„œ ์ƒ์„ฑ๊ธฐ ์ค‘ ๊ฐ€์žฅ ๋„๋ฆฌ ์‚ฌ์šฉ๋ผ. JSDoc ์Šคํƒ€์ผ์˜ ์ฃผ์„์„ ์ฝ์–ด์„œ ์ •์  HTML ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•ด์ค˜.
TypeDoc์˜ ์ฃผ์š” ํŠน์ง• ๐ŸŽจ ๊น”๋”ํ•œ ๋””์ž์ธ: ๊ธฐ๋ณธ ํ…Œ๋งˆ๊ฐ€ ํ˜„๋Œ€์ ์ด๊ณ  ์ฝ๊ธฐ ํŽธํ•ด
๐Ÿ” ๊ฐ•๋ ฅํ•œ ๊ฒ€์ƒ‰: ์ „์ฒด ๋ฌธ์„œ์—์„œ ๋น ๋ฅด๊ฒŒ ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅ
๐ŸŽฏ ํƒ€์ž… ์ •๋ณด: ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์˜ ํƒ€์ž… ์ •๋ณด๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ํ‘œ์‹œ
๐Ÿ”Œ ํ”Œ๋Ÿฌ๊ทธ์ธ ์‹œ์Šคํ…œ: ๊ธฐ๋Šฅ ํ™•์žฅ์ด ์‰ฌ์›Œ
โš™๏ธ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•: ํ…Œ๋งˆ์™€ ๋ ˆ์ด์•„์›ƒ์„ ์ž์œ ๋กญ๊ฒŒ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ

TypeDoc ์„ค์น˜ ๋ฐ ์„ค์ •

TypeDoc์„ ์‹œ์ž‘ํ•˜๋Š” ๊ฑด ์ •๋ง ๊ฐ„๋‹จํ•ด. npm์œผ๋กœ ์„ค์น˜ํ•˜๊ณ  ๊ฐ„๋‹จํ•œ ์„ค์ •๋งŒ ํ•˜๋ฉด ๋ผ.
# TypeDoc ์„ค์น˜
npm install --save-dev typedoc

# ๊ธฐ๋ณธ ๋ฌธ์„œ ์ƒ์„ฑ
npx typedoc src/index.ts
ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ์— typedoc.json ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์„œ ๋” ์„ธ๋ฐ€ํ•˜๊ฒŒ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์–ด:
{
  "entryPoints": ["src/index.ts"],
  "out": "docs",
  "name": "My Awesome Library",
  "excludePrivate": true,
  "excludeProtected": false,
  "excludeExternals": true,
  "readme": "README.md",
  "theme": "default",
  "includeVersion": true,
  "categorizeByGroup": true,
  "sort": ["source-order"]
}
package.json์— ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๋” ํŽธํ•ด:
{
  "scripts": {
    "docs": "typedoc",
    "docs:watch": "typedoc --watch"
  }
}
์ด์ œ npm run docs ๋ช…๋ น์–ด ํ•˜๋‚˜๋กœ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด! ๐ŸŽ‰

๐Ÿ“— API Extractor + API Documenter

๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ์—์„œ ๋งŒ๋“  ๋„๊ตฌ ์กฐํ•ฉ์ด์•ผ. ํŠนํžˆ ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ๋‚˜ ๋ชจ๋…ธ๋ ˆํฌ์—์„œ ๊ฐ•๋ ฅํ•ด.
API Extractor์˜ ๊ฐ•์  ๐Ÿ“ฆ API ๋ฆฌํฌํŠธ: ๊ณต๊ฐœ API์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์ถ”์ 
๐Ÿ”’ API ๋ ˆ๋ฒจ ๊ด€๋ฆฌ: public, beta, internal ๋“ฑ์œผ๋กœ ๊ตฌ๋ถ„
๐Ÿ“Š ๋ฒˆ๋“ค ๋ถ„์„: .d.ts ํŒŒ์ผ์„ ํ•˜๋‚˜๋กœ ๋กค์—…
โœ… ๊ฒ€์ฆ: API ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ CI/CD์—์„œ ์ž๋™ ๊ฒ€์ฆ

API Extractor ์„ค์ •

# ์„ค์น˜
npm install --save-dev @microsoft/api-extractor @microsoft/api-documenter

# ์„ค์ • ํŒŒ์ผ ์ƒ์„ฑ
npx api-extractor init
์ƒ์„ฑ๋œ api-extractor.json์„ ํ”„๋กœ์ ํŠธ์— ๋งž๊ฒŒ ์ˆ˜์ •ํ•ด:
{
  "mainEntryPointFilePath": "dist/index.d.ts",
  "apiReport": {
    "enabled": true,
    "reportFolder": "temp"
  },
  "docModel": {
    "enabled": true,
    "apiJsonFilePath": "temp/<unscopedPackageName>.api.json"
  },
  "dtsRollup": {
    "enabled": true,
    "untrimmedFilePath": "dist/index.d.ts"
  }
}
๋ฌธ์„œ ์ƒ์„ฑ์€ ๋‘ ๋‹จ๊ณ„๋กœ ์ง„ํ–‰๋ผ:
# 1. API ๋ชจ๋ธ ์ถ”์ถœ
npx api-extractor run --local

# 2. ๋ฌธ์„œ ์ƒ์„ฑ
npx api-documenter markdown -i temp -o docs

๐Ÿ“™ TSDoc ํŒŒ์„œ ์ง์ ‘ ์‚ฌ์šฉํ•˜๊ธฐ

๋” ์„ธ๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด TSDoc ํŒŒ์„œ๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์–ด. ์ปค์Šคํ…€ ๋ฌธ์„œ ์ƒ์„ฑ๊ธฐ๋ฅผ ๋งŒ๋“ค ๋•Œ ์œ ์šฉํ•ด.
import { TSDocParser } from '@microsoft/tsdoc';
import * as ts from 'typescript';

const parser = new TSDocParser();

function extractDocumentation(sourceFile: ts.SourceFile) {
  ts.forEachChild(sourceFile, node => {
    if (ts.isFunctionDeclaration(node) && node.name) {
      const jsDocComments = ts.getJSDocCommentsAndTags(node);
      
      jsDocComments.forEach(comment => {
        const text = comment.comment || '';
        const parserContext = parser.parseString(text.toString());
        
        console.log('Function:', node.name?.text);
        console.log('Summary:', parserContext.docComment.summarySection);
      });
    }
  });
}
์ด ๋ฐฉ๋ฒ•์€ ๋ณต์žกํ•˜์ง€๋งŒ, ์™„์ „ํ•œ ์ž์œ ๋„๋ฅผ ์ œ๊ณตํ•ด. ํŠน๋ณ„ํ•œ ํ˜•์‹์˜ ๋ฌธ์„œ๊ฐ€ ํ•„์š”ํ•˜๊ฑฐ๋‚˜, ๋‹ค๋ฅธ ์‹œ์Šคํ…œ๊ณผ ํ†ตํ•ฉํ•ด์•ผ ํ•  ๋•Œ ์œ ์šฉํ•ด! ๐ŸŽฏ
๐Ÿ’ก ๋„๊ตฌ ์„ ํƒ ๊ฐ€์ด๋“œ: TypeDoc: ๋น ๋ฅด๊ฒŒ ์‹œ์ž‘ํ•˜๊ณ  ์‹ถ๊ณ , ํ‘œ์ค€์ ์ธ ๋ฌธ์„œ๊ฐ€ ํ•„์š”ํ•  ๋•Œ
API Extractor: ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ, API ๋ฒ„์ „ ๊ด€๋ฆฌ๊ฐ€ ์ค‘์š”ํ•  ๋•Œ
์ปค์Šคํ…€ ํŒŒ์„œ: ํŠน์ˆ˜ํ•œ ์š”๊ตฌ์‚ฌํ•ญ์ด ์žˆ๊ฑฐ๋‚˜, ๊ธฐ์กด ์‹œ์Šคํ…œ๊ณผ ํ†ตํ•ฉํ•  ๋•Œ

๐ŸŽฏ ์‹ค์ „ ํ”„๋กœ์ ํŠธ ์ ์šฉ ์‚ฌ๋ก€

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์–ด๋–ป๊ฒŒ ์ ์šฉํ•˜๋Š”์ง€ ์‚ดํŽด๋ณด์ž! ์‹ค๋ฌด์—์„œ ์ž์ฃผ ๋งˆ์ฃผ์น˜๋Š” ์ƒํ™ฉ๋“ค์„ ์˜ˆ์ œ๋กœ ์ค€๋น„ํ–ˆ์–ด. ๐Ÿ’ผ

๐Ÿ—๏ธ ํด๋ž˜์Šค ๋ฌธ์„œํ™”

ํด๋ž˜์Šค๋Š” ์—ฌ๋Ÿฌ ๋ฉ”์„œ๋“œ์™€ ์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์„œ ๋ฌธ์„œํ™”๊ฐ€ ์ข€ ๋” ๋ณต์žกํ•ด. ํ•˜์ง€๋งŒ ์ฒด๊ณ„์ ์œผ๋กœ ์ ‘๊ทผํ•˜๋ฉด ์–ด๋ ต์ง€ ์•Š์•„!
/**
 * ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์„œ๋น„์Šค ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.
 * 
 * @remarks
 * ์ด ํด๋ž˜์Šค๋Š” ์‹ฑ๊ธ€ํ†ค ํŒจํ„ด์œผ๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
 * `getInstance()` ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์ธ์Šคํ„ด์Šค๋ฅผ ์–ป์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @example
 * ```typescript
 * const userService = UserService.getInstance();
 * const user = await userService.createUser({
 *   name: 'Alice',
 *   email: 'alice@example.com'
 * });
 * ```
 */
export class UserService {
  /**
   * ์„œ๋น„์Šค์˜ ์‹ฑ๊ธ€ํ†ค ์ธ์Šคํ„ด์Šค
   * @internal
   */
  private static instance: UserService;

  /**
   * ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ์ €์žฅ์†Œ
   */
  private readonly repository: UserRepository;

  /**
   * @internal
   * ์ƒ์„ฑ์ž๋Š” private์œผ๋กœ ์„ ์–ธ๋˜์–ด ์™ธ๋ถ€์—์„œ ์ง์ ‘ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
   */
  private constructor() {
    this.repository = new UserRepository();
  }

  /**
   * UserService์˜ ์‹ฑ๊ธ€ํ†ค ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
   * 
   * @returns UserService ์ธ์Šคํ„ด์Šค
   */
  public static getInstance(): UserService {
    if (!UserService.instance) {
      UserService.instance = new UserService();
    }
    return UserService.instance;
  }

  /**
   * ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
   * 
   * @param userData - ์ƒ์„ฑํ•  ์‚ฌ์šฉ์ž์˜ ์ •๋ณด
   * @param userData.name - ์‚ฌ์šฉ์ž ์ด๋ฆ„ (2-50์ž)
   * @param userData.email - ์ด๋ฉ”์ผ ์ฃผ์†Œ (์œ ํšจํ•œ ํ˜•์‹์ด์–ด์•ผ ํ•จ)
   * @param userData.age - ๋‚˜์ด (์„ ํƒ์‚ฌํ•ญ, 0-150)
   * @returns ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ๊ฐ์ฒด
   * 
   * @throws {ValidationError} ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์„ ๋•Œ
   * @throws {DuplicateError} ์ด๋ฏธ ์กด์žฌํ•˜๋Š” ์ด๋ฉ”์ผ์ผ ๋•Œ
   * 
   * @example
   * ```typescript
   * const user = await userService.createUser({
   *   name: 'Bob',
   *   email: 'bob@example.com',
   *   age: 25
   * });
   * console.log(user.id); // ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ID
   * ```
   */
  public async createUser(userData: CreateUserDto): Promise<User> {
    // ๊ตฌํ˜„ ์ฝ”๋“œ
  }

  /**
   * ID๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
   * 
   * @param userId - ์กฐํšŒํ•  ์‚ฌ์šฉ์ž์˜ ID
   * @returns ์‚ฌ์šฉ์ž ๊ฐ์ฒด ๋˜๋Š” null (์‚ฌ์šฉ์ž๊ฐ€ ์—†์„ ๊ฒฝ์šฐ)
   * 
   * @example
   * ```typescript
   * const user = await userService.getUserById('user-123');
   * if (user) {
   *   console.log(user.name);
   * } else {
   *   console.log('์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค');
   * }
   * ```
   */
  public async getUserById(userId: string): Promise<User | null> {
    // ๊ตฌํ˜„ ์ฝ”๋“œ
  }
}
ํด๋ž˜์Šค ๋ ˆ๋ฒจ, ์†์„ฑ, ๋ฉ”์„œ๋“œ ๊ฐ๊ฐ์— ์ ์ ˆํ•œ ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒŒ ํฌ์ธํŠธ์•ผ. ๐ŸŽช

๐Ÿ”ง ์ธํ„ฐํŽ˜์ด์Šค์™€ ํƒ€์ž… ๋ฌธ์„œํ™”

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ์—์„œ ์ธํ„ฐํŽ˜์ด์Šค์™€ ํƒ€์ž…์€ ์ •๋ง ์ค‘์š”ํ•˜์ง€. ์ด๊ฒƒ๋“ค๋„ ์ž˜ ๋ฌธ์„œํ™”ํ•ด์•ผ ํ•ด!
/**
 * ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค.
 * 
 * @remarks
 * ์ด ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ์™€ 1:1๋กœ ๋งคํ•‘๋ฉ๋‹ˆ๋‹ค.
 * ๋ชจ๋“  ํ•„๋“œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋˜๋Š” ๊ฐ’๊ณผ ๋™์ผํ•œ ํ˜•์‹์„ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
 */
export interface User {
  /**
   * ์‚ฌ์šฉ์ž์˜ ๊ณ ์œ  ์‹๋ณ„์ž
   * @remarks UUID v4 ํ˜•์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค
   */
  id: string;

  /**
   * ์‚ฌ์šฉ์ž ์ด๋ฆ„
   * @remarks 2-50์ž ์‚ฌ์ด์—ฌ์•ผ ํ•˜๋ฉฐ, ํŠน์ˆ˜๋ฌธ์ž๋Š” ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค
   */
  name: string;

  /**
   * ์ด๋ฉ”์ผ ์ฃผ์†Œ
   * @remarks ์œ ์ผํ•ด์•ผ ํ•˜๋ฉฐ, ์œ ํšจํ•œ ์ด๋ฉ”์ผ ํ˜•์‹์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค
   */
  email: string;

  /**
   * ๊ณ„์ • ์ƒ์„ฑ ์ผ์‹œ
   * @remarks ISO 8601 ํ˜•์‹์˜ ํƒ€์ž„์Šคํƒฌํ”„
   */
  createdAt: Date;

  /**
   * ๋งˆ์ง€๋ง‰ ๋กœ๊ทธ์ธ ์ผ์‹œ
   * @remarks ํ•œ ๋ฒˆ๋„ ๋กœ๊ทธ์ธํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ null
   */
  lastLoginAt: Date | null;
}

/**
 * ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์‹œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ ์ „์†ก ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.
 * 
 * @remarks
 * ์ด ํƒ€์ž…์€ {@link User} ์ธํ„ฐํŽ˜์ด์Šค์—์„œ ์‹œ์Šคํ…œ์ด ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋Š”
 * ํ•„๋“œ๋“ค(id, createdAt ๋“ฑ)์„ ์ œ์™ธํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.
 */
export type CreateUserDto = Pick<User, 'name' | 'email'> & {
  /**
   * ์ดˆ๊ธฐ ๋น„๋ฐ€๋ฒˆํ˜ธ
   * @remarks ์ตœ์†Œ 8์ž ์ด์ƒ, ์˜๋ฌธ/์ˆซ์ž/ํŠน์ˆ˜๋ฌธ์ž ์กฐํ•ฉ
   */
  password: string;
};

/**
 * ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ฒฐ๊ณผ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์ž…๋‹ˆ๋‹ค.
 * 
 * @typeParam T - ํŽ˜์ด์ง€๋„ค์ด์…˜ํ•  ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…
 * 
 * @example
 * ```typescript
 * const result: PaginatedResult<User> = {
 *   items: [user1, user2, user3],
 *   total: 100,
 *   page: 1,
 *   pageSize: 10
 * };
 * ```
 */
export interface PaginatedResult<T> {
  /** ํ˜„์žฌ ํŽ˜์ด์ง€์˜ ํ•ญ๋ชฉ๋“ค */
  items: T[];
  
  /** ์ „์ฒด ํ•ญ๋ชฉ ์ˆ˜ */
  total: number;
  
  /** ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ (1๋ถ€ํ„ฐ ์‹œ์ž‘) */
  page: number;
  
  /** ํŽ˜์ด์ง€๋‹น ํ•ญ๋ชฉ ์ˆ˜ */
  pageSize: number;
  
  /** ์ „์ฒด ํŽ˜์ด์ง€ ์ˆ˜ */
  get totalPages(): number;
  
  /** ๋‹ค์Œ ํŽ˜์ด์ง€ ์กด์žฌ ์—ฌ๋ถ€ */
  get hasNext(): boolean;
  
  /** ์ด์ „ ํŽ˜์ด์ง€ ์กด์žฌ ์—ฌ๋ถ€ */
  get hasPrevious(): boolean;
}

๐ŸŽจ React ์ปดํฌ๋„ŒํŠธ ๋ฌธ์„œํ™”

React๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์ปดํฌ๋„ŒํŠธ ๋ฌธ์„œํ™”๋„ ์ค‘์š”ํ•ด. Props ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ž˜ ๋ฌธ์„œํ™”ํ•˜๋ฉด ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๋“ค์ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด!
/**
 * ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ ์นด๋“œ ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.
 * 
 * @remarks
 * ์ด ์ปดํฌ๋„ŒํŠธ๋Š” ์‚ฌ์šฉ์ž์˜ ๊ธฐ๋ณธ ์ •๋ณด๋ฅผ ์นด๋“œ ํ˜•ํƒœ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.
 * ์•„๋ฐ”ํƒ€, ์ด๋ฆ„, ์ด๋ฉ”์ผ, ๊ทธ๋ฆฌ๊ณ  ์„ ํƒ์ ์œผ๋กœ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
 * 
 * @example
 * ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•:
 * ```tsx
 * <UserProfileCard
 *   user={currentUser}
 *   onEdit={() => handleEdit(currentUser.id)}
 * />
 * ```
 * 
 * @example
 * ์ถ”๊ฐ€ ์ •๋ณด์™€ ํ•จ๊ป˜:
 * ```tsx
 * <UserProfileCard
 *   user={currentUser}
 *   showBio
 *   showJoinDate
 *   variant="detailed"
 * />
 * ```
 */
export const UserProfileCard: React.FC<UserProfileCardProps> = (props) => {
  // ๊ตฌํ˜„ ์ฝ”๋“œ
};

/**
 * UserProfileCard ์ปดํฌ๋„ŒํŠธ์˜ Props ์ธํ„ฐํŽ˜์ด์Šค์ž…๋‹ˆ๋‹ค.
 */
export interface UserProfileCardProps {
  /**
   * ํ‘œ์‹œํ•  ์‚ฌ์šฉ์ž ๊ฐ์ฒด
   */
  user: User;

  /**
   * ์นด๋“œ์˜ ํ‘œ์‹œ ์Šคํƒ€์ผ
   * @defaultValue 'compact'
   */
  variant?: 'compact' | 'detailed' | 'minimal';

  /**
   * ์‚ฌ์šฉ์ž ์†Œ๊ฐœ๋ฅผ ํ‘œ์‹œํ• ์ง€ ์—ฌ๋ถ€
   * @defaultValue false
   */
  showBio?: boolean;

  /**
   * ๊ฐ€์ž… ๋‚ ์งœ๋ฅผ ํ‘œ์‹œํ• ์ง€ ์—ฌ๋ถ€
   * @defaultValue false
   */
  showJoinDate?: boolean;

  /**
   * ํŽธ์ง‘ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ํ˜ธ์ถœ๋˜๋Š” ์ฝœ๋ฐฑ
   * @param userId - ํŽธ์ง‘ํ•  ์‚ฌ์šฉ์ž์˜ ID
   */
  onEdit?: (userId: string) => void;

  /**
   * ์‚ญ์ œ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ํ˜ธ์ถœ๋˜๋Š” ์ฝœ๋ฐฑ
   * @param userId - ์‚ญ์ œํ•  ์‚ฌ์šฉ์ž์˜ ID
   */
  onDelete?: (userId: string) => void;

  /**
   * ์ถ”๊ฐ€ CSS ํด๋ž˜์Šค๋ช…
   */
  className?: string;

  /**
   * ์ธ๋ผ์ธ ์Šคํƒ€์ผ
   */
  style?: React.CSSProperties;
}
React ์ปดํฌ๋„ŒํŠธ๋Š” ํŠนํžˆ @defaultValue ํƒœ๊ทธ๋ฅผ ํ™œ์šฉํ•ด์„œ ๊ธฐ๋ณธ๊ฐ’์„ ๋ช…์‹œํ•˜๋Š” ๊ฒŒ ์ข‹์•„. ์‚ฌ์šฉ์ž๊ฐ€ ์–ด๋–ค props๊ฐ€ ํ•„์ˆ˜์ด๊ณ  ์„ ํƒ์ธ์ง€ ์‰ฝ๊ฒŒ ์•Œ ์ˆ˜ ์žˆ๊ฑฐ๋“ ! โš›๏ธ

๐Ÿ”Œ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ๋ชจ์Œ ๋ฌธ์„œํ™”

์—ฌ๋Ÿฌ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋ฅผ ๋ชจ์•„๋†“์€ ๋ชจ๋“ˆ๋„ ์ฒด๊ณ„์ ์œผ๋กœ ๋ฌธ์„œํ™”ํ•ด์•ผ ํ•ด.
/**
 * ๋ฌธ์ž์—ด ์ฒ˜๋ฆฌ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ๋ชจ์Œ์ž…๋‹ˆ๋‹ค.
 * 
 * @remarks
 * ์ด ๋ชจ๋“ˆ์€ ์ผ๋ฐ˜์ ์ธ ๋ฌธ์ž์—ด ์กฐ์ž‘ ์ž‘์—…์„ ์œ„ํ•œ ํ—ฌํผ ํ•จ์ˆ˜๋“ค์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
 * ๋ชจ๋“  ํ•จ์ˆ˜๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ์œผ๋ฉฐ, ์›๋ณธ ๋ฌธ์ž์—ด์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
 * 
 * @packageDocumentation
 */

/**
 * ๋ฌธ์ž์—ด์˜ ์ฒซ ๊ธ€์ž๋ฅผ ๋Œ€๋ฌธ์ž๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @param str - ๋ณ€ํ™˜ํ•  ๋ฌธ์ž์—ด
 * @returns ์ฒซ ๊ธ€์ž๊ฐ€ ๋Œ€๋ฌธ์ž๋กœ ๋ณ€ํ™˜๋œ ๋ฌธ์ž์—ด
 * 
 * @example
 * ```typescript
 * capitalize('hello'); // 'Hello'
 * capitalize('WORLD'); // 'WORLD'
 * capitalize(''); // ''
 * ```
 */
export function capitalize(str: string): string {
  if (!str) return str;
  return str.charAt(0).toUpperCase() + str.slice(1);
}

/**
 * ์นด๋ฉœ์ผ€์ด์Šค ๋ฌธ์ž์—ด์„ ์ผ€๋ฐฅ์ผ€์ด์Šค๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @param str - ๋ณ€ํ™˜ํ•  ์นด๋ฉœ์ผ€์ด์Šค ๋ฌธ์ž์—ด
 * @returns ์ผ€๋ฐฅ์ผ€์ด์Šค๋กœ ๋ณ€ํ™˜๋œ ๋ฌธ์ž์—ด
 * 
 * @example
 * ```typescript
 * camelToKebab('userName'); // 'user-name'
 * camelToKebab('getUserById'); // 'get-user-by-id'
 * camelToKebab('API'); // 'a-p-i'
 * ```
 */
export function camelToKebab(str: string): string {
  return str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
}

/**
 * ๋ฌธ์ž์—ด์„ ์ง€์ •๋œ ๊ธธ์ด๋กœ ์ž๋ฅด๊ณ  ๋ง์ค„์ž„ํ‘œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @param str - ์ž๋ฅผ ๋ฌธ์ž์—ด
 * @param maxLength - ์ตœ๋Œ€ ๊ธธ์ด (๋ง์ค„์ž„ํ‘œ ํฌํ•จ)
 * @param ellipsis - ๋ง์ค„์ž„ํ‘œ ๋ฌธ์ž (๊ธฐ๋ณธ๊ฐ’: '...')
 * @returns ์ž˜๋ฆฐ ๋ฌธ์ž์—ด
 * 
 * @throws {Error} maxLength๊ฐ€ ellipsis ๊ธธ์ด๋ณด๋‹ค ์ž‘์„ ๋•Œ
 * 
 * @example
 * ```typescript
 * truncate('Hello World', 8); // 'Hello...'
 * truncate('Short', 10); // 'Short'
 * truncate('Long text here', 10, 'โ€ฆ'); // 'Long textโ€ฆ'
 * ```
 */
export function truncate(
  str: string,
  maxLength: number,
  ellipsis: string = '...'
): string {
  if (maxLength < ellipsis.length) {
    throw new Error('maxLength must be greater than ellipsis length');
  }
  if (str.length <= maxLength) return str;
  return str.slice(0, maxLength - ellipsis.length) + ellipsis;
}
์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋Š” ๋‹ค์–‘ํ•œ ์ƒํ™ฉ์—์„œ ์‚ฌ์šฉ๋˜๋‹ˆ๊นŒ ์˜ˆ์ œ๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ์ œ๊ณตํ•˜๋Š” ๊ฒŒ ์ข‹์•„! ๐Ÿ› ๏ธ

โœจ ๋ฌธ์„œํ™” ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

์ด์ œ ์‹ค์ „ ํŒ๋“ค์„ ์•Œ๋ ค์ค„๊ฒŒ! ์ด๋Ÿฐ ์›์น™๋“ค์„ ๋”ฐ๋ฅด๋ฉด ์ •๋ง ํ›Œ๋ฅญํ•œ ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. ๐ŸŒŸ

๐Ÿ“‹ ์ผ๊ด€์„ฑ ์œ ์ง€ํ•˜๊ธฐ

์ผ๊ด€์„ฑ์ด ๊ฐ€์žฅ ์ค‘์š”ํ•ด! ๊ฐ™์€ ํ”„๋กœ์ ํŠธ ๋‚ด์—์„œ๋Š” ๋™์ผํ•œ ์Šคํƒ€์ผ๊ณผ ํ˜•์‹์„ ์œ ์ง€ํ•ด์•ผ ํ•ด. ์˜ˆ๋ฅผ ๋“ค์–ด:

โœ… ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค๋ช…์€ ํ•ญ์ƒ ๋ช…์‚ฌ๊ตฌ๋กœ ์‹œ์ž‘
โœ… ํ•จ์ˆ˜ ์„ค๋ช…์€ ๋™์‚ฌ๋กœ ์‹œ์ž‘ ("~ํ•ฉ๋‹ˆ๋‹ค", "~๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค")
โœ… ์˜ˆ์ œ๋Š” ํ•ญ์ƒ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ๋กœ ์ž‘์„ฑ
โœ… ์—๋Ÿฌ ์กฐ๊ฑด์€ @throws ํƒœ๊ทธ๋กœ ๋ช…์‹œ
ํŒ€ ์ „์ฒด๊ฐ€ ๋”ฐ๋ฅผ ์ˆ˜ ์žˆ๋Š” ๋ฌธ์„œํ™” ๊ฐ€์ด๋“œ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ์žฌ๋Šฅ๋„ท(https://www.jaenung.net)์—์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•  ๋•Œ๋„ ์ด๋Ÿฐ ๊ฐ€์ด๋“œ๊ฐ€ ์žˆ์œผ๋ฉด ํ˜‘์—…์ด ํ›จ์”ฌ ์ˆ˜์›”ํ•ด์ ธ!

๐ŸŽฏ ๋ช…ํ™•ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ

๋ฌธ์„œ๋Š” ๋ช…ํ™•ํ•ด์•ผ ํ•ด. ํ•˜์ง€๋งŒ ๋„ˆ๋ฌด ์žฅํ™ฉํ•˜๋ฉด ์•ˆ ๋ผ. ํ•ต์‹ฌ๋งŒ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ „๋‹ฌํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด.
// โŒ ๋‚˜์œ ์˜ˆ: ๋„ˆ๋ฌด ์žฅํ™ฉํ•จ
/**
 * ์ด ํ•จ์ˆ˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ๋‘ ๊ฐœ์˜ ์ˆซ์ž๋ฅผ ๋ฐ›์•„์„œ
 * ๊ทธ๊ฒƒ๋“ค์„ ๋”ํ•œ ๋‹ค์Œ์— ๊ทธ ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ธฐ๋Šฅ์„
 * ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•˜์ง€๋งŒ ์œ ์šฉํ•˜๊ฒŒ
 * ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
 */
function add(a: number, b: number): number {
  return a + b;
}

// โœ… ์ข‹์€ ์˜ˆ: ๋ช…ํ™•ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•จ
/**
 * ๋‘ ์ˆซ์ž๋ฅผ ๋”ํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @param a - ์ฒซ ๋ฒˆ์งธ ์ˆซ์ž
 * @param b - ๋‘ ๋ฒˆ์งธ ์ˆซ์ž
 * @returns ๋‘ ์ˆซ์ž์˜ ํ•ฉ
 */
function add(a: number, b: number): number {
  return a + b;
}

๐Ÿ“š ์˜ˆ์ œ๋Š” ํ•„์ˆ˜!

์ฝ”๋“œ ์˜ˆ์ œ๋Š” ์ •๋ง ์ค‘์š”ํ•ด. ํŠนํžˆ ๋ณต์žกํ•œ API๋‚˜ ํ•จ์ˆ˜์˜ ๊ฒฝ์šฐ, ์˜ˆ์ œ ํ•˜๋‚˜๊ฐ€ ๊ธด ์„ค๋ช…๋ณด๋‹ค ํ›จ์”ฌ ํšจ๊ณผ์ ์ด์•ผ.
๐Ÿ’ก ์ข‹์€ ์˜ˆ์ œ ์ž‘์„ฑ ํŒ: 1๏ธโƒฃ ์‹ค์ œ๋กœ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด
2๏ธโƒฃ ์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด
3๏ธโƒฃ ์—ฃ์ง€ ์ผ€์ด์Šค๋„ ์˜ˆ์ œ๋กœ ๋ณด์—ฌ์ค˜
4๏ธโƒฃ ์˜ˆ์ œ์— ์ฃผ์„์„ ๋‹ฌ์•„์„œ ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•ด
5๏ธโƒฃ ์—ฌ๋Ÿฌ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๋ณด์—ฌ์ฃผ๋ ค๋ฉด @example์„ ์—ฌ๋Ÿฌ ๋ฒˆ ์‚ฌ์šฉํ•ด

๐Ÿ”„ ๋ฌธ์„œ๋ฅผ ์ตœ์‹  ์ƒํƒœ๋กœ ์œ ์ง€

์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ๋ฌธ์„œ๋„ ํ•จ๊ป˜ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ด. ์˜ค๋ž˜๋œ ๋ฌธ์„œ๋Š” ์—†๋Š” ๊ฒƒ๋ณด๋‹ค ๋‚˜์  ์ˆ˜ ์žˆ์–ด!
// ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ฒดํฌ๋ฆฌ์ŠคํŠธ์— ๋ฌธ์„œ ์—…๋ฐ์ดํŠธ ํฌํ•จ
// .github/pull_request_template.md

## ์ฒดํฌ๋ฆฌ์ŠคํŠธ
- [ ] ์ฝ”๋“œ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ๋ฌธ์„œ์— ๋ฐ˜์˜๋˜์—ˆ๋‚˜์š”?
- [ ] ์ƒˆ๋กœ์šด ๊ณต๊ฐœ API์— TSDoc ์ฃผ์„์ด ์ถ”๊ฐ€๋˜์—ˆ๋‚˜์š”?
- [ ] ์˜ˆ์ œ ์ฝ”๋“œ๊ฐ€ ์ตœ์‹  API์™€ ํ˜ธํ™˜๋˜๋‚˜์š”?
- [ ] ๋ณ€๊ฒฝ๋œ ํŒŒ๋ผ๋ฏธํ„ฐ๋‚˜ ๋ฐ˜ํ™˜๊ฐ’์ด ๋ฌธ์„œํ™”๋˜์—ˆ๋‚˜์š”?

โš ๏ธ ์ค‘์š”ํ•œ ์ •๋ณด๋Š” ๊ฐ•์กฐํ•˜๊ธฐ

ํŠน๋ณ„ํžˆ ์ฃผ์˜ํ•ด์•ผ ํ•  ์‚ฌํ•ญ์ด๋‚˜ ์ค‘์š”ํ•œ ์ •๋ณด๋Š” ๋ˆˆ์— ๋„๊ฒŒ ํ‘œ์‹œํ•ด์•ผ ํ•ด.
/**
 * ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @remarks
 * โš ๏ธ **์ค‘์š”**: ์ด ํ•จ์ˆ˜๋Š” ๋ฉ”๋ชจ๋ฆฌ์— ์ „์ฒด ํŒŒ์ผ์„ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.
 * 10MB ์ด์ƒ์˜ ํŒŒ์ผ์€ `processLargeFileStream()`์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
 * 
 * ๐Ÿ’ก **ํŒ**: ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋ฉด `processFileParallel()`์„ ๊ณ ๋ คํ•˜์„ธ์š”.
 * 
 * @param filePath - ์ฒ˜๋ฆฌํ•  ํŒŒ์ผ ๊ฒฝ๋กœ
 * @returns ์ฒ˜๋ฆฌ ๊ฒฐ๊ณผ
 * 
 * @see {@link processLargeFileStream} - ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ์šฉ ์ŠคํŠธ๋ฆฌ๋ฐ ๋ฒ„์ „
 */
function processFile(filePath: string): ProcessResult {
  // ๊ตฌํ˜„ ์ฝ”๋“œ
}

๐Ÿ”— ๊ด€๋ จ ํ•ญ๋ชฉ ์—ฐ๊ฒฐํ•˜๊ธฐ

API ๋ฌธ์„œ๋Š” ๋„คํŠธ์›Œํฌ์ฒ˜๋Ÿผ ์„œ๋กœ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์–ด์•ผ ํ•ด. @see ํƒœ๊ทธ๋ฅผ ์ ๊ทน ํ™œ์šฉํ•ด์„œ ๊ด€๋ จ ํ•จ์ˆ˜๋‚˜ ํด๋ž˜์Šค๋ฅผ ์—ฐ๊ฒฐํ•ด์ค˜.
/**
 * ์‚ฌ์šฉ์ž๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @see {@link updateUser} - ์‚ฌ์šฉ์ž ์ •๋ณด ์ˆ˜์ •
 * @see {@link deleteUser} - ์‚ฌ์šฉ์ž ์‚ญ์ œ
 * @see {@link getUserById} - ์‚ฌ์šฉ์ž ์กฐํšŒ
 * @see {@link User} - ์‚ฌ์šฉ์ž ์ธํ„ฐํŽ˜์ด์Šค
 */
function createUser(data: CreateUserDto): Promise<User> {
  // ๊ตฌํ˜„ ์ฝ”๋“œ
}

๐ŸŽจ ์ฝ”๋“œ ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ ํ†ตํ•ฉ

ESLint ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฌธ์„œํ™” ๊ทœ์น™์„ ์ž๋™์œผ๋กœ ๊ฒ€์‚ฌํ•  ์ˆ˜ ์žˆ์–ด!
# eslint-plugin-tsdoc ์„ค์น˜
npm install --save-dev eslint-plugin-tsdoc

# .eslintrc.json ์„ค์ •
{
  "plugins": ["eslint-plugin-tsdoc"],
  "rules": {
    "tsdoc/syntax": "warn"
  }
}
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด TSDoc ๋ฌธ๋ฒ• ์˜ค๋ฅ˜๋ฅผ ์ฝ”๋“œ ์ž‘์„ฑ ์ค‘์— ๋ฐ”๋กœ ์žก์„ ์ˆ˜ ์žˆ์–ด! ๐ŸŽฏ

๐Ÿš€ CI/CD ํŒŒ์ดํ”„๋ผ์ธ์— ๋ฌธ์„œ ์ƒ์„ฑ ํ†ตํ•ฉํ•˜๊ธฐ

๋ฌธ์„œ ์ƒ์„ฑ์„ ์ž๋™ํ™”ํ•˜๋ฉด ํ•ญ์ƒ ์ตœ์‹  ๋ฌธ์„œ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์–ด. CI/CD ํŒŒ์ดํ”„๋ผ์ธ์— ํ†ตํ•ฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž! โš™๏ธ

๐Ÿ“ฆ GitHub Actions๋กœ ์ž๋™ ๋ฐฐํฌ

GitHub Actions๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ํ‘ธ์‹œ๋  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์–ด.
# .github/workflows/docs.yml
name: Generate and Deploy Documentation

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  docs:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build project
        run: npm run build
      
      - name: Generate documentation
        run: npm run docs
      
      - name: Deploy to GitHub Pages
        if: github.ref == 'refs/heads/main'
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./docs
          cname: docs.yourproject.com
์ด์ œ main ๋ธŒ๋žœ์น˜์— ํ‘ธ์‹œํ•  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ๋ฌธ์„œ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ  GitHub Pages์— ๋ฐฐํฌ๋ผ! ๐ŸŽ‰

๐Ÿ” ๋ฌธ์„œ ํ’ˆ์งˆ ๊ฒ€์‚ฌ

๋ฌธ์„œ๊ฐ€ ์ œ๋Œ€๋กœ ์ž‘์„ฑ๋˜์—ˆ๋Š”์ง€ ์ž๋™์œผ๋กœ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•ด.
# .github/workflows/docs-check.yml
name: Documentation Quality Check

on: [pull_request]

jobs:
  check-docs:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Check TSDoc syntax
        run: npm run lint
      
      - name: Generate docs (dry run)
        run: npm run docs -- --dry-run
      
      - name: Check for undocumented exports
        run: |
          npm run docs -- --validation.notDocumented
      
      - name: Comment PR with results
        uses: actions/github-script@v6
        if: failure()
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: 'โš ๏ธ ๋ฌธ์„œํ™” ๊ฒ€์‚ฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ๊ณต๊ฐœ API์— TSDoc ์ฃผ์„์„ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”!'
            })

๐Ÿ“Š ๋ฌธ์„œ ์ปค๋ฒ„๋ฆฌ์ง€ ์ธก์ •

์–ผ๋งˆ๋‚˜ ๋งŽ์€ ์ฝ”๋“œ๊ฐ€ ๋ฌธ์„œํ™”๋˜์—ˆ๋Š”์ง€ ์ธก์ •ํ•˜๋Š” ๊ฒƒ๋„ ์œ ์šฉํ•ด.
// scripts/check-docs-coverage.ts
import * as ts from 'typescript';
import * as fs from 'fs';

interface CoverageStats {
  total: number;
  documented: number;
  percentage: number;
}

function checkDocsCoverage(fileNames: string[]): CoverageStats {
  const program = ts.createProgram(fileNames, {});
  let total = 0;
  let documented = 0;

  for (const sourceFile of program.getSourceFiles()) {
    if (sourceFile.fileName.includes('node_modules')) continue;

    ts.forEachChild(sourceFile, node => {
      if (isExportedDeclaration(node)) {
        total++;
        if (hasDocComment(node)) {
          documented++;
        }
      }
    });
  }

  return {
    total,
    documented,
    percentage: total > 0 ? (documented / total) * 100 : 0
  };
}

function isExportedDeclaration(node: ts.Node): boolean {
  return (
    (ts.isFunctionDeclaration(node) ||
     ts.isClassDeclaration(node) ||
     ts.isInterfaceDeclaration(node) ||
     ts.isTypeAliasDeclaration(node)) &&
    node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)
  );
}

function hasDocComment(node: ts.Node): boolean {
  const comments = ts.getLeadingCommentRanges(
    node.getSourceFile().getFullText(),
    node.getFullStart()
  );
  return comments?.some(c => c.kind === ts.SyntaxKind.MultiLineCommentTrivia) ?? false;
}

// ์‹คํ–‰
const stats = checkDocsCoverage(['src/**/*.ts']);
console.log(`๋ฌธ์„œํ™” ์ปค๋ฒ„๋ฆฌ์ง€: ${stats.percentage.toFixed(2)}%`);
console.log(`(${stats.documented}/${stats.total} ํ•ญ๋ชฉ)`);

if (stats.percentage < 80) {
  console.error('โŒ ๋ฌธ์„œํ™” ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ 80% ๋ฏธ๋งŒ์ž…๋‹ˆ๋‹ค!');
  process.exit(1);
}

๐ŸŒ ๋‹ค์–‘ํ•œ ํ”Œ๋žซํผ์— ๋ฐฐํฌ

์ƒ์„ฑ๋œ ๋ฌธ์„œ๋Š” ์—ฌ๋Ÿฌ ํ”Œ๋žซํผ์— ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์–ด:
๋ฌธ์„œ ๋ฐฐํฌ ์˜ต์…˜ ๐Ÿ“„ GitHub Pages: ๋ฌด๋ฃŒ์ด๊ณ  ์„ค์ •์ด ๊ฐ„๋‹จํ•ด
๐ŸŒ Netlify/Vercel: ์ž๋™ ๋นŒ๋“œ์™€ ํ”„๋ฆฌ๋ทฐ ๊ธฐ๋Šฅ
๐Ÿ“š Read the Docs: ๋ฒ„์ „ ๊ด€๋ฆฌ์™€ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์ด ๊ฐ•๋ ฅํ•ด
๐Ÿ”ท Azure Static Web Apps: ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ๊ธ‰ ํ˜ธ์ŠคํŒ…
โ˜๏ธ AWS S3 + CloudFront: ํ™•์žฅ์„ฑ์ด ๋›ฐ์–ด๋‚˜
์ฝ”๋“œ ์ž‘์„ฑ ๐Ÿ’ป TSDoc ์ฃผ์„ ๐Ÿ“ CI/CD โš™๏ธ ๋ฌธ์„œ ๋ฐฐํฌ ๐ŸŒ ์ž๋™ํ™”์˜ ์žฅ์  โœ“ ํ•ญ์ƒ ์ตœ์‹  ๋ฌธ์„œ ์œ ์ง€ โœ“ ์ˆ˜๋™ ์ž‘์—… ์‹œ๊ฐ„ ์ ˆ์•ฝ โœ“ ์ผ๊ด€๋œ ํ’ˆ์งˆ ๋ณด์žฅ โœ“ ํŒ€ ํ˜‘์—… ํšจ์œจ ํ–ฅ์ƒ ๋ฐฐํฌ ํ”Œ๋žซํผ โ€ข GitHub Pages โ€ข Netlify / Vercel โ€ข Read the Docs โ€ข AWS / Azure ๋ฌธ์„œ ์ž๋™ํ™” ์›Œํฌํ”Œ๋กœ์šฐ

๐ŸŽ“ ์‹ค๋ฌด์—์„œ ๋งˆ์ฃผ์น˜๋Š” ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ์ฑ…

์ด๋ก ์€ ์™„๋ฒฝํ•ด ๋ณด์ด์ง€๋งŒ, ์‹ค์ œ๋กœ ์ ์šฉํ•˜๋‹ค ๋ณด๋ฉด ์—ฌ๋Ÿฌ ๋ฌธ์ œ์— ๋ถ€๋”ชํžˆ๊ฒŒ ๋ผ. ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ๊ณต์œ ํ• ๊ฒŒ! ๐Ÿ”ง

โ“ ๋ฌธ์ œ 1: ๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ ๋ฌธ์„œํ™”

์ด๋ฏธ ์ž‘์„ฑ๋œ ๋Œ€๋Ÿ‰์˜ ์ฝ”๋“œ์— ๋ฌธ์„œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฑด ์ •๋ง ํž˜๋“  ์ผ์ด์•ผ. ํ•œ ๋ฒˆ์— ๋‹ค ํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์ง€์ณ๋ฒ„๋ฆฌ๊ธฐ ์‰ฌ์›Œ.
ํ•ด๊ฒฐ ์ „๋žต: 1๏ธโƒฃ ์šฐ์„ ์ˆœ์œ„ ์ •ํ•˜๊ธฐ: ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ๊ณต๊ฐœ API๋ถ€ํ„ฐ ์‹œ์ž‘
2๏ธโƒฃ ์ ์ง„์  ๊ฐœ์„ : ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ๋งˆ๋‹ค ๋ฌธ์„œ๋„ ํ•จ๊ป˜ ์ถ”๊ฐ€
3๏ธโƒฃ ํŒ€ ๋ถ„๋‹ด: ๊ฐ์ž ๋‹ด๋‹น ๋ชจ๋“ˆ์˜ ๋ฌธ์„œํ™” ์ฑ…์ž„
4๏ธโƒฃ ์ž๋™ํ™” ๋„๊ตฌ: AI ๋„๊ตฌ๋กœ ์ดˆ์•ˆ ์ƒ์„ฑ ํ›„ ๊ฒ€ํ† 
// ์šฐ์„ ์ˆœ์œ„ ๊ธฐ๋ฐ˜ ๋ฌธ์„œํ™” ์ฒดํฌ๋ฆฌ์ŠคํŠธ
// docs-priority.md

## Phase 1: ํ•ต์‹ฌ ๊ณต๊ฐœ API (1์ฃผ์ฐจ)
- [ ] User ๊ด€๋ จ ํ•จ์ˆ˜๋“ค
- [ ] Authentication ๋ชจ๋“ˆ
- [ ] ์ฃผ์š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜

## Phase 2: ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋‚ด๋ถ€ API (2์ฃผ์ฐจ)
- [ ] Database ํ—ฌํผ
- [ ] Validation ํ•จ์ˆ˜๋“ค
- [ ] Error ํ•ธ๋“ค๋ง

## Phase 3: ๋‚˜๋จธ์ง€ ์ฝ”๋“œ (3-4์ฃผ์ฐจ)
- [ ] ํ…Œ์ŠคํŠธ ์œ ํ‹ธ๋ฆฌํ‹ฐ
- [ ] ์„ค์ • ๊ด€๋ จ ์ฝ”๋“œ
- [ ] ๊ธฐํƒ€ ํ—ฌํผ ํ•จ์ˆ˜๋“ค

โ“ ๋ฌธ์ œ 2: ๋ฌธ์„œ์™€ ์ฝ”๋“œ์˜ ๋ถˆ์ผ์น˜

์ฝ”๋“œ๋Š” ๋ณ€๊ฒฝํ–ˆ๋Š”๋ฐ ๋ฌธ์„œ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๊ฑธ ๊นœ๋นกํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„. ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ๋ฌธ์„œ๊ฐ€ ์˜คํžˆ๋ ค ํ˜ผ๋ž€์„ ์ค„ ์ˆ˜ ์žˆ์–ด.
ํ•ด๊ฒฐ ์ „๋žต: ๐Ÿ”’ PR ์ฒดํฌ๋ฆฌ์ŠคํŠธ: ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์‹œ ๋ฌธ์„œ ์—…๋ฐ์ดํŠธ ํ™•์ธ
๐Ÿค– ์ž๋™ ๊ฒ€์ฆ: CI์—์„œ ๋ฌธ์„œ ์ƒ์„ฑ ์‹คํŒจ ์‹œ ๋นŒ๋“œ ์‹คํŒจ
๐Ÿ“‹ ๋ฆฐํ„ฐ ๊ทœ์น™: ๊ณต๊ฐœ API๋Š” ๋ฐ˜๋“œ์‹œ ๋ฌธ์„œํ™” ๊ฐ•์ œ
๐Ÿ‘ฅ ํŽ˜์–ด ํ”„๋กœ๊ทธ๋ž˜๋ฐ: ์ฝ”๋“œ์™€ ๋ฌธ์„œ๋ฅผ ํ•จ๊ป˜ ์ž‘์„ฑ
// .eslintrc.json - ๋ฌธ์„œํ™” ๊ฐ•์ œ
{
  "rules": {
    "jsdoc/require-jsdoc": ["error", {
      "require": {
        "FunctionDeclaration": true,
        "MethodDefinition": true,
        "ClassDeclaration": true,
        "ArrowFunctionExpression": false,
        "FunctionExpression": false
      },
      "publicOnly": true
    }],
    "jsdoc/require-param": "error",
    "jsdoc/require-returns": "error",
    "jsdoc/require-description": "warn"
  }
}

โ“ ๋ฌธ์ œ 3: ๊ณผ๋„ํ•œ ๋ฌธ์„œํ™”

๋ชจ๋“  ๊ฑธ ๋ฌธ์„œํ™”ํ•˜๋ ค๋‹ค ๋ณด๋ฉด ์˜คํžˆ๋ ค ํ•ต์‹ฌ์ด ํ๋ ค์งˆ ์ˆ˜ ์žˆ์–ด. ๋„ˆ๋ฌด ์ž๋ช…ํ•œ ๊ฒƒ๊นŒ์ง€ ์„ค๋ช…ํ•˜๋ฉด ๋ฌธ์„œ๊ฐ€ ์ง€์ €๋ถ„ํ•ด์ ธ.
โš ๏ธ ๊ณผ๋„ํ•œ ๋ฌธ์„œํ™” ์˜ˆ์‹œ:

/**
 * ์ˆซ์ž๋ฅผ 1 ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.
 * @param n - ์ฆ๊ฐ€์‹œํ‚ฌ ์ˆซ์ž
 * @returns 1์ด ์ฆ๊ฐ€๋œ ์ˆซ์ž
 * @example
 * increment(5) // 6์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค
 */
function increment(n: number): number {
  return n + 1;
}
์ด๋Ÿฐ ๊ฑด ๋„ˆ๋ฌด ์ž๋ช…ํ•ด์„œ ๋ฌธ์„œ๊ฐ€ ๋ถˆํ•„์š”ํ•ด!
์ ์ ˆํ•œ ๋ฌธ์„œํ™” ๊ธฐ์ค€:
  • โœ… ๊ณต๊ฐœ API๋Š” ๋ฐ˜๋“œ์‹œ ๋ฌธ์„œํ™”
  • โœ… ๋ณต์žกํ•œ ๋กœ์ง์ด๋‚˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ ์ƒ์„ธํžˆ ์„ค๋ช…
  • โœ… ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋™์ž‘์ด๋‚˜ ์ œ์•ฝ์‚ฌํ•ญ์€ ๋ช…์‹œ
  • โŒ ์ฝ”๋“œ๋งŒ ๋ด๋„ ๋ช…ํ™•ํ•œ ๊ฒƒ์€ ์ƒ๋žต ๊ฐ€๋Šฅ
  • โŒ ๋‚ด๋ถ€ ํ—ฌํผ ํ•จ์ˆ˜๋Š” ๊ฐ„๋‹จํžˆ๋งŒ

โ“ ๋ฌธ์ œ 4: ๋‹ค๊ตญ์–ด ๋ฌธ์„œ ์ง€์›

๊ธ€๋กœ๋ฒŒ ํ”„๋กœ์ ํŠธ๋ผ๋ฉด ์—ฌ๋Ÿฌ ์–ธ์–ด๋กœ ๋ฌธ์„œ๋ฅผ ์ œ๊ณตํ•ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์–ด.
/**
 * Creates a new user account.
 * ์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž ๊ณ„์ •์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
 * 
 * @param userData - User information / ์‚ฌ์šฉ์ž ์ •๋ณด
 * @param userData.email - Email address / ์ด๋ฉ”์ผ ์ฃผ์†Œ
 * @param userData.name - User name / ์‚ฌ์šฉ์ž ์ด๋ฆ„
 * @returns Created user object / ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž ๊ฐ์ฒด
 * 
 * @example
 * ```typescript
 * const user = await createUser({
 *   email: 'user@example.com',
 *   name: 'John Doe'
 * });
 * ```
 */
async function createUser(userData: CreateUserDto): Promise<User> {
  // Implementation
}
ํ•˜์ง€๋งŒ ์ด ๋ฐฉ๋ฒ•์€ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ค์›Œ. ๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์€ ๋ฌธ์„œ ์ƒ์„ฑ ๋„๊ตฌ์—์„œ ๋‹ค๊ตญ์–ด๋ฅผ ์ง€์›ํ•˜๋Š” ๊ฑฐ์•ผ.

โ“ ๋ฌธ์ œ 5: ์„ฑ๋Šฅ ๋ฌธ์„œํ™”

ํ•จ์ˆ˜์˜ ์‹œ๊ฐ„ ๋ณต์žก๋„๋‚˜ ๊ณต๊ฐ„ ๋ณต์žก๋„๋„ ์ค‘์š”ํ•œ ์ •๋ณด์•ผ. ํŠนํžˆ ์„ฑ๋Šฅ์ด ์ค‘์š”ํ•œ ํ•จ์ˆ˜๋ผ๋ฉด ๋ฐ˜๋“œ์‹œ ๋ช…์‹œํ•ด์•ผ ํ•ด.
/**
 * ๋ฐฐ์—ด์—์„œ ํŠน์ • ๊ฐ’์„ ์ฐพ์Šต๋‹ˆ๋‹ค.
 * 
 * @remarks
 * **์‹œ๊ฐ„ ๋ณต์žก๋„**: O(n) - ์„ ํ˜• ํƒ์ƒ‰<br>
 * **๊ณต๊ฐ„ ๋ณต์žก๋„**: O(1) - ์ถ”๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ์—†์Œ
 * 
 * ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ์—์„œ ๋น ๋ฅธ ๊ฒ€์ƒ‰์ด ํ•„์š”ํ•˜๋ฉด
 * {@link binarySearch}๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š” (O(log n)).
 * 
 * @param array - ๊ฒ€์ƒ‰ํ•  ๋ฐฐ์—ด
 * @param target - ์ฐพ์„ ๊ฐ’
 * @returns ๊ฐ’์˜ ์ธ๋ฑ์Šค, ์—†์œผ๋ฉด -1
 */
function linearSearch<T>(array: T[], target: T): number {
  for (let i = 0; i < array.length; i++) {
    if (array[i] === target) return i;
  }
  return -1;
}
์„ฑ๋Šฅ ํŠน์„ฑ์„ ๋ฌธ์„œํ™”ํ•˜๋ฉด ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๋“ค์ด ์ ์ ˆํ•œ ํ•จ์ˆ˜๋ฅผ ์„ ํƒํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ผ! โšก

๐ŸŽ ๋ณด๋„ˆ์Šค: ์œ ์šฉํ•œ ๋„๊ตฌ์™€ ๋ฆฌ์†Œ์Šค

๋ฌธ์„œํ™”๋ฅผ ๋” ์‰ฝ๊ณ  ํšจ์œจ์ ์œผ๋กœ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๋„๊ตฌ๋“ค์„ ์†Œ๊ฐœํ• ๊ฒŒ! ๐Ÿ› ๏ธ

๐Ÿ”Œ VS Code ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ

ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ ๊ธฐ๋Šฅ ์ถ”์ฒœ๋„
Document This ์ž๋™์œผ๋กœ TSDoc ํ…œํ”Œ๋ฆฟ ์ƒ์„ฑ โญโญโญโญโญ
Better Comments ์ฃผ์„์„ ์ƒ‰์ƒ์œผ๋กœ ๊ตฌ๋ถ„ โญโญโญโญ
TypeDoc Generator VS Code์—์„œ ๋ฐ”๋กœ ๋ฌธ์„œ ์ƒ์„ฑ โญโญโญโญ
TSDoc Comment TSDoc ๋ฌธ๋ฒ• ํ•˜์ด๋ผ์ดํŒ… โญโญโญโญโญ

๐Ÿ“š ํ•™์Šต ๋ฆฌ์†Œ์Šค

์ถ”์ฒœ ํ•™์Šต ์ž๋ฃŒ ๐Ÿ“– TSDoc ๊ณต์‹ ๋ฌธ์„œ: https://tsdoc.org
๐Ÿ“˜ TypeDoc ๊ฐ€์ด๋“œ: https://typedoc.org/guides/overview
๐ŸŽ“ Microsoft API Extractor: https://api-extractor.com
๐Ÿ’ก JSDoc ์ฐธ๊ณ : https://jsdoc.app (TSDoc์˜ ๊ธฐ๋ฐ˜)

๐Ÿค– AI ๋„๊ตฌ ํ™œ์šฉ

์ตœ๊ทผ์—๋Š” AI๊ฐ€ ๋ฌธ์„œ ์ž‘์„ฑ์„ ๋„์™€์ค„ ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ์™„์ „ํžˆ ์˜์กดํ•˜๋ฉด ์•ˆ ๋˜๊ณ , ๋ฐ˜๋“œ์‹œ ๊ฒ€ํ† ํ•˜๊ณ  ์ˆ˜์ •ํ•ด์•ผ ํ•ด!
// GitHub Copilot์„ ํ™œ์šฉํ•œ ๋ฌธ์„œ ์ž‘์„ฑ
// ํ•จ์ˆ˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ์ž‘์„ฑํ•˜๊ณ  /** ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด
// Copilot์ด ์ž๋™์œผ๋กœ ๋ฌธ์„œ๋ฅผ ์ œ์•ˆํ•ด์ค˜

function processUserData(
  userId: string,
  options: ProcessOptions
): Promise<ProcessResult> {
  // Copilot์ด ์œ„ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ๋ณด๊ณ 
  // ์ ์ ˆํ•œ TSDoc ์ฃผ์„์„ ์ œ์•ˆํ•ด์ค˜
}

๐ŸŽจ ๋ฌธ์„œ ํ…Œ๋งˆ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

TypeDoc์˜ ๊ธฐ๋ณธ ํ…Œ๋งˆ๋„ ์ข‹์ง€๋งŒ, ํ”„๋กœ์ ํŠธ์— ๋งž๊ฒŒ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ์–ด.
// typedoc.json
{
  "theme": "default",
  "customCss": "./docs-theme/custom.css",
  "customJs": "./docs-theme/custom.js",
  "favicon": "./docs-theme/favicon.ico",
  "logo": "./docs-theme/logo.png",
  "hideGenerator": true,
  "footerDate": true
}
์ปค์Šคํ…€ CSS๋กœ ๋ธŒ๋žœ๋“œ ์ปฌ๋Ÿฌ๋ฅผ ์ ์šฉํ•˜๋ฉด ๋” ์ „๋ฌธ์ ์œผ๋กœ ๋ณด์—ฌ! ๐ŸŽจ

๐Ÿ“Š ๋ฌธ์„œ ๋ถ„์„ ๋„๊ตฌ

๋ฌธ์„œ์˜ ํ’ˆ์งˆ์„ ์ธก์ •ํ•˜๊ณ  ๊ฐœ์„ ์ ์„ ์ฐพ์•„์ฃผ๋Š” ๋„๊ตฌ๋“ค๋„ ์žˆ์–ด.
# documentation.js - ๋ฌธ์„œ ํ’ˆ์งˆ ๋ถ„์„
npm install -g documentation

# ๋ฌธ์„œ ์ปค๋ฒ„๋ฆฌ์ง€ ๋ฆฌํฌํŠธ ์ƒ์„ฑ
documentation lint src/**/*.ts

# ๋ฌธ์„œ ํ’ˆ์งˆ ์ ์ˆ˜ ํ™•์ธ
documentation coverage src/**/*.ts

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์–ด. ๐ŸŽ‰ ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ๋ฌธ์„œํ™”์™€ TSDoc์— ๋Œ€ํ•ด ๋งŽ์€ ๊ฑธ ๋ฐฐ์› ์ง€? ๋ฌธ์„œํ™”๋Š” ์ฒ˜์Œ์—๋Š” ๊ท€์ฐฎ๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด์„œ ๊ทธ ๊ฐ€์น˜๋ฅผ ํ™•์‹คํžˆ ๋А๋ผ๊ฒŒ ๋  ๊ฑฐ์•ผ. ํŠนํžˆ ํŒ€ ํ”„๋กœ์ ํŠธ๋‚˜ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ •๋ง ์ค‘์š”ํ•ด!
ํ•ต์‹ฌ ์š”์•ฝ ์ •๋ฆฌ ๐Ÿ“ TSDoc์€ ํ‘œ์ค€: ์ผ๊ด€๋œ ๋ฌธ์„œํ™”๋ฅผ ์œ„ํ•œ ํ‘œ์ค€ ํ˜•์‹
๐Ÿท๏ธ ํƒœ๊ทธ ํ™œ์šฉ: @param, @returns, @example ๋“ฑ์„ ์ ๊ทน ํ™œ์šฉ
๐Ÿ› ๏ธ ์ž๋™ํ™”: TypeDoc์ด๋‚˜ API Extractor๋กœ ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑ
โš™๏ธ CI/CD ํ†ตํ•ฉ: ๋ฌธ์„œ ์ƒ์„ฑ์„ ์ž๋™ํ™” ํŒŒ์ดํ”„๋ผ์ธ์— ํฌํ•จ
โœจ ํ’ˆ์งˆ ์œ ์ง€: ๋ฆฐํ„ฐ์™€ ๊ฒ€์ฆ ๋„๊ตฌ๋กœ ๋ฌธ์„œ ํ’ˆ์งˆ ๊ด€๋ฆฌ
๐ŸŽฏ ์‹ค์šฉ์„ฑ: ๊ณผ๋„ํ•˜์ง€ ์•Š๊ฒŒ, ํ•„์š”ํ•œ ๋งŒํผ๋งŒ ๋ฌธ์„œํ™”
์ข‹์€ ๋ฌธ์„œ๋Š” ์ฝ”๋“œ์˜ ๊ฐ€์น˜๋ฅผ ๋ฐฐ๊ฐ€์‹œ์ผœ. ๋ฏธ๋ž˜์˜ ๋‹น์‹ ๊ณผ ํŒ€์›๋“ค์„ ์œ„ํ•ด, ๊ทธ๋ฆฌ๊ณ  ์˜คํ”ˆ์†Œ์Šค ์ปค๋ฎค๋‹ˆํ‹ฐ๋ฅผ ์œ„ํ•ด ์ข‹์€ ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์Šต๊ด€์„ ๋“ค์ด์ž! ๐Ÿ’ช ๋ฌธ์„œํ™”๋Š” ๋‹จ์ˆœํžˆ ์ฃผ์„์„ ๋‹ค๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์ด์•ผ๊ธฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ์˜ˆ์ˆ ์ด์•ผ. ๋‹น์‹ ์˜ ์ฝ”๋“œ๊ฐ€ ๋” ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ์ดํ•ด๋ฐ›๊ณ  ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋„๋ก, ์˜ค๋Š˜๋ถ€ํ„ฐ TSDoc์„ ํ™œ์šฉํ•ด๋ณด๋Š” ๊ฑด ์–ด๋•Œ? ํ–‰๋ณตํ•œ ์ฝ”๋”ฉ, ๊ทธ๋ฆฌ๊ณ  ๋ฉ‹์ง„ ๋ฌธ์„œํ™” ๋˜๊ธธ! ๐Ÿš€โœจ
๐Ÿ’ก ๋‹ค์Œ ๋‹จ๊ณ„: 1. ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์— TypeDoc ์„ค์น˜ํ•˜๊ธฐ
2. ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ณต๊ฐœ API 3๊ฐœ ๋ฌธ์„œํ™”ํ•˜๊ธฐ
3. CI/CD์— ๋ฌธ์„œ ์ƒ์„ฑ ์ถ”๊ฐ€ํ•˜๊ธฐ
4. ํŒ€์›๋“ค๊ณผ ๋ฌธ์„œํ™” ๊ฐ€์ด๋“œ๋ผ์ธ ๋งŒ๋“ค๊ธฐ
5. ์ •๊ธฐ์ ์œผ๋กœ ๋ฌธ์„œ ํ’ˆ์งˆ ๋ฆฌ๋ทฐํ•˜๊ธฐ
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.