-
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-style-guide-kr
- Uber์ Go์ธ์ด ์คํ์ผ ๊ฐ์ด๋ (Uber's Go Style Guide)
- ์๊ฐ (Introduction)
- ๊ฐ์ด๋๋ผ์ธ (Guidelines)
- ์ธํฐํ์ด์ค์ ๋ํ ํฌ์ธํฐ (Pointers to Interfaces)
- ์์ ์(Receivers)์ ์ธํฐํ์ด์ค(Interfaces)
- ์ ๋ก ๊ฐ ๋ฎคํ ์ค(Zero-value Mutexes)๋ ์ ํจํ๋ค
- ์ฌ๋ผ์ด์ค ๋ณต์ฌ(Copy Slices)์ ๋ฐ์ด๋๋ฆฌ ์์์ ๋งต(Maps at Boundaries)
- Defer์์ Clean Up๊น์ง
- ์ฑ๋์ ํฌ๊ธฐ(Channel Size)๋ ํ๋(One) ํน์ ์ ๋ก(None)
- Enums์ 1์์๋ถํฐ ์์ํ๋ผ
- ์๋ฌ ํ(Error Types)
- ์ค๋ฅ ๋ํ(Error Wrapping)
- ํ์ ์ ์ด์ค์ ์คํจ ๋ค๋ฃจ๊ธฐ (Handle Type Assertion Failures)
- ํจ๋์ ํผํ ๊ฒ (Don't Panic)
- go.uber.org/atomic์ ์ฌ์ฉ
- ์ฑ๋ฅ(Performance)
- ์คํ์ผ (Style)
- ๊ทธ๋ฃน ์ ์ฌ ์ ์ธ (Group Similar Declarations)
- Import ๊ทธ๋ฃน ์ ๋ฆฌ/๋ฐฐ์น (Import Group Ordering)
- ํจํค์ง ์ด๋ฆ (Package Names)
- ํจ์ ์ด๋ฆ (Function Names)
- Import ๋ณ์นญ (Import Aliasing)
- ํจ์ ๊ทธ๋ฃนํ์ ์ ๋ ฌ/๋ฐฐ์น (Function Grouping and Ordering)
- ์ค์ฒฉ ๊ฐ์ (Reduce Nesting)
- ๋ถํ์ํ else (Unnecessary Else)
- ์ต์์ ๋ณ์ ์ ์ธ (Top-level Variable Declarations)
- ์์ถ๋์ง ์์ ์ ์ญ์ _์ ๋ถ์ฌ๋ผ (Prefix Unexported Globals with _)
- ๊ตฌ์กฐ์ฒด์์์ ์๋ฒ ๋ฉ (Embedding in Structs)
- ๊ตฌ์กฐ์ฒด ์ด๊ธฐํ๋ฅผ ์ํด ํ๋์ ์ฌ์ฉํด๋ผ (Use Field Names to initialize Structs)
- ์ง์ญ ๋ณ์ ์ ์ธ (Local Variable Declarations)
- nil์ ์ ํจํ ์ฌ๋ผ์ด์ค (nil is a valid slice)
- ๋ณ์์ ๋ฒ์๋ฅผ ์ค์ฌ๋ผ (Reduce Scope of Variables)
- Naked ๋งค๊ฐ๋ณ์๋ฅผ ํผํด๋ผ (Avoid Naked Parameters)
- ์ด์ค์ผ์ดํ์ ํผํ๊ธฐ ์ํด ์์ ๋ฌธ์ ๋ฆฌํฐ๋ด ์ฌ์ฉ (Use Raw String Literals to Avoid Escaping)
- ๊ตฌ์กฐ์ฒด ์ฐธ์กฐ ์ด๊ธฐํ (Initializing Struct References)
- Printf์ธ๋ถ์ ๋ฌธ์์ด ํ์ (Format Strings outside Printf)
- Printf-์คํ์ผ ํจ์์ ์ด๋ฆ (Naming Printf-style Functions)
- ํจํด (Patterns)
์คํ์ผ์ ์ฝ๋๋ฅผ ํต์ ํ๋(govern) ๊ด์ต์ด๋ค. ์ด๋ฌํ ๊ด์ต(convention)์ ์์คํ์ผ ํฌ๋งทํ (e.g. gofmt)๋ณด๋ค ๋ ๋ง์ ์์ญ์ ๋์ํ๊ธฐ (cover) ๋๋ฌธ์, "์คํ์ผ" ์ด๋ผ๋ ๋จ์ด ์์ฒด๊ฐ ์ฝ๊ฐ ๋ถ์ ์ ํ ์๋ ์๋ค.
๋ณธ ๊ฐ์ด๋์ ๋ชฉํ๋ Uber์์ Go ์ฝ๋๋ฅผ ์์ฑํ ๋ ํด์ผ ํ ๊ฒ๊ณผ ํ์ง ๋ง์์ผ ํ ๊ฒ (Dos and Don'ts)์ ๋ํ์ฌ ์์ธํ๊ฒ ์ค๋ช ํ์ฌ ํ๋ก๊ทธ๋๋ฐ ์ปจ๋ฒค์ ์ ๋ณต์ก์ฑ์ ๊ด๋ฆฌํ๋ ๊ฒ์ด๋ค. ์ด๋ฐ ๊ท์น๋ค์ ์์ง๋์ด๋ค์ด Go ์ธ์ด์ ํน์ฑ์(feature) ์์ฐ์ ์ผ๋ก๊ฐ๊ณ์ ์ฌ์ฉํ ์ ์๋๋ก ์ฝ๋ ๋ฒ ์ด์ค๋ฅผ ๊ด๋ฆฌ๊ฐ๋ฅํ๊ฒ ์ ์งํ๊ธฐ์ํด ์กด์ฌํ๋ค.
์ด ๊ฐ์ด๋๋ ์๋ Prashant Varanasi์ Simon Newton์ด ๋๋ฃ๋ค์๊ฒ Go๋ฅผ ์ฌ์ฉํ๋ฉด์ ๊ฐ๋ฐ์๋ ํฅ์์ ๋๋ชจํ๊ธฐ ์ํด ์๊ฐ๋์๋ค. ๋ํ, ์ ๋ ์ ๊ฑฐ์ณ์ ๋ค๋ฅธ ์ฌ๋๋ค๋ก๋ถํฐ์ ํผ๋๋ฐฑ์ ํตํด์ ๊ฐ์ ๋ ์ค๊ณ ์๋ค.
์ด ๋ฌธ์๋ Uber์์์ ์์ง๋์ด๋ค์ด ์งํฅํ๋ Go์ธ์ด ์ฝ๋์ ๊ด์ฉ์ ๊ท์น์ ์ค๋ช ํ๋ค. ์๋น ์์ ๊ท์น๋ค์ Go์ธ์ด์ ๋ํ ์ผ๋ฐ์ ์ธ ๊ฐ์ด๋๋ผ์ธ์ด๋ฉฐ, ๋ค๋ฅธ ๋ถ๋ถ์ ๋ํด์๋ ์ธ๋ถ ๋ ํผ๋ฐ์ค์ ์ํด ํ์ฅ๋๋ค (์๋ ์ฐธ๊ณ )
๋ชจ๋ ์ฝ๋๋ golint
์ go vet
๋ฅผ ์คํํ ๋ ์๋ฌ๊ฐ ์์ด์ผ ํ๋ค. ๋ํ ์ฐ๋ฆฌ๋ ์ฌ๋ฌ๋ถ๋ค์ ์๋ํฐ๋ฅผ ์๋์ ๊ฐ์ด ์ค์ ํ๊ธฐ๋ฅผ ๊ถ๊ณ ํ๋ค:
- Run
goimports
on save - Run
golint
andgo vet
to check for errors
์๋์ ๋งํฌ๋ฅผ ํตํด์ Go ํด์ ์ง์ํ๋ ์๋ํฐ์ ๋ํ ์ ๋ณด๋ฅผ ์ป์ ์ ์๋ค: https://github.com/golang/go/wiki/IDEsAndTextEditorPlugins
์ผ๋ฐ์ ์ผ๋ก ์ธํฐํ์ด์ค์ ๋ํ ํฌ์ธํฐ๋ ๊ฑฐ์ ํ์ํ์ง ์์ ๊ฒ์ด๋ค. ์ฌ๋ฌ๋ถ๋ค์ ์ธํฐํ์ด์ค๋ฅผ ๊ฐ(value)์ผ๋ก์ ์ ๋ฌ(passing)ํด์ผ ํ ๊ฒ์ด๋ฉฐ, ์ธํฐํ์ด์ค์ ๋ํ ๊ธฐ๋ณธ ๋ฐ์ดํฐ(underlying data)๋ ์ฌ์ ํ ํฌ์ธํฐ๊ฐ ๋ ์ ์๋ค.
ํ๋์ ์ธํฐํ์ด์ค๋ ๋ ๊ฐ์ง ํ๋์ด๋ค:
- ํ์ -ํน์ ์ ๋ณด(type-specific information)์ ๋ํ ํฌ์ธํฐ. ์ฌ๋ฌ๋ถ๋ค์ ์ด๊ฒ์ "ํ์ "์ผ๋ก ๊ฐ์ฃผํ ์ ์๋ค.
- ๋ฐ์ดํฐ ํฌ์ธํฐ. ์ ์ฅ๋ ๋ฐ์ดํฐ๊ฐ ํฌ์ธํฐ์ผ ๊ฒฝ์ฐ, ์ด๊ฒ์ ์ง์ ์ ์ผ๋ก ์ ์ฅ๋ ์ ์๋ค. ๋ง์ฝ, ์ ์ฅ๋ ๋ฐ์ดํฐ๊ฐ ๊ฐ(value)์ธ ๊ฒฝ์ฐ, ๊ฐ์ ๋ํ ํฌ์ธํฐ๊ฐ ์ ์ฅ๋๋ค.
๋ง์ฝ ์ฌ๋ฌ๋ถ๋ค์ด ๊ธฐ๋ณธ ๋ฐ์ดํฐ(underlying data) ์์ ํ๊ธฐ ์ํ ์ธํฐํ์ด์ค ๋ฉ์๋ (interface methods)๋ฅผ ์ํ๋ค๋ฉด, ๋ฐ๋์ ํฌ์ธํฐ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
๊ฐ ์์ ์ (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์ ๋ํ ์ข์ ๊ธ์ด ์์ผ๋ ์ฐธ๊ณ ํ๊ธฐ ๋ฐ๋๋ค.
sync.Mutex
์ sync.RWMutex
์ ์ ๋ก ๊ฐ์ ์ ํจํ๋ฏ๋ก, ๊ฑฐ์ ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๋ฎคํ
์ค์ ๋ํ ํฌ์ธํฐ๋ ํ์๋ก ํ์ง ์๋๋ค.
Bad | Good |
---|---|
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)๋ฅผ ์ฌ์ฉํจ. |
์ฌ๋ผ์ด์ค(Slices)์ ๋งต(maps)์ ๊ธฐ๋ณธ ๋ฐ์ดํฐ(underlying data)์ ๋ํ ํฌ์ธํฐ๋ฅผ ํฌํจํ๊ณ ์์ผ๋ฏ๋ก ์ด๋ค์ ๋ณต์ฌ ํด์ผ ํ ๋์ ์ํฉ(scenarios)์ ๋ํด์ ์ฃผ์ํ ํ์๊ฐ ์๋ค.
์ฐธ์กฐ/๋ ํผ๋ฐ์ค(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] = ... |
๋ง์ฐฌ๊ฐ์ง๋ก, ๋ด๋ถ ์ํ(internal status)๋ฅผ ๋ ธ์ถ์ํค๋ ์ฌ๋ผ์ด์ค๋ ๋งต์ ๋ํ ์ฌ์ฉ์์ ์์ ์ ์ฃผ์ํ๋ผ.
Bad | Good |
---|---|
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
๋ฅผ ์ฌ์ฉํด์ฌ ํ์ผ(files)๊ณผ ์ ๊ธ(locks)๊ณผ ๊ฐ์ ๋ฆฌ์์ค๋ฅผ ์ ๋ฆฌํ๋ผ.
Bad | Good |
---|---|
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
๋ณด๋ค ๋ ์ค์ํ๋ค.
์ฑ๋์ ํฌ๊ธฐ๋ ์ผ๋ฐ์ ์ผ๋ก 1 ์ด๊ฑฐ๋ ํน์ ๋ฒํผ๋ง ๋์ง ์์์ผ ํ๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก, ์ฑ๋์ ๋ฒํผ๋ง๋์ง ์์ผ๋ฉฐ ํฌ๊ธฐ๋ 0์ด๋ค. 0 ์ด์ธ์ ๋ค๋ฅธ ํฌ๊ธฐ๋ ๋์ ์์ค์ ์ฒ ์ ํ ๊ฒํ ํน์ ์ ๋ฐ์กฐ์ฌ(scrutiny)๋ฅผ ๋ฐ์์ผ ํ๋ค. ์ด๋ป๊ฒ ํฌ๊ธฐ๋ฅผ ๊ฒฐ์ (determined)ํ ์ง ๊ณ ๋ คํ๋ผ. ๋ฌด์์ด ์ฑ๋์ด ๋ก๋ํ ๊ฒฝ์ฐ ๊ฐ๋ ์ฐจ๊ฑฐ๋ writer๊ฐ ๋งํ๋(blocked) ๊ฒ์ ์๋ฐฉํ๋์ง ๊ทธ๋ฆฌ๊ณ ์ด๋ฌํ ๊ฒ์ด ๋ฐ์ํ ๊ฒฝ์ฐ ์ด๋ค ์ผ์ด ์ผ์ด๋ ์ง ์ถฉ๋ถํ ์๊ฐํด์ผ ํ๋ค.
Bad | Good |
---|---|
// ๋๊ตฌ์๊ฒ๋ ์ถฉ๋ถํ๋ค!
c := make(chan int, 64) |
// ์ฌ์ด์ฆ 1
c := make(chan int, 1) // ํน์
// ๋ฒํผ๋ง ๋์ง ์๋ ์ฑ๋, ์ฌ์ด์ฆ 0
c := make(chan int) |
Go์์ ์ด๊ฑฐํ(enumerations)์ ๋์
ํ๋ ์ผ๋ฐ์ ๋ฐฉ์(standard way)์ ์ฌ์ฉ์์ ์ํ(a custom type) ๊ทธ๋ฆฌ๊ณ const
๊ทธ๋ฃน์ iota
์ ํจ๊ป ์ ์ ์ธ(declare)ํ๋ ๊ฒ์ด๋ค.
๋ณ์์ ๊ธฐ๋ณธ๊ฐ(default value)๋ 0์ด๊ธฐ ๋๋ฌธ์, ์ฌ๋ฌ๋ถ๋ค์ ์ผ๋ฐ์ ์ผ๋ก ์ด๊ฑฐํ์ 0์ด ์๋ ๊ฐ(non-zero value)๋ก ์์ํด์ผ ํ๋ค.
Bad | Good |
---|---|
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
์๋ฌ๋ฅผ ์ ์ธํ๋๋ฐ ์์ด์ ๋ค์ํ ์ต์ ๋ค์ด ์กด์ฌํ๋ค:
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
์ ์๋ฌ๋ฅผ ์ฌ์ฉํด๋ผ.
Bad | Good |
---|---|
// 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")
}
} |
๋ง์ฝ ํด๋ผ์ด์ธํธ๊ฐ ๊ฐ์งํด์ผ ํ ์ค๋ฅ๊ฐ ์๊ณ ์ฌ๋ฌ๋ถ๋ค์ด ์ด๋ฅผ ์ถ๊ฐํ๋ ค๊ณ ํ๋ ๊ฒฝ์ฐ, ๊ทธ๊ฒ์ ๋ํ ์์ธํ ์ ๋ณด๋ฅผ ์ถ๊ฐํ๊ณ ์ถ์ ๊ฒ์ด๋ค. (์๋ฅผ๋ค์ด, ์ ์ ๋ฌธ์์ด์ด ์๋ ๊ฒฝ์ฐ), ์ด๋ฌํ ๊ฒฝ์ฐ, ์ฌ๋ฌ๋ถ๋ค์ ์ปค์คํ ํ์ ์ ์ฌ์ฉํด์ผ ํ๋ค.
Bad | Good |
---|---|
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")
}
}
ํธ์ถ์ด ์คํจํ ๊ฒฝ์ฐ ์๋ฌ๋ฅผ ์ ํ(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) ๊ณ์ํด์ ์์ด๊ฒ ๋๋ค:
Bad | Good |
---|---|
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)
} |
|
|
๊ทธ๋ฌ๋, ์ผ๋จ ์ค๋ฅ๊ฐ ๋ค๋ฅธ ์์คํ
์ผ๋ก ์ ์ก๋๋ฉด, ๊ทธ ๋ฉ์์ง๊ฐ ์ค๋ฅ์์ ๋ถ๋ช
ํ ํด์ผ ํ๋ค. (์๋ฅผ๋ค์ด err
ํ๊ทธ(tag) ํน์ ๋ก๊ทธ์์์ "Failed" ์ ๋์ฌ ์ฌ์ฉ)
๋ํ ๋ค์์ ๊ธ์ ์ฐธ๊ณ ํ๋ผ: Don't just check errors, handle them gracefully.
type assertion์ ๋จ์ผ ๋ฐํ ๊ฐ ํ์(the single return value form)์ ์๋ชป๋ ํ์ ์ ํจ๋ ์ํ๊ฐ ๋๋ค. ๋ฐ๋ผ์ ํญ์ "comma ok" ๊ด์ฉ๊ตฌ(idiom)์ ์ฌ์ฉํ๋ ๊ฒ์ ๊ถ์ฅํ๋ค.
Bad | Good |
---|---|
t := i.(string) |
t, ok := i.(string)
if !ok {
// handle the error gracefully
} |
ํ๋ก๋์ ํ๊ฒฝ์์ ์คํ๋๋ ์ฝ๋๋ ํจ๋์ ๋ฐ๋์ ํผํด์ผ ํ๋ค. ํจ๋์ cascading failures์ ์ฃผ์ ์์ธ์ด๋ค. ๋ง์ฝ ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ, ํจ์๋ ์๋ฌ๋ฅผ ๋ฆฌํดํ๊ณ ํธ์ถ์(caller)๊ฐ ์ค๋ฅ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ์ ๊ฒฐ์ ํ ์ ์๋๋ก ํด์ผ ํ๋ค.
Bad | Good |
---|---|
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
๊ฐ ์ ํธ๋๋ค.
Bad | Good |
---|---|
// 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")
} |
sync/atomic ํจํค์ง๋ฅผ ์ฌ์ฉํ ์ํ ๋ฏน ์ฐ์ฐ(atomic operation)์ ์์ ํ์
(raw type: e.g. int32
, int64
, etc.)์์ ์๋ํ๋ฏ๋ก, ์ํ ๋ฏน ์ฐ์ฐ์ ์ฌ์ฉํ์ฌ ๋ณ์๋ฅผ ์ฝ๊ฑฐ๋ ์์ ํ๋ ๊ฒ์ ์ฝ๊ฒ ์์ด๋ฒ๋ฆด ์ ์๋ค.
go.uber.org/atomic๋ ๊ธฐ๋ณธ ํ์
(underlying type)์ ์จ๊ฒจ์ ์ด๋ฐ ์ ํ์ ์ฐ์ฐ์ ํ์
์์ ์ฑ์ ๋ถ์ฌํ๋ค(add type safety). ๋ํ, ์ด๋ ๊ฐํธํ atomic.Bool
ํ์
์ ํฌํจํ๊ณ ์๋ค.
Bad | Good |
---|---|
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-specific)๊ฐ์ด๋๋ผ์ธ์ ์ฑ๋ฅ์ ๋ฏผ๊ฐํ(hot path) ๊ฒฝ์ฐ์๋ง ์ ์ฉ๋๋ค.
ํ๋ฆฌ๋ฏธํฐ๋ธ(primitives)๋ฅผ ๋ฌธ์์ด๋ก / ๋ฌธ์์ด์์ ๋ณํ ํ ๋, strconv
๊ฐ fmt
๋ณด๋ค ๋น ๋ฅด๋ค.
fmt
.
Bad | Good |
---|---|
for i := 0; i < b.N; i++ {
s := fmt.Sprint(rand.Int())
} |
for i := 0; i < b.N; i++ {
s := strconv.Itoa(rand.Int())
} |
|
|
๊ณ ์ ๋ฌธ์์ด(fixed string)์์ ๋ฐ์ดํธ ์ฌ๋ผ์ด์ค(byte slices)๋ฅผ ๋ฐ๋ณตํด์ ์์ฑํ์ง ๋ง๋ผ. ๋์ ๋ณํ(conversion)์ ํ๋ฒ ์คํํ๊ณ , ๊ฒฐ๊ณผ๋ฅผ ์บก์ณํด๋ผ.
Bad | Good |
---|---|
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)
} |
|
|
Go๋ ์ ์ฌํ ์ ์ธ ๊ทธ๋ฃนํ๋ฅผ ์ง์ํ๋ค.
Bad | Good |
---|---|
import "a"
import "b" |
import (
"a"
"b"
) |
์ด๋ ๋ํ ์์, ๋ณ์, ๊ทธ๋ฆฌ๊ณ ํ์ ์ ์ธ์์๋ ์ ํจํ๋ค.
Bad | Good |
---|---|
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
) |
์ค์ง ๊ด๋ จ๋ ์ ์ธ๋ง ๊ทธ๋ฃนํ ํ ๊ฒ. ๊ด๋ จ๋์ง ์์ ์ ์ธ๋ค์ ๋ํด์๋ ๊ทธ๋ฃนํ ํ์ง ๋ง๊ฒ.
Bad | Good |
---|---|
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" |
๊ทธ๋ฃนํ๋ฅผ ์ฌ์ฉํ๋ ์ฅ์๋ ์ ํ๋์ด ์์ง ์๋ค. ์๋ฅผ ๋ค์ด, ํจ์ ๋ด์์๋ ๊ทธ๋ฃนํ๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
Bad | Good |
---|---|
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)
)
...
} |
2๊ฐ์ง import ๊ทธ๋ฃน๋ค์ด ์กด์ฌํ๋ค:
- ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ (Standard library)
- ๊ทธ ์ธ ๋ชจ๋ ๊ฒ (Everything else)
์ด๋ ๊ธฐ๋ณธ(default)์ผ๋ก goimports
์ ์ํด์ ์ ์ฉ๋๋ ๊ทธ๋ฃน๋ค์ด๋ค.
Bad | Good |
---|---|
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
) |
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
) |
ํจํค์ง ์ด๋ฆ์ ์ ํ ๋, ์๋์ ๊ฐ์ ์ด๋ฆ์ ์ ํํ๋ผ:
- ๋ชจ๋ ์ํ๋ฒณ ์๋ฌธ์ ์ฌ์ฉ, ๋๋ฌธ์์ ์ธ๋์ค์ฝ์ด (_)๋ ์ฌ์ฉํ์ง ๋ง ๊ฒ.
- ๋๋ถ๋ถ์ ํธ์ถ ์ง์ (call sites)์์ named import๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ๋ช ๋ช (renamed)์ ํ ํ์๊ฐ ์๋ค.
- ์งง๊ณ ๊ฐ๊ฒฐํ๊ฒ. ์ด๋ฆ(name)์ ๋ชจ๋ ํธ์ถ ์ง์ (call site)์์ ์๋ณ๋จ์ ์๊ธฐํ๋ผ.
- ๋ณต์ํ(plural) ์ฌ์ฉ ๊ธ์ง. ์๋ฅผ ๋ค์ด,
net/urls
๊ฐ ์๋net/url
. - "common", "util", "shared", ๋๋ "lib"์ ์ฉ์ด ์ฌ์ฉ ๊ธ์ง. ์ ๋ณด๊ฐ ์๋ ์ข์ง ๋ชปํ ์ด๋ฆ์.
๋ํ Package Names ์ Style guideline for Go packages๋ฅผ ์ฐธ๊ณ ํ๊ธฐ ๋ฐ๋๋ค.
์ฐ๋ฆฌ๋ Go ์ปค๋ฎค๋ํฐ์ MixedCaps for function names์ ์ฌ์ฉ์ ์ํ ์ปจ๋ฒค์
์ ๋ฐ๋ฅธ๋ค. ํ
์คํธ ํจ์(test functions)๋ ์์ธ์ด๋ค. ์ด๋ ๊ด๋ จ ํ
์คํธ์ผ์ด์ค๋ฅผ ๊ทธ๋ฃนํ ํ ๋ชฉ์ ์ผ๋ก ์ธ๋์ค์ฝ์ด(_)๋ฅผ ํฌํจํ ์ ์๋ค, ์๋ฅผ๋ค์ด, TestMyFunction_WhatIsBeingTested
.
ํจํค์ง ์ด๋ฆ์ด import path์ ๋ง์ง๋ง ์์์ ์ผ์นํ์ง ์์ ๊ฒฝ์ฐ ๋ณ๋ช ์ ์ฌ์ฉํด์ผ ํ๋ค.
import (
"net/http"
client "example.com/client-go"
trace "example.com/trace/v2"
)
๋ค๋ฅธ ๋ชจ๋ ์๋๋ฆฌ์ค์ ๊ฒฝ์ฐ, import ๋ณ์นญ์ ์ฌ์ฉ์ importํ๋ฉด์ ๋ import๊ฐ ์ง์ ์ ์ถฉ๋(import direct conflict)์ด ๋ฐ์ํ์ง ์๋ ํ ์ง์ํด์ผ ํ๋ค.
Bad | Good |
---|---|
import (
"fmt"
"os"
nettrace "golang.net/x/trace"
) |
import (
"fmt"
"os"
"runtime/trace"
nettrace "golang.net/x/trace"
) |
- ํจ์๋ ๋๋ต์ ํธ์ถ ์์์ ์ํด์ ์ ๋ ฌ๋์ด์ผ ํ๋ค.
- ํ์ผ๋ด์์์ ํจ์๋ ๋ฆฌ์๋ฒ์ ์ํด์ ๊ทธ๋ฃน์ง์ด์ ธ์ผ ํ๋ค.
๊ทธ๋ฌ๋ฏ๋ก, ์์ถ๋๋ ํจ์ (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)๋ ํ์ผ์ ๋ท๋ถ๋ถ์ ๋ํ๋์ผ ํ๋ค.
Bad | Good |
---|---|
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 {...} |
์ฝ๋๋ ์๋ฌ ์ผ์ด์ค ํน์ ํน์ ์กฐ๊ฑด(error cases / special conditions)์ ๋จผ์ ์ฒ๋ฆฌํ๊ณ ๋ฃจํ๋ฅผ ์ผ์ฐ ๋ฆฌํดํ๊ฑฐ๋ ๊ณ์ ์ง์ํจ์ผ๋ก์จ ๊ฐ๋ฅํ ์ค์ฒฉ(nesting)์ ์ค์ผ ์ ์์ด์ผ ํ๋ค. ์ฌ๋ฌ ๋ ๋ฒจ๋ก ์ค์ฒฉ๋(nested multiple levels)์ฝ๋์ ์์ ์ค์ด๋๋ก ํด๋ผ.
Bad | Good |
---|---|
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()
} |
๋ณ์๊ฐ if์ ๋ ๊ฐ์ง ๋ถ๊ธฐ๋ฌธ์ ์ํด์ ์ค์ ๋ ๊ฒฝ์ฐ, ์ด๋ ๋จ์ผ if
๋ฌธ (simple if)์ผ๋ก ๋์ฒด ํ ์ ์๋ค.
Bad | Good |
---|---|
var a int
if b {
a = 100
} else {
a = 10
} |
a := 10
if b {
a = 100
} |
์ต์์ ๋ ๋ฒจ์์ (At the top level), ํ์ค var
ํค์๋๋ฅผ ์ฌ์ฉํด๋ผ. ํํ์(expression)r๊ณผ๊ฐ์ ๊ฐ์ ํ์
์ด ์๋ ์ด์, ํ์
์ ํน์ ์ง์ง ๋ง๋ผ.
Bad | Good |
---|---|
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
์์ถ๋์ง ์์ ์ต์์(top-level) var
์ const
์ ์ ๋์ฌ _
๋ฅผ ๋ถ์์ผ๋ก์จ ๊ทธ๋ค์ด ์ฌ์ฉ๋ ๋, ์ ์ญ ๊ธฐํธ(global symbols)์์ ๋ช
ํํ๊ฒ ํด๋ผ.
์์ธ: ์์ถ๋์ง ์๋ ์๋ฌ ๊ฐ (Unexported error values)์ err
์ ์ ๋์ฌ๋ฅผ ๊ฐ์ ธ์ผ ํ๋ค.
์ด์ : ์ต์์ ๋ณ์ ๋ฐ ์์ (Top-level variables and constants)๋ ํจํค์ง ๋ฒ์(package scope)๋ฅผ ๊ฐ์ง๋ค. ์ ๋ค๋ฆญ ์ด๋ฆ(generic names)์ ์ฌ์ฉ ํ๋ ๊ฒ์ ๋ค๋ฅธ ํ์ผ์์ ์๋ชป๋ ๊ฐ์ ์ค์๋ก ์ฝ๊ฒ ์ฌ์ฉ ํ ์ ์๋ค.
Bad | Good |
---|---|
// 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"
) |
๋ฎคํ ์ค์ ๊ฐ์ ์๋ฒ ๋๋ ํ์ ์ ๊ตฌ์กฐ์ฒด์ ํ๋ ๋ชฉ๋ก ๊ฐ์ฅ ์์์ธต์ ์์ด์ผ ํ๊ณ , ์๋ฒ ๋ ๋ ํ๋๋ฅผ ์ผ๋ฐ ํ๋์ ๋ถ๋ฆฌํ๋ empty line์ด ์์ด์ผ ํ๋ค.
Bad | Good |
---|---|
type Client struct {
version int
http.Client
} |
type Client struct {
http.Client
version int
} |
๊ตฌ์กฐ์ฒด๋ฅผ ์ด๊ธฐํ ํ ๋์๋ ๊ฑฐ์ ๋๋ถ๋ถ ํ๋ ๋ช
์ ์ง์ ํด์ผ ํ๋ค. ์ด๊ฒ์ ์ด์ go vet
์ ์ํด์ ๊ฐ์ ํ๊ณ ์๋ค.
Bad | Good |
---|---|
k := User{"John", "Doe", true} |
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
} |
์์ธ: ํ ์คํธ ํ ์ด๋ธ์์ ํ๋๋ช ์ 3๊ฐ ์ผ๋ ํน์ ์ด๋ณด๋ค ์ ์ ๋ ์๋ต๋ ์ ์์.
tests := []struct{
op Operation
want string
}{
{Add, "add"},
{Subtract, "subtract"},
}
๋ณ์๋ฅผ ๋ช
์์ ์ผ๋ก ํน์ ๊ฐ์ผ๋ก ์ค์ ํ๋ ๊ฒฝ์ฐ ์งง์ ๋ณ์ ์ ์ธ (Short variable declarations, :=
)์ ์ฌ์ฉํด์ผ ํ๋ค.
Bad | Good |
---|---|
var s = "foo" |
s := "foo" |
๊ทธ๋ฌ๋, var
ํค์๋๋ฅผ ์ฌ์ฉํ ๋ ๊ธฐ๋ณธ๊ฐ(default value)๊ฐ ๋ ๋ช
ํํ ๋๊ฐ ์๋ค. ์๋ฅผ ๋ค๋ฉด, Declaring Empty Slices.
Bad | Good |
---|---|
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
์ ๊ธธ์ด๊ฐ 0์ธ ์ ํจํ ์ฌ๋ผ์ด์ค์ด๋ค. ์ด๋ ๋ค์๊ณผ ๊ฐ์์ ์๋ฏธํ๋ค:
-
๊ธธ์ด๊ฐ 0์ธ ์ฌ๋ผ์ด์ค๋ฅผ ๋ช ์์ ์ผ๋ก ๋ฐํํด์๋ ์๋๋ค. ๋์ nil์ ๋ฐํํ๋ผ.
Bad Good if x == "" { return []int{} }
if x == "" { return nil }
-
์ฌ๋ผ์ด์ค๊ฐ ๋น์ด์๋์ง ํ์ธํ๊ธฐ ์ํด์ ํญ์
len(s) == 0
์ ์ฌ์ฉํด๋ผ.nil
์ ์ฒดํฌํ์ง ๋ง ๊ฒ.Bad Good func isEmpty(s []string) bool { return s == nil }
func isEmpty(s []string) bool { return len(s) == 0 }
-
์ ๋ก ๊ฐ(The zero value),
var
๋ก ์ ์ธ๋ ์ฌ๋ผ์ด์ค์ ๊ฒฝ์ฐ,์make()
์์ด ๋ฐ๋ก ์ฌ์ฉ ํ ์ ์๋ค.Bad Good 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 Nesting๊ณผ์ ์ถ๋ํ๋ ๊ฒฝ์ฐ ๋ฒ์๋ฅผ ์ค์ด๋ฉด ์๋๋ค.
Bad | Good |
---|---|
err := ioutil.WriteFile(name, data, 0644)
if err != nil {
return err
} |
if err := ioutil.WriteFile(name, data, 0644); err != nil {
return err
} |
if
์ธ๋ถ์์ ํจ์ ํธ์ถ์ ๊ฒฐ๊ณผ๊ฐ ํ์ํ ๊ฒฝ์ฐ, ๋ฒ์๋ฅผ ์ค์ด๋ ค๊ณ ์๋ํด์๋ ์๋๋ค.
Bad | Good |
---|---|
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 parameteres๋ ๊ฐ๋
์ฑ์ ๋จ์ด ๋จ๋ฆด ์ ์๋ค. ์๋ฏธ๊ฐ ๋ช
ํํ์ง ์์ ๊ฒฝ์ฐ, C์ธ์ด ์คํ์ผ์ ์ฃผ์ (/* ... */
)์ ์ถ๊ฐํ๊ธฐ ๋ฐ๋๋ค.
Bad | Good |
---|---|
// 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๋ฅผ ํผํ๊ธฐ ์ํด์ ์์ ๋ฌธ์ ๋ฆฌํฐ๋ด์ ์ฌ์ฉํด๋ผ.
Bad | Good |
---|---|
wantError := "unknown name:\"test\"" |
wantError := `unknown error:"test"` |
๊ตฌ์กฐ์ฒด ์ฐธ์กฐ(struct reference)๋ฅผ ์ด๊ธฐํ ํ ๋, new(T)
๋์ ์ &T{}
์ ์ฌ์ฉํ์ฌ ๊ตฌ์กฐ์ฒด ์ด๊ธฐํ์ ์ผ๊ด์ฑ์ ๊ฐ์ง๋๋ก ํด๋ผ.
Bad | Good |
---|---|
sval := T{Name: "foo"}
// inconsistent
sptr := new(T)
sptr.Name = "bar" |
sval := T{Name: "foo"}
sptr := &T{Name: "bar"} |
๋ฌธ์์ด ๋ฆฌํฐ๋ด ์ธ๋ถ์ Printf
-์คํ์ผ์ ํจ์์ ๋ํ ํ์ ๋ฌธ์์ด(format strings)์ ์ ์ธํ๋ ๊ฒฝ์ฐ const
๊ฐ (const value)๋ก ๋ง๋ค์ด๋ผ.
์ด๋ go vet
์ด ํ์ ๋ฌธ์์ด์ ์ ์ ๋ถ์(static analysis) ์ํํ๋๋ฐ ๋์์ด ๋๋ค.
Bad | Good |
---|---|
msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2) |
const msg = "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2) |
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.
ํต์ฌ์ ํ ์คํธ ๋ก์ง(the core test logic)์ด ๋ฐ๋ณต์ ์ผ ๋, ์ฝ๋ ์ค๋ณต์ ํผํ๋ ค๋ฉด subtests์ ํจ๊ป table-driven tests๋ฅผ ์ฌ์ฉํด๋ผ.
Bad | Good |
---|---|
// 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)์ ์ผ๋ถ ๋ด๋ถ ๊ตฌ์กฐ์ฒด (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๊ฐ ์ด์์ ์ธ์๊ฐ ์๋ ๊ฒฝ์ฐ์ ์ด ํจํด์ ์ฌ์ฉํ๊ธฐ๋ฅผ ๊ถ์ฅํ๋ค.
Bad | Good |
---|---|
// 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),
) |
๋ํ, ์๋์ ์๋ฃ๋ฅผ ์ฐธ๊ณ ํ๊ธฐ ๋ฐ๋๋ค: