์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ Go์–ธ์–ด ๋ฌธ์„œํ™”์™€ godoc์œผ๋กœ API ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑํ•˜๊ธฐ

๐Ÿš€ Go์–ธ์–ด ๋ฌธ์„œํ™”์™€ godoc์œผ๋กœ API ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑํ•˜๊ธฐ

์นœ๊ตฌ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ๋ฐฐ์šฐ๋Š” Go ๋ฌธ์„œํ™”์˜ ๋ชจ๋“  ๊ฒƒ
Go Code package main func Hello() // comments godoc Processing... Documentation API Docs

์•ˆ๋…•! ๐Ÿ‘‹ ์˜ค๋Š˜์€ Go ์–ธ์–ด๋กœ ๊ฐœ๋ฐœํ•  ๋•Œ ์ •๋ง ์ค‘์š”ํ•œ๋ฐ ๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ๊ฐ„๊ณผํ•˜๋Š” ๋ฌธ์„œํ™”์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณผ๊นŒ ํ•ด. ํŠนํžˆ godoc์ด๋ผ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ•ด์„œ API ๋ฌธ์„œ๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์นœ๊ตฌ์—๊ฒŒ ์„ค๋ช…ํ•˜๋“ฏ ์‰ฝ๊ฒŒ ํ’€์–ด๋ณผ๊ฒŒ!

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


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

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

๐Ÿ“Œ ๋ฌธ์„œํ™”๊ฐ€ ํ•„์š”ํ•œ ์ด์œ  1. ๋ฏธ๋ž˜์˜ ๋‚˜๋ฅผ ์œ„ํ•ด - 3๊ฐœ์›”๋งŒ ์ง€๋‚˜๋„ ๋‚ด ์ฝ”๋“œ๊ฐ€ ๋‚ฏ์„ค์–ด์ ธ
2. ํŒ€์›๋“ค๊ณผ์˜ ํ˜‘์—… - ๋‹ค๋ฅธ ์‚ฌ๋žŒ์ด ๋‚ด ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜๊ณ  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ
3. ์œ ์ง€๋ณด์ˆ˜ ํšจ์œจ์„ฑ - ๋ฒ„๊ทธ ์ˆ˜์ •์ด๋‚˜ ๊ธฐ๋Šฅ ์ถ”๊ฐ€๊ฐ€ ํ›จ์”ฌ ์‰ฌ์›Œ์ ธ
4. ํ”„๋กœํŽ˜์…”๋„ํ•จ - ์˜คํ”ˆ์†Œ์Šค๋‚˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ณต๊ฐœํ•  ๋•Œ ํ•„์ˆ˜์ ์ด์•ผ
5. ์˜จ๋ณด๋”ฉ ์‹œ๊ฐ„ ๋‹จ์ถ• - ์ƒˆ๋กœ์šด ํŒ€์›์ด ๋น ๋ฅด๊ฒŒ ์ ์‘ํ•  ์ˆ˜ ์žˆ์–ด

ํŠนํžˆ ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ๊ฐœ๋ฐœ ๊ด€๋ จ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•  ๋•Œ, ์ž˜ ์ •๋ฆฌ๋œ ๋ฌธ์„œ๋Š” ๋‹น์‹ ์˜ ์ „๋ฌธ์„ฑ์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ฐ•๋ ฅํ•œ ๋ฌด๊ธฐ๊ฐ€ ๋ผ. ํด๋ผ์ด์–ธํŠธ๋‚˜ ํ˜‘์—…์ž๋“ค์ด "์ด ์‚ฌ๋žŒ์€ ํ”„๋กœ๊ตฌ๋‚˜!" ํ•˜๊ณ  ๋А๋ผ๊ฒŒ ๋˜๊ฑฐ๋“ . ๐Ÿ’ผ

๐ŸŽฏ Go์˜ ๋ฌธ์„œํ™” ์ฒ ํ•™

Go ์–ธ์–ด๋ฅผ ๋งŒ๋“  ๊ตฌ๊ธ€ ์—”์ง€๋‹ˆ์–ด๋“ค์€ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋ฌธ์„œํ™”๋ฅผ ์–ธ์–ด์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์œผ๋กœ ์„ค๊ณ„ํ–ˆ์–ด. ๊ทธ๋“ค์˜ ์ฒ ํ•™์€ ๊ฐ„๋‹จํ•ด:

๐Ÿ’ก Go์˜ ๋ฌธ์„œํ™” ์ฒ ํ•™ "๋ฌธ์„œ๋Š” ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์žˆ์–ด์•ผ ํ•˜๊ณ , ์ž‘์„ฑํ•˜๊ธฐ ์‰ฌ์›Œ์•ผ ํ•˜๋ฉฐ, ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค."

์ด ์ฒ ํ•™ ๋•๋ถ„์— Go์—์„œ๋Š” ๋ณ„๋„์˜ ๋ณต์žกํ•œ ๋ฌธ์„œํ™” ๋„๊ตฌ๋‚˜ ๋งˆํฌ์—… ์–ธ์–ด๋ฅผ ๋ฐฐ์šธ ํ•„์š”๊ฐ€ ์—†์–ด. ๊ทธ๋ƒฅ ํ‰๋ฒ”ํ•œ ์ฃผ์„์„ ์ž‘์„ฑํ•˜๋ฉด ๋ผ! ๐Ÿ˜Š

Go ๋ฌธ์„œํ™”์˜ ํ•ต์‹ฌ ์›์น™

Go์˜ ๋ฌธ์„œํ™”๋Š” ๋ช‡ ๊ฐ€์ง€ ๊ฐ„๋‹จํ•œ ์›์น™์„ ๋”ฐ๋ผ:

1. ์„ ์–ธ ๋ฐ”๋กœ ์œ„์— ์ฃผ์„ ์ž‘์„ฑ
ํ•จ์ˆ˜, ํƒ€์ž…, ๋ณ€์ˆ˜, ์ƒ์ˆ˜ ๋“ฑ์„ ์„ ์–ธํ•˜๊ธฐ ๋ฐ”๋กœ ์ „์— ์ฃผ์„์„ ๋‹ฌ๋ฉด ๊ทธ๊ฒŒ ๋ฐ”๋กœ ๋ฌธ์„œ๊ฐ€ ๋ผ. ๋งˆ์น˜ ํฌ์ŠคํŠธ์ž‡์„ ๋ถ™์ด๋“ฏ์ด ๋ง์ด์•ผ! ๐Ÿ“

2. ์ฒซ ๋ฌธ์žฅ์ด ์š”์•ฝ
์ฃผ์„์˜ ์ฒซ ๋ฌธ์žฅ์€ ์„ ์–ธํ•˜๋Š” ๋Œ€์ƒ์˜ ์ด๋ฆ„์œผ๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ด. ์ด๊ฒŒ ๋ฌธ์„œ์˜ ์š”์•ฝ์ด ๋˜๊ฑฐ๋“ .

3. ์™„์ „ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์ž‘์„ฑ
๋‹จํŽธ์ ์ธ ํ‚ค์›Œ๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ์™„์ „ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•ด. ๋งˆ์นจํ‘œ๋„ ์žŠ์ง€ ๋ง๊ณ !

4. ๊ฐ„๊ฒฐํ•˜๊ณ  ๋ช…ํ™•ํ•˜๊ฒŒ
์žฅํ™ฉํ•œ ์„ค๋ช…๋ณด๋‹ค๋Š” ํ•ต์‹ฌ์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ „๋‹ฌํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด.

๐Ÿ› ๏ธ godoc ์†Œ๊ฐœ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ godoc์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž! godoc์€ Go ์ฝ”๋“œ์—์„œ ๋ฌธ์„œ๋ฅผ ์ถ”์ถœํ•˜๊ณ  ํฌ๋งทํŒ…ํ•ด์„œ ๋ณด์—ฌ์ฃผ๋Š” ๋„๊ตฌ์•ผ. ๐Ÿ”ง

godoc์˜ ๋‘ ๊ฐ€์ง€ ๋ชจ๋“œ

godoc์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด:

๐Ÿ–ฅ๏ธ ์ปค๋งจ๋“œ๋ผ์ธ ๋ชจ๋“œ ํ„ฐ๋ฏธ๋„์—์„œ ์ง์ ‘ ๋ฌธ์„œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด. ๋น ๋ฅด๊ฒŒ ํŠน์ • ํŒจํ‚ค์ง€๋‚˜ ํ•จ์ˆ˜์˜ ๋ฌธ์„œ๋ฅผ ๋ณด๊ณ  ์‹ถ์„ ๋•Œ ์œ ์šฉํ•ด.
๐ŸŒ ์›น ์„œ๋ฒ„ ๋ชจ๋“œ ๋กœ์ปฌ ์›น ์„œ๋ฒ„๋ฅผ ๋„์›Œ์„œ ๋ธŒ๋ผ์šฐ์ €๋กœ ๋ฌธ์„œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์–ด. ํ›จ์”ฌ ๋ณด๊ธฐ ํŽธํ•˜๊ณ  ํƒ์ƒ‰ํ•˜๊ธฐ ์ข‹์ง€!

godoc ์„ค์น˜ํ•˜๊ธฐ

Go 1.13 ์ด์ „ ๋ฒ„์ „์—๋Š” godoc์ด ๊ธฐ๋ณธ์œผ๋กœ ํฌํ•จ๋˜์–ด ์žˆ์—ˆ๋Š”๋ฐ, ์ดํ›„ ๋ฒ„์ „๋ถ€ํ„ฐ๋Š” ๋ณ„๋„๋กœ ์„ค์น˜ํ•ด์•ผ ํ•ด. ๊ฑฑ์ • ๋งˆ, ์—„์ฒญ ๊ฐ„๋‹จํ•ด! ๐Ÿ˜Ž

go install golang.org/x/tools/cmd/godoc@latest

์ด ๋ช…๋ น์–ด ํ•˜๋‚˜๋ฉด ๋! ์„ค์น˜๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด godoc ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ Go 1.18 ์ดํ›„๋ถ€ํ„ฐ๋Š” go doc ๋ช…๋ น์–ด๊ฐ€ ๊ธฐ๋ณธ ์ œ๊ณต๋ผ. godoc๊ณผ go doc์€ ๋น„์Šทํ•˜์ง€๋งŒ ์•ฝ๊ฐ„ ๋‹ค๋ฅธ ๋„๊ตฌ์•ผ. go doc์€ ์ปค๋งจ๋“œ๋ผ์ธ ์ „์šฉ์ด๊ณ , godoc์€ ์›น ์„œ๋ฒ„ ๊ธฐ๋Šฅ๋„ ์ œ๊ณตํ•ด.

โœ๏ธ ํšจ๊ณผ์ ์ธ ์ฃผ์„ ์ž‘์„ฑ๋ฒ•

์ด์ œ ์‹ค์ „์œผ๋กœ ๋“ค์–ด๊ฐ€๋ณด์ž! Go์—์„œ ๋ฌธ์„œํ™”๋ฅผ ์œ„ํ•œ ์ฃผ์„์„ ์–ด๋–ป๊ฒŒ ์ž‘์„ฑํ•˜๋Š”์ง€ ๊ตฌ์ฒด์ ์œผ๋กœ ์•Œ์•„๋ณผ๊ฒŒ. ๐ŸŽจ

ํŒจํ‚ค์ง€ ์ฃผ์„

๋ชจ๋“  ํŒจํ‚ค์ง€๋Š” ํŒจํ‚ค์ง€ ์ฃผ์„์„ ๊ฐ€์ ธ์•ผ ํ•ด. ์ด๊ฑด ํŒจํ‚ค์ง€๊ฐ€ ๋ฌด์—‡์„ ํ•˜๋Š”์ง€ ์„ค๋ช…ํ•˜๋Š” ๊ฑฐ์•ผ. ๋ณดํ†ต ํŒจํ‚ค์ง€์˜ ๋ฉ”์ธ ํŒŒ์ผ(๊ด€๋ก€์ ์œผ๋กœ doc.go)์— ์ž‘์„ฑํ•ด.

// Package calculator provides basic arithmetic operations.
// It supports addition, subtraction, multiplication, and division
// with proper error handling for edge cases like division by zero.
//
// Example usage:
//
//     result, err := calculator.Add(5, 3)
//     if err != nil {
//         log.Fatal(err)
//     }
//     fmt.Println(result) // Output: 8
//
package calculator

๋ณด๋‹ค์‹œํ”ผ ํŒจํ‚ค์ง€ ์ฃผ์„์€ Package๋กœ ์‹œ์ž‘ํ•˜๊ณ , ํŒจํ‚ค์ง€๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ฐ„๋žตํ•˜๊ฒŒ ์„ค๋ช…ํ•ด. ์˜ˆ์ œ ์ฝ”๋“œ๋ฅผ ํฌํ•จํ•˜๋ฉด ๋” ์ข‹์•„! ๐Ÿ‘

ํ•จ์ˆ˜ ์ฃผ์„

ํ•จ์ˆ˜ ์ฃผ์„์€ ํ•จ์ˆ˜ ์„ ์–ธ ๋ฐ”๋กœ ์œ„์— ์ž‘์„ฑํ•˜๊ณ , ํ•จ์ˆ˜ ์ด๋ฆ„์œผ๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ด. ์ด๊ฒŒ ์ •๋ง ์ค‘์š”ํ•œ ๊ทœ์น™์ด์•ผ!

// Add returns the sum of two integers.
// It performs simple addition without overflow checking.
func Add(a, b int) int {
    return a + b
}

// Divide divides the numerator by the denominator.
// It returns an error if the denominator is zero.
func Divide(numerator, denominator float64) (float64, error) {
    if denominator == 0 {
        return 0, errors.New("division by zero")
    }
    return numerator / denominator, nil
}

ํ•จ์ˆ˜๊ฐ€ ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ์–ด๋–ค ๊ฒฝ์šฐ์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”์ง€ ๋ช…ํ™•ํžˆ ์„ค๋ช…ํ•ด์ฃผ๋Š” ๊ฒŒ ์ข‹์•„. ์‚ฌ์šฉ์ž๊ฐ€ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ์ œ๋Œ€๋กœ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ง์ด์•ผ! ๐Ÿšจ

ํƒ€์ž… ์ฃผ์„

๊ตฌ์กฐ์ฒด๋‚˜ ์ธํ„ฐํŽ˜์ด์Šค ๊ฐ™์€ ํƒ€์ž…๋„ ๋ฌธ์„œํ™”๊ฐ€ ํ•„์š”ํ•ด. ๊ฐ ํ•„๋“œ์— ๋Œ€ํ•œ ์„ค๋ช…๋„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์–ด.

// User represents a user in the system.
// It contains basic information needed for authentication and identification.
type User struct {
    // ID is the unique identifier for the user.
    ID int
    
    // Username is the user's login name.
    // It must be unique across the system.
    Username string
    
    // Email is the user's email address.
    // It's used for notifications and password recovery.
    Email string
    
    // CreatedAt is the timestamp when the user was created.
    CreatedAt time.Time
}

๊ตฌ์กฐ์ฒด์˜ ๊ฐ ํ•„๋“œ์— ์ฃผ์„์„ ๋‹ฌ๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ๊ฐ ํ•„๋“œ์˜ ๋ชฉ์ ์„ ๋ช…ํ™•ํžˆ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ ํ•„๋“œ ์ด๋ฆ„๋งŒ์œผ๋กœ๋Š” ์˜๋ฏธ๊ฐ€ ๋ถˆ๋ถ„๋ช…ํ•  ๋•Œ ์œ ์šฉํ•˜์ง€! ๐Ÿ“‹

์ƒ์ˆ˜์™€ ๋ณ€์ˆ˜ ์ฃผ์„

์ƒ์ˆ˜๋‚˜ ๋ณ€์ˆ˜๋„ ๋ฌธ์„œํ™”ํ•  ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ ๊ณต๊ฐœ(exported) ์ƒ์ˆ˜๋Š” ๋ฐ˜๋“œ์‹œ ๋ฌธ์„œํ™”ํ•ด์•ผ ํ•ด.

// MaxRetries is the maximum number of retry attempts for failed operations.
const MaxRetries = 3

// DefaultTimeout is the default timeout duration for network requests.
const DefaultTimeout = 30 * time.Second

// ErrNotFound is returned when a requested resource is not found.
var ErrNotFound = errors.New("resource not found")

์˜ˆ์ œ ์ฝ”๋“œ ์ž‘์„ฑ

Go์˜ ๋ฌธ์„œํ™”์—์„œ ์ •๋ง ๋ฉ‹์ง„ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋กœ ์˜ˆ์ œ ์ฝ”๋“œ์•ผ! ์˜ˆ์ œ๋Š” ๋ณ„๋„์˜ ํ…Œ์ŠคํŠธ ํŒŒ์ผ์— ์ž‘์„ฑํ•˜๋Š”๋ฐ, ํ•จ์ˆ˜ ์ด๋ฆ„์„ Example๋กœ ์‹œ์ž‘ํ•˜๋ฉด ๋ผ.

// calculator_test.go ํŒŒ์ผ์— ์ž‘์„ฑ

package calculator_test

import (
    "fmt"
    "yourmodule/calculator"
)

// ExampleAdd demonstrates basic addition.
func ExampleAdd() {
    result := calculator.Add(2, 3)
    fmt.Println(result)
    // Output: 5
}

// ExampleDivide demonstrates division with error handling.
func ExampleDivide() {
    result, err := calculator.Divide(10, 2)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println(result)
    // Output: 5
}

// Output: ์ฃผ์„์€ ํŠน๋ณ„ํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ€์ ธ. ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ ์‹ค์ œ ์ถœ๋ ฅ์ด ์ด ์ฃผ์„๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•ด! ์ผ์„์ด์กฐ์ง€? ๋ฌธ์„œํ™”๋„ ๋˜๊ณ  ํ…Œ์ŠคํŠธ๋„ ๋˜๊ณ ! ๐ŸŽฏ

๐Ÿ’ก ํ”„๋กœ ํŒ ์˜ˆ์ œ ํ•จ์ˆ˜ ์ด๋ฆ„์— ์–ธ๋”์Šค์ฝ”์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ํŠน์ • ๋ฉ”์„œ๋“œ๋‚˜ ์ƒํ™ฉ์— ๋Œ€ํ•œ ์˜ˆ์ œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด.
์˜ˆ: ExampleUser_Save()๋Š” User ํƒ€์ž…์˜ Save ๋ฉ”์„œ๋“œ ์˜ˆ์ œ๊ฐ€ ๋ผ.

๐Ÿš€ godoc ์‹คํ–‰ํ•˜๊ธฐ

์ž, ์ด์ œ ์ฃผ์„๋„ ์—ด์‹ฌํžˆ ๋‹ฌ์•˜์œผ๋‹ˆ ์‹ค์ œ๋กœ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•ด๋ณด์ž! ๐ŸŽ‰

์ปค๋งจ๋“œ๋ผ์ธ์—์„œ ๋ฌธ์„œ ๋ณด๊ธฐ

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์€ go doc ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฑฐ์•ผ:

# ํ˜„์žฌ ํŒจํ‚ค์ง€์˜ ๋ฌธ์„œ ๋ณด๊ธฐ
go doc

# ํŠน์ • ํŒจํ‚ค์ง€์˜ ๋ฌธ์„œ ๋ณด๊ธฐ
go doc fmt

# ํŠน์ • ํ•จ์ˆ˜์˜ ๋ฌธ์„œ ๋ณด๊ธฐ
go doc fmt.Println

# ๋” ์ž์„ธํ•œ ์ •๋ณด ๋ณด๊ธฐ
go doc -all fmt

ํ„ฐ๋ฏธ๋„์—์„œ ๋ฐ”๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ •๋ง ํŽธํ•ด! ์ฝ”๋”ฉํ•˜๋‹ค๊ฐ€ ๊ถ๊ธˆํ•œ ๊ฒŒ ์žˆ์œผ๋ฉด ๋ฐ”๋กœ๋ฐ”๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ . โšก

์›น ์„œ๋ฒ„๋กœ ๋ฌธ์„œ ๋ณด๊ธฐ

๋” ๋ฉ‹์ง„ ๋ฐฉ๋ฒ•์€ ์›น ์„œ๋ฒ„๋ฅผ ๋„์šฐ๋Š” ๊ฑฐ์•ผ. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €์—์„œ ์˜ˆ์˜๊ฒŒ ํฌ๋งท๋œ ๋ฌธ์„œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์–ด!

# godoc ์›น ์„œ๋ฒ„ ์‹œ์ž‘ (๊ธฐ๋ณธ ํฌํŠธ 6060)
godoc -http=:6060

# ๋‹ค๋ฅธ ํฌํŠธ ์‚ฌ์šฉ
godoc -http=:8080

์„œ๋ฒ„๊ฐ€ ์‹œ์ž‘๋˜๋ฉด ๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:6060์œผ๋กœ ์ ‘์†ํ•ด๋ด. ์งœ์ž”! โœจ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฌธ์„œ๋ถ€ํ„ฐ ๋‚ด๊ฐ€ ์ž‘์„ฑํ•œ ํŒจํ‚ค์ง€ ๋ฌธ์„œ๊นŒ์ง€ ๋ชจ๋‘ ๋ณผ ์ˆ˜ ์žˆ์–ด.

๐ŸŒŸ ์›น ์„œ๋ฒ„ ๋ชจ๋“œ์˜ ์žฅ์  โ€ข ๊น”๋”ํ•œ HTML ํฌ๋งท์œผ๋กœ ๋ณด๊ธฐ ํŽธํ•ด
โ€ข ํŒจํ‚ค์ง€ ๊ฐ„ ๋งํฌ๋กœ ์‰ฝ๊ฒŒ ํƒ์ƒ‰ ๊ฐ€๋Šฅ
โ€ข ์˜ˆ์ œ ์ฝ”๋“œ๊ฐ€ ํ•˜์ด๋ผ์ดํŒ…๋˜์–ด ํ‘œ์‹œ๋ผ
โ€ข ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ์ œ๊ณต (์ธ๋ฑ์Šค ์ƒ์„ฑ ํ•„์š”)
โ€ข ์†Œ์Šค ์ฝ”๋“œ๋„ ํ•จ๊ป˜ ๋ณผ ์ˆ˜ ์žˆ์–ด

๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ํ™œ์„ฑํ™”

๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์ธ๋ฑ์Šค๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•ด. ์ฒ˜์Œ์—” ์‹œ๊ฐ„์ด ์ข€ ๊ฑธ๋ฆฌ์ง€๋งŒ, ํ•œ ๋ฒˆ๋งŒ ํ•˜๋ฉด ๋ผ!

# ์ธ๋ฑ์Šค์™€ ํ•จ๊ป˜ ์„œ๋ฒ„ ์‹œ์ž‘
godoc -http=:6060 -index

# ์ธ๋ฑ์Šค ์—…๋ฐ์ดํŠธ ๊ฐ„๊ฒฉ ์„ค์ • (๊ธฐ๋ณธ 5๋ถ„)
godoc -http=:6060 -index -index_interval=10m

๐Ÿ“ฆ ์‹ค์ „ ํ”„๋กœ์ ํŠธ ๋ฌธ์„œํ™”

์ด๋ก ์€ ์ถฉ๋ถ„ํžˆ ๋ฐฐ์› ์œผ๋‹ˆ, ์ด์ œ ์‹ค์ œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋ฌธ์„œํ™”ํ•ด๋ณด์ž! ๊ฐ„๋‹จํ•œ REST API ์„œ๋ฒ„๋ฅผ ์˜ˆ๋กœ ๋“ค์–ด๋ณผ๊ฒŒ. ๐Ÿ—๏ธ

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

myapi/
โ”œโ”€โ”€ doc.go
โ”œโ”€โ”€ server.go
โ”œโ”€โ”€ handlers/
โ”‚   โ”œโ”€โ”€ doc.go
โ”‚   โ”œโ”€โ”€ user.go
โ”‚   โ””โ”€โ”€ user_test.go
โ”œโ”€โ”€ models/
โ”‚   โ”œโ”€โ”€ doc.go
โ”‚   โ””โ”€โ”€ user.go
โ””โ”€โ”€ utils/
    โ”œโ”€โ”€ doc.go
    โ””โ”€โ”€ validator.go

๋ฉ”์ธ ํŒจํ‚ค์ง€ ๋ฌธ์„œ (doc.go)

// Package myapi provides a RESTful API server for user management.
//
// This package implements a complete HTTP server with endpoints for
// creating, reading, updating, and deleting user records. It includes
// input validation, error handling, and JSON serialization.
//
// # Architecture
//
// The API is organized into three main components:
//   - handlers: HTTP request handlers
//   - models: Data structures and business logic
//   - utils: Helper functions and utilities
//
// # Quick Start
//
// To start the server:
//
//     server := myapi.NewServer(":8080")
//     if err := server.Start(); err != nil {
//         log.Fatal(err)
//     }
//
// # API Endpoints
//
//     POST   /users      - Create a new user
//     GET    /users/:id  - Get user by ID
//     PUT    /users/:id  - Update user
//     DELETE /users/:id  - Delete user
//
// For more detailed information, see the handlers package.
package myapi

ํŒจํ‚ค์ง€ ๋ฌธ์„œ์—์„œ #์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ์ค„์€ ์„น์…˜ ํ—ค๋”๊ฐ€ ๋ผ. ๋ฌธ์„œ๋ฅผ ๊ตฌ์กฐํ™”ํ•˜๋Š” ๋ฐ ์œ ์šฉํ•˜์ง€! ๐Ÿ“‘

ํ•ธ๋“ค๋Ÿฌ ๋ฌธ์„œํ™”

// Package handlers implements HTTP request handlers for the API.
package handlers

import (
    "encoding/json"
    "net/http"
    "myapi/models"
)

// UserHandler handles HTTP requests related to user operations.
// It provides methods for CRUD operations on user resources.
type UserHandler struct {
    store models.UserStore
}

// NewUserHandler creates a new UserHandler with the given store.
// The store parameter must not be nil.
func NewUserHandler(store models.UserStore) *UserHandler {
    return &UserHandler{store: store}
}

// CreateUser handles POST requests to create a new user.
// It expects a JSON payload with user information in the request body.
//
// Request body example:
//
//     {
//       "username": "john_doe",
//       "email": "john@example.com"
//     }
//
// Response codes:
//   - 201: User created successfully
//   - 400: Invalid request body or validation error
//   - 500: Internal server error
func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
    var user models.User
    
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        http.Error(w, "Invalid request body", http.StatusBadRequest)
        return
    }
    
    if err := h.store.Create(&user); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(user)
}

HTTP ํ•ธ๋“ค๋Ÿฌ๋ฅผ ๋ฌธ์„œํ™”ํ•  ๋•Œ๋Š” ์š”์ฒญ ํ˜•์‹, ์‘๋‹ต ์ฝ”๋“œ, ์—๋Ÿฌ ์ƒํ™ฉ ๋“ฑ์„ ๋ช…ํ™•ํžˆ ์„ค๋ช…ํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด. API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์‚ฌ๋žŒ์ด ํ—ค๋งค์ง€ ์•Š๋„๋ก ๋ง์ด์•ผ! ๐Ÿ—บ๏ธ

๋ชจ๋ธ ๋ฌธ์„œํ™”

// Package models defines data structures and business logic.
package models

import "time"

// User represents a user in the system.
// All fields are required unless marked as optional.
type User struct {
    // ID is the unique identifier for the user.
    // It's automatically generated when the user is created.
    ID int `json:"id"`
    
    // Username is the user's login name.
    // It must be unique and contain only alphanumeric characters.
    Username string `json:"username"`
    
    // Email is the user's email address.
    // It must be a valid email format.
    Email string `json:"email"`
    
    // CreatedAt is the timestamp when the user was created.
    CreatedAt time.Time `json:"created_at"`
    
    // UpdatedAt is the timestamp of the last update.
    // This field is optional and may be nil.
    UpdatedAt *time.Time `json:"updated_at,omitempty"`
}

// UserStore defines the interface for user data persistence.
// Implementations must be safe for concurrent use.
type UserStore interface {
    // Create adds a new user to the store.
    // It returns an error if the username or email already exists.
    Create(user *User) error
    
    // GetByID retrieves a user by their ID.
    // It returns ErrNotFound if the user doesn't exist.
    GetByID(id int) (*User, error)
    
    // Update modifies an existing user.
    // It returns ErrNotFound if the user doesn't exist.
    Update(user *User) error
    
    // Delete removes a user from the store.
    // It returns ErrNotFound if the user doesn't exist.
    Delete(id int) error
}

// ErrNotFound is returned when a requested user is not found.
var ErrNotFound = errors.New("user not found")

์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋ฌธ์„œํ™”ํ•  ๋•Œ๋Š” ๊ฐ ๋ฉ”์„œ๋“œ์˜ ๊ณ„์•ฝ(contract)์„ ๋ช…ํ™•ํžˆ ํ•ด์•ผ ํ•ด. ์–ด๋–ค ์ž…๋ ฅ์„ ๋ฐ›๊ณ , ์–ด๋–ค ์ถœ๋ ฅ์„ ๋‚ด๊ณ , ์–ด๋–ค ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š”์ง€ ๋ง์ด์•ผ. ๐Ÿค

์˜ˆ์ œ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

// user_test.go
package handlers_test

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "net/http/httptest"
    "myapi/handlers"
    "myapi/models"
)

// ExampleUserHandler_CreateUser demonstrates creating a new user.
func ExampleUserHandler_CreateUser() {
    // ํ…Œ์ŠคํŠธ์šฉ ์Šคํ† ์–ด ์ƒ์„ฑ
    store := models.NewMemoryStore()
    handler := handlers.NewUserHandler(store)
    
    // ์š”์ฒญ ์ƒ์„ฑ
    user := models.User{
        Username: "testuser",
        Email:    "test@example.com",
    }
    body, _ := json.Marshal(user)
    req := httptest.NewRequest("POST", "/users", bytes.NewReader(body))
    w := httptest.NewRecorder()
    
    // ํ•ธ๋“ค๋Ÿฌ ์‹คํ–‰
    handler.CreateUser(w, req)
    
    fmt.Println(w.Code)
    // Output: 201
}

// ExampleUser demonstrates basic user creation.
func ExampleUser() {
    user := models.User{
        ID:       1,
        Username: "john_doe",
        Email:    "john@example.com",
    }
    
    fmt.Printf("User: %s (%s)\n", user.Username, user.Email)
    // Output: User: john_doe (john@example.com)
}

์˜ˆ์ œ ํ…Œ์ŠคํŠธ๋Š” ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๋Š” ์ฝ”๋“œ์—ฌ์•ผ ํ•ด. ๊ทธ๋ž˜์•ผ ๋ฌธ์„œ๊ฐ€ ํ•ญ์ƒ ์ตœ์‹  ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ ! ๐Ÿ“š

๐ŸŽจ ๊ณ ๊ธ‰ ๋ฌธ์„œํ™” ๊ธฐ๋ฒ•

๊ธฐ๋ณธ์€ ๋งˆ์Šคํ„ฐํ–ˆ์œผ๋‹ˆ, ์ด์ œ ์ข€ ๋” ๊ณ ๊ธ‰ ๊ธฐ๋ฒ•๋“ค์„ ์•Œ์•„๋ณด์ž! ๐Ÿš€

์ฝ”๋“œ ๋ธ”๋ก ํฌ๋งทํŒ…

์ฃผ์„์—์„œ ์ฝ”๋“œ ๋ธ”๋ก์„ ํ‘œํ˜„ํ•˜๋ ค๋ฉด ๋“ค์—ฌ์“ฐ๊ธฐ๋ฅผ ์‚ฌ์šฉํ•ด. ํƒญ์ด๋‚˜ ์ŠคํŽ˜์ด์Šค 4๊ฐœ๋กœ ๋“ค์—ฌ์“ฐ๋ฉด ์ฝ”๋“œ ๋ธ”๋ก์œผ๋กœ ์ธ์‹๋ผ.

// Process handles the data processing workflow.
// 
// Example usage:
//
//     processor := NewProcessor()
//     result, err := processor.Process(data)
//     if err != nil {
//         return err
//     }
//     fmt.Println(result)
//
// The processor supports multiple data formats including JSON and XML.
func Process(data []byte) ([]byte, error) {
    // implementation
}

๋งํฌ์™€ ์ฐธ์กฐ

godoc์€ ์ž๋™์œผ๋กœ ํƒ€์ž…, ํ•จ์ˆ˜, ํŒจํ‚ค์ง€ ์ด๋ฆ„์„ ๋งํฌ๋กœ ๋งŒ๋“ค์–ด์ค˜. ๊ทธ๋ƒฅ ์ด๋ฆ„์„ ์–ธ๊ธ‰ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ๋ผ!

// ParseUser parses user data from JSON.
// It uses the User type defined in the models package.
// Returns ErrInvalidFormat if the JSON is malformed.
//
// See also: User, ValidateUser, ErrInvalidFormat
func ParseUser(data []byte) (*User, error) {
    // implementation
}

Deprecated ํ‘œ์‹œ

๋” ์ด์ƒ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํ•จ์ˆ˜๋‚˜ ํƒ€์ž…์€ Deprecated:๋กœ ํ‘œ์‹œํ•ด์ค˜. ์‚ฌ์šฉ์ž๋“ค์ด ๋Œ€์•ˆ์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋„๋ก!

// OldFunction does something in the old way.
//
// Deprecated: Use NewFunction instead. OldFunction will be removed in v2.0.
func OldFunction() {
    // old implementation
}

// NewFunction does something in the new, improved way.
// It replaces OldFunction with better performance and cleaner API.
func NewFunction() {
    // new implementation
}

๋ฒ„์ „ ์ •๋ณด์™€ ๋ณ€๊ฒฝ ์ด๋ ฅ

ํŒจํ‚ค์ง€ ๋ฌธ์„œ์— ๋ฒ„์ „ ์ •๋ณด๋‚˜ ์ฃผ์š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ํฌํ•จํ•˜๋ฉด ์ข‹์•„.

// Package myapi provides a RESTful API server.
//
// # Version History
//
// v1.2.0 (2024-01-15)
//   - Added support for pagination
//   - Improved error messages
//   - Fixed memory leak in connection pool
//
// v1.1.0 (2023-12-01)
//   - Added user authentication
//   - New endpoint for bulk operations
//
// v1.0.0 (2023-10-01)
//   - Initial release
//
package myapi

๐Ÿ”ง ๋ฌธ์„œํ™” ์ž๋™ํ™”์™€ CI/CD ํ†ตํ•ฉ

ํ”„๋กœํŽ˜์…”๋„ํ•œ ํ”„๋กœ์ ํŠธ๋ผ๋ฉด ๋ฌธ์„œ ์ƒ์„ฑ์„ ์ž๋™ํ™”ํ•˜๋Š” ๊ฒŒ ์ข‹์•„. ํŠนํžˆ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋‚˜ ํŒ€ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ํ•„์ˆ˜์ ์ด์ง€! ๐Ÿค–

GitHub Actions๋กœ ์ž๋™ ๋ฐฐํฌ

GitHub Actions๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ํ‘ธ์‹œ๋  ๋•Œ๋งˆ๋‹ค ์ž๋™์œผ๋กœ ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  GitHub Pages์— ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์–ด.

# .github/workflows/docs.yml
name: Generate Documentation

on:
  push:
    branches: [ main ]

jobs:
  docs:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Go
      uses: actions/setup-go@v4
      with:
        go-version: '1.21'
    
    - name: Install godoc
      run: go install golang.org/x/tools/cmd/godoc@latest
    
    - name: Generate documentation
      run: |
        godoc -html=mypackage > docs/index.html
    
    - name: Deploy to GitHub Pages
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./docs

Makefile๋กœ ๊ฐ„ํŽธํ•˜๊ฒŒ

Makefile์„ ๋งŒ๋“ค์–ด๋‘๋ฉด ๋ฌธ์„œ ์ƒ์„ฑ์ด ํ›จ์”ฌ ํŽธํ•ด์ ธ.

# Makefile
.PHONY: docs docs-serve docs-open

# ๋ฌธ์„œ HTML ์ƒ์„ฑ
docs:
	@echo "Generating documentation..."
	@godoc -html=./mypackage > docs/index.html
	@echo "Documentation generated in docs/index.html"

# ๋ฌธ์„œ ์„œ๋ฒ„ ์‹œ์ž‘
docs-serve:
	@echo "Starting documentation server on http://localhost:6060"
	@godoc -http=:6060

# ๋ฌธ์„œ ์„œ๋ฒ„ ์‹œ์ž‘ํ•˜๊ณ  ๋ธŒ๋ผ์šฐ์ € ์—ด๊ธฐ
docs-open:
	@echo "Opening documentation in browser..."
	@(sleep 2 && open http://localhost:6060/pkg/mypackage) &
	@godoc -http=:6060

์ด์ œ make docs๋งŒ ์ž…๋ ฅํ•˜๋ฉด ๋ฌธ์„œ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , make docs-serve๋กœ ์„œ๋ฒ„๋ฅผ ๋„์šธ ์ˆ˜ ์žˆ์–ด! ํŽธํ•˜์ง€? ๐Ÿ˜Š

๐Ÿ“Š ๋ฌธ์„œ ํ’ˆ์งˆ ์ฒดํฌ

๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค๋ฉด ํ’ˆ์งˆ๋„ ํ™•์ธํ•ด์•ผ๊ฒ ์ง€? ๋ช‡ ๊ฐ€์ง€ ์œ ์šฉํ•œ ๋„๊ตฌ๋“ค์„ ์†Œ๊ฐœํ• ๊ฒŒ! ๐Ÿ”

go vet์œผ๋กœ ๊ธฐ๋ณธ ์ฒดํฌ

# ์ฝ”๋“œ ๊ฒ€์‚ฌ (๋ฌธ์„œ ๊ด€๋ จ ๊ฒฝ๊ณ  ํฌํ•จ)
go vet ./...

# ๋” ์ƒ์„ธํ•œ ๊ฒ€์‚ฌ
go vet -v ./...

golint๋กœ ์Šคํƒ€์ผ ์ฒดํฌ

golint๋Š” Go ์ฝ”๋“œ์˜ ์Šคํƒ€์ผ์„ ์ฒดํฌํ•˜๋Š”๋ฐ, ๋ฌธ์„œํ™” ๊ด€๋ จ ๊ฒฝ๊ณ ๋„ ํ•ด์ค˜.

# golint ์„ค์น˜
go install golang.org/x/lint/golint@latest

# ์ „์ฒด ํ”„๋กœ์ ํŠธ ๊ฒ€์‚ฌ
golint ./...

# ํŠน์ • ํŒจํ‚ค์ง€๋งŒ ๊ฒ€์‚ฌ
golint ./handlers

golint๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ฒƒ๋“ค์„ ์ฒดํฌํ•ด:

โ€ข ๊ณต๊ฐœ ํ•จ์ˆ˜/ํƒ€์ž…์— ์ฃผ์„์ด ์žˆ๋Š”์ง€
โ€ข ์ฃผ์„์ด ์˜ฌ๋ฐ”๋ฅธ ํ˜•์‹์ธ์ง€ (์ด๋ฆ„์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š”์ง€)
โ€ข ์ฃผ์„์ด ์™„์ „ํ•œ ๋ฌธ์žฅ์ธ์ง€
โ€ข ํŒจํ‚ค์ง€ ์ฃผ์„์ด ์žˆ๋Š”์ง€

golangci-lint๋กœ ์ข…ํ•ฉ ๊ฒ€์‚ฌ

golangci-lint๋Š” ์—ฌ๋Ÿฌ ๋ฆฐํ„ฐ๋ฅผ ํ†ตํ•ฉํ•œ ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์•ผ. ๋ฌธ์„œํ™” ๊ด€๋ จ ๊ฒ€์‚ฌ๋„ ํฌํ•จ๋ผ ์žˆ์–ด!

# golangci-lint ์„ค์น˜
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

# ์‹คํ–‰
golangci-lint run

# ํŠน์ • ๋ฆฐํ„ฐ๋งŒ ์‹คํ–‰
golangci-lint run --enable=godot,godox

๐ŸŒŸ ์‹ค๋ฌด ํŒ๊ณผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

์ด์ œ ์‹ค๋ฌด์—์„œ ๋ฐ”๋กœ ์จ๋จน์„ ์ˆ˜ ์žˆ๋Š” ํŒ๋“ค์„ ๊ณต์œ ํ• ๊ฒŒ! ๋‚ด๊ฐ€ ์‹ค์ œ๋กœ ํ”„๋กœ์ ํŠธํ•˜๋ฉด์„œ ๋ฐฐ์šด ๊ฒƒ๋“ค์ด์•ผ. ๐Ÿ’Ž

1. ์ผ๊ด€์„ฑ ์žˆ๋Š” ์Šคํƒ€์ผ ์œ ์ง€

๐Ÿ’ก ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ โ€ข ๋ชจ๋“  ๊ณต๊ฐœ ์š”์†Œ์— ์ฃผ์„ ๋‹ฌ๊ธฐ
โ€ข ์ฃผ์„์€ ํ•ญ์ƒ ์™„์ „ํ•œ ๋ฌธ์žฅ์œผ๋กœ
โ€ข ์ฒซ ๋ฌธ์žฅ์€ ์š”์•ฝ์œผ๋กœ, ์„ ์–ธ ์ด๋ฆ„์œผ๋กœ ์‹œ์ž‘
โ€ข ์˜ˆ์ œ ์ฝ”๋“œ๋Š” ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๋Š” ์ฝ”๋“œ๋กœ
โ€ข ์—๋Ÿฌ ์กฐ๊ฑด์€ ๋ช…ํ™•ํ•˜๊ฒŒ ์„ค๋ช…

2. ์‚ฌ์šฉ์ž ๊ด€์ ์—์„œ ์ž‘์„ฑ

๋ฌธ์„œ๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ ์‚ฌ๋žŒ์ด ์•„๋‹ˆ๋ผ ์‚ฌ์šฉํ•  ์‚ฌ๋žŒ์„ ์œ„ํ•œ ๊ฑฐ์•ผ. ๋‚ด๋ถ€ ๊ตฌํ˜„๋ณด๋‹ค๋Š” ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉํ•˜๋Š”์ง€์— ์ดˆ์ ์„ ๋งž์ถฐ!

// โŒ ๋‚˜์œ ์˜ˆ: ๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ์— ์ง‘์ค‘
// Connect establishes a TCP connection using net.Dial
// and stores the connection in the internal conn field.

// โœ… ์ข‹์€ ์˜ˆ: ์‚ฌ์šฉ๋ฒ•์— ์ง‘์ค‘
// Connect establishes a connection to the database.
// It returns an error if the connection fails or times out.
// The connection is automatically retried up to 3 times.

3. ์˜ˆ์ œ๋Š” ํ˜„์‹ค์ ์œผ๋กœ

์˜ˆ์ œ ์ฝ”๋“œ๋Š” ์‹ค์ œ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ๋ฐ˜์˜ํ•ด์•ผ ํ•ด. ๋„ˆ๋ฌด ๊ฐ„๋‹จํ•˜๊ฑฐ๋‚˜ ๋น„ํ˜„์‹ค์ ์ธ ์˜ˆ์ œ๋Š” ์˜คํžˆ๋ ค ํ˜ผ๋ž€์„ ์ค„ ์ˆ˜ ์žˆ์–ด.

// โŒ ๋„ˆ๋ฌด ๋‹จ์ˆœํ•œ ์˜ˆ์ œ
// Example:
//     result := Add(1, 2)

// โœ… ํ˜„์‹ค์ ์ธ ์˜ˆ์ œ
// Example:
//     // ์‚ฌ์šฉ์ž ์ž…๋ ฅ ์ฒ˜๋ฆฌ
//     input1, _ := strconv.Atoi(os.Args[1])
//     input2, _ := strconv.Atoi(os.Args[2])
//     
//     result := Add(input1, input2)
//     fmt.Printf("Result: %d\n", result)

4. ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฌธ์„œํ™”

Go์—์„œ๋Š” ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ์ค‘์š”ํ•˜๋‹ˆ๊นŒ, ์–ด๋–ค ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ๋ช…ํ™•ํžˆ ํ•ด์ค˜์•ผ ํ•ด.

// ReadConfig reads and parses the configuration file.
//
// It returns the following errors:
//   - ErrFileNotFound: if the config file doesn't exist
//   - ErrInvalidFormat: if the file is not valid YAML
//   - ErrPermissionDenied: if the file cannot be read
//
// Example:
//
//     config, err := ReadConfig("config.yaml")
//     if errors.Is(err, ErrFileNotFound) {
//         // handle missing file
//     }
func ReadConfig(path string) (*Config, error) {
    // implementation
}

5. ์„ฑ๋Šฅ ๊ด€๋ จ ์ •๋ณด ํฌํ•จ

ํ•จ์ˆ˜์˜ ์‹œ๊ฐ„ ๋ณต์žก๋„๋‚˜ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๊ฐ™์€ ์„ฑ๋Šฅ ์ •๋ณด๋„ ์œ ์šฉํ•ด.

// Sort sorts the slice in ascending order.
// It uses quicksort algorithm with O(n log n) average time complexity.
// The sort is not stable and modifies the slice in-place.
//
// For stable sorting, use StableSort instead.
func Sort(data []int) {
    // implementation
}

6. ์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ ๋ช…์‹œ

๋™์‹œ์„ฑ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ๋Š” ์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ์ด ์ค‘์š”ํ•ด. ๋ฐ˜๋“œ์‹œ ๋ช…์‹œํ•ด์ค˜!

// Cache provides an in-memory cache with expiration support.
// All methods are safe for concurrent use by multiple goroutines.
type Cache struct {
    mu    sync.RWMutex
    items map[string]*item
}

// Set adds or updates a value in the cache.
// This method is safe for concurrent use.
func (c *Cache) Set(key string, value interface{}) {
    // implementation
}

๐ŸŽ“ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์™„์ „ํ•œ ๋ฌธ์„œํ™” ์˜ˆ์ œ

์ž, ์ด์ œ ๋ฐฐ์šด ๊ฑธ ๋ชจ๋‘ ์ข…ํ•ฉํ•ด์„œ ์™„์ „ํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋ฌธ์„œํ™”ํ•ด๋ณด์ž! ์‹ค์ œ๋กœ **์žฌ๋Šฅ๋„ท** ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•  ๋•Œ ์ด๋Ÿฐ ์ˆ˜์ค€์˜ ๋ฌธ์„œ๊ฐ€ ์žˆ์œผ๋ฉด ์ •๋ง ์ „๋ฌธ์ ์œผ๋กœ ๋ณด์ด๊ฑฐ๋“ ! ๐ŸŒŸ

์™„์„ฑ๋œ ํŒจํ‚ค์ง€ ๊ตฌ์กฐ

// Package taskqueue provides a simple, thread-safe task queue
// with priority support and worker pool management.
//
// # Overview
//
// This package implements a priority-based task queue that allows
// you to submit tasks and process them asynchronously using a pool
// of worker goroutines. Tasks are executed in priority order, with
// higher priority tasks being processed first.
//
// # Features
//
//   - Thread-safe operations
//   - Priority-based task scheduling
//   - Configurable worker pool size
//   - Graceful shutdown support
//   - Task timeout handling
//   - Comprehensive error reporting
//
// # Quick Start
//
// Create a new queue and start processing tasks:
//
//     queue := taskqueue.New(taskqueue.Config{
//         Workers: 5,
//         BufferSize: 100,
//     })
//     
//     queue.Start()
//     defer queue.Stop()
//     
//     // Submit a task
//     err := queue.Submit(taskqueue.Task{
//         ID: "task-1",
//         Priority: taskqueue.PriorityHigh,
//         Handler: func(ctx context.Context) error {
//             // Your task logic here
//             return nil
//         },
//     })
//
// # Architecture
//
// The queue consists of three main components:
//
//   1. Queue: Manages task submission and distribution
//   2. Worker Pool: Executes tasks concurrently
//   3. Priority Heap: Maintains task ordering
//
// # Performance Considerations
//
// The queue uses a heap-based priority queue with O(log n) insertion
// and removal. Worker goroutines are pre-allocated and reused for
// efficiency. Memory usage scales with the buffer size and number
// of pending tasks.
//
// # Thread Safety
//
// All public methods are safe for concurrent use. Internal
// synchronization is handled using mutexes and channels.
//
package taskqueue

import (
    "context"
    "errors"
    "sync"
    "time"
)

// Priority represents the priority level of a task.
// Higher values indicate higher priority.
type Priority int

const (
    // PriorityLow is for non-urgent background tasks.
    PriorityLow Priority = 1
    
    // PriorityNormal is the default priority for regular tasks.
    PriorityNormal Priority = 5
    
    // PriorityHigh is for urgent tasks that should be processed quickly.
    PriorityHigh Priority = 10
    
    // PriorityCritical is for critical tasks that must be processed immediately.
    PriorityCritical Priority = 100
)

// Task represents a unit of work to be executed by the queue.
// Tasks are executed asynchronously by worker goroutines.
type Task struct {
    // ID is a unique identifier for the task.
    // It's used for tracking and logging purposes.
    ID string
    
    // Priority determines the execution order.
    // Tasks with higher priority are executed first.
    Priority Priority
    
    // Handler is the function that performs the actual work.
    // It receives a context that may be cancelled if the task times out.
    Handler func(context.Context) error
    
    // Timeout specifies the maximum duration for task execution.
    // Zero means no timeout. Default is 30 seconds.
    Timeout time.Duration
    
    // Retries specifies the number of retry attempts on failure.
    // Zero means no retries. Default is 0.
    Retries int
}

// Config holds configuration options for the queue.
type Config struct {
    // Workers is the number of worker goroutines.
    // Default is the number of CPU cores.
    Workers int
    
    // BufferSize is the capacity of the internal task buffer.
    // Larger values allow more tasks to be queued but use more memory.
    // Default is 100.
    BufferSize int
    
    // DefaultTimeout is the default timeout for tasks without a specific timeout.
    // Default is 30 seconds.
    DefaultTimeout time.Duration
    
    // ShutdownTimeout is the maximum time to wait for tasks to complete during shutdown.
    // Default is 10 seconds.
    ShutdownTimeout time.Duration
}

// Queue manages task submission and execution.
// It maintains a pool of worker goroutines that process tasks concurrently.
//
// All methods are safe for concurrent use by multiple goroutines.
type Queue struct {
    config  Config
    tasks   chan Task
    workers []*worker
    wg      sync.WaitGroup
    mu      sync.RWMutex
    running bool
}

// New creates a new Queue with the given configuration.
// If config is nil, default values are used.
//
// The queue is created in a stopped state. Call Start to begin processing tasks.
func New(config Config) *Queue {
    // Set defaults
    if config.Workers <= 0 {
        config.Workers = runtime.NumCPU()
    }
    if config.BufferSize <= 0 {
        config.BufferSize = 100
    }
    if config.DefaultTimeout == 0 {
        config.DefaultTimeout = 30 * time.Second
    }
    if config.ShutdownTimeout == 0 {
        config.ShutdownTimeout = 10 * time.Second
    }
    
    return &Queue{
        config: config,
        tasks:  make(chan Task, config.BufferSize),
    }
}

// Start begins processing tasks.
// It spawns worker goroutines according to the configuration.
//
// This method is idempotent - calling it multiple times has no effect
// if the queue is already running.
//
// Returns an error if the queue is already stopped and cannot be restarted.
func (q *Queue) Start() error {
    q.mu.Lock()
    defer q.mu.Unlock()
    
    if q.running {
        return nil
    }
    
    // Start workers
    q.workers = make([]*worker, q.config.Workers)
    for i := 0; i < q.config.Workers; i++ {
        w := &worker{
            id:    i,
            tasks: q.tasks,
        }
        q.workers[i] = w
        q.wg.Add(1)
        go w.run(&q.wg)
    }
    
    q.running = true
    return nil
}

// Submit adds a task to the queue for processing.
// The task will be executed by an available worker goroutine.
//
// If the task buffer is full, this method blocks until space becomes available
// or the context is cancelled.
//
// Returns an error if:
//   - The queue is not running
//   - The task ID is empty
//   - The task handler is nil
//   - The context is cancelled
func (q *Queue) Submit(task Task) error {
    q.mu.RLock()
    defer q.mu.RUnlock()
    
    if !q.running {
        return errors.New("queue is not running")
    }
    
    if task.ID == "" {
        return errors.New("task ID cannot be empty")
    }
    
    if task.Handler == nil {
        return errors.New("task handler cannot be nil")
    }
    
    // Set default timeout if not specified
    if task.Timeout == 0 {
        task.Timeout = q.config.DefaultTimeout
    }
    
    select {
    case q.tasks <- task:
        return nil
    default:
        return errors.New("queue is full")
    }
}

// Stop gracefully shuts down the queue.
// It waits for all currently executing tasks to complete,
// up to the configured shutdown timeout.
//
// Tasks that are queued but not yet started will not be executed.
// This method blocks until all workers have stopped or the timeout expires.
//
// After calling Stop, the queue cannot be restarted.
func (q *Queue) Stop() error {
    q.mu.Lock()
    if !q.running {
        q.mu.Unlock()
        return nil
    }
    q.running = false
    close(q.tasks)
    q.mu.Unlock()
    
    // Wait for workers with timeout
    done := make(chan struct{})
    go func() {
        q.wg.Wait()
        close(done)
    }()
    
    select {
    case <-done:
        return nil
    case <-time.After(q.config.ShutdownTimeout):
        return errors.New("shutdown timeout exceeded")
    }
}

// Stats returns current queue statistics.
// This method is safe for concurrent use.
func (q *Queue) Stats() Stats {
    q.mu.RLock()
    defer q.mu.RUnlock()
    
    return Stats{
        Workers:     q.config.Workers,
        QueuedTasks: len(q.tasks),
        Running:     q.running,
    }
}

// Stats contains queue statistics.
type Stats struct {
    // Workers is the number of worker goroutines.
    Workers int
    
    // QueuedTasks is the number of tasks waiting to be processed.
    QueuedTasks int
    
    // Running indicates whether the queue is currently processing tasks.
    Running bool
}

ํ…Œ์ŠคํŠธ์™€ ์˜ˆ์ œ

// taskqueue_test.go
package taskqueue_test

import (
    "context"
    "fmt"
    "time"
    "yourmodule/taskqueue"
)

// ExampleQueue demonstrates basic queue usage.
func ExampleQueue() {
    // Create and start queue
    queue := taskqueue.New(taskqueue.Config{
        Workers: 2,
        BufferSize: 10,
    })
    
    queue.Start()
    defer queue.Stop()
    
    // Submit a simple task
    err := queue.Submit(taskqueue.Task{
        ID: "example-task",
        Priority: taskqueue.PriorityNormal,
        Handler: func(ctx context.Context) error {
            fmt.Println("Task executed!")
            return nil
        },
    })
    
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    
    // Wait a bit for task to complete
    time.Sleep(100 * time.Millisecond)
    
    // Output: Task executed!
}

// ExampleQueue_priority demonstrates priority-based execution.
func ExampleQueue_priority() {
    queue := taskqueue.New(taskqueue.Config{
        Workers: 1, // Single worker to see priority effect
        BufferSize: 10,
    })
    
    queue.Start()
    defer queue.Stop()
    
    // Submit tasks with different priorities
    tasks := []struct {
        id       string
        priority taskqueue.Priority
    }{
        {"low", taskqueue.PriorityLow},
        {"high", taskqueue.PriorityHigh},
        {"normal", taskqueue.PriorityNormal},
        {"critical", taskqueue.PriorityCritical},
    }
    
    for _, t := range tasks {
        queue.Submit(taskqueue.Task{
            ID: t.id,
            Priority: t.priority,
            Handler: func(ctx context.Context) error {
                fmt.Printf("Executing: %s\n", t.id)
                return nil
            },
        })
    }
    
    time.Sleep(500 * time.Millisecond)
    
    // Output:
    // Executing: critical
    // Executing: high
    // Executing: normal
    // Executing: low
}

// ExampleQueue_timeout demonstrates task timeout handling.
func ExampleQueue_timeout() {
    queue := taskqueue.New(taskqueue.Config{
        Workers: 1,
    })
    
    queue.Start()
    defer queue.Stop()
    
    // Submit a task with short timeout
    queue.Submit(taskqueue.Task{
        ID: "timeout-task",
        Timeout: 100 * time.Millisecond,
        Handler: func(ctx context.Context) error {
            select {
            case <-time.After(1 * time.Second):
                return nil
            case <-ctx.Done():
                fmt.Println("Task cancelled due to timeout")
                return ctx.Err()
            }
        },
    })
    
    time.Sleep(200 * time.Millisecond)
    
    // Output: Task cancelled due to timeout
}

๐Ÿš€ pkg.go.dev์— ๋ฌธ์„œ ๊ฒŒ์‹œํ•˜๊ธฐ

Go ๋ชจ๋“ˆ์„ ๊ณต๊ฐœํ–ˆ๋‹ค๋ฉด pkg.go.dev์— ์ž๋™์œผ๋กœ ๋ฌธ์„œ๊ฐ€ ๊ฒŒ์‹œ๋ผ. ์ด๊ฑด Go ์ปค๋ฎค๋‹ˆํ‹ฐ์˜ ๊ณต์‹ ํŒจํ‚ค์ง€ ๋ฌธ์„œ ์‚ฌ์ดํŠธ์•ผ! ๐ŸŒ

๐Ÿ“ฆ pkg.go.dev ๊ฒŒ์‹œ ๊ณผ์ • 1๋‹จ๊ณ„: GitHub ๋“ฑ์— ์ฝ”๋“œ ํ‘ธ์‹œ
2๋‹จ๊ณ„: go.mod ํŒŒ์ผ์ด ์žˆ๋Š”์ง€ ํ™•์ธ
3๋‹จ๊ณ„: ํƒœ๊ทธ๋กœ ๋ฒ„์ „ ์ง€์ • (์˜ˆ: v1.0.0)
4๋‹จ๊ณ„: pkg.go.dev๊ฐ€ ์ž๋™์œผ๋กœ ๋ฐœ๊ฒฌํ•˜๊ณ  ์ธ๋ฑ์‹ฑ
5๋‹จ๊ณ„: ๋ฌธ์„œ๊ฐ€ ์ž๋™ ์ƒ์„ฑ๋˜์–ด ๊ณต๊ฐœ๋จ

๋ˆ„๊ตฐ๊ฐ€ ๋‹น์‹ ์˜ ํŒจํ‚ค์ง€๋ฅผ go getํ•˜๋ฉด pkg.go.dev๊ฐ€ ์ž๋™์œผ๋กœ ๊ฐ์ง€ํ•˜๊ณ  ๋ฌธ์„œ๋ฅผ ์ƒ์„ฑํ•ด. ๋ณ„๋„๋กœ ํ•  ์ผ์ด ์—†์–ด! ๐Ÿ˜Ž

๐Ÿ’ก ํ”„๋กœ ํŒ pkg.go.dev์—์„œ ๋ฌธ์„œ๊ฐ€ ์˜ˆ์˜๊ฒŒ ๋ณด์ด๋ ค๋ฉด:
โ€ข README.md ํŒŒ์ผ์„ ์ž‘์„ฑํ•ด (ํŒจํ‚ค์ง€ ๊ฐœ์š”๋กœ ์‚ฌ์šฉ๋จ)
โ€ข LICENSE ํŒŒ์ผ์„ ํฌํ•จํ•ด
โ€ข ์˜ˆ์ œ ์ฝ”๋“œ๋ฅผ ์ถฉ๋ถ„ํžˆ ์ž‘์„ฑํ•ด
โ€ข ๋ชจ๋“  ๊ณต๊ฐœ API์— ์ฃผ์„์„ ๋‹ฌ์•„

๐ŸŽฏ ๋ฌธ์„œํ™” ์ฒดํฌ๋ฆฌ์ŠคํŠธ

ํ”„๋กœ์ ํŠธ๋ฅผ ๋ฐฐํฌํ•˜๊ธฐ ์ „์— ์ด ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋ฅผ ํ™•์ธํ•ด๋ด! โœ…

ํ•ญ๋ชฉ ์„ค๋ช… ์ค‘์š”๋„
ํŒจํ‚ค์ง€ ์ฃผ์„ ํŒจํ‚ค์ง€์˜ ๋ชฉ์ ๊ณผ ์‚ฌ์šฉ๋ฒ• ์„ค๋ช… ํ•„์ˆ˜
๊ณต๊ฐœ ํ•จ์ˆ˜ ์ฃผ์„ ๋ชจ๋“  exported ํ•จ์ˆ˜์— ์ฃผ์„ ํ•„์ˆ˜
๊ณต๊ฐœ ํƒ€์ž… ์ฃผ์„ ๋ชจ๋“  exported ํƒ€์ž…์— ์ฃผ์„ ํ•„์ˆ˜
์˜ˆ์ œ ์ฝ”๋“œ ์ฃผ์š” ๊ธฐ๋Šฅ์— ๋Œ€ํ•œ ์˜ˆ์ œ ๊ถŒ์žฅ
์—๋Ÿฌ ๋ฌธ์„œํ™” ๋ฐœ์ƒ ๊ฐ€๋Šฅํ•œ ์—๋Ÿฌ ์„ค๋ช… ๊ถŒ์žฅ
README.md ํ”„๋กœ์ ํŠธ ๊ฐœ์š” ๋ฐ ์‹œ์ž‘ ๊ฐ€์ด๋“œ ํ•„์ˆ˜
LICENSE ๋ผ์ด์„ ์Šค ์ •๋ณด ํ•„์ˆ˜
CHANGELOG ๋ฒ„์ „๋ณ„ ๋ณ€๊ฒฝ ์‚ฌํ•ญ ๊ถŒ์žฅ
์„ฑ๋Šฅ ์ •๋ณด ์‹œ๊ฐ„/๊ณต๊ฐ„ ๋ณต์žก๋„ ๋“ฑ ์„ ํƒ
์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ ๋™์‹œ์„ฑ ๊ด€๋ จ ์ •๋ณด ๊ถŒ์žฅ

๐Ÿค ํŒ€ ํ”„๋กœ์ ํŠธ์—์„œ์˜ ๋ฌธ์„œํ™”

ํ˜ผ์ž ๊ฐœ๋ฐœํ•  ๋•Œ๋„ ๋ฌธ์„œํ™”๊ฐ€ ์ค‘์š”ํ•˜์ง€๋งŒ, ํŒ€ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋”๋”์šฑ ์ค‘์š”ํ•ด! ํŠนํžˆ **์žฌ๋Šฅ๋„ท**์—์„œ ํŒ€ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ฑฐ๋‚˜ ํ˜‘์—…ํ•  ๋•Œ ๋ง์ด์•ผ. ๐Ÿค

๋ฌธ์„œํ™” ๊ทœ์น™ ์ •ํ•˜๊ธฐ

ํŒ€์—์„œ ์ผ๊ด€๋œ ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•˜๋ ค๋ฉด ๊ทœ์น™์ด ํ•„์š”ํ•ด. ๊ฐ„๋‹จํ•œ ๊ฐ€์ด๋“œ๋ผ์ธ์„ ๋งŒ๋“ค์–ด๋ณด์ž:

# CONTRIBUTING.md

## ๋ฌธ์„œํ™” ๊ฐ€์ด๋“œ๋ผ์ธ

### ํ•„์ˆ˜ ์‚ฌํ•ญ
- ๋ชจ๋“  ๊ณต๊ฐœ ํ•จ์ˆ˜/ํƒ€์ž…์— ์ฃผ์„ ์ž‘์„ฑ
- ์ฃผ์„์€ ์„ ์–ธ ์ด๋ฆ„์œผ๋กœ ์‹œ์ž‘
- ์™„์ „ํ•œ ๋ฌธ์žฅ์œผ๋กœ ์ž‘์„ฑ (๋งˆ์นจํ‘œ ํฌํ•จ)
- ์—๋Ÿฌ ์กฐ๊ฑด ๋ช…์‹œ

### ๊ถŒ์žฅ ์‚ฌํ•ญ
- ๋ณต์žกํ•œ ๋กœ์ง์—๋Š” ์˜ˆ์ œ ์ฝ”๋“œ ์ถ”๊ฐ€
- ์„ฑ๋Šฅ ํŠน์„ฑ ๋ฌธ์„œํ™”
- ์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ ๋ช…์‹œ

### ์ฝ”๋“œ ๋ฆฌ๋ทฐ ์ฒดํฌ๋ฆฌ์ŠคํŠธ
- [ ] ๋ชจ๋“  ๊ณต๊ฐœ API์— ์ฃผ์„์ด ์žˆ๋Š”๊ฐ€?
- [ ] ์ฃผ์„์ด ์˜ฌ๋ฐ”๋ฅธ ํ˜•์‹์ธ๊ฐ€?
- [ ] ์˜ˆ์ œ ์ฝ”๋“œ๊ฐ€ ๋™์ž‘ํ•˜๋Š”๊ฐ€?
- [ ] golint ๊ฒฝ๊ณ ๊ฐ€ ์—†๋Š”๊ฐ€?

๋ฌธ์„œ ๋ฆฌ๋ทฐ ํ”„๋กœ์„ธ์Šค

์ฝ”๋“œ ๋ฆฌ๋ทฐํ•  ๋•Œ ๋ฌธ์„œ๋„ ํ•จ๊ป˜ ๋ฆฌ๋ทฐํ•ด์•ผ ํ•ด. ๋‹ค์Œ ์‚ฌํ•ญ๋“ค์„ ์ฒดํฌํ•ด๋ด:

๐Ÿ“ ๋ฌธ์„œ ๋ฆฌ๋ทฐ ํฌ์ธํŠธ ๋ช…ํ™•์„ฑ: ์„ค๋ช…์ด ๋ช…ํ™•ํ•˜๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์šด๊ฐ€?
์™„์ „์„ฑ: ํ•„์š”ํ•œ ๋ชจ๋“  ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”๊ฐ€?
์ •ํ™•์„ฑ: ์ฝ”๋“œ์™€ ๋ฌธ์„œ๊ฐ€ ์ผ์น˜ํ•˜๋Š”๊ฐ€?
์˜ˆ์ œ: ์˜ˆ์ œ ์ฝ”๋“œ๊ฐ€ ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๋Š”๊ฐ€?
์ผ๊ด€์„ฑ: ํ”„๋กœ์ ํŠธ์˜ ๋‹ค๋ฅธ ๋ฌธ์„œ์™€ ์Šคํƒ€์ผ์ด ์ผ๊ด€์ ์ธ๊ฐ€?

๐Ÿ”„ ๋ฌธ์„œ ์œ ์ง€๋ณด์ˆ˜

๋ฌธ์„œ๋Š” ํ•œ ๋ฒˆ ์ž‘์„ฑํ•˜๊ณ  ๋์ด ์•„๋‹ˆ์•ผ. ์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ๋ฌธ์„œ๋„ ํ•จ๊ป˜ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ด! ๐Ÿ“

๋ฌธ์„œ ์—…๋ฐ์ดํŠธ ์ „๋žต

โš ๏ธ ํ”ํ•œ ์‹ค์ˆ˜ ์ฝ”๋“œ๋Š” ์ˆ˜์ •ํ–ˆ๋Š”๋ฐ ๋ฌธ์„œ๋Š” ์—…๋ฐ์ดํŠธ ์•ˆ ํ•˜๋Š” ๊ฒƒ! ์ด๋Ÿฌ๋ฉด ๋ฌธ์„œ๊ฐ€ ์˜คํžˆ๋ ค ๋…์ด ๋ผ. ์ž˜๋ชป๋œ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•˜๋Š” ๋ฌธ์„œ๋Š” ์—†๋Š” ๊ฒƒ๋งŒ ๋ชปํ•˜๊ฑฐ๋“ .

๋ฌธ์„œ๋ฅผ ์ตœ์‹  ์ƒํƒœ๋กœ ์œ ์ง€ํ•˜๋Š” ํŒ:

1. ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ปค๋ฐ‹
์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ ๊ด€๋ จ ๋ฌธ์„œ๋„ ํ•จ๊ป˜ ์ˆ˜์ •ํ•˜๊ณ  ๊ฐ™์€ ์ปค๋ฐ‹์— ํฌํ•จ์‹œ์ผœ.

2. PR ์ฒดํฌ๋ฆฌ์ŠคํŠธ ํ™œ์šฉ
Pull Request ํ…œํ”Œ๋ฆฟ์— ๋ฌธ์„œ ์—…๋ฐ์ดํŠธ ํ•ญ๋ชฉ์„ ํฌํ•จ์‹œ์ผœ.

## PR ์ฒดํฌ๋ฆฌ์ŠคํŠธ
- [ ] ์ฝ”๋“œ ๋ณ€๊ฒฝ์‚ฌํ•ญ ๊ตฌํ˜„
- [ ] ํ…Œ์ŠคํŠธ ์ž‘์„ฑ/์—…๋ฐ์ดํŠธ
- [ ] ๋ฌธ์„œ ์—…๋ฐ์ดํŠธ
- [ ] CHANGELOG ์—…๋ฐ์ดํŠธ
- [ ] ์˜ˆ์ œ ์ฝ”๋“œ ๊ฒ€์ฆ

3. ์ž๋™ํ™” ๋„๊ตฌ ํ™œ์šฉ
CI/CD ํŒŒ์ดํ”„๋ผ์ธ์— ๋ฌธ์„œ ๊ฒ€์ฆ ๋‹จ๊ณ„๋ฅผ ์ถ”๊ฐ€ํ•ด.

# .github/workflows/docs-check.yml
name: Documentation Check

on: [pull_request]

jobs:
  docs:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-go@v4
      with:
        go-version: '1.21'
    
    - name: Check documentation
      run: |
        go install golang.org/x/lint/golint@latest
        golint -set_exit_status ./...
    
    - name: Verify examples
      run: |
        go test -run Example

๐Ÿ’ก ์‹ค์ „ ์‹œ๋‚˜๋ฆฌ์˜ค์™€ ํ•ด๊ฒฐ์ฑ…

์‹ค์ œ๋กœ ๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ์—ฌ๋Ÿฌ ์ƒํ™ฉ์— ๋ถ€๋”ชํžˆ๊ฒŒ ๋ผ. ๋ช‡ ๊ฐ€์ง€ ํ”ํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค์™€ ํ•ด๊ฒฐ์ฑ…์„ ๊ณต์œ ํ• ๊ฒŒ! ๐ŸŽฏ

์‹œ๋‚˜๋ฆฌ์˜ค 1: ๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ ๋ฌธ์„œํ™”

๋ฌธ์„œ๊ฐ€ ์ „ํ˜€ ์—†๋Š” ๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ๋ฅผ ๋งก์•˜๋‹ค๋ฉด? ๐Ÿ˜ฑ

๐Ÿ’ก ํ•ด๊ฒฐ ์ „๋žต 1๋‹จ๊ณ„: ๊ณต๊ฐœ API๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด (๊ฐ€์žฅ ์ค‘์š”ํ•˜๋‹ˆ๊นŒ)
2๋‹จ๊ณ„: ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ํ•จ์ˆ˜ ์šฐ์„ ์ˆœ์œ„๋กœ
3๋‹จ๊ณ„: ๋ณต์žกํ•œ ๋กœ์ง์— ์ง‘์ค‘
4๋‹จ๊ณ„: ์ ์ง„์ ์œผ๋กœ ํ™•๋Œ€
5๋‹จ๊ณ„: ์ƒˆ๋กœ์šด ์ฝ”๋“œ๋Š” ๋ฐ˜๋“œ์‹œ ๋ฌธ์„œํ™”

์‹œ๋‚˜๋ฆฌ์˜ค 2: ๋‚ด๋ถ€ ๊ตฌํ˜„ vs ๊ณต๊ฐœ API

๋‚ด๋ถ€ ํ•จ์ˆ˜๋„ ๋ฌธ์„œํ™”ํ•ด์•ผ ํ• ๊นŒ? ๐Ÿค”

๋‹ต: ๋ณต์žกํ•œ ๋‚ด๋ถ€ ๋กœ์ง์€ ๋ฌธ์„œํ™”ํ•˜๋Š” ๊ฒŒ ์ข‹์•„. ํ•˜์ง€๋งŒ ๊ณต๊ฐœ API๋งŒํผ ์ƒ์„ธํ•  ํ•„์š”๋Š” ์—†์–ด. ํ•ต์‹ฌ๋งŒ ๊ฐ„๋‹จํžˆ!

// ๊ณต๊ฐœ API - ์ƒ์„ธํ•œ ๋ฌธ์„œ
// ProcessData processes the input data according to the specified rules.
// It validates the input, applies transformations, and returns the result.
//
// The function supports multiple data formats including JSON and XML.
// Invalid input returns ErrInvalidFormat.
//
// Example:
//     result, err := ProcessData(input, rules)
func ProcessData(input []byte, rules Rules) ([]byte, error) {
    // ...
}

// ๋‚ด๋ถ€ ํ•จ์ˆ˜ - ๊ฐ„๋‹จํ•œ ์„ค๋ช…
// validateInput checks if the input data is valid.
// Returns error if validation fails.
func validateInput(input []byte) error {
    // ...
}

์‹œ๋‚˜๋ฆฌ์˜ค 3: ๋ณต์žกํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์„ค๋ช…

๋ณต์žกํ•œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ์–ด๋–ป๊ฒŒ ๋ฌธ์„œํ™”ํ• ๊นŒ? ๐Ÿ“Š

// FindShortestPath finds the shortest path between two nodes using Dijkstra's algorithm.
//
// # Algorithm
//
// The function implements Dijkstra's shortest path algorithm:
//   1. Initialize distances to all nodes as infinite except start node (0)
//   2. Create a priority queue with the start node
//   3. While queue is not empty:
//      - Extract node with minimum distance
//      - For each neighbor, calculate tentative distance
//      - Update distance if shorter path found
//   4. Return the path and total distance
//
// # Complexity
//
// Time: O((V + E) log V) where V is vertices and E is edges
// Space: O(V) for the distance map and priority queue
//
// # Example
//
//     graph := NewGraph()
//     graph.AddEdge("A", "B", 5)
//     graph.AddEdge("B", "C", 3)
//     
//     path, distance := FindShortestPath(graph, "A", "C")
//     // path: ["A", "B", "C"], distance: 8
//
func FindShortestPath(graph *Graph, start, end string) ([]string, int, error) {
    // implementation
}

๐ŸŒˆ ๋ฌธ์„œํ™”์˜ ๋ฏธ๋ž˜

Go์˜ ๋ฌธ์„œํ™” ๋„๊ตฌ๋Š” ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด. ์•ž์œผ๋กœ ์–ด๋–ค ๋ณ€ํ™”๊ฐ€ ์žˆ์„์ง€ ์‚ด์ง ์—ฟ๋ณด์ž! ๐Ÿ”ฎ

Go 1.19+ ์˜ ๊ฐœ์„ ์‚ฌํ•ญ

์ตœ๊ทผ Go ๋ฒ„์ „์—์„œ๋Š” ๋ฌธ์„œํ™” ๊ด€๋ จ ์—ฌ๋Ÿฌ ๊ฐœ์„ ์ด ์žˆ์—ˆ์–ด:

๐Ÿ†• ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ๋“ค โ€ข go doc ๋ช…๋ น์–ด ๊ฐœ์„ : ๋” ๋‚˜์€ ํฌ๋งทํŒ…๊ณผ ๊ฒ€์ƒ‰
โ€ข ๋งํฌ ์ง€์› ๊ฐ•ํ™”: ์ž๋™ ๋งํฌ ์ƒ์„ฑ ๊ฐœ์„ 
โ€ข ๋งˆํฌ๋‹ค์šด ์Šคํƒ€์ผ: ํ—ค๋”ฉ, ๋ฆฌ์ŠคํŠธ ๋“ฑ ๋” ๋‚˜์€ ํฌ๋งทํŒ…
โ€ข ์ฝ”๋“œ ๋ธ”๋ก: ๋” ๋‚˜์€ ์ฝ”๋“œ ํ•˜์ด๋ผ์ดํŒ…

์ปค๋ฎค๋‹ˆํ‹ฐ ๋„๊ตฌ๋“ค

godoc ์™ธ์—๋„ ์œ ์šฉํ•œ ๋„๊ตฌ๋“ค์ด ๋งŽ์•„:

gomarkdoc: Markdown ํ˜•์‹์œผ๋กœ ๋ฌธ์„œ ์ƒ์„ฑ
swag: Swagger/OpenAPI ๋ฌธ์„œ ์ž๋™ ์ƒ์„ฑ
pkgsite: pkg.go.dev๋ฅผ ๋กœ์ปฌ์—์„œ ์‹คํ–‰
doctoc: README์— ๋ชฉ์ฐจ ์ž๋™ ์ƒ์„ฑ

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

๋” ๊นŠ์ด ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ด๋Ÿฐ ๋ฆฌ์†Œ์Šค๋“ค์„ ์ถ”์ฒœํ•ด! ๐Ÿ“š

๐Ÿ“– ๊ณต์‹ ๋ฌธ์„œ โ€ข Go Doc Comments - ๊ณต์‹ ๊ฐ€์ด๋“œ
โ€ข pkg.go.dev - Go ํŒจํ‚ค์ง€ ๋ฌธ์„œ
โ€ข Godoc: documenting Go code - ๊ณต์‹ ๋ธ”๋กœ๊ทธ
๐ŸŽฏ ์‹ค์ „ ์˜ˆ์ œ โ€ข Go ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์†Œ์Šค ์ฝ”๋“œ - ์ตœ๊ณ ์˜ ์˜ˆ์ œ!
โ€ข ์œ ๋ช… ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ (Docker, Kubernetes ๋“ฑ)
โ€ข Effective Go - Go ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

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

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์™”๋‹ค๋‹ˆ ๋Œ€๋‹จํ•ด! ๐Ÿ‘ ์šฐ๋ฆฌ๊ฐ€ ํ•จ๊ป˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ณด์ž:

Go ๋ฌธ์„œํ™” ์—ฌ์ • ์™„๋ฃŒ! 1 ๊ธฐ๋ณธ ๊ฐœ๋… ์ฃผ์„ ์ž‘์„ฑ๋ฒ• godoc ์‚ฌ์šฉ 2 ์‹ค์ „ ๊ธฐ๋ฒ• ์˜ˆ์ œ ์ž‘์„ฑ API ๋ฌธ์„œํ™” 3 ์ž๋™ํ™” CI/CD ํ†ตํ•ฉ ํ’ˆ์งˆ ์ฒดํฌ 4 ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค ํŒ€ ํ˜‘์—… ์œ ์ง€๋ณด์ˆ˜ ํ•ต์‹ฌ ํฌ์ธํŠธ โœ… ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ๋ฌธ์„œ๋ฅผ ์ž‘์„ฑํ•˜๋ผ โœ… ์‚ฌ์šฉ์ž ๊ด€์ ์—์„œ ์„ค๋ช…ํ•˜๋ผ โœ… ์˜ˆ์ œ ์ฝ”๋“œ๋ฅผ ํฌํ•จํ•˜๋ผ โœ… ์ž๋™ํ™”๋ฅผ ํ™œ์šฉํ•˜๋ผ โœ… ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๋ผ โœ… ์ง€์†์ ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๋ผ

๋ฌธ์„œํ™”๋Š” ์ฒ˜์Œ์—” ๊ท€์ฐฎ๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ์Šต๊ด€์ด ๋˜๋ฉด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ผ. ๊ทธ๋ฆฌ๊ณ  ๋‚˜์ค‘์— ์ •๋ง ํฐ ๋„์›€์ด ๋ผ! ๐ŸŒŸ

ํŠนํžˆ ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๊ฑฐ๋‚˜, ํŒ€์—์„œ ์ผํ•˜๊ฑฐ๋‚˜, ํ”„๋ฆฌ๋žœ์„œ๋กœ ์ผํ•  ๋•Œ ์ž˜ ์ž‘์„ฑ๋œ ๋ฌธ์„œ๋Š” ๋‹น์‹ ์˜ ์ „๋ฌธ์„ฑ์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ฐ•๋ ฅํ•œ ๋ฌด๊ธฐ๊ฐ€ ๋ผ. ์ฝ”๋“œ๋งŒ ์ž˜ ์งœ๋Š” ๊ฐœ๋ฐœ์ž๊ฐ€ ์•„๋‹ˆ๋ผ, ์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜๋„ ์ž˜ํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋ผ๋Š” ์ธ์ƒ์„ ์ฃผ๊ฑฐ๋“ ! ๐Ÿ’ช

๐Ÿ’ก ๋งˆ์ง€๋ง‰ ์กฐ์–ธ ์™„๋ฒฝํ•œ ๋ฌธ์„œ๋ฅผ ํ•œ ๋ฒˆ์— ์ž‘์„ฑํ•˜๋ ค๊ณ  ํ•˜์ง€ ๋งˆ. ์กฐ๊ธˆ์”ฉ, ๊พธ์ค€ํžˆ ๊ฐœ์„ ํ•ด๋‚˜๊ฐ€๋Š” ๊ฒŒ ์ค‘์š”ํ•ด. ์˜ค๋Š˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ํ•˜๋‚˜์”ฉ ์ ์šฉํ•ด๋ณด๋ฉด์„œ ์ž์‹ ๋งŒ์˜ ์Šคํƒ€์ผ์„ ์ฐพ์•„๊ฐ€๋ด!

๊ทธ๋ฆฌ๊ณ  ๊ธฐ์–ตํ•ด, ์ข‹์€ ๋ฌธ์„œ๋Š” ๋ฏธ๋ž˜์˜ ๋‹น์‹ ๊ณผ ๋™๋ฃŒ๋“ค์—๊ฒŒ ์ฃผ๋Š” ์„ ๋ฌผ์ด์•ผ. ๐ŸŽ 6๊ฐœ์›” ํ›„์— "์•„, ๊ทธ๋•Œ ๋ฌธ์„œ ์ž˜ ์จ๋†จ๋„ค!" ํ•˜๋Š” ์ˆœ๊ฐ„์ด ๋ฐ˜๋“œ์‹œ ์˜ฌ ๊ฑฐ์•ผ!

์ด์ œ ๋‹น์‹ ๋„ Go ๋ฌธ์„œํ™” ๋งˆ์Šคํ„ฐ! ๐ŸŽ“ ์˜ค๋Š˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณด๊ณ , ๋ฉ‹์ง„ API ๋ฌธ์„œ๋ฅผ ๋งŒ๋“ค์–ด๋ด. ํ™”์ดํŒ…! ๐Ÿš€


๐ŸŒŸ Happy Documenting! ๐ŸŒŸ

์ฝ”๋“œ๋„ ์ค‘์š”ํ•˜์ง€๋งŒ, ๋ฌธ์„œ๋Š” ์ฝ”๋“œ๋ฅผ ๋น›๋‚˜๊ฒŒ ํ•ด์ฃผ๋Š” ๋งˆ๋ฒ•์ด์•ผ! โœจ

๋Œ“๊ธ€ ์ž‘์„ฑ

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

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

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