Skip to content

Uber's Go Style Guide Official Translation in Korean. Linked to the uber-go/guide as a part of contributions

License

Notifications You must be signed in to change notification settings

TangoEnSkai/uber-go-style-guide-kr

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

50 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

uber-go-style-guide-kr

  • Translated in Korean

    • First translation done with original doc on 17th of Oct, 2019 from uber-go/guide
    • Please feel free to fork and PR if you find any updates, issues or improvement.
  • ํ•œ๊ตญ์–ด ๋ฒˆ์—ญ๋ณธ

    • ์ดˆ๋ฒŒ ๋ฒˆ์—ญ์€ uber-go/guide์˜ 2019๋…„ 10์›” 17์ผ ์˜ style.md ํŒŒ์ผ์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์™„์„ฑ๋˜์—ˆ์Œ.
    • ๊ธฐ์ˆ  ์šฉ์–ด์— ๋Œ€ํ•œ ๊ณผ๋„ํ•œ ํ•œ๊ตญ์–ด ๋ฒˆ์—ญ์€ ์ง€์–‘ํ•˜์˜€์œผ๋ฉฐ, ํŠน์ • ์šฉ์–ด์— ๋Œ€ํ•œ ํ•œ๊ตญ์–ด ๋ฒˆ์—ญ์„ ํ–ˆ์„ ๋•Œ์—๋Š” ๊ด„ํ˜ธ๋กœ ์›๋ฌธ์˜ ๋‹จ์–ด๋ฅผ ์‚ด๋ ค๋‘์–ด ์ตœ๋Œ€ํ•œ ์›๋ฌธ์˜ ์˜๋„๋ฅผ ์™œ๊ณกํ•˜์ง€ ์•Š๋Š” ๋ฐฉํ–ฅ์—์„œ ๋ฒˆ์—ญ ํ•จ.

Uber์˜ Go์–ธ์–ด ์Šคํƒ€์ผ ๊ฐ€์ด๋“œ (Uber's Go Style Guide)

์†Œ๊ฐœ (Introduction)

์Šคํƒ€์ผ(styles)์€ ์ฝ”๋“œ๋ฅผ ๊ด€๋ฆฌ(govern)ํ•˜๋Š” ์ปจ๋ฒค์…˜/๊ทœ์น™(conventions)์ด๋‹ค. ์ปจ๋ฒค์…˜์€ ์ž˜ ๋ชป ์ดํ•ด ๋  ์ˆ˜ ์žˆ๋Š”๋ฐ ์™œ๋ƒํ•˜๋ฉด ๋‹จ์ˆœํžˆ gofmt๊ฐ€ ์ˆ˜ํ–‰ํ•˜๋Š” ์†Œ์Šค ์ฝ”๋“œ ํฌ๋งทํŒ… ์ด์™ธ์˜ ์˜๋ฏธ๋„ ํฌํ•จํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ด ๊ฐ€์ด๋“œ์˜ ๋ชฉํ‘œ๋Š” Uber์—์„œ Go ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ํ•ด์•ผ ํ•  ๊ฒƒ๊ณผ ํ•˜์ง€ ๋ง์•„์•ผ ํ•  ๊ฒƒ์„ ์ž์„ธํžˆ ์„ค๋ช…ํ•˜์—ฌ, ์ปจ๋ฒค์…˜์˜ ๋ณต์žก์„ฑ์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ด ์ปจ๋ฒค์…˜์€ ์—”์ง€๋‹ˆ์–ด๊ฐ€ Go์–ธ์–ด์„ ์ƒ์‚ฐ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋ฉด์„œ ์ฝ”๋“œ๋ฅผ ๊ด€๋ฆฌ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์กด์žฌํ•œ๋‹ค.

์ด๋Š” ์›๋ž˜ Prashant Varanasi์™€ Simon Newton์ด ์ผ๋ถ€ ๋™๋ฃŒ๋“ค์—๊ฒŒ Go๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ๊ฐœ๋ฐœ์†๋„ ํ–ฅ์ƒ์„ ๋„๋ชจํ•˜๊ธฐ ์œ„ํ•ด ์†Œ๊ฐœ๋˜์—ˆ๋‹ค. ์ˆ˜ ๋…„์— ๊ฑธ์ณ ํ”ผ๋“œ๋ฐฑ์„ ํ†ตํ•ด ๊ฐœ์„ ํ•˜๊ณ  ์žˆ๋‹ค.

์ด ๋ฌธ์„œ๋Š” Uber์—์„œ ๋”ฐ๋ฅด๋Š” Go ์ฝ”๋“œ ์ปจ๋ฒค์…˜์„ ์ •๋ฆฌํ•œ๋‹ค. ์ด๋“ค ์ค‘ ๋งŽ์€ ๋ถ€๋ถ„์ด Go์— ๋Œ€ํ•œ ์ผ๋ฐ˜์  ์ง€์นจ์ด๊ณ , ๋‚˜๋จธ์ง€๋Š” ์™ธ๋ถ€ ๋ฆฌ์†Œ์Šค์— ๋”ฐ๋ผ ํ™•์žฅํ•œ๋‹ค:

  1. Effective Go
  2. Go Common Mistakes
  3. Go Code Review Comments

๋ชจ๋“  ์ฝ”๋“œ๋Š” golint ๋ฐ go vet๋ฅผ ์‹คํ–‰ํ•  ๋•Œ ์˜ค๋ฅ˜๊ฐ€ ์—†์–ด์•ผ ํ•œ๋‹ค. ์ฝ”๋“œ ์—๋””ํ„ฐ๋ฅผ ๋‹ค์Œ์™€ ๊ฐ™์ด ์„ค์ •ํ•˜๊ธฐ๋ฅผ ๊ถŒ์žฅํ•œ๋‹ค:

  • ์ฝ”๋“œ ์ €์žฅ์‹œ goimports ์‹คํ–‰
  • golint ๋ฐ go vet๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์˜ค๋ฅ˜ ํ™•์ธ

์—ฌ๊ธฐ์—์„œ Go ๋„๊ตฌ์— ๋Œ€ํ•œ ํŽธ์ง‘๊ธฐ ์ง€์› ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค: https://github.com/golang/go/wiki/IDEsAndTextEditorPlugins

๊ฐ€์ด๋“œ๋ผ์ธ (Guidelines)

์ธํ„ฐํŽ˜์ด์Šค์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ (Pointers to Interfaces)

์ธํ„ฐํŽ˜์ด์Šค์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ๋Š” ๊ฑฐ์˜ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค. ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๊ฐ’(value)์œผ๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•œ๋‹ค. ์ธํ„ฐํŽ˜์ด์Šค์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ(underlying data)๋Š” ์—ฌ์ „ํžˆ ํฌ์ธํ„ฐ ์ผ ์ˆ˜ ์žˆ๋‹ค.

ํ•˜๋‚˜์˜ ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋‘ ๊ฐ€์ง€ ํ•„๋“œ์ด๋‹ค:

  1. ํƒ€์ž…-ํŠน์ • ์ •๋ณด(type-specific information)์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ. ์ด๊ฒƒ์„ "ํƒ€์ž…"์œผ๋กœ ๊ฐ„์ฃผํ•  ์ˆ˜ ์žˆ๋‹ค.
  2. ๋ฐ์ดํ„ฐ ํฌ์ธํ„ฐ. ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌ์ธํ„ฐ์ผ ๊ฒฝ์šฐ ์ง์ ‘ ์ €์žฅ๋œ๋‹ค. ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐ’์ด๋ฉด ๊ฐ’์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ๊ฐ€ ์ €์žฅ๋œ๋‹ค.

์ธํ„ฐํŽ˜์ด์Šค ๋ฉ”์„œ๋“œ๊ฐ€ ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ(underlying data)๋ฅผ ์ˆ˜์ •ํ•˜๋„๋ก ํ•˜๋ ค๋ฉด ๋ฐ˜๋“œ์‹œ ํฌ์ธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

์ธํ„ฐํŽ˜์ด์Šค ์ปดํ”Œ๋ผ์ด์–ธ์Šค ๊ฒ€์ฆ

์ ์ ˆํ•œ ๊ฒฝ์šฐ, ์ปดํŒŒ์ผ ์‹œ๊ฐ„์— ์ธํ„ฐํŽ˜์ด์Šค ์ปดํ”Œ๋ผ์ด์–ธ์Šค๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค. ์ด๋Š” ๋‹ค์Œ์„ ํฌํ•จํ•œ๋‹ค:

  • API contract์˜ ์ผ๋ถ€๋กœ ํŠน์ • ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ exported ํƒ€์ž…
  • ๋™์ผํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ํƒ€์ž…์˜ ์ปฌ๋ ‰์…˜์˜ ์ผ๋ถ€์ธ exported ๋˜๋Š” unexported ํƒ€์ž…
  • ๊ธฐํƒ€ ์ธํ„ฐํŽ˜์ด์Šค ์œ„๋ฐ˜์œผ๋กœ ์ธํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ์ค‘๋‹จ๋˜๋Š” ๊ฒฝ์šฐ
BadGood
type Handler struct {
  // ...
}



func (h *Handler) ServeHTTP(
  w http.ResponseWriter,
  r *http.Request,
) {
  ...
}
type Handler struct {
  // ...
}

var _ http.Handler = (*Handler)(nil)

func (h *Handler) ServeHTTP(
  w http.ResponseWriter,
  r *http.Request,
) {
  // ...
}

var _ http.Handler = (*Handler)(nil)๊ตฌ๋ฌธ์€ *Handler๊ฐ€ http.Handler ์ธํ„ฐํŽ˜์ด์Šค์™€ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ์ปดํŒŒ์ผ์— ์‹คํŒจํ•œ๋‹ค.

ํ• ๋‹น๋ฌธ์˜ ์šฐ๋ณ€ (the right hand side of the assignment)์€ ์–ด์„ค์…˜๋œ ํƒ€์ž…์˜ ์ œ๋กœ ๊ฐ’(zero value)์ด์–ด์•ผ ํ•œ๋‹ค. ์ด๊ฒƒ์€ ํฌ์ธํ„ฐ ํƒ€์ž…(*Handler์™€ ๊ฐ™์€), slice ๋ฐ map์˜ ๊ฒฝ์šฐ nil์ด๊ณ  struct ํƒ€์ž…์˜ ๊ฒฝ์šฐ ๋นˆ ๊ตฌ์กฐ์ฒด๋‹ค.

type LogHandler struct {
  h   http.Handler
  log *zap.Logger
}

var _ http.Handler = LogHandler{}

func (h LogHandler) ServeHTTP(
  w http.ResponseWriter,
  r *http.Request,
) {
  // ...
}

๋ฆฌ์‹œ๋ฒ„(Receivers)์™€ ์ธํ„ฐํŽ˜์ด์Šค(Interfaces)

๊ฐ’ ๋ฆฌ์‹œ๋ฒ„๊ฐ€ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋Š” ๊ฐ’ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ํฌ์ธํ„ฐ์—์„œ๋„ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํฌ์ธํ„ฐ ๋ฆฌ์‹œ๋ฒ„ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋Š” ํฌ์ธํ„ฐ ๋˜๋Š” ์ฃผ์†Œ ์ง€์ • ๊ฐ€๋Šฅํ•œ ๊ฐ’(addressable value)์—์„œ๋งŒ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค๋ฉด,

type S struct {
  data string
}

func (s S) Read() string {
  return s.data
}

func (s *S) Write(str string) {
  s.data = str
}

sVals := map[int]S{1: {"A"}}

// You can only call Read using a value
sVals[1].Read()

// This will not compile:
//  sVals[1].Write("test")

sPtrs := map[int]*S{1: {"A"}}

// You can call both Read and Write using a pointer
sPtrs[1].Read()
sPtrs[1].Write("test")

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋ฉ”์„œ๋“œ์— ๊ฐ’ ๋ฆฌ์‹œ๋ฒ„๊ฐ€ ์žˆ๋”๋ผ๋„ ์ธํ„ฐํŽ˜์ด์Šค๋Š” ํฌ์ธํ„ฐ๋กœ ์ถฉ์กฑ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

type F interface {
  f()
}

type S1 struct{}

func (s S1) f() {}

type S2 struct{}

func (s *S2) f() {}

s1Val := S1{}
s1Ptr := &S1{}
s2Val := S2{}
s2Ptr := &S2{}

var i F
i = s1Val
i = s1Ptr
i = s2Ptr

// The following doesn't compile, since s2Val is a value, and there is no value receiver for f.
//   i = s2Val

Effective Go์— Pointers vs. Values์— ๋Œ€ํ•œ ์ข‹์€ ๊ธ€์ด ์žˆ์œผ๋‹ˆ ์ฐธ๊ณ ํ•˜๊ธฐ ๋ฐ”๋ž€๋‹ค.

์ œ๋กœ ๊ฐ’ ๋ฎคํ…์Šค(Zero-value Mutexes)๋Š” ์œ ํšจํ•˜๋‹ค

sync.Mutex ๋ฐ sync.RWMutex์˜ ์ œ๋กœ ๊ฐ’(zero-value)์€ ์œ ํšจํ•˜๋ฏ€๋กœ ๋ฎคํ…์Šค์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ๊ฐ€ ๊ฑฐ์˜ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค.

BadGood
mu := new(sync.Mutex)
mu.Lock()
var mu sync.Mutex
mu.Lock()

ํฌ์ธํ„ฐ๋กœ ๊ตฌ์กฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ๋ฎคํ…์Šค๋Š” ํฌ์ธํ„ฐ๊ฐ€ ์•„๋‹Œ ํ•„๋“œ์—ฌ์•ผ ํ•œ๋‹ค. ๊ตฌ์กฐ์ฒด๋ฅผ ๋‚ด๋ณด๋‚ด์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋ผ๋„(not exported), ๊ตฌ์กฐ์ฒด์— ๋ฎคํ…์Šค๋ฅผ ํฌํ•จํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค.

type smap struct {
  sync.Mutex // ์˜ค์ง ์ˆ˜์ถœ๋˜์ง€ ์•Š์€ ํƒ€์ž…์„ ์œ„ํ•ด์„œ ์‚ฌ์šฉ

  data map[string]string
}

func newSMap() *smap {
  return &smap{
    data: make(map[string]string),
  }
}

func (m *smap) Get(k string) string {
  m.Lock()
  defer m.Unlock()

  return m.data[k]
}
type SMap struct {
  mu sync.Mutex

  data map[string]string
}

func NewSMap() *SMap {
  return &SMap{
    data: make(map[string]string),
  }
}

func (m *SMap) Get(k string) string {
  m.mu.Lock()
  defer m.mu.Unlock()

  return m.data[k]
}
`Mutex` ํ•„๋“œ์™€ `Lock` ๋ฐ `Unlock` ๋ฉ”์„œ๋“œ๋Š” ์˜๋„ํ•˜์ง€ ์•Š๊ฒŒ, `SMap`์˜ Exported API์˜ ์ผ๋ถ€์ด๋‹ค. ๋ฎคํ…์Šค์™€ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋Š” ํ˜ธ์ถœ์ž์—๊ฒŒ๋Š” ์ˆจ๊ฒจ์ง„ SMap์˜ ๊ตฌํ˜„ ์„ธ๋ถ€ ์ •๋ณด๋‹ค.

๋ฐ”์šด๋”๋ฆฌ์—์„œ ์Šฌ๋ผ์ด์Šค ๋ฐ ๋งต ๋ณต์‚ฌ

์Šฌ๋ผ์ด์Šค ๋ฐ ๋งต์—๋Š” ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ๋ณต์‚ฌํ•ด์•ผ ํ•˜๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค์— ์ฃผ์˜ ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

์Šฌ๋ผ์ด์Šค์™€ ๋งต ์ˆ˜์‹ 

์ฐธ์กฐ/๋ ˆํผ๋Ÿฐ์Šค(reference)๋ฅผ ์ €์žฅํ•˜๋ฉด ์ธ์ˆ˜(argument)๋กœ ๋ฐ›์€ ๋งต์ด๋‚˜ ์Šฌ๋ผ์ด์Šค๋ฅผ ์‚ฌ์šฉ์ž๊ฐ€ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Œ์„ ๋ช…์‹ฌํ•˜์ž.

Bad Good
func (d *Driver) SetTrips(trips []Trip) {
  d.trips = trips
}

trips := ...
d1.SetTrips(trips)

// Did you mean to modify d1.trips?
trips[0] = ...
func (d *Driver) SetTrips(trips []Trip) {
  d.trips = make([]Trip, len(trips))
  copy(d.trips, trips)
}

trips := ...
d1.SetTrips(trips)

// We can now modify trips[0] without affecting d1.trips.
trips[0] = ...

์Šฌ๋ผ์ด์Šค์™€ ๋งต ๋ฐ˜ํ™˜

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋‚ด๋ถ€ ์ƒํƒœ(internal state)๋ฅผ ๋…ธ์ถœํ•˜๋Š” ๋งต ๋˜๋Š” ์Šฌ๋ผ์ด์Šค์— ๋Œ€ํ•œ ์‚ฌ์šฉ์ž ์ˆ˜์ •์— ์ฃผ์˜ํ•˜์ž.

BadGood
type Stats struct {
  mu sync.Mutex
  counters map[string]int
}

// Snapshot returns the current stats.
func (s *Stats) Snapshot() map[string]int {
  s.mu.Lock()
  defer s.mu.Unlock()

  return s.counters
}

// snapshot is no longer protected by the mutex, so any
// access to the snapshot is subject to data races.
snapshot := stats.Snapshot()
type Stats struct {
  mu sync.Mutex
  counters map[string]int
}

func (s *Stats) Snapshot() map[string]int {
  s.mu.Lock()
  defer s.mu.Unlock()

  result := make(map[string]int, len(s.counters))
  for k, v := range s.counters {
    result[k] = v
  }
  return result
}

// Snapshot is now a copy.
snapshot := stats.Snapshot()

Clean Up ํ•˜๊ธฐ ์œ„ํ•œ Defer

defer๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŒŒ์ผ(files) ๋ฐ ์ž ๊ธˆ(locks)๊ณผ ๊ฐ™์€ ๋ฆฌ์†Œ์Šค๋ฅผ ์ •๋ฆฌํ•œ๋‹ค.

BadGood
p.Lock()
if p.count < 10 {
  p.Unlock()
  return p.count
}

p.count++
newCount := p.count
p.Unlock()

return newCount

// easy to miss unlocks due to multiple returns
p.Lock()
defer p.Unlock()

if p.count < 10 {
  return p.count
}

p.count++
return p.count

// more readable

defer๋Š” ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๊ทนํžˆ ์ž‘์œผ๋ฉฐ ํ•จ์ˆ˜ ์‹คํ–‰ ์‹œ๊ฐ„์ด ๋Œ€๋žต nanoseconds(ns) ์ˆ˜์ค€์ž„์„ ์ฆ๋ช…ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉ์„ ํ”ผํ•ด์•ผ ํ•œ๋‹ค. defer ์‚ฌ์šฉ์œผ๋กœ ์ธํ•œ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ์€ ์‚ฌ์šฉ์— ๋”ฐ๋ฅธ ์†Œ์•ก์˜ ๋น„์šฉ์„ ์ง€๋ถˆ ํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ๋‹ค. ์ด๋Š” ๋‹ค๋ฅธ ๊ณ„์‚ฐ์ด defer๋ณด๋‹ค ๋” ์ค‘์š”ํ•œ, ๋‹จ์ˆœํ•œ ๋ฉ”๋ชจ๋ฆฌ ์•ก์„ธ์Šค ์ด์ƒ์˜ ๋Œ€๊ทœ๋ชจ ๋ฉ”์„œ๋“œ์— ํŠนํžˆ ํ•ด๋‹นํ•œ๋‹ค.

์ฑ„๋„์˜ ํฌ๊ธฐ(Channel Size)๋Š” ํ•˜๋‚˜(One) ํ˜น์€ ์ œ๋กœ(None)

์ฑ„๋„์˜ ํฌ๊ธฐ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ 1 ์ด๊ฑฐ๋‚˜ ํ˜น์€ ๋ฒ„ํผ๋ง ๋˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ, ์ฑ„๋„์€ ๋ฒ„ํผ๋ง๋˜์ง€ ์•Š์œผ๋ฉฐ ํฌ๊ธฐ๋Š” 0์ด๋‹ค. 0 ์ด์™ธ์˜ ๋‹ค๋ฅธ ํฌ๊ธฐ๋Š” ๋†’์€ ์ˆ˜์ค€์˜ ์ฒ ์ €ํ•œ ๊ฒ€ํ†  ํ˜น์€ ์ •๋ฐ€์กฐ์‚ฌ(scrutiny)๋ฅผ ๋ฐ›์•„์•ผ ํ•œ๋‹ค. ์–ด๋–ป๊ฒŒ ํฌ๊ธฐ๋ฅผ ๊ฒฐ์ •(determined)ํ•  ์ง€ ๊ณ ๋ คํ•˜๋ผ. ๋ฌด์—‡์ด ์ฑ„๋„์ด ๋กœ๋“œํ•  ๊ฒฝ์šฐ ๊ฐ€๋“ ์ฐจ๊ฑฐ๋‚˜ writer๊ฐ€ ๋ง‰ํžˆ๋Š”(blocked) ๊ฒƒ์„ ์˜ˆ๋ฐฉํ•˜๋Š”์ง€ ๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฌํ•œ ๊ฒƒ์ด ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ์–ด๋–ค ์ผ์ด ์ผ์–ด๋‚  ์ง€ ์ถฉ๋ถ„ํžˆ ์ƒ๊ฐํ•ด์•ผ ํ•œ๋‹ค.

BadGood
// ๋ˆ„๊ตฌ์—๊ฒŒ๋‚˜ ์ถฉ๋ถ„ํ•˜๋‹ค!
c := make(chan int, 64)
// ์‚ฌ์ด์ฆˆ 1
c := make(chan int, 1) // ํ˜น์€
// ๋ฒ„ํผ๋ง ๋˜์ง€ ์•Š๋Š” ์ฑ„๋„, ์‚ฌ์ด์ฆˆ 0
c := make(chan int)

Enums์€ 1์—์„œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋ผ

Go์—์„œ ์—ด๊ฑฐํ˜•(enumerations)์„ ๋„์ž…ํ•˜๋Š” ์ผ๋ฐ˜์  ๋ฐฉ์‹(standard way)์€ ์‚ฌ์šฉ์ž์ •์˜ํ˜•(a custom type) ๊ทธ๋ฆฌ๊ณ  const๊ทธ๋ฃน์„ iota์™€ ํ•จ๊ป˜ ์„ ์„ ์–ธ(declare)ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

๋ณ€์ˆ˜์˜ ๊ธฐ๋ณธ๊ฐ’(default value)๋Š” 0์ด๊ธฐ ๋•Œ๋ฌธ์—, ์—ฌ๋Ÿฌ๋ถ„๋“ค์€ ์ผ๋ฐ˜์ ์œผ๋กœ ์—ด๊ฑฐํ˜•์„ 0์ด ์•„๋‹Œ ๊ฐ’(non-zero value)๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•œ๋‹ค.

BadGood
type Operation int

const (
  Add Operation = iota
  Subtract
  Multiply
)

// Add=0, Subtract=1, Multiply=2
type Operation int

const (
  Add Operation = iota + 1
  Subtract
  Multiply
)

// Add=1, Subtract=2, Multiply=3

์ œ๋กœ ๊ฐ’(zero value)๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ ์ ˆํ•  ๋•Œ๋„ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด, ์ œ๋กœ ๊ฐ’์ด 0์ธ ๊ฒฝ์šฐ ๋ฐ”๋žŒ์งํ•œ ๊ธฐ๋ณธ ๋™์ž‘(default behaviour)์ด๋‹ค.

type LogOutput int

const (
  LogToStdout LogOutput = iota
  LogToFile
  LogToRemote
)

// LogToStdout=0, LogToFile=1, LogToRemote=2

์‹œ๊ฐ„์„ ์ฒ˜๋ฆฌํ•˜๋ ค๋ฉด "time"์„ ์‚ฌ์šฉํ•˜๋ผ

์‹œ๊ฐ„์€ ๋ณต์žกํ•˜๋‹ค. ์‹œ๊ฐ„์— ๋Œ€ํ•ด ์ข…์ข… ์ž˜๋ชป๋œ ๊ฐ€์ •๋“ค ์ค‘์—๋Š” ๋‹ค์Œ๊ณผ ๋‚ด์šฉ์ด ์žˆ๋‹ค.

  1. ํ•˜๋ฃจ๋Š” 24์‹œ๊ฐ„
  2. ํ•œ ์‹œ๊ฐ„์€ 60๋ถ„
  3. ์ผ์ฃผ์ผ์€ 7์ผ
  4. ์ผ๋…„์€ 365์ผ
  5. ๋” ์‚ดํŽด๋ณด๊ธฐ

์˜ˆ๋ฅผ๋“ค๋ฉด, 1 ์€ ํŠน์ • ์‹œ์ ์— 24์‹œ๊ฐ„์„ ๋”ํ•œ๋‹ค๊ณ  ํ•ด์„œ ํ•ญ์ƒ ์ƒˆ๋กœ์šด ๋‚ ์งœ๊ฐ€ ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋ผ๋Š” ๋œป์ด๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ, ์‹œ๊ฐ„์„ ๋‹ค๋ฃฐ ๋•Œ์—๋Š” "time" ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. ์ž˜๋ชป ๋œ ๊ฐ€์ •๋“ค์„ ๋” ์•ˆ์ „ํ•˜๊ณ  ์ •ํ™•ํ•œ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š”๋ฐ ๋„์›€์„ ์ฃผ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

์‹œ๊ฐ„์˜ ์ˆœ๊ฐ„(instants of time)์„ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด์„œ๋Š” time.Time ๋ฅผ ์‚ฌ์šฉํ•˜๋ผ

์‹œ๊ฐ„์˜ ์ˆœ๊ฐ„(instants of time)์„ ์ฒ˜๋ฆฌํ•  ๋•Œ๋Š” time.Time ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ์‹œ๊ฐ„์„ ๋น„๊ตํ•˜๊ฑฐ๋‚˜ ๋”ํ•˜๊ฑฐ๋‚˜ ๋นผ๋Š” ์ž‘์—…์„ ํ• ๋•Œ๋Š” time.Time์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด๋ผ.

BadGood
func isActive(now, start, stop int) bool {
  return start <= now && now < stop
}
func isActive(now, start, stop time.Time) bool {
  return (start.Before(now) || start.Equal(now)) && now.Before(stop)
}

์‹œ๊ฐ„์˜ ๊ธฐ๊ฐ„(periods of time)์„ ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด time.Duration ์„ ์‚ฌ์šฉํ•˜๋ผ

์‹œ๊ฐ„์˜ ๊ธฐ๊ฐ„(periods of time)์„ ์ฒ˜๋ฆฌํ•  ๋•Œ๋Š” time.Duration ์„ ์‚ฌ์šฉํ•˜๋ผ.

BadGood
func poll(delay int) {
  for {
    // ...
    time.Sleep(time.Duration(delay) * time.Millisecond)
  }
}

poll(10) // ์ด ๊ฐ’์€ ์ดˆ(seconds) ์ธ๊ฐ€ ๋ฐ€๋ฆฌ์ดˆ(milliseconds) ์ธ๊ฐ€?
func poll(delay time.Duration) {
  for {
    // ...
    time.Sleep(delay)
  }
}

poll(10*time.Second)

ํŠน์ • ์‹œ์ ์— 24์‹œ๊ฐ„์„ ๋”ํ•˜๋Š” ์˜ˆ์‹œ๋กœ ๋Œ์•„๊ฐ€๋ฉด, ์‹œ๊ฐ„์„ ๋”ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์˜๋„์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ์‚ฌ์šฉ๋œ๋‹ค. ํ•˜๋ฃจ ์ค‘ ๊ฐ™์€ ๋‚ฎ ์‹œ๊ฐ„์„ ์œ ์ง€ํ•˜๋˜ ๋‹ค์Œ ๋‚ ์งœ๋กœ ๋„˜์–ด๊ฐ€๊ธธ ์›ํ•œ๋‹ค๋ฉด Time.AddDate๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜, ์ด์ „ ์‹œ๊ฐ„์œผ๋กœ๋ถ€ํ„ฐ ์ •ํ™•ํžˆ 24์‹œ๊ฐ„์ด ์ง€๋‚œ ์‹œ๊ฐ„์„ ์–ป๊ณ  ์‹ถ๋‹ค๋ฉด Time.Add๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.

newDay := t.AddDate(0 /* years */, 0 /* months */, 1 /* days */)
maybeNewDay := t.Add(24 * time.Hour)

time.Time ๊ณผ time.Duration์„ ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ ์‚ฌ์šฉํ•˜๊ธฐ

๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ ์™ธ๋ถ€ ์‹œ์Šคํ…œ๊ณผ ์ƒํ˜ธ์ž‘์šฉ ํ•  ๋•Œ๋Š” time.Duration ๊ณผ time.Time ์„ ์‚ฌ์šฉํ•ด๋ผ. ์—๋ฅผ ๋“ค๋ฉด:

  • Command-line flags: flag ๋Š” time.ParseDuration๋ฅผ ํ†ตํ•ด time.Duration์„ ์ง€์›ํ•œ๋‹ค.
  • JSON: encoding/json์€ [UnmarshalJSON ๋ฉ”์„œ๋“œ]๋ฅผ ํ†ตํ•ด time.Time์„ RFC 3339 ๋ฌธ์ž์—ด๋กœ ์ธ์ฝ”๋”ฉํ•˜๋Š” ๊ฒƒ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  • SQL: database/sql์€ DATETIME ๋˜๋Š” TIMESTAMP ์—ด์„ time.Time์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  ๊ธฐ๋ณธ ๋“œ๋ผ์ด๋ฒ„๊ฐ€ ์ง€์›ํ•˜๋Š” ๊ฒฝ์šฐ ๊ทธ ๋ฐ˜๋Œ€๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
  • YAML: gopkg.in/yaml.v2๋Š” RFC 3339 ๋ฌธ์ž์—ด๋กœ time.Time์„ ์ง€์›ํ•˜๊ณ  time.ParseDuration์„ ํ†ตํ•ด time.Duration์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

time.Duration์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ, int ๋‚˜ float64๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ํ•„๋“œ ์ด๋ฆ„์— ๋‹จ์œ„๋ฅผ ํฌํ•จํ•ด๋ผ.

์˜ˆ๋ฅผ ๋“ค์–ด, encoding/json ์ด time.Duration์„ ์ง€์›ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํ•„๋“œ ์ด๋ฆ„์— ๋‹จ์œ„๋ฅผ ํฌํ•จํ•ด์•ผ ํ•œ๋‹ค.

BadGood
// {"interval": 2}
type Config struct {
  Interval int `json:"interval"`
}
// {"intervalMillis": 2000}
type Config struct {
  IntervalMillis int `json:"intervalMillis"`
}

time.Time์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ, ๋Œ€์•ˆ์ด ํ•ฉ์˜๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด RFC 3339์— ์ •์˜๋œ ํƒ€์ž„์Šคํƒฌํ”„ ํ˜•์‹์œผ๋กœ string ์‚ฌ์šฉํ•ด๋ผ. ์ด ํ˜•์‹์€ Time.UnmarshalText์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋ฉฐ, time.RFC3339๋ฅผ ํ†ตํ•ด time.format ๋ฐ time.Parse์—์„œ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋‹ค.

์‹ค์ œ๋กœ๋Š” ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š๋Š” ๊ฒฝํ–ฅ์ด ์žˆ์ง€๋งŒ, "time" ํŒจํ‚ค์ง€๋Š” ์œค์ดˆ(leap seconds)๊ฐ€ ํฌํ•จ๋œ ํƒ€์ž„์Šคํƒฌํ”„ ๊ตฌ๋ฌธ ๋ถ„์„์„ ์ง€์›ํ•˜์ง€ ์•Š์œผ๋ฉฐ(8728), ๊ณ„์‚ฐ ์‹œ ์œค์ดˆ(leap seconds)๋ฅผ ๊ณ ๋ คํ•˜์ง€๋„ ์•Š์Šต๋‹ˆ๋‹ค(15190). ๋งŒ์•ฝ ๋‘ ์‹œ๊ฐ„์˜ ์ˆœ๊ฐ„(instants of time)์„ ๋น„๊ตํ•œ๋‹ค๋ฉด, ๊ทธ ์‚ฌ์ด์— ๋ฐœ์ƒ ํ•  ์ˆ˜ ์žˆ๋Š” ์œค์ดˆ(leap seconds)๋Š” ์ฐจ์ด์— ๋ฐ˜์˜ ๋˜์ง€ ์•Š์„ ๊ฒƒ ์ด๋‹ค.

์—๋Ÿฌ ํ˜•(Error Types)

์—๋Ÿฌ๋ฅผ ์„ ์–ธํ•˜๋Š”๋ฐ ์žˆ์–ด์„œ ๋‹ค์–‘ํ•œ ์˜ต์…˜๋“ค์ด ์กด์žฌํ•œ๋‹ค:

  • errors.New ๊ฐ„๋‹จํ•œ ์ •์  ๋ฌธ์ž์—ด(simple static strings)๊ณผ ํ•จ๊ป˜ํ•˜๋Š” ์—๋Ÿฌ
  • fmt.Errorf ํ˜•์‹ํ™”๋œ ์˜ค๋ฅ˜ ๋ฌธ์ž์—ด
  • Error() ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ ์ปค์Šคํ…€ ํƒ€์ž… (Custom types)
  • "pkg/errors".Wrap๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ž˜ํ•‘ ๋œ(wrapped) ์˜ค๋ฅ˜

์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•  ๋•Œ, ๊ฐ€์žฅ ์ข‹์€ ์„ ํƒ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ์•„๋ž˜์˜ ์‚ฌํ•ญ์„ ๊ณ ๋ คํ•˜๋ผ:

  • ์ถ”๊ฐ€ ์ •๋ณด๊ฐ€ ํ•„์š”์—†๋Š” ๊ฐ„๋‹จํ•œ ์—๋Ÿฌ์ธ๊ฐ€? ๊ทธ๋ ‡๋‹ค๋ฉด, errors.New๊ฐ€ ์ถฉ๋ถ„ํ•˜๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์˜ค๋ฅ˜๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์ฒ˜๋ฆฌ(handle)ํ•ด์•ผ ํ•˜๋Š”๊ฐ€? ๊ทธ๋ ‡๋‹ค๋ฉด, ์ปค์Šคํ…€ ํƒ€์ž…์„ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๊ณ  Error() ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.
  • ๋‹ค์šด์ŠคํŠธ๋ฆผ ํ•จ์ˆ˜(downstream function)์— ์˜ํ•ด ๋ฐ˜ํ™˜๋œ ์—๋Ÿฌ๋ฅผ ์ „ํŒŒ(propagating)ํ•˜๊ณ  ์žˆ๋Š”๊ฐ€? ๊ทธ๋ ‡๋‹ค๋ฉด, ์˜ค๋ฅ˜ ํฌ์žฅ(Error Wrapping)์„ ์ฐธ๊ณ ํ•˜๋ผ.
  • ์ด์™ธ์˜ ๊ฒฝ์šฐ, fmt.Errorf ๋กœ ์ถฉ๋ถ„ํ•˜๋‹ค.

๋งŒ์•ฝ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์˜ค๋ฅ˜๋ฅผ ๊ฐ์ง€ํ•ด์•ผ ํ•˜๊ณ , ์—ฌ๋Ÿฌ๋ถ„๋“ค์ด errors.New์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•œ ์—๋Ÿฌ๋ฅผ ์ƒ์„ฑํ•œ ๊ฒฝ์šฐ, var์— ์—๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•ด๋ผ.

BadGood
// package foo

func Open() error {
  return errors.New("could not open")
}

// package bar

func use() {
  if err := foo.Open(); err != nil {
    if err.Error() == "could not open" {
      // handle
    } else {
      panic("unknown error")
    }
  }
}
// package foo

var ErrCouldNotOpen = errors.New("could not open")

func Open() error {
  return ErrCouldNotOpen
}

// package bar

if err := foo.Open(); err != nil {
  if err == foo.ErrCouldNotOpen {
    // handle
  } else {
    panic("unknown error")
  }
}

๋งŒ์•ฝ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ฐ์ง€ํ•ด์•ผ ํ•  ์˜ค๋ฅ˜๊ฐ€ ์žˆ๊ณ  ์—ฌ๋Ÿฌ๋ถ„๋“ค์ด ์ด๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๊ณ  ํ•˜๋Š” ๊ฒฝ์šฐ, ๊ทธ๊ฒƒ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์„ ๊ฒƒ์ด๋‹ค. (์˜ˆ๋ฅผ๋“ค์–ด, ์ •์  ๋ฌธ์ž์—ด์ด ์•„๋‹Œ ๊ฒฝ์šฐ), ์ด๋Ÿฌํ•  ๊ฒฝ์šฐ, ์—ฌ๋Ÿฌ๋ถ„๋“ค์€ ์ปค์Šคํ…€ ํƒ€์ž…์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

BadGood
func open(file string) error {
  return fmt.Errorf("file %q not found", file)
}

func use() {
  if err := open(); err != nil {
    if strings.Contains(err.Error(), "not found") {
      // handle
    } else {
      panic("unknown error")
    }
  }
}
type errNotFound struct {
  file string
}

func (e errNotFound) Error() string {
  return fmt.Sprintf("file %q not found", e.file)
}

func open(file string) error {
  return errNotFound{file: file}
}

func use() {
  if err := open(); err != nil {
    if _, ok := err.(errNotFound); ok {
      // handle
    } else {
      panic("unknown error")
    }
  }
}

์‚ฌ์šฉ์ž ์ •์˜ ์˜ค๋ฅ˜ ํƒ€์ž…(custom error types)์„ ์ง์ ‘์ ์œผ๋กœ ๋‚ด๋ณด๋‚ด๋Š”(exporting) ๊ฒฝ์šฐ ์ฃผ์˜ํ•ด์•ผ ํ•œ๋‹ค. ์™œ๋ƒํ•˜๋ฉด ๊ทธ๋“ค์€ ํŒจํ‚ค์ง€์˜ ๊ณต์šฉ API (the public API of the package)์˜ ์ผ๋ถ€๊ฐ€ ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋Œ€์‹ ์—, ์˜ค๋ฅ˜๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋งค์ฒ˜ ํ•จ์ˆ˜(matcher functions)๋ฅผ ๋…ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค(preferable).

// package foo

type errNotFound struct {
  file string
}

func (e errNotFound) Error() string {
  return fmt.Sprintf("file %q not found", e.file)
}

func IsNotFoundError(err error) bool {
  _, ok := err.(errNotFound)
  return ok
}

func Open(file string) error {
  return errNotFound{file: file}
}

// package bar

if err := foo.Open("foo"); err != nil {
  if foo.IsNotFoundError(err) {
    // handle
  } else {
    panic("unknown error")
  }
}

์˜ค๋ฅ˜ ๋ž˜ํ•‘(Error Wrapping)

ํ˜ธ์ถœ์ด ์‹คํŒจํ•  ๊ฒฝ์šฐ ์—๋Ÿฌ๋ฅผ ์ „ํŒŒ(propagating)ํ•˜๊ธฐ ์œ„ํ•œ 3๊ฐ€์ง€ ์ฃผ์š” ์˜ต์…˜์ด ์žˆ๋‹ค:

  • ์ถ”๊ฐ€์ ์ธ ์ปจํ…์ŠคํŠธ(additional context)๊ฐ€ ์—†๊ณ  ์›๋ž˜์˜ ์—๋Ÿฌ ํƒ€์ž…์„ ์œ ์ง€ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ๋ณธ๋ž˜์˜ ์—๋Ÿฌ(original error)๋ฅผ ๋ฐ˜ํ™˜.
  • ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋” ๋งŽ์€ ์ปจํ…์ŠคํŠธ๋ฅผ ์ œ๊ณตํ•˜๋ฉด์„œ "pkg/errors".Cause๊ฐ€ ์›๋ž˜ ์˜ค๋ฅ˜๋ฅผ ์ถ”์ถœํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ๋„๋ก "pkg/errors".Wrap์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปจํ…์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€.
  • ํ˜ธ์ถœ์ž(callers)๊ฐ€ ํŠน์ •ํ•œ ์—๋Ÿฌ ์ผ€์ด์Šค๋ฅผ(specific error case)๋ฅผ ๊ฐ์ง€ํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฃฐ(handle) ํ•„์š”๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ fmt.Errorf๋ฅผ ์‚ฌ์šฉ.

"connection refused"์™€ ๊ฐ™์€ ๋ชจํ˜ธํ•œ ์˜ค๋ฅ˜๋ณด๋‹ค, ์ปจํ…์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์—ฌ๋Ÿฌ๋ถ„๋“ค์€ "call service foo: connection refused."์™€ ๊ฐ™์ด ๋”์šฑ ์œ ์šฉํ•œ ์—๋Ÿฌ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

๋ฐ˜ํ™˜๋œ ์˜ค๋ฅ˜์—์„œ ์ปจํ…์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ ํ•  ๋•Œ, "failed to"์™€ ๊ฐ™์€ ์‚ฌ์กฑ์˜ ๋ช…๋ฐฑํ•œ ๋ฌธ๊ตฌ๋ฅผ ํ”ผํ•˜๋ฉฐ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์œ ์ง€ํ•˜๋„๋ก ํ•ด๋ผ. ์ด๋Ÿฌํ•œ ๋ฌธ๊ตฌ๋“ค์ด ์—๋Ÿฌ๊ฐ€ ์Šคํƒ์— ํผ์ง€๋ฉด์„œ/์Šค๋ฉฐ๋“ค๋ฉด์„œ(percolates) ๊ณ„์†ํ•ด์„œ ์Œ“์ด๊ฒŒ ๋œ๋‹ค:

BadGood
s, err := store.New()
if err != nil {
    return fmt.Errorf(
        "failed to create new store: %s", err)
}
s, err := store.New()
if err != nil {
    return fmt.Errorf(
        "new store: %s", err)
}
failed to x: failed to y: failed to create new store: the error
x: y: new store: the error

๊ทธ๋Ÿฌ๋‚˜, ์ผ๋‹จ ์˜ค๋ฅ˜๊ฐ€ ๋‹ค๋ฅธ ์‹œ์Šคํ…œ์œผ๋กœ ์ „์†ก๋˜๋ฉด, ๊ทธ ๋ฉ”์‹œ์ง€๊ฐ€ ์˜ค๋ฅ˜์ž„์€ ๋ถ„๋ช…ํžˆ ํ•ด์•ผ ํ•œ๋‹ค. (์˜ˆ๋ฅผ๋“ค์–ด err ํƒœ๊ทธ(tag) ํ˜น์€ ๋กœ๊ทธ์—์„œ์˜ "Failed" ์ ‘๋‘์‚ฌ ์‚ฌ์šฉ)

๋˜ํ•œ ๋‹ค์Œ์˜ ๊ธ€์„ ์ฐธ๊ณ ํ•˜๋ผ: Don't just check errors, handle them gracefully.

ํƒ€์ž…์˜ ์–ด์„ค์…˜ ์‹คํŒจ ๋‹ค๋ฃจ๊ธฐ (Handle Type Assertion Failures)

type assertion์˜ ๋‹จ์ผ ๋ฐ˜ํ™˜ ๊ฐ’ ํ˜•์‹(the single return value form)์€ ์ž˜๋ชป๋œ ํƒ€์ž…์— ํŒจ๋‹‰ ์ƒํƒœ๊ฐ€ ๋œ๋‹ค. ๋”ฐ๋ผ์„œ ํ•ญ์ƒ "comma ok" ๊ด€์šฉ๊ตฌ(idiom)์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.

BadGood
t := i.(string)
t, ok := i.(string)
if !ok {
  // handle the error gracefully
}

ํŒจ๋‹‰์„ ํ”ผํ•  ๊ฒƒ (Don't Panic)

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ๋Š” ํŒจ๋‹‰์„ ๋ฐ˜๋“œ์‹œ ํ”ผํ•ด์•ผ ํ•œ๋‹ค. ํŒจ๋‹‰์€ cascading failures์˜ ์ฃผ์š” ์›์ธ์ด๋‹ค. ๋งŒ์•ฝ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ, ํ•จ์ˆ˜๋Š” ์—๋Ÿฌ๋ฅผ ๋ฆฌํ„ดํ•˜๊ณ  ํ˜ธ์ถœ์ž(caller)๊ฐ€ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•์„ ๊ฒฐ์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ•œ๋‹ค.

BadGood
func foo(bar string) {
  if len(bar) == 0 {
    panic("bar must not be empty")
  }
  // ...
}

func main() {
  if len(os.Args) != 2 {
    fmt.Println("USAGE: foo <bar>")
    os.Exit(1)
  }
  foo(os.Args[1])
}
func foo(bar string) error {
  if len(bar) == 0 {
    return errors.New("bar must not be empty")
  }
  // ...
  return nil
}

func main() {
  if len(os.Args) != 2 {
    fmt.Println("USAGE: foo <bar>")
    os.Exit(1)
  }
  if err := foo(os.Args[1]); err != nil {
    panic(err)
  }
}

Panic/recover๋Š” ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ ์ „๋žต(error handling strategy)์ด ์ด๋‹ˆ๋‹ค. nil dereference์™€ ๊ฐ™์ด ๋ณต๊ตฌ ํ•  ์ˆ˜ ์—†๋Š” ์ผ์ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ์—๋งŒ ํ”„๋กœ๊ทธ๋žจ์ด ํŒจ๋‹‰ ์ƒํƒœ์—ฌ์•ผ ํ•œ๋‹ค. ํ”„๋กœ๊ทธ๋žจ ์ดˆ๊ธฐํ™”๋Š” ์—ฌ๊ธฐ์—์„œ ์˜ˆ์™ธ๋‹ค: ํ”„๋กœ๊ทธ๋žจ์„ ์‹œ์ž‘ ํ•  ๋•Œ, ํ”„๋กœ๊ทธ๋žจ์„ ์ค‘๋‹จํ•ด์•ผ ํ•  ์ •๋„์˜ ์ข‹์ง€ ๋ชปํ•œ ์ผ(bad things)์ด ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ํŒจ๋‹‰์„ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

var _statusTemplate = template.Must(template.New("name").Parse("_statusHTML"))

ํ…Œ์ŠคํŠธ์—์„œ ์กฐ์ฐจ๋„, ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•œ ๊ฒƒ์œผ๋กœ ํ‘œ๊ธฐ๋˜๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด panic๋ณด๋‹ค๋Š” t.Fatal ํ˜น์€ t.FailNow๊ฐ€ ์„ ํ˜ธ๋œ๋‹ค.

BadGood
// func TestFoo(t *testing.T)

f, err := ioutil.TempFile("", "test")
if err != nil {
  panic("failed to set up test")
}
// func TestFoo(t *testing.T)

f, err := ioutil.TempFile("", "test")
if err != nil {
  t.Fatal("failed to set up test")
}

go.uber.org/atomic์˜ ์‚ฌ์šฉ

sync/atomic ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•œ ์•„ํ† ๋ฏน ์—ฐ์‚ฐ(atomic operation)์€ ์›์‹œ ํƒ€์ž… (raw type: e.g. int32, int64, etc.)์—์„œ ์ž‘๋™ํ•˜๋ฏ€๋กœ, ์•„ํ† ๋ฏน ์—ฐ์‚ฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ณ€์ˆ˜๋ฅผ ์ฝ๊ฑฐ๋‚˜ ์ˆ˜์ •ํ•˜๋Š” ๊ฒƒ์„ ์‰ฝ๊ฒŒ ์žŠ์–ด๋ฒ„๋ฆด ์ˆ˜ ์žˆ๋‹ค.

go.uber.org/atomic๋Š” ๊ธฐ๋ณธ ํƒ€์ž…(underlying type)์„ ์ˆจ๊ฒจ์„œ ์ด๋Ÿฐ ์œ ํ˜•์˜ ์—ฐ์‚ฐ์— ํƒ€์ž… ์•ˆ์ „์„ฑ์„ ๋ถ€์—ฌํ•œ๋‹ค(add type safety). ๋˜ํ•œ, ์ด๋Š” ๊ฐ„ํŽธํ•œ atomic.Bool ํƒ€์ž…์„ ํฌํ•จํ•˜๊ณ  ์žˆ๋‹ค.

BadGood
type foo struct {
  running int32  // atomic
}

func (f* foo) start() {
  if atomic.SwapInt32(&f.running, 1) == 1 {
     // already runningโ€ฆ
     return
  }
  // start the Foo
}

func (f *foo) isRunning() bool {
  return f.running == 1  // race!
}
type foo struct {
  running atomic.Bool
}

func (f *foo) start() {
  if f.running.Swap(true) {
     // already runningโ€ฆ
     return
  }
  // start the Foo
}

func (f *foo) isRunning() bool {
  return f.running.Load()
}

๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•œ(mutable) ์ „์—ญ๋ณ€์ˆ˜ ํ”ผํ•˜๊ธฐ

๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•œ(mutable) ์ „์—ญ๋ณ€์ˆ˜๋ฅผ ํ”ผํ•˜๊ณ , ๋Œ€์‹  ์˜์กด์„ฑ ์ฃผ์ž…์„ ์„ ํƒํ•ด๋ผ. ์ด ์‚ฌํ•ญ์€ ํ•จ์ˆ˜ ํฌ์ธํ„ฐ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹ค๋ฅธ ์ข…๋ฅ˜์˜ ๊ฐ’์—๋„ ์ ์šฉ๋œ๋‹ค.

BadGood
// sign.go

var _timeNow = time.Now

func sign(msg string) string {
  now := _timeNow()
  return signWithTime(msg, now)
}
// sign.go

type signer struct {
  now func() time.Time
}

func newSigner() *signer {
  return &signer{
    now: time.Now,
  }
}

func (s *signer) Sign(msg string) string {
  now := s.now()
  return signWithTime(msg, now)
}
// sign_test.go

func TestSign(t *testing.T) {
  oldTimeNow := _timeNow
  _timeNow = func() time.Time {
    return someFixedTime
  }
  defer func() { _timeNow = oldTimeNow }()

  assert.Equal(t, want, sign(give))
}
// sign_test.go

func TestSigner(t *testing.T) {
  s := newSigner()
  s.now = func() time.Time {
    return someFixedTime
  }

  assert.Equal(t, want, s.Sign(give))
}

๊ณต๊ฐœ ๊ตฌ์กฐ์ฒด(public struct)์—์„œ ๋‚ด์žฅ ํƒ€์ž…๋“ค(Embedding Types) ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ

์ด๋Ÿฌํ•œ ๋‚ด์žฅ๋œ(embedded) ํƒ€์ž…๋“ค์€ ๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ์„ ๋…ธ์ถœ์‹œํ‚ค๊ณ , ํƒ€์ž… ๊ตฌ์กฐ๋ฅผ ๋ฐœ์ „์‹œํ‚ค๋Š” ๊ฒƒ์„ ์–ด๋ ต๊ฒŒ ํ•˜๋ฉฐ, ๋ฌธ์„œํ™”๋ฅผ ์–ด๋ ต๊ฒŒ ํ•œ๋‹ค.

์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ ๋ฆฌ์ŠคํŠธ ์œ ํ˜•์„ ๊ณต์œ ๋œ AbstractList๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๋ฉด, ๊ตฌ์ฒด์ ์ธ ๊ตฌํ˜„์ฒด์— AbstractList๋ฅผ ๋‚ด์žฅ(embedding)ํ•˜๋Š” ๊ฒƒ์„ ํ”ผํ•˜๋ผ. ๋Œ€์‹ , ์ถ”์ƒ ๋ชฉ๋ก์— ์œ„์ž„ํ•  ๊ตฌ์ฒด์ ์ธ ๋ชฉ๋ก์˜ ๋ฉ”์„œ๋“œ(method)๋งŒ ์ง์ ‘ ์ž‘์„ฑํ•˜๋ผ.

type AbstractList struct {}

// Add adds an entity to the list.
func (l *AbstractList) Add(e Entity) {
  // ...
}

// Remove removes an entity from the list.
func (l *AbstractList) Remove(e Entity) {
  // ...
}
BadGood
// ConcreteList is a list of entities.
type ConcreteList struct {
  *AbstractList
}
// ConcreteList is a list of entities.
type ConcreteList struct {
  list *AbstractList
}

// Add adds an entity to the list.
func (l *ConcreteList) Add(e Entity) {
  l.list.Add(e)
}

// Remove removes an entity from the list.
func (l *ConcreteList) Remove(e Entity) {
  l.list.Remove(e)
}

Go๋Š” ์ƒ์†(inheritance)๊ณผ ํ•ฉ์„ฑ(composition) ์‚ฌ์ด์˜ ํƒ€ํ˜‘์œผ๋กœ ํƒ€์ž… ๋‚ด์žฅ(type embedding)์„ ํ—ˆ์šฉํ•œ๋‹ค. ์™ธ๋ถ€ ํƒ€์ž…์€ ๋‚ด์žฅ๋œ ํƒ€์ž…์˜ ๋ฉ”์„œ๋“œ๋ฅผ ์•”์‹œ์ ์œผ๋กœ ๋ณต์‚ฌํ•œ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฉ”์„œ๋“œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‚ด์žฅ๋œ ์ธ์Šคํ„ด์Šค์˜ ๋™์ผํ•œ ๋ฉ”์„œ๋“œ์— ์œ„์ž„๋œ๋‹ค.

๋˜ํ•œ ๊ตฌ์กฐ์ฒด๋Š” ๊ฐ™์€ ์ด๋ฆ„์˜ ํ•„๋“œ๋ฅผ ํš๋“ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ, ๋‚ด์žฅ๋œ ํƒ€์ž…(embedded type)์ด ๊ณต๊ฐœ๋˜๋ฉด, ํ•ด๋‹น ํ•„๋“œ๋„ ๊ณต๊ฐœ ๋œ๋‹ค. ์ด์ „ ๋ฒ„์ „๊ณผ ํ˜ธํ™˜์„ฑ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด, ์™ธ๋ถ€ ํƒ€์ž…์˜ ํ–ฅํ›„ ๋ฒ„์ „์€ ๋‚ด์žฅ๋œ ํƒ€์ž…(embedded type)์„ ๊ณ„์† ์œ ์ง€ ํ•ด์•ผ ํ•œ๋‹ค.

๋‚ด์žฅ๋œ ํƒ€์ž…(embedded type)์€ ๊ฑฐ์˜ ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค. ์ด๊ฒƒ์€ ๋ฒˆ๊ฑฐ๋กœ์šด ๋Œ€๋ฆฌ์ž ๋ฉ”์„œ๋“œ(delegate method)๋“ค์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์„ ํ”ผํ•  ์ˆ˜ ์žˆ๋Š” ํŽธ์˜ ๊ธฐ๋Šฅ์ด๋‹ค.

๊ตฌ์กฐ์ฒด ๋Œ€์‹ ์— ํ˜ธํ™˜๊ฐ€๋Šฅํ•œ AbstractList interface ๋‚ด์žฅํ•˜๋Š”๊ฒŒ ๊ฐœ๋ฐœ์ž์—๊ฒŒ ํ–ฅํ›„ ๋ณ€๊ฒฝ์— ๋Œ€ํ•œ ๋” ๋งŽ์€ ์œ ์—ฐ์„ฑ์„ ์ œ๊ณตํ•˜์ง€๋งŒ, ์—ฌ์ „ํžˆ ๊ตฌ์ฒด์ ์ธ ๋ฆฌ์ŠคํŠธ๊ฐ€ ์ถ”์ƒ์ ์ธ ๊ตฌํ˜„(abstract implementation)์„ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ์„ธ๋ถ€์‚ฌํ•ญ์„ ๋…ธ์ถœ ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

BadGood
// AbstractList is a generalized implementation
// for various kinds of lists of entities.
type AbstractList interface {
  Add(Entity)
  Remove(Entity)
}

// ConcreteList is a list of entities.
type ConcreteList struct {
  AbstractList
}
// ConcreteList is a list of entities.
type ConcreteList struct {
  list AbstractList
}

// Add adds an entity to the list.
func (l *ConcreteList) Add(e Entity) {
  l.list.Add(e)
}

// Remove removes an entity from the list.
func (l *ConcreteList) Remove(e Entity) {
  l.list.Remove(e)
}

๋‚ด์žฅ๋œ ๊ตฌ์กฐ์ฒด(embedded struct)์™€ ๋‚ด์žฅ๋œ ์ธํ„ฐํŽ˜์ด์Šค(embedded interface) ๋ชจ๋‘, ๋‚ด์žฅ๋œ ํƒ€์ž…์€ ํƒ€์ž…์˜ ๋ฐœ์ „(evolution)์— ์ œ์•ฝ์„ ๊ฐ€ํ•œ๋‹ค.

  • ๋‚ด์žฅ๋œ ์ธํ„ฐํŽ˜์ด์Šค์— ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์€ ํ˜ธํ™˜์„ฑ์„ ๊บ ๋Š” ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด๋‹ค.
  • ๋‚ด์žฅ๋œ ๊ตฌ์กฐ์ฒด์—์„œ ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์€ ํ˜ธํ™˜์„ฑ์„ ๊นจ๋Š” ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด๋‹ค.
  • ๋‚ด์žฅ๋œ ํƒ€์ž…์„์„ ์ œ๊ฑฐํ•˜๋Š” ๊ฒƒ์€ ํ˜ธํ™˜์„ฑ์„ ๊นจ๋Š” ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด๋‹ค.
  • ๋™์ผํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ถฉ์กฑํ•˜๋Š” ๋Œ€์•ˆ์œผ๋กœ ๋‚ด์žฅ๋œ ํƒ€์ž…์„ ๋Œ€์ฒดํ•˜๋Š” ๊ฒƒ์กฐ์ฐจ ํ˜ธํ™˜์„ฑ์„ ๊นจ๋Š” ๋ณ€๊ฒฝ์ด๋‹ค.

์ด๋Ÿฌํ•œ ์œ„์ž„ ๋ฉ”์„œ๋“œ(delegate method)๋“ค์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ๋ฒˆ๊ฑฐ๋กœ์šธ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ด ์ถ”๊ฐ€์ ์ธ ๋…ธ๋ ฅ์œผ๋กœ ์ธํ•ด ๊ตฌํ˜„ ์„ธ๋ถ€์‚ฌํ•ญ์ด ์ˆจ๊ฒจ์ง€๊ณ , ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐํšŒ๋ฅผ ๋” ๋งŽ์ด ์ œ๊ณตํ•˜๋ฉฐ, ๋˜ํ•œ ๋ฌธ์„œ์—์„œ List ์ธํ„ฐํŽ˜์ด์Šค ์ „์ฒด๋ฅผ ์ฐพ์•„๊ฐ€๋Š” ๊ฐ„์ ‘์ ์ธ ๋ฐฉ๋ฒ•์„ ์ œ๊ฑฐํ•œ๋‹ค.

๋‚ด์žฅ๋œ(built-in) ์ด๋ฆ„ ์‚ฌ์šฉ์„ ํ”ผํ•ด๋ผ

Go ์–ธ์–ด ๋ช…์„ธ(language specification)์—๋Š” Go ํ”„๋กœ๊ทธ๋žจ ๋‚ด์—์„œ ์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉ ํ•ด์„œ๋Š” ์•ˆ๋˜๋Š” ๋ฏธ๋ฆฌ ์„ ์–ธ๋œ ์‹๋ณ„์ž(predeclared identifiers)๋“ค์ด ๋ช…์‹œ ๋˜์–ด ์žˆ๋‹ค.

์ƒํ™ฉ์— ๋”ฐ๋ผ, ์ด๋Ÿฌํ•œ ์‹๋ณ„์ž(identifier)๋“ค์„ ์ด๋ฆ„์œผ๋กœ ์žฌ์‚ฌ์šฉํ•˜๋ฉด ํ˜„์žฌ ์–ดํœ˜์  ์Šค์ฝ”ํ”„(lexical scope) ๋ฐ ๋ชจ๋“  ์ค‘์ฒฉ ์Šค์ฝ”ํ”„(nested scope)๋‚ด์—์„œ ์›๋ณธ์„ ๊ฐ€๋ฆฌ๊ฒŒ ๋˜๊ฑฐ๋‚˜ ์˜ํ–ฅ์„ ๋ฐ›๋Š” ์ฝ”๋“œ๋ฅผ ํ˜ผ๋ž€์Šค๋Ÿฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค. ๊ฐ€์žฅ ์ข‹์€ ๊ฒฝ์šฐ์—๋Š” ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ๊ฒฝ๊ณ ๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์ง€๋งŒ; ์ตœ์•…์˜ ๊ฒฝ์šฐ, ์ด๋Ÿฌํ•œ ์ฝ”๋“œ๋Š” ์ž ์žฌ์ ์œผ๋กœ ์ฐพ๊ธฐ ์–ด๋ ค์šด ๋ฒ„๊ทธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

BadGood
var error string
// `error` shadows the builtin

// or

func handleErrorMessage(error string) {
    // `error` shadows the builtin
}
var errorMessage string
// `error` refers to the builtin

// or

func handleErrorMessage(msg string) {
    // `error` refers to the builtin
}
type Foo struct {
    // ์ด๋Ÿฌํ•œ ํ•„๋“œ๋Š” ๊ธฐ์ˆ ์ ์œผ๋กœ ์„€๋„์ž‰(shadowing)์„
    // ๊ตฌ์„ฑํ•˜์ง€๋Š” ์•Š์ง€๋งŒ
    // `error` ๋˜๋Š” `string` ๋ฌธ์ž์—ด์€ ์ด์ œ
    // ๋ชจํ˜ธํ•ด์กŒ๋‹ค.
    error  error
    string string
}

func (f Foo) Error() error {
    // `error` ์™€ `f.error`๋Š”
    // ์‹œ๊ฐ์ ์œผ๋กœ ์œ ์‚ฌํ•˜๋‹ค.
    return f.error
}

func (f Foo) String() string {
    // `string` ๊ณผ `f.string`์€
    // ์‹œ๊ฐ์ ์œผ๋กœ ์œ ์‚ฌํ•˜๋‹ค.
    return f.string
}
type Foo struct {
    // `error` ์™€ `string` ๋ฌธ์ž์—ด์€
    // ์ด์ œ ๋ชจํ˜ธํ•˜์ง€ ์•Š๋‹ค.
    err error
    str string
}

func (f Foo) Error() error {
    return f.err
}

func (f Foo) String() string {
    return f.str
}

์ปดํŒŒ์ผ๋Š” ๋ฏธ๋ฆฌ ์„ ์–ธ๋œ ์‹๋ณ„์ž(predeclared identifier)๋“ค์„ ์‚ฌ์šฉ ํ•  ๋•Œ ์˜ค๋ฅ˜๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š์ง€๋งŒ, go vet๊ณผ ๊ฐ™์€ ๋„๊ตฌ๋Š” ์ด์™€ ๊ฐ™์€ ์„€๋„์ž‰(shadowing) ๊ฒฝ์šฐ์™€ ๋‹ค๋ฅธ ๊ฒฝ์šฐ๋“ค์„ ์ •ํ™•ํ•˜๊ฒŒ ์ง€์ ํ•ด ์ค„ ๊ฒƒ์ด๋‹ค.

init() ์‚ฌ์šฉ์„ ํ”ผํ•ด๋ผ

๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด init() ์‚ฌ์šฉ์„ ํ”ผํ•ด๋ผ. init() ์„ ํ”ผํ•  ์ˆ˜ ์—†๊ฑฐ๋‚˜ ์›ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์ฝ”๋“œ๋Š” ๋‹ค์Œ ์‚ฌํ•ญ์„ ์‹œ๋„ํ•ด์•ผ ํ•œ๋‹ค.

  1. ํ”„๋กœ๊ทธ๋žจ์ด ์‹คํ–‰๋˜๋Š” ํ™˜๊ฒฝ์ด๋‚˜ ํ˜ธ์ถœ ๋ฐฉ์‹์— ๊ด€๊ณ„์—†์ด, ์ฝ”๋“œ ๋™์ž‘์ด ์˜ˆ์ธก๊ฐ€๋Šฅํ•˜๊ณ  ์ผ๊ด€๋˜์–ด์•ผ ํ•œ๋‹ค(Be completely deterministic).
  2. ๋‹ค๋ฅธ init() ํ•จ์ˆ˜๋“ค์˜ ์ˆœ์„œ ๋˜๋Š” ๋ถ€์ž‘์šฉ(side-effect)์˜ ์˜์กด์„ฑ์„ ํ”ผํ•ด์•ผํ•œ๋‹ค. init()์˜ ์ˆœ์„œ๋Š” ์ž˜ ์•Œ๋ ค์ ธ ์žˆ์ง€๋งŒ, ์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ init() ํ•จ์ˆ˜๋“ค ๊ฐ„์˜ ๊ด€๊ณ„๋Š” ์ฝ”๋“œ๋ฅผ ๋ง๊ฐ€์ง€๊ธฐ ์‰ฝ๊ณ  ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
  3. ๊ธฐ๊ณ„์ •๋ณด(machine information), ํ™˜๊ฒฝ๋ณ€์ˆ˜(enviroment variables), ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ(working directory), ํ”„๋กœ๊ทธ๋žจ ์ธ์ž/์ž…๋ ฅ(argument/input)๋“ฑ๊ณผ ๊ฐ™์€ ์ „์—ญ ๋˜๋Š” ํ™˜๊ฒฝ ์ƒํƒœ์— ์ ‘๊ทผํ•˜๊ฑฐ๋‚˜ ์กฐ์ž‘ํ•˜์ง€ ์•Š๋„๋ก ํ•ด์•ผํ•œ๋‹ค.
  4. ํŒŒ์ผ์‹œ์Šคํ…œ, ๋„คํŠธ์›Œํฌ, ์‹œ์Šคํ…œํ˜ธ์ถœ์„ ํฌํ•จํ•œ I/O๋ฅผ ํ”ผํ•ด์•ผ ํ•œ๋‹ค.

์ด๋Ÿฌํ•œ ์š”๊ตฌ์‚ฌํ•ญ์„ ์ถฉ์กฑ์‹œํ‚ค๊ธฐ ์–ด๋ ค์šด ์ฝ”๋“œ๋Š” main()(๋˜๋Š” ํ”„๋กœ๊ทธ๋žจ ์ˆ˜๋ช… ์ฃผ๊ธฐ์˜ ๋‹ค๋ฅธ ๊ณณ)์—์„œ ํ˜ธ์ถœ ๋˜๋Š” ๋ถ€์ˆ˜์ ์ธ ๋„์šฐ๋ฏธ(helper)๊ฐ€ ๋˜๊ฑฐ๋‚˜, ํ˜น์€ main() ๋‚ด๋ถ€์—์„œ ์ง์ ‘ ์ž‘์„ฑ ๋  ์ˆ˜ ์žˆ๋‹ค. ํŠนํžˆ, ๋‹ค๋ฅธ ํ”„๋กœ๊ทธ๋žจ์—์„œ ์‚ฌ์šฉํ•  ๋ชฉ์ ์œผ๋กœ ์ œ์ž‘๋œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์™„์ „ํžˆ ๊ฒฐ์ •๋ก ์ (deterministic)์ด๊ณ  init magic์„ ํ–‰ํ•˜์ง€ ์•Š๋„๋ก ํ•ด์•ผํ•œ๋‹ค.

BadGood
type Foo struct {
    // ...
}

var _defaultFoo Foo

func init() {
    _defaultFoo = Foo{
        // ...
    }
}
var _defaultFoo = Foo{
    // ...
}

// ๋˜๋Š”, ํ…Œ์ŠคํŠธ๋ฅผ ์œ ์šฉํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ ๋‚˜์€ ๋ฐฉ๋ฒ•:

var _defaultFoo = defaultFoo()

func defaultFoo() Foo {
    return Foo{
        // ...
    }
}
type Config struct {
    // ...
}

var _config Config

func init() {
    // Bad: ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ ๊ธฐ์ค€(based on current directory)
    cwd, _ := os.Getwd()

    // Bad: I/O
    raw, _ := os.ReadFile(
        path.Join(cwd, "config", "config.yaml"),
    )

    yaml.Unmarshal(raw, &_config)
}
type Config struct {
    // ...
}

func loadConfig() Config {
    cwd, err := os.Getwd()
    // handle err

    raw, err := os.ReadFile(
        path.Join(cwd, "config", "config.yaml"),
    )
    // handle err

    var config Config
    yaml.Unmarshal(raw, &config)

    return config
}

์œ„๋ฅผ ๊ณ ๋ คํ•  ๋•Œ, init() ์ด ์„ ํ˜ธ๋˜๊ฑฐ๋‚˜ ํ•„์š”ํ•œ ๋ช‡๊ฐ€์ง€ ์ƒํ™ฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์„ ์ˆ˜ ์žˆ๋‹ค.

  • ๋‹จ์ผ ๋Œ€์ž…(single assignment)์œผ๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์—†๋Š” ๋ณต์žกํ•œ ํ‘œํ˜„์‹
  • database/sql ๋ฐฉ์–ธ(dialect), ์ธ์ฝ”๋”ฉ ์œ ํ˜• ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๋“ฑ๊ณผ ๊ฐ™์€ ์—ฐ๊ฒฐ๊ฐ€๋Šฅํ•œ(pluggable) ํ›…
  • Google Cloud Functions ๋ฐ ๊ฒฐ์ •๋ก ์  ์‚ฌ์ „ ๊ณ„์‚ฐ(precomputation)์˜ ๋‹ค๋ฅธ ํ˜•ํƒœ์— ๋Œ€ํ•œ ์ตœ์ ํ™”

์„ฑ๋Šฅ(Performance)

์„ฑ๋Šฅ-ํŠน์ •์˜(performance-specific)๊ฐ€์ด๋“œ๋ผ์ธ์€ ์„ฑ๋Šฅ์— ๋ฏผ๊ฐํ•œ(hot path) ๊ฒฝ์šฐ์—๋งŒ ์ ์šฉ๋œ๋‹ค.

fmt ๋ณด๋‹ค strconv ์„ ํ˜ธ

ํ”„๋ฆฌ๋ฏธํ‹ฐ๋ธŒ(primitives)๋ฅผ ๋ฌธ์ž์—ด๋กœ / ๋ฌธ์ž์—ด์—์„œ ๋ณ€ํ™˜ ํ•  ๋•Œ, strconv๊ฐ€ fmt๋ณด๋‹ค ๋น ๋ฅด๋‹ค. fmt.

BadGood
for i := 0; i < b.N; i++ {
  s := fmt.Sprint(rand.Int())
}
for i := 0; i < b.N; i++ {
  s := strconv.Itoa(rand.Int())
}
BenchmarkFmtSprint-4    143 ns/op    2 allocs/op
BenchmarkStrconv-4    64.2 ns/op    1 allocs/op

string-to-byte ๋ณ€ํ™˜์„ ํ”ผํ•ด๋ผ

๊ณ ์ • ๋ฌธ์ž์—ด(fixed string)์—์„œ ๋ฐ”์ดํŠธ ์Šฌ๋ผ์ด์Šค(byte slices)๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ์ƒ์„ฑํ•˜์ง€ ๋งˆ๋ผ. ๋Œ€์‹  ๋ณ€ํ™˜(conversion)์„ ํ•œ๋ฒˆ ์‹คํ–‰ํ•˜๊ณ , ๊ฒฐ๊ณผ๋ฅผ ์บก์ณํ•ด๋ผ.

BadGood
for i := 0; i < b.N; i++ {
  w.Write([]byte("Hello world"))
}
data := []byte("Hello world")
for i := 0; i < b.N; i++ {
  w.Write(data)
}
BenchmarkBad-4   50000000   22.2 ns/op
BenchmarkGood-4  500000000   3.25 ns/op

์Šคํƒ€์ผ (Style)

๊ทธ๋ฃน ์œ ์‚ฌ ์„ ์–ธ (Group Similar Declarations)

Go๋Š” ์œ ์‚ฌํ•œ ์„ ์–ธ ๊ทธ๋ฃนํ™”๋ฅผ ์ง€์›ํ•œ๋‹ค.

BadGood
import "a"
import "b"
import (
  "a"
  "b"
)

์ด๋Š” ๋˜ํ•œ ์ƒ์ˆ˜, ๋ณ€์ˆ˜, ๊ทธ๋ฆฌ๊ณ  ํƒ€์ž… ์„ ์–ธ์—์„œ๋„ ์œ ํšจํ•˜๋‹ค.

BadGood
const a = 1
const b = 2



var a = 1
var b = 2



type Area float64
type Volume float64
const (
  a = 1
  b = 2
)

var (
  a = 1
  b = 2
)

type (
  Area float64
  Volume float64
)

์˜ค์ง ๊ด€๋ จ๋œ ์„ ์–ธ๋งŒ ๊ทธ๋ฃนํ™” ํ•  ๊ฒƒ. ๊ด€๋ จ๋˜์ง€ ์•Š์€ ์„ ์–ธ๋“ค์— ๋Œ€ํ•ด์„œ๋Š” ๊ทธ๋ฃนํ™” ํ•˜์ง€ ๋ง๊ฒƒ.

BadGood
type Operation int

const (
  Add Operation = iota + 1
  Subtract
  Multiply
  ENV_VAR = "MY_ENV"
)
type Operation int

const (
  Add Operation = iota + 1
  Subtract
  Multiply
)

const ENV_VAR = "MY_ENV"

๊ทธ๋ฃนํ™”๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์žฅ์†Œ๋Š” ์ œํ•œ๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ•จ์ˆ˜ ๋‚ด์—์„œ๋„ ๊ทธ๋ฃนํ™”๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

BadGood
func f() string {
  var red = color.New(0xff0000)
  var green = color.New(0x00ff00)
  var blue = color.New(0x0000ff)

  ...
}
func f() string {
  var (
    red   = color.New(0xff0000)
    green = color.New(0x00ff00)
    blue  = color.New(0x0000ff)
  )

  ...
}

Import ๊ทธ๋ฃน ์ •๋ฆฌ/๋ฐฐ์น˜ (Import Group Ordering)

2๊ฐ€์ง€ import ๊ทธ๋ฃน๋“ค์ด ์กด์žฌํ•œ๋‹ค:

  • ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ (Standard library)
  • ๊ทธ ์™ธ ๋ชจ๋“  ๊ฒƒ (Everything else)

์ด๋Š” ๊ธฐ๋ณธ(default)์œผ๋กœ goimports์— ์˜ํ•ด์„œ ์ ์šฉ๋˜๋Š” ๊ทธ๋ฃน๋“ค์ด๋‹ค.

BadGood
import (
  "fmt"
  "os"
  "go.uber.org/atomic"
  "golang.org/x/sync/errgroup"
)
import (
  "fmt"
  "os"

  "go.uber.org/atomic"
  "golang.org/x/sync/errgroup"
)

ํŒจํ‚ค์ง€ ์ด๋ฆ„ (Package Names)

ํŒจํ‚ค์ง€ ์ด๋ฆ„์„ ์ •ํ•  ๋•Œ, ์•„๋ž˜์™€ ๊ฐ™์€ ์ด๋ฆ„์„ ์„ ํƒํ•˜๋ผ:

  • ๋ชจ๋‘ ์•ŒํŒŒ๋ฒณ ์†Œ๋ฌธ์ž ์‚ฌ์šฉ, ๋Œ€๋ฌธ์ž์™€ ์–ธ๋”์Šค์ฝ”์–ด (_)๋Š” ์‚ฌ์šฉํ•˜์ง€ ๋ง ๊ฒƒ.
  • ๋Œ€๋ถ€๋ถ„์˜ ํ˜ธ์ถœ ์ง€์ (call sites)์—์„œ named import๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์žฌ๋ช…๋ช…(renamed)์„ ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
  • ์งง๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ. ์ด๋ฆ„(name)์€ ๋ชจ๋“  ํ˜ธ์ถœ ์ง€์ (call site)์—์„œ ์‹๋ณ„๋จ์„ ์ƒ๊ธฐํ•˜๋ผ.
  • ๋ณต์ˆ˜ํ˜•(plural) ์‚ฌ์šฉ ๊ธˆ์ง€. ์˜ˆ๋ฅผ ๋“ค์–ด, net/urls ๊ฐ€ ์•„๋‹Œ net/url.
  • "common", "util", "shared", ๋˜๋Š” "lib"์˜ ์šฉ์–ด ์‚ฌ์šฉ ๊ธˆ์ง€. ์ •๋ณด๊ฐ€ ์—†๋Š” ์ข‹์ง€ ๋ชปํ•œ ์ด๋ฆ„์ž„.

๋˜ํ•œ Package Names ์™€ Style guideline for Go packages๋ฅผ ์ฐธ๊ณ ํ•˜๊ธฐ ๋ฐ”๋ž€๋‹ค.

ํ•จ์ˆ˜ ์ด๋ฆ„ (Function Names)

์šฐ๋ฆฌ๋Š” Go ์ปค๋ฎค๋‹ˆํ‹ฐ์˜ MixedCaps for function names์˜ ์‚ฌ์šฉ์— ์˜ํ•œ ์ปจ๋ฒค์…˜์„ ๋”ฐ๋ฅธ๋‹ค. ํ…Œ์ŠคํŠธ ํ•จ์ˆ˜(test functions)๋Š” ์˜ˆ์™ธ์ด๋‹ค. ์ด๋Š” ๊ด€๋ จ ํ…Œ์ŠคํŠธ์ผ€์ด์Šค๋ฅผ ๊ทธ๋ฃนํ™” ํ•  ๋ชฉ์ ์œผ๋กœ ์–ธ๋”์Šค์ฝ”์–ด(_)๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋‹ค, ์˜ˆ๋ฅผ๋“ค์–ด, TestMyFunction_WhatIsBeingTested.

Import ๋ณ„์นญ (Import Aliasing)

ํŒจํ‚ค์ง€ ์ด๋ฆ„์ด import path์˜ ๋งˆ์ง€๋ง‰ ์š”์†Œ์™€ ์ผ์น˜ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ๋ณ„๋ช…์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

import (
  "net/http"

  client "example.com/client-go"
  trace "example.com/trace/v2"
)

๋‹ค๋ฅธ ๋ชจ๋“  ์‹œ๋‚˜๋ฆฌ์˜ค์˜ ๊ฒฝ์šฐ, import ๋ณ„์นญ์˜ ์‚ฌ์šฉ์€ importํ•˜๋ฉด์„œ ๋‘ import๊ฐ„ ์ง์ ‘์  ์ถฉ๋Œ(import direct conflict)์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š” ํ•œ ์ง€์–‘ํ•ด์•ผ ํ•œ๋‹ค.

BadGood
import (
  "fmt"
  "os"


  nettrace "golang.net/x/trace"
)
import (
  "fmt"
  "os"
  "runtime/trace"

  nettrace "golang.net/x/trace"
)

ํ•จ์ˆ˜ ๊ทธ๋ฃนํ™”์™€ ์ •๋ ฌ/๋ฐฐ์น˜ (Function Grouping and Ordering)

  • ํ•จ์ˆ˜๋Š” ๋Œ€๋žต์  ํ˜ธ์ถœ ์ˆœ์„œ์— ์˜ํ•ด์„œ ์ •๋ ฌ๋˜์–ด์•ผ ํ•œ๋‹ค.
  • ํŒŒ์ผ๋‚ด์—์„œ์˜ ํ•จ์ˆ˜๋Š” ๋ฆฌ์‹œ๋ฒ„์— ์˜ํ•ด์„œ ๊ทธ๋ฃน์ง€์–ด์ ธ์•ผ ํ•œ๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ, ์ˆ˜์ถœ๋˜๋Š” ํ•จ์ˆ˜ (exported function)๋Š” ํŒŒ์ผ ๋‚ด์˜ struct, const, var์˜ ์ •์˜ ๊ตฌ๋ฌธ ์ดํ›„์˜ ์‹œ์ž‘ ๋ถ€๋ถ„์— ๋‚˜ํƒ€๋‚˜์•ผ ํ•œ๋‹ค.

newXYZ()/NewXYZ()๊ฐ€ ํƒ€์ž…์ด ์ •์˜๋œ ๋’ท๋ถ€๋ถ„์— ๋‚˜ํƒ€๋‚  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ด๋Š” ๋‚˜๋จธ์ง€ ๋ฆฌ์‹œ๋ฒ„(receiver)์˜ ๋ฉ”์„œ๋“œ๋“ค ์ „์— ๋‚˜ํƒ€๋‚˜์•ผ ํ•œ๋‹ค (may appear after the type is defined, but before the rest of the methods on the receiver.)

ํ•จ์ˆ˜๋“ค์€ ๋ฆฌ์‹œ๋ฒ„์— ์˜ํ•ด ๊ทธ๋ฃนํ™” ๋˜๋ฏ€๋กœ, ์ผ๋ฐ˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋“ค(plain utility functions)๋Š” ํŒŒ์ผ์˜ ๋’ท๋ถ€๋ถ„์— ๋‚˜ํƒ€๋‚˜์•ผ ํ•œ๋‹ค.

BadGood
func (s *something) Cost() {
  return calcCost(s.weights)
}

type something struct{ ... }

func calcCost(n []int) int {...}

func (s *something) Stop() {...}

func newSomething() *something {
    return &something{}
}
type something struct{ ... }

func newSomething() *something {
    return &something{}
}

func (s *something) Cost() {
  return calcCost(s.weights)
}

func (s *something) Stop() {...}

func calcCost(n []int) int {...}

์ค‘์ฒฉ ๊ฐ์†Œ (Reduce Nesting)

์ฝ”๋“œ๋Š” ์—๋Ÿฌ ์ผ€์ด์Šค ํ˜น์€ ํŠน์ˆ˜ ์กฐ๊ฑด(error cases / special conditions)์„ ๋จผ์ € ์ฒ˜๋ฆฌํ•˜๊ณ  ๋ฃจํ”„๋ฅผ ์ผ์ฐ ๋ฆฌํ„ดํ•˜๊ฑฐ๋‚˜ ๊ณ„์† ์ง€์†ํ•จ์œผ๋กœ์จ ๊ฐ€๋Šฅํ•œ ์ค‘์ฒฉ(nesting)์„ ์ค„์ผ ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค. ์—ฌ๋Ÿฌ ๋ ˆ๋ฒจ๋กœ ์ค‘์ฒฉ๋œ(nested multiple levels)์ฝ”๋“œ์˜ ์–‘์„ ์ค„์ด๋„๋ก ํ•ด๋ผ.

BadGood
for _, v := range data {
  if v.F1 == 1 {
    v = process(v)
    if err := v.Call(); err == nil {
      v.Send()
    } else {
      return err
    }
  } else {
    log.Printf("Invalid v: %v", v)
  }
}
for _, v := range data {
  if v.F1 != 1 {
    log.Printf("Invalid v: %v", v)
    continue
  }

  v = process(v)
  if err := v.Call(); err != nil {
    return err
  }
  v.Send()
}

๋ถˆํ•„์š”ํ•œ else (Unnecessary Else)

๋ณ€์ˆ˜๊ฐ€ if์˜ ๋‘ ๊ฐ€์ง€ ๋ถ„๊ธฐ๋ฌธ์— ์˜ํ•ด์„œ ์„ค์ •๋  ๊ฒฝ์šฐ, ์ด๋Š” ๋‹จ์ผ if๋ฌธ (simple if)์œผ๋กœ ๋Œ€์ฒด ํ•  ์ˆ˜ ์žˆ๋‹ค.

BadGood
var a int
if b {
  a = 100
} else {
  a = 10
}
a := 10
if b {
  a = 100
}

์ตœ์ƒ์œ„ ๋ณ€์ˆ˜ ์„ ์–ธ (Top-level Variable Declarations)

์ตœ์ƒ์œ„ ๋ ˆ๋ฒจ์—์„œ (At the top level), ํ‘œ์ค€ var ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด๋ผ. ํ‘œํ˜„์‹(expression)r๊ณผ๊ฐ™์€ ๊ฐ™์€ ํƒ€์ž…์ด ์•„๋‹Œ ์ด์ƒ, ํƒ€์ž…์„ ํŠน์ •์ง“์ง€ ๋ง๋ผ.

BadGood
var _s string = F()

func F() string { return "A" }
var _s = F()
// F๋Š” ์ด๋ฏธ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค๊ณ  ๋ช…์‹œํ•˜๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์—
// ํƒ€์ž…์„ ๋‹ค์‹œ ์ง€์ •ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.

func F() string { return "A" }

ํ‘œํ˜„์‹์˜ ํƒ€์ž…์ด ์›ํ•˜๋Š” ํƒ€์ž…๊ณผ ์ •ํ™•ํ•˜๊ฒŒ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ ํƒ€์ž…์„ ์ง€์ •ํ•ด๋ผ.

type myError struct{}

func (myError) Error() string { return "error" }

func F() myError { return myError{} }

var _e error = F()
// F๋Š” myError ํƒ€์ž…์˜ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€๋งŒ, ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” ๊ฒƒ์€ error

์ˆ˜์ถœ๋˜์ง€ ์•Š์€ ์ „์—ญ์— _์„ ๋ถ™์—ฌ๋ผ (Prefix Unexported Globals with _)

์ˆ˜์ถœ๋˜์ง€ ์•Š์€ ์ตœ์ƒ์œ„(top-level) var์™€ const์— ์ ‘๋‘์‚ฌ _๋ฅผ ๋ถ™์ž„์œผ๋กœ์จ ๊ทธ๋“ค์ด ์‚ฌ์šฉ๋  ๋•Œ, ์ „์—ญ ๊ธฐํ˜ธ(global symbols)์ž„์„ ๋ช…ํ™•ํ•˜๊ฒŒ ํ•ด๋ผ.

์˜ˆ์™ธ: ์ˆ˜์ถœ๋˜์ง€ ์•Š๋Š” ์—๋Ÿฌ ๊ฐ’ (Unexported error values)์€ err์˜ ์ ‘๋‘์‚ฌ๋ฅผ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค.

์ด์œ : ์ตœ์ƒ์œ„ ๋ณ€์ˆ˜ ๋ฐ ์ƒ์ˆ˜ (Top-level variables and constants)๋Š” ํŒจํ‚ค์ง€ ๋ฒ”์œ„(package scope)๋ฅผ ๊ฐ€์ง„๋‹ค. ์ œ๋„ค๋ฆญ ์ด๋ฆ„(generic names)์„ ์‚ฌ์šฉ ํ•˜๋Š” ๊ฒƒ์€ ๋‹ค๋ฅธ ํŒŒ์ผ์—์„œ ์ž˜๋ชป๋œ ๊ฐ’์„ ์‹ค์ˆ˜๋กœ ์‰ฝ๊ฒŒ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋‹ค.

BadGood
// foo.go

const (
  defaultPort = 8080
  defaultUser = "user"
)

// bar.go

func Bar() {
  defaultPort := 9090
  ...
  fmt.Println("Default port", defaultPort)

  // ๋งŒ์•ฝ Bar()์˜ ์ฒซ๋ฒˆ์งธ ๋ผ์ธ์ด ์ง€์›Œ์ง€๋ฉด
  // ์ปดํŒŒ์ผ ์—๋Ÿฌ์— ์ง๋ฉดํ•˜์ง€ ์•Š๋Š”๋‹ค.
}
// foo.go

const (
  _defaultPort = 8080
  _defaultUser = "user"
)

๊ตฌ์กฐ์ฒด์—์„œ์˜ ์ž„๋ฒ ๋”ฉ (Embedding in Structs)

๋ฎคํ…์Šค์™€ ๊ฐ™์€ ์ž„๋ฒ ๋“œ๋œ ํƒ€์ž…์€ ๊ตฌ์กฐ์ฒด์˜ ํ•„๋“œ ๋ชฉ๋ก ๊ฐ€์žฅ ์ƒ์œ„์ธต์— ์žˆ์–ด์•ผ ํ•˜๊ณ , ์ž„๋ฒ ๋“œ ๋œ ํ•„๋“œ๋ฅผ ์ผ๋ฐ˜ ํ•„๋“œ์™€ ๋ถ„๋ฆฌํ•˜๋Š” empty line์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค.

BadGood
type Client struct {
  version int
  http.Client
}
type Client struct {
  http.Client

  version int
}

๊ตฌ์กฐ์ฒด ์ดˆ๊ธฐํ™”๋ฅผ ์œ„ํ•ด ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•ด๋ผ (Use Field Names to initialize Structs)

๊ตฌ์กฐ์ฒด๋ฅผ ์ดˆ๊ธฐํ™” ํ•  ๋•Œ์—๋Š” ๊ฑฐ์˜ ๋Œ€๋ถ€๋ถ„ ํ•„๋“œ ๋ช…์„ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค. ์ด๊ฒƒ์€ ์ด์ œ go vet์— ์˜ํ•ด์„œ ๊ฐ•์ œํ•˜๊ณ  ์žˆ๋‹ค.

BadGood
k := User{"John", "Doe", true}
k := User{
    FirstName: "John",
    LastName: "Doe",
    Admin: true,
}

์˜ˆ์™ธ: ํ…Œ์ŠคํŠธ ํ…Œ์ด๋ธ”์—์„œ ํ•„๋“œ๋ช…์€ 3๊ฐœ ์ผ๋•Œ ํ˜น์€ ์ด๋ณด๋‹ค ์ ์„ ๋•Œ ์ƒ๋žต๋  ์ˆ˜ ์žˆ์Œ.

tests := []struct{
  op Operation
  want string
}{
  {Add, "add"},
  {Subtract, "subtract"},
}

์ง€์—ญ ๋ณ€์ˆ˜ ์„ ์–ธ (Local Variable Declarations)

๋ณ€์ˆ˜๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ํŠน์ • ๊ฐ’์œผ๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒฝ์šฐ ์งง์€ ๋ณ€์ˆ˜ ์„ ์–ธ (Short variable declarations, :=)์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

BadGood
var s = "foo"
s := "foo"

๊ทธ๋Ÿฌ๋‚˜, var ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๊ธฐ๋ณธ๊ฐ’(default value)๊ฐ€ ๋” ๋ช…ํ™•ํ•  ๋•Œ๊ฐ€ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค๋ฉด, Declaring Empty Slices.

BadGood
func f(list []int) {
  filtered := []int{}
  for _, v := range list {
    if v > 10 {
      filtered = append(filtered, v)
    }
  }
}
func f(list []int) {
  var filtered []int
  for _, v := range list {
    if v > 10 {
      filtered = append(filtered, v)
    }
  }
}

nil์€ ์œ ํšจํ•œ ์Šฌ๋ผ์ด์Šค (nil is a valid slice)

nil์€ ๊ธธ์ด๊ฐ€ 0์ธ ์œ ํšจํ•œ ์Šฌ๋ผ์ด์Šค์ด๋‹ค. ์ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Œ์„ ์˜๋ฏธํ•œ๋‹ค:

  • ๊ธธ์ด๊ฐ€ 0์ธ ์Šฌ๋ผ์ด์Šค๋ฅผ ๋ช…์‹œ์ ์œผ๋กœ ๋ฐ˜ํ™˜ํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค. ๋Œ€์‹  nil์„ ๋ฐ˜ํ™˜ํ•˜๋ผ.

    BadGood
    if x == "" {
      return []int{}
    }
    if x == "" {
      return nil
    }
  • ์Šฌ๋ผ์ด์Šค๊ฐ€ ๋น„์–ด์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ ํ•ญ์ƒ len(s) == 0์„ ์‚ฌ์šฉํ•ด๋ผ. nil์„ ์ฒดํฌํ•˜์ง€ ๋ง ๊ฒƒ.

    BadGood
    func isEmpty(s []string) bool {
      return s == nil
    }
    func isEmpty(s []string) bool {
      return len(s) == 0
    }
  • ์ œ๋กœ ๊ฐ’(The zero value), var๋กœ ์„ ์–ธ๋œ ์Šฌ๋ผ์ด์Šค์˜ ๊ฒฝ์šฐ,์€ make()์—†์ด ๋ฐ”๋กœ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋‹ค.

    BadGood
    nums := []int{}
    // or, nums := make([]int)
    
    if add1 {
      nums = append(nums, 1)
    }
    
    if add2 {
      nums = append(nums, 2)
    }
    var nums []int
    
    if add1 {
      nums = append(nums, 1)
    }
    
    if add2 {
      nums = append(nums, 2)
    }

๋ณ€์ˆ˜์˜ ๋ฒ”์œ„๋ฅผ ์ค„์—ฌ๋ผ (Reduce Scope of Variables)

๊ฐ€๋Šฅํ•œ ๋ณ€์ˆ˜์˜ ๋ฒ”์œ„๋ฅผ ์ค„์—ฌ๋ผ. ๋งŒ์•ฝ Reduce Nesting๊ณผ์˜ ์ถฉ๋Œํ•˜๋Š” ๊ฒฝ์šฐ ๋ฒ”์œ„๋ฅผ ์ค„์ด๋ฉด ์•ˆ๋œ๋‹ค.

BadGood
err := ioutil.WriteFile(name, data, 0644)
if err != nil {
 return err
}
if err := ioutil.WriteFile(name, data, 0644); err != nil {
 return err
}

if์™ธ๋ถ€์—์„œ ํ•จ์ˆ˜ ํ˜ธ์ถœ์˜ ๊ฒฐ๊ณผ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ, ๋ฒ”์œ„๋ฅผ ์ค„์ด๋ ค๊ณ  ์‹œ๋„ํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค.

BadGood
if data, err := ioutil.ReadFile(name); err == nil {
  err = cfg.Decode(data)
  if err != nil {
    return err
  }

  fmt.Println(cfg)
  return nil
} else {
  return err
}
data, err := ioutil.ReadFile(name)
if err != nil {
   return err
}

if err := cfg.Decode(data); err != nil {
  return err
}

fmt.Println(cfg)
return nil

Naked ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํ”ผํ•ด๋ผ (Avoid Naked Parameters)

ํ•จ์ˆ˜ ํ˜ธ์ถœ์—์„œ์˜ naked parameters๋Š” ๊ฐ€๋…์„ฑ์„ ๋–จ์–ด ๋œจ๋ฆด ์ˆ˜ ์žˆ๋‹ค. ์˜๋ฏธ๊ฐ€ ๋ช…ํ™•ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ, C์–ธ์–ด ์Šคํƒ€์ผ์˜ ์ฃผ์„ (/* ... */)์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ๋ฐ”๋ž€๋‹ค.

BadGood
// func printInfo(name string, isLocal, done bool)

printInfo("foo", true, true)
// func printInfo(name string, isLocal, done bool)

printInfo("foo", true /* isLocal */, true /* done */)

๋” ๋‚˜์€ ๋ฐฉ๋ฒ•์€, naked bool ํƒ€์ž…์„ ๋” ์ฝ๊ธฐ ์‰ฝ๊ณ  ํƒ€์ž…-์•ˆ์ •์ (type-safe)์ธ ์ฝ”๋“œ๋ฅผ ์œ„ํ•ด์„œ ์‚ฌ์šฉ์ž ์ •์˜ ํƒ€์ž…(custom type)์œผ๋กœ ๋Œ€์ฒดํ•ด๋ผ. ์ด๋ฅผ ํ†ตํ•ด์„œ ํ–ฅํ›„ ํ•ด๋‹น ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋Œ€ํ•ด์„œ ๋‘๊ฐœ ์ด์ƒ์˜ ์ƒํƒœ (true/false)๋ฅผ ํ—ˆ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

type Region int

const (
  UnknownRegion Region = iota
  Local
)

type Status int

const (
  StatusReady = iota + 1
  StatusDone
  // ํ–ฅํ›„์— StatusInProgress๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
)

func printInfo(name string, region Region, status Status)

์ด์Šค์ผ€์ดํ•‘์„ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ์›์‹œ ๋ฌธ์ž ๋ฆฌํ„ฐ๋Ÿด ์‚ฌ์šฉ (Use Raw String Literals to Avoid Escaping)

Go๋Š” raw string literals์„ ์ง€์›ํ•˜๋ฉฐ ์—ฌ๋Ÿฌ ์ค„์— ๊ฑธ์ณ์นœ ์ฝ”๋“œ์™€ ๋”ฐ์˜ดํ‘œ๋ฅผ ํ•จ๊ป˜ ํฌํ•จํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฝ๊ธฐ ์–ด๋ ค์šด hand-escaped strings๋ฅผ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด์„œ ์›์‹œ ๋ฌธ์ž ๋ฆฌํ„ฐ๋Ÿด์„ ์‚ฌ์šฉํ•ด๋ผ.

BadGood
wantError := "unknown name:\"test\""
wantError := `unknown error:"test"`

๊ตฌ์กฐ์ฒด ์ฐธ์กฐ ์ดˆ๊ธฐํ™” (Initializing Struct References)

๊ตฌ์กฐ์ฒด ์ฐธ์กฐ(struct reference)๋ฅผ ์ดˆ๊ธฐํ™” ํ•  ๋•Œ, new(T)๋Œ€์‹ ์— &T{}์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌ์กฐ์ฒด ์ดˆ๊ธฐํ™”์™€ ์ผ๊ด€์„ฑ์„ ๊ฐ€์ง€๋„๋ก ํ•ด๋ผ.

BadGood
sval := T{Name: "foo"}

// inconsistent
sptr := new(T)
sptr.Name = "bar"
sval := T{Name: "foo"}

sptr := &T{Name: "bar"}

Printf์™ธ๋ถ€์˜ ๋ฌธ์ž์—ด ํ˜•์‹ (Format Strings outside Printf)

๋ฌธ์ž์—ด ๋ฆฌํ„ฐ๋Ÿด ์™ธ๋ถ€์˜ Printf-์Šคํƒ€์ผ์˜ ํ•จ์ˆ˜์— ๋Œ€ํ•œ ํ˜•์‹ ๋ฌธ์ž์—ด(format strings)์„ ์„ ์–ธํ•˜๋Š” ๊ฒฝ์šฐ const๊ฐ’ (const value)๋กœ ๋งŒ๋“ค์–ด๋ผ.

์ด๋Š” go vet์ด ํ˜•์‹ ๋ฌธ์ž์—ด์˜ ์ •์  ๋ถ„์„(static analysis) ์ˆ˜ํ–‰ํ•˜๋Š”๋ฐ ๋„์›€์ด ๋œ๋‹ค.

BadGood
msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
const msg = "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)

Printf-์Šคํƒ€์ผ ํ•จ์ˆ˜์˜ ์ด๋ฆ„ (Naming Printf-style Functions)

Printf-์Šคํƒ€์ผ์˜ ํ•จ์ˆ˜๋ฅผ ์„ ์–ธํ•  ๋•Œ, go vet์ด ์ด๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ํ˜•์‹ ๋ฌธ์ž์—ด (format string)์„ ์ฒดํฌ ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธํ•ด๋ผ.

์ด๊ฒƒ์€ ๋ฏธ๋ฆฌ ์ •์˜ ๋œ Printf-์Šคํƒ€์ผ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. go vet์ด ์ด๋ฅผ ๋””ํดํŠธ๋กœ ์ฒดํฌํ•œ๋‹ค. ์ž์„ธํ•œ ์ •๋ณด๋Š” ๋‹ค์Œ์„ ์ฐธ์กฐํ•˜๊ธฐ ๋ฐ”๋ž€๋‹ค: Printf family

๋ฏธ๋ฆฌ ์ •์˜๋œ ์ด๋ฆ„(pre-defined names)์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์˜ต์…˜์ด ์•„๋‹ˆ๋ผ๋ฉด, ์„ ํƒํ•œ ์ด๋ฆ„์„ f๋กœ ๋๋‚ด๋„๋ก ํ•ด๋ผ: Wrap์ด ์•„๋‹Œ Wrapf. go vet์€ ํŠน์ • Printf-์Šคํƒ€์ผ์˜ ์ด๋ฆ„์„ ํ™•์ธํ•˜๋„๋ก ์š”์ฒญ๋ฐ›์„ ์ˆ˜ ์žˆ์œผ๋‚˜ ์ด๋“ค์˜ ์ด๋ฆ„์€ ๋ชจ๋‘ f๋กœ ๋๋‚˜์•ผ๋งŒ ํ•œ๋‹ค.

$ go vet -printfuncs=wrapf,statusf

๋˜ํ•œ ๋‹ค์Œ์„ ์ฐธ๊ณ ํ•ด๋ผ: go vet: Printf family check.

ํŒจํ„ด (Patterns)

ํ…Œ์ŠคํŠธ ํ…Œ์ด๋ธ” (Test Tables)

ํ•ต์‹ฌ์  ํ…Œ์ŠคํŠธ ๋กœ์ง(the core test logic)์ด ๋ฐ˜๋ณต์ ์ผ ๋•Œ, ์ฝ”๋“œ ์ค‘๋ณต์„ ํ”ผํ•˜๋ ค๋ฉด subtests์™€ ํ•จ๊ป˜ table-driven tests๋ฅผ ์‚ฌ์šฉํ•ด๋ผ.

BadGood
// func TestSplitHostPort(t *testing.T)

host, port, err := net.SplitHostPort("192.0.2.0:8000")
require.NoError(t, err)
assert.Equal(t, "192.0.2.0", host)
assert.Equal(t, "8000", port)

host, port, err = net.SplitHostPort("192.0.2.0:http")
require.NoError(t, err)
assert.Equal(t, "192.0.2.0", host)
assert.Equal(t, "http", port)

host, port, err = net.SplitHostPort(":8000")
require.NoError(t, err)
assert.Equal(t, "", host)
assert.Equal(t, "8000", port)

host, port, err = net.SplitHostPort("1:8")
require.NoError(t, err)
assert.Equal(t, "1", host)
assert.Equal(t, "8", port)
// func TestSplitHostPort(t *testing.T)

tests := []struct{
  give     string
  wantHost string
  wantPort string
}{
  {
    give:     "192.0.2.0:8000",
    wantHost: "192.0.2.0",
    wantPort: "8000",
  },
  {
    give:     "192.0.2.0:http",
    wantHost: "192.0.2.0",
    wantPort: "http",
  },
  {
    give:     ":8000",
    wantHost: "",
    wantPort: "8000",
  },
  {
    give:     "1:8",
    wantHost: "1",
    wantPort: "8",
  },
}

for _, tt := range tests {
  t.Run(tt.give, func(t *testing.T) {
    host, port, err := net.SplitHostPort(tt.give)
    require.NoError(t, err)
    assert.Equal(t, tt.wantHost, host)
    assert.Equal(t, tt.wantPort, port)
  })
}

ํ…Œ์ŠคํŠธ ํ…Œ์ด๋ธ”์„ ์‚ฌ์šฉํ•˜๋ฉด ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์— ์ปจํ…์ŠคํŠธ๋ฅผ ์‰ฝ๊ฒŒ ์ถ”๊ฐ€ํ•˜๊ณ , ์ค‘๋ณต๋œ ๋กœ์ง์„ ์ค„์ผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์‰ฝ๊ฒŒ ์ƒˆ๋กœ์šด ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

์šฐ๋ฆฌ๋Š” ๊ตฌ์กฐ์ฒด ์Šฌ๋ผ์ด์Šค๋ฅผ tests๋ผ๊ณ  ํ•˜๊ณ , ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ tt๋ผ๊ณ  ํ•œ๋‹ค. ๋˜ํ•œ ๊ฐ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค์˜ ์ž…๋ ฅ ๋ฐ ์ถœ๋ ฅ ๊ฐ’์„ give ๋ฐ want ์ ‘๋‘์–ด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ค๋ช…(explicating)ํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.

tests := []struct{
  give     string
  wantHost string
  wantPort string
}{
  // ...
}

for _, tt := range tests {
  // ...
}

๊ธฐ๋Šฅ์  ์˜ต์…˜ (Functional Options)

๊ธฐ๋Šฅ์  ์˜ต์…˜(functional options)์€ ์ผ๋ถ€ ๋‚ด๋ถ€ ๊ตฌ์กฐ์ฒด (internal struct)์— ์ •๋ณด๋ฅผ ๊ธฐ๋กํ•˜๋Š” ๋ถˆํˆฌ๋ช…ํ•œ Option ํƒ€์ž… (opaque option type)์„ ์„ ์–ธํ•˜๋Š” ํŒจํ„ด์ด๋‹ค. ์—ฌ๋Ÿฌ๋ถ„๋“ค์€ ๋‹ค์–‘ํ•œ ์˜ต์…˜ (variadic number of these options)์„ ๋ฐ›์•„๋“ค์ด๊ณ  ๋‚ด๋ถ€ ๊ตฌ์กฐ์ฒด์˜ ์˜ต์…˜์— ์˜ํ•ด ๊ธฐ๋ก๋œ ๋ชจ๋“  ์ •๋ณด์— ๋”ฐ๋ผ ํ–‰๋™ํ•˜๊ฒŒ ๋œ๋‹ค(act opon the full info. recorded by the options on the internal struct).

ํ™•์žฅ ํ•  ํ•„์š”๊ฐ€ ์žˆ๋Š” ์ƒ์„ฑ์ž(constructors) ๋ฐ ๊ธฐํƒ€ ๊ณต์šฉ API (other public APIs)์˜ ์„ ํƒ์  ์ธ์ˆ˜ (optional arguments), ํŠนํžˆ๋‚˜ ํ•ด๋‹นํ•˜๋Š” ํ•จ์ˆ˜์— ์ด๋ฏธ 3๊ฐœ ์ด์ƒ์˜ ์ธ์ˆ˜๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ์— ์ด ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜๊ธฐ๋ฅผ ๊ถŒ์žฅํ•œ๋‹ค.

BadGood
// package db

func Connect(
  addr string,
  timeout time.Duration,
  caching bool,
) (*Connection, error) {
  // ...
}

// Timeout and caching must always be provided,
// even if the user wants to use the default.

db.Connect(addr, db.DefaultTimeout, db.DefaultCaching)
db.Connect(addr, newTimeout, db.DefaultCaching)
db.Connect(addr, db.DefaultTimeout, false /* caching */)
db.Connect(addr, newTimeout, false /* caching */)
type options struct {
  timeout time.Duration
  caching bool
}

// Option overrides behavior of Connect.
type Option interface {
  apply(*options)
}

type optionFunc func(*options)

func (f optionFunc) apply(o *options) {
  f(o)
}

func WithTimeout(t time.Duration) Option {
  return optionFunc(func(o *options) {
    o.timeout = t
  })
}

func WithCaching(cache bool) Option {
  return optionFunc(func(o *options) {
    o.caching = cache
  })
}

// Connect creates a connection.
func Connect(
  addr string,
  opts ...Option,
) (*Connection, error) {
  options := options{
    timeout: defaultTimeout,
    caching: defaultCaching,
  }

  for _, o := range opts {
    o.apply(&options)
  }

  // ...
}

// Options must be provided only if needed.

db.Connect(addr)
db.Connect(addr, db.WithTimeout(newTimeout))
db.Connect(addr, db.WithCaching(false))
db.Connect(
  addr,
  db.WithCaching(false),
  db.WithTimeout(newTimeout),
)

๋˜ํ•œ, ์•„๋ž˜์˜ ์ž๋ฃŒ๋ฅผ ์ฐธ๊ณ ํ•˜๊ธฐ ๋ฐ”๋ž€๋‹ค:

About

Uber's Go Style Guide Official Translation in Korean. Linked to the uber-go/guide as a part of contributions

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published