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

ben-swit/uber-go-style-guide-kr

ย 
ย 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

33 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)

์Šคํƒ€์ผ์€ ์ฝ”๋“œ๋ฅผ ํ†ต์ œํ•˜๋Š”(govern) ๊ด€์Šต์ด๋‹ค. ์ด๋Ÿฌํ•œ ๊ด€์Šต(convention)์€ ์†Œ์ŠคํŒŒ์ผ ํฌ๋งทํŒ… (e.g. gofmt)๋ณด๋‹ค ๋” ๋งŽ์€ ์˜์—ญ์— ๋Œ€์‘ํ•˜๊ธฐ (cover) ๋•Œ๋ฌธ์—, "์Šคํƒ€์ผ" ์ด๋ผ๋Š” ๋‹จ์–ด ์ž์ฒด๊ฐ€ ์•ฝ๊ฐ„ ๋ถ€์ ์ ˆ ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

๋ณธ ๊ฐ€์ด๋“œ์˜ ๋ชฉํ‘œ๋Š” Uber์—์„œ Go ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ํ•ด์•ผ ํ•  ๊ฒƒ๊ณผ ํ•˜์ง€ ๋ง์•„์•ผ ํ•  ๊ฒƒ (Dos and Don'ts)์— ๋Œ€ํ•˜์—ฌ ์ž์„ธํ•˜๊ฒŒ ์„ค๋ช…ํ•˜์—ฌ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ปจ๋ฒค์…˜์˜ ๋ณต์žก์„ฑ์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ด๋Ÿฐ ๊ทœ์น™๋“ค์€ ์—”์ง€๋‹ˆ์–ด๋“ค์ด Go ์–ธ์–ด์˜ ํŠน์„ฑ์„(feature) ์ƒ์‚ฐ์ ์œผ๋กœ๊ฐœ๊ณ„์† ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ฝ”๋“œ ๋ฒ ์ด์Šค๋ฅผ ๊ด€๋ฆฌ๊ฐ€๋Šฅํ•˜๊ฒŒ ์œ ์ง€ํ•˜๊ธฐ์œ„ํ•ด ์กด์žฌํ•œ๋‹ค.

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

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

  1. Effective Go
  2. The Go common mistakes guide

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

  • Run goimports on save
  • Run golint and go vet to check for errors

์•„๋ž˜์˜ ๋งํฌ๋ฅผ ํ†ตํ•ด์„œ Go ํˆด์„ ์ง€์›ํ•˜๋Š” ์—๋””ํ„ฐ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค: https://github.com/golang/go/wiki/IDEsAndTextEditorPlugins

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

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

์ผ๋ฐ˜์ ์œผ๋กœ ์ธํ„ฐํŽ˜์ด์Šค์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ๋Š” ๊ฑฐ์˜ ํ•„์š”ํ•˜์ง€ ์•Š์„ ๊ฒƒ์ด๋‹ค. ์—ฌ๋Ÿฌ๋ถ„๋“ค์€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ฐ’(value)์œผ๋กœ์„œ ์ „๋‹ฌ(passing)ํ•ด์•ผ ํ•  ๊ฒƒ์ด๋ฉฐ, ์ธํ„ฐํŽ˜์ด์Šค์— ๋Œ€ํ•œ ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ(underlying data)๋Š” ์—ฌ์ „ํžˆ ํฌ์ธํ„ฐ๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค.

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

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

๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„๋“ค์ด ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ(underlying data) ์ˆ˜์ •ํ•˜๊ธฐ ์œ„ํ•œ ์ธํ„ฐํŽ˜์ด์Šค ๋ฉ”์„œ๋“œ (interface methods)๋ฅผ ์›ํ•œ๋‹ค๋ฉด, ๋ฐ˜๋“œ์‹œ ํฌ์ธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

์ˆ˜์‹ ์ž(Receivers)์™€ ์ธํ„ฐํŽ˜์ด์Šค(Interfaces)

๊ฐ’ ์ˆ˜์‹ ์ž (value receivers)์™€ ๋ฉ”์„œ๋“œ(Methods)๋Š” ํฌ์ธํ„ฐ ํ˜น์€ ๊ฐ’์— ์˜ํ•ด์„œ ํ˜ธ์ถœ ๋  ์ˆ˜ ์žˆ๋‹ค.

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

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"}}

// ์˜ค์ง ๊ฐ’๋งŒ ์‚ฌ์šฉํ•˜์—ฌ Read๋ฅผ ํ˜ธ์ถœ ํ•  ์ˆ˜ ์žˆ๋‹ค.
sVals[1].Read()

// ์•„๋ž˜ ์ฝ”๋“œ๋Š” ์ปดํŒŒ์ผ ๋˜์ง€ ์•Š์„ ๊ฒƒ:
//  sVals[1].Write("test")

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

// ํฌ์ธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Read์™€ Write ๋ชจ๋‘ ํ˜ธ์ถœ ํ•  ์ˆ˜ ์žˆ๋‹ค.
sPtrs[1].Read()
sPtrs[1].Write("test")

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ, ๋ฉ”์„œ๋“œ๊ฐ€ ๊ฐ’ ์ˆ˜์‹ ์ž(value receiver)๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  ํ•˜๋”๋ผ๋„ ํฌ์ธํ„ฐ๊ฐ€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ถฉ์กฑ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

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

// s2Val์ด ๊ฐ’์ด๊ณ  f์— ๋Œ€ํ•œ ์ˆ˜์‹ ์ž๊ฐ€ ์—†๊ธฐ ๋•Œ๋ฌธ์—, ์•„๋ž˜์˜ ์ฝ”๋“œ๋Š” ์ปดํŒŒ์ผ ๋˜์ง€ ์•Š๋Š”๋‹ค.
//   i = s2Val

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

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

sync.Mutex์™€ sync.RWMutex ์˜ ์ œ๋กœ ๊ฐ’์€ ์œ ํšจํ•˜๋ฏ€๋กœ, ๊ฑฐ์˜ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๋ฎคํ…์Šค์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ๋Š” ํ•„์š”๋กœ ํ•˜์ง€ ์•Š๋Š”๋‹ค.

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

ํฌ์ธํ„ฐ๋กœ ๊ตฌ์กฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ, ๋ฎคํ…์Šค๋Š” ํฌ์ธํ„ฐ๊ฐ€ ์•„๋‹Œ ํ•„๋“œ(non-pointer field)๊ฐ€ ๋  ์ˆ˜ ์žˆ๋‹ค.

๊ตฌ์กฐ์ฒด์˜ ํ•„๋“œ๋ฅผ ๋ณดํ˜ธํ•˜๊ธฐ ์œ„ํ•ด ๋ฎคํ…์Šค๋ฅผ ์‚ฌ์šฉํ•œ ์ˆ˜์ถœ๋˜์ง€ ์•Š๋Š” ๊ตฌ์กฐ์ฒด(unexported structs)๋Š” ๋ฎคํ…์Šค๋ฅผ ํฌํ•จ(embed) ํ•  ์ˆ˜ ์žˆ๋‹ค.

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]
}
๋ฎคํ…์Šค ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” ์ „์šฉ ํƒ€์ž…(private type) ํ˜น์€ ํƒ€์ž…์— ํฌํ•จ๋จ. ์ˆ˜์ถœ๋˜๋Š” ํƒ€์ž…(exported type)์— ๋Œ€ํ•ด์„œ๋Š” ์ „์šฉ ํ•„๋“œ (private field)๋ฅผ ์‚ฌ์šฉํ•จ.

์Šฌ๋ผ์ด์Šค ๋ณต์‚ฌ(Copy Slices)์™€ ๋ฐ”์šด๋”๋ฆฌ ์—์„œ์˜ ๋งต(Maps at Boundaries)

์Šฌ๋ผ์ด์Šค(Slices)์™€ ๋งต(maps)์€ ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ(underlying data)์— ๋Œ€ํ•œ ํฌ์ธํ„ฐ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ ์ด๋“ค์„ ๋ณต์‚ฌ ํ•ด์•ผ ํ•  ๋•Œ์˜ ์ƒํ™ฉ(scenarios)์— ๋Œ€ํ•ด์„œ ์ฃผ์˜ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค.

Slices์™€ Maps์˜ ์ˆ˜์‹ (receiving)

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

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

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

// d1.trips์„ ์ˆ˜์ •ํ•  ๊ฒƒ์„ ์˜๋ฏธํ•˜๋Š”๊ฐ€?
trips[0] = ...
func (d *Driver) SetTrips(trips []Trip) {
  d.trips = make([]Trip, len(trips))
  copy(d.trips, trips)
}

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

// ์ด์ œ d1.trips์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๊ณ ์„œ trips[0]์„ ์ˆ˜์ • ํ•  ์ˆ˜ ์žˆ๋‹ค.
trips[0] = ...

์Šฌ๋ผ์ด์Šค(Slices)์™€ ๋งต(Maps)์˜ ๋ฆฌํ„ด

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

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

// Snapshot์€ ํ˜„์žฌ์˜ stats์„ ๋ฐ˜ํ™˜(return)ํ•œ๋‹ค
func (s *Stats) Snapshot() map[string]int {
  s.mu.Lock()
  defer s.mu.Unlock()

  return s.counters
}

// snapshot์€ ๋”์ด์ƒ ๋ฎคํ…์Šค์— ์˜ํ•ด์„œ ๋ณดํ˜ธ๋˜์ง€ ์•Š๋Š”๋‹ค.
// ๋”ฐ๋ผ์„œ, snapshot์— ๋Œ€ํ•œ ์ ‘๊ทผ์€ ๋ ˆ์ด์Šค ์ปจ๋””์…˜์„ ์•ผ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค.
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๋Š” ์นดํ”ผ(copy)๋‹ค.
snapshot := stats.Snapshot()

Defer์—์„œ Clean Up๊นŒ์ง€

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

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

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

return newCount

// ์—ฌ๋Ÿฌ๊ฐœ์˜ return์œผ๋กœ ์ธํ•ด์„œ Unlockํ˜ธ์ถœ์„ ๋†“์น˜๊ธฐ ์‰ฌ์›€
p.Lock()
defer p.Unlock()

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

p.count++
return p.count

// ๋” ๋‚˜์€ ๊ฐ€๋…์„ฑ 

defer๋Š” ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์ƒ๋‹นํžˆ ์ž‘์œผ๋ฉฐ ํ•จ์ˆ˜ ์‹คํ–‰ ์‹œ๊ฐ„์ด ๋‚˜๋…ธ์ดˆ ๋‹จ์œ„์ž„์„ ์ฆ๋ช…ํ•  ์ˆ˜ ์žˆ์„ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹Œ ์ด์ƒ ํ”ผํ•˜์ง€ ์•Š๊ณ  ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. defer์˜ ์‚ฌ์šฉ์œผ๋กœ ์ธํ•œ ๊ฐ€๋…์„ฑ์˜ ์ด์ ์œผ๋กœ ์ธํ•˜์—ฌ ์ง€์—ฐ์„ ์‚ฌ์šฉํ•˜๋Š” ๋น„์šฉ์€ ์ ๋‹ค. ๊ฐ„๋‹จํ•œ ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ(simple memory accesses)์ด์ƒ์„ ๊ฐ€์ง€๋Š” ๊ฑฐ๋Œ€ํ•œ ๋ฉ”์„œ๋กœ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, ๋‹ค๋ฅธ ๊ณ„์‚ฐ์ด 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 parameteres๋Š” ๊ฐ€๋…์„ฑ์„ ๋–จ์–ด ๋œจ๋ฆด ์ˆ˜ ์žˆ๋‹ค. ์˜๋ฏธ๊ฐ€ ๋ช…ํ™•ํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ, 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