• Stars
    star
    203
  • Rank 192,890 (Top 4 %)
  • Language
  • License
    Apache License 2.0
  • Created about 5 years ago
  • Updated 11 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

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

์—๋Ÿฌ ํ˜•(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()
}

์„ฑ๋Šฅ(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),
)

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