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)
- uber-go-style-guide-kr
- Uber์ Go์ธ์ด ์คํ์ผ ๊ฐ์ด๋ (Uber's Go Style Guide)
- ์๊ฐ (Introduction)
- ๊ฐ์ด๋๋ผ์ธ (Guidelines)
- ์ธํฐํ์ด์ค์ ๋ํ ํฌ์ธํฐ (Pointers to Interfaces)
- ์ธํฐํ์ด์ค ์ปดํ๋ผ์ด์ธ์ค ๊ฒ์ฆ
- ๋ฆฌ์๋ฒ(Receivers)์ ์ธํฐํ์ด์ค(Interfaces)
- ์ ๋ก ๊ฐ ๋ฎคํ ์ค(Zero-value Mutexes)๋ ์ ํจํ๋ค
- ๋ฐ์ด๋๋ฆฌ์์ ์ฌ๋ผ์ด์ค ๋ฐ ๋งต ๋ณต์ฌ
- Clean Up ํ๊ธฐ ์ํ Defer
- ์ฑ๋์ ํฌ๊ธฐ(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)
์๊ฐ (Introduction)
์คํ์ผ(styles)์ ์ฝ๋๋ฅผ ๊ด๋ฆฌ(govern)ํ๋ ์ปจ๋ฒค์
/๊ท์น(conventions)์ด๋ค. ์ปจ๋ฒค์
์ ์ ๋ชป ์ดํด ๋ ์ ์๋๋ฐ ์๋ํ๋ฉด ๋จ์ํ gofmt
๊ฐ ์ํํ๋ ์์ค ์ฝ๋ ํฌ๋งทํ
์ด์ธ์ ์๋ฏธ๋ ํฌํจํ๊ธฐ ๋๋ฌธ์ด๋ค.
์ด ๊ฐ์ด๋์ ๋ชฉํ๋ Uber์์ Go ์ฝ๋๋ฅผ ์์ฑํ ๋ ํด์ผ ํ ๊ฒ๊ณผ ํ์ง ๋ง์์ผ ํ ๊ฒ์ ์์ธํ ์ค๋ช ํ์ฌ, ์ปจ๋ฒค์ ์ ๋ณต์ก์ฑ์ ๊ด๋ฆฌํ๋ ๊ฒ์ด๋ค. ์ด ์ปจ๋ฒค์ ์ ์์ง๋์ด๊ฐ Go์ธ์ด์ ์์ฐ์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋๋ก ํ๋ฉด์ ์ฝ๋๋ฅผ ๊ด๋ฆฌ ๊ฐ๋ฅํ๊ฒ ์ ์งํ๊ธฐ ์ํด ์กด์ฌํ๋ค.
์ด๋ ์๋ Prashant Varanasi์ Simon Newton์ด ์ผ๋ถ ๋๋ฃ๋ค์๊ฒ Go๋ฅผ ์ฌ์ฉํ๋ฉด์ ๊ฐ๋ฐ์๋ ํฅ์์ ๋๋ชจํ๊ธฐ ์ํด ์๊ฐ๋์๋ค. ์ ๋ ์ ๊ฑธ์ณ ํผ๋๋ฐฑ์ ํตํด ๊ฐ์ ํ๊ณ ์๋ค.
์ด ๋ฌธ์๋ Uber์์ ๋ฐ๋ฅด๋ Go ์ฝ๋ ์ปจ๋ฒค์ ์ ์ ๋ฆฌํ๋ค. ์ด๋ค ์ค ๋ง์ ๋ถ๋ถ์ด Go์ ๋ํ ์ผ๋ฐ์ ์ง์นจ์ด๊ณ , ๋๋จธ์ง๋ ์ธ๋ถ ๋ฆฌ์์ค์ ๋ฐ๋ผ ํ์ฅํ๋ค:
๋ชจ๋ ์ฝ๋๋ golint
๋ฐ go vet
๋ฅผ ์คํํ ๋ ์ค๋ฅ๊ฐ ์์ด์ผ ํ๋ค.
์ฝ๋ ์๋ํฐ๋ฅผ ๋ค์์ ๊ฐ์ด ์ค์ ํ๊ธฐ๋ฅผ ๊ถ์ฅํ๋ค:
- ์ฝ๋ ์ ์ฅ์
goimports
์คํ golint
๋ฐgo vet
๋ฅผ ์คํํ์ฌ ์ค๋ฅ ํ์ธ
์ฌ๊ธฐ์์ Go ๋๊ตฌ์ ๋ํ ํธ์ง๊ธฐ ์ง์ ์ ๋ณด๋ฅผ ์ฐพ์ ์ ์๋ค: https://github.com/golang/go/wiki/IDEsAndTextEditorPlugins
๊ฐ์ด๋๋ผ์ธ (Guidelines)
์ธํฐํ์ด์ค์ ๋ํ ํฌ์ธํฐ (Pointers to Interfaces)
์ธํฐํ์ด์ค์ ๋ํ ํฌ์ธํฐ๋ ๊ฑฐ์ ํ์ํ์ง ์๋ค. ์ธํฐํ์ด์ค๋ ๊ฐ(value)์ผ๋ก ์ ๋ฌํด์ผ ํ๋ค. ์ธํฐํ์ด์ค์ ๋ํ ๊ธฐ๋ณธ ๋ฐ์ดํฐ(underlying data)๋ ์ฌ์ ํ ํฌ์ธํฐ ์ผ ์ ์๋ค.
ํ๋์ ์ธํฐํ์ด์ค๋ ๋ ๊ฐ์ง ํ๋์ด๋ค:
- ํ์ -ํน์ ์ ๋ณด(type-specific information)์ ๋ํ ํฌ์ธํฐ. ์ด๊ฒ์ "ํ์ "์ผ๋ก ๊ฐ์ฃผํ ์ ์๋ค.
- ๋ฐ์ดํฐ ํฌ์ธํฐ. ์ ์ฅ๋ ๋ฐ์ดํฐ๊ฐ ํฌ์ธํฐ์ผ ๊ฒฝ์ฐ ์ง์ ์ ์ฅ๋๋ค. ์ ์ฅ๋ ๋ฐ์ดํฐ๊ฐ ๊ฐ์ด๋ฉด ๊ฐ์ ๋ํ ํฌ์ธํฐ๊ฐ ์ ์ฅ๋๋ค.
์ธํฐํ์ด์ค ๋ฉ์๋๊ฐ ๊ธฐ๋ณธ ๋ฐ์ดํฐ(underlying data)๋ฅผ ์์ ํ๋๋ก ํ๋ ค๋ฉด ๋ฐ๋์ ํฌ์ธํฐ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
์ธํฐํ์ด์ค ์ปดํ๋ผ์ด์ธ์ค ๊ฒ์ฆ
์ ์ ํ ๊ฒฝ์ฐ, ์ปดํ์ผ ์๊ฐ์ ์ธํฐํ์ด์ค ์ปดํ๋ผ์ด์ธ์ค๋ฅผ ๊ฒ์ฆํ๋ค. ์ด๋ ๋ค์์ ํฌํจํ๋ค:
- API contract์ ์ผ๋ถ๋ก ํน์ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋๋ฐ ํ์ํ exported ํ์
- ๋์ผํ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ ํ์ ์ ์ปฌ๋ ์ ์ ์ผ๋ถ์ธ exported ๋๋ unexported ํ์
- ๊ธฐํ ์ธํฐํ์ด์ค ์๋ฐ์ผ๋ก ์ธํด ์ฌ์ฉ์๊ฐ ์ค๋จ๋๋ ๊ฒฝ์ฐ
Bad | Good |
---|---|
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)์ ์ ํจํ๋ฏ๋ก ๋ฎคํ
์ค์ ๋ํ ํฌ์ธํฐ๊ฐ ๊ฑฐ์ ํ์ํ์ง ์๋ค.
Bad | Good |
---|---|
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)๋ฅผ ๋ ธ์ถํ๋ ๋งต ๋๋ ์ฌ๋ผ์ด์ค์ ๋ํ ์ฌ์ฉ์ ์์ ์ ์ฃผ์ํ์.
Bad | Good |
---|---|
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)๊ณผ ๊ฐ์ ๋ฆฌ์์ค๋ฅผ ์ ๋ฆฌํ๋ค.
Bad | Good |
---|---|
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) ๊ฒ์ ์๋ฐฉํ๋์ง ๊ทธ๋ฆฌ๊ณ ์ด๋ฌํ ๊ฒ์ด ๋ฐ์ํ ๊ฒฝ์ฐ ์ด๋ค ์ผ์ด ์ผ์ด๋ ์ง ์ถฉ๋ถํ ์๊ฐํด์ผ ํ๋ค.
Bad | Good |
---|---|
// ๋๊ตฌ์๊ฒ๋ ์ถฉ๋ถํ๋ค!
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)๋ก ์์ํด์ผ ํ๋ค.
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
์๋ฌ ํ(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
์ ์๋ฌ๋ฅผ ์ฌ์ฉํด๋ผ.
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")
}
}
์ค๋ฅ ๋ํ(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) ๊ณ์ํด์ ์์ด๊ฒ ๋๋ค:
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.
ํ์ ์ ์ด์ค์ ์คํจ ๋ค๋ฃจ๊ธฐ (Handle Type Assertion Failures)
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
} |
ํจ๋์ ํผํ ๊ฒ (Don't Panic)
ํ๋ก๋์ ํ๊ฒฝ์์ ์คํ๋๋ ์ฝ๋๋ ํจ๋์ ๋ฐ๋์ ํผํด์ผ ํ๋ค. ํจ๋์ 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")
} |
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
ํ์
์ ํฌํจํ๊ณ ์๋ค.
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)
์ฑ๋ฅ-ํน์ ์(performance-specific)๊ฐ์ด๋๋ผ์ธ์ ์ฑ๋ฅ์ ๋ฏผ๊ฐํ(hot path) ๊ฒฝ์ฐ์๋ง ์ ์ฉ๋๋ค.
fmt
๋ณด๋ค strconv
์ ํธ
ํ๋ฆฌ๋ฏธํฐ๋ธ(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())
} |
|
|
string-to-byte ๋ณํ์ ํผํด๋ผ
๊ณ ์ ๋ฌธ์์ด(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)
} |
|
|
์คํ์ผ (Style)
๊ทธ๋ฃน ์ ์ฌ ์ ์ธ (Group Similar Declarations)
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)
)
...
} |
Import ๊ทธ๋ฃน ์ ๋ฆฌ/๋ฐฐ์น (Import Group Ordering)
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"
) |
ํจํค์ง ์ด๋ฆ (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)์ด ๋ฐ์ํ์ง ์๋ ํ ์ง์ํด์ผ ํ๋ค.
Bad | Good |
---|---|
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)๋ ํ์ผ์ ๋ท๋ถ๋ถ์ ๋ํ๋์ผ ํ๋ค.
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 {...} |
์ค์ฒฉ ๊ฐ์ (Reduce Nesting)
์ฝ๋๋ ์๋ฌ ์ผ์ด์ค ํน์ ํน์ ์กฐ๊ฑด(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()
} |
๋ถํ์ํ else (Unnecessary Else)
๋ณ์๊ฐ if์ ๋ ๊ฐ์ง ๋ถ๊ธฐ๋ฌธ์ ์ํด์ ์ค์ ๋ ๊ฒฝ์ฐ, ์ด๋ ๋จ์ผ if
๋ฌธ (simple if)์ผ๋ก ๋์ฒด ํ ์ ์๋ค.
Bad | Good |
---|---|
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๊ณผ๊ฐ์ ๊ฐ์ ํ์
์ด ์๋ ์ด์, ํ์
์ ํน์ ์ง์ง ๋ง๋ผ.
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
์์ถ๋์ง ์์ ์ ์ญ์ _์ ๋ถ์ฌ๋ผ (Prefix Unexported Globals with _)
์์ถ๋์ง ์์ ์ต์์(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"
) |
๊ตฌ์กฐ์ฒด์์์ ์๋ฒ ๋ฉ (Embedding in Structs)
๋ฎคํ ์ค์ ๊ฐ์ ์๋ฒ ๋๋ ํ์ ์ ๊ตฌ์กฐ์ฒด์ ํ๋ ๋ชฉ๋ก ๊ฐ์ฅ ์์์ธต์ ์์ด์ผ ํ๊ณ , ์๋ฒ ๋ ๋ ํ๋๋ฅผ ์ผ๋ฐ ํ๋์ ๋ถ๋ฆฌํ๋ empty line์ด ์์ด์ผ ํ๋ค.
Bad | Good |
---|---|
type Client struct {
version int
http.Client
} |
type Client struct {
http.Client
version int
} |
๊ตฌ์กฐ์ฒด ์ด๊ธฐํ๋ฅผ ์ํด ํ๋๋ฅผ ์ฌ์ฉํด๋ผ (Use Field Names to initialize Structs)
๊ตฌ์กฐ์ฒด๋ฅผ ์ด๊ธฐํ ํ ๋์๋ ๊ฑฐ์ ๋๋ถ๋ถ ํ๋ ๋ช
์ ์ง์ ํด์ผ ํ๋ค. ์ด๊ฒ์ ์ด์ 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"},
}
์ง์ญ ๋ณ์ ์ ์ธ (Local Variable Declarations)
๋ณ์๋ฅผ ๋ช
์์ ์ผ๋ก ํน์ ๊ฐ์ผ๋ก ์ค์ ํ๋ ๊ฒฝ์ฐ ์งง์ ๋ณ์ ์ ์ธ (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์ ์ ํจํ ์ฌ๋ผ์ด์ค (nil is a valid slice)
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 Scope of Variables)
๊ฐ๋ฅํ ๋ณ์์ ๋ฒ์๋ฅผ ์ค์ฌ๋ผ. ๋ง์ฝ 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 ๋งค๊ฐ๋ณ์๋ฅผ ํผํด๋ผ (Avoid Naked Parameters)
ํจ์ ํธ์ถ์์์ naked parameters๋ ๊ฐ๋
์ฑ์ ๋จ์ด ๋จ๋ฆด ์ ์๋ค. ์๋ฏธ๊ฐ ๋ช
ํํ์ง ์์ ๊ฒฝ์ฐ, 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"` |
๊ตฌ์กฐ์ฒด ์ฐธ์กฐ ์ด๊ธฐํ (Initializing Struct References)
๊ตฌ์กฐ์ฒด ์ฐธ์กฐ(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 outside Printf)
๋ฌธ์์ด ๋ฆฌํฐ๋ด ์ธ๋ถ์ 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-์คํ์ผ ํจ์์ ์ด๋ฆ (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๋ฅผ ์ฌ์ฉํด๋ผ.
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)
๊ธฐ๋ฅ์ ์ต์
(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),
) |
๋ํ, ์๋์ ์๋ฃ๋ฅผ ์ฐธ๊ณ ํ๊ธฐ ๋ฐ๋๋ค: