-
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)๋ ์ ํจํ๋ค
- ๋ฐ์ด๋๋ฆฌ์์ ์ฌ๋ผ์ด์ค ๋ฐ ๋งต ๋ณต์ฌ
- Clean Up ํ๊ธฐ ์ํ Defer
- ์ฑ๋์ ํฌ๊ธฐ(Channel Size)๋ ํ๋(One) ํน์ ์ ๋ก(None)
- Enums์ 1์์๋ถํฐ ์์ํ๋ผ
- ์๊ฐ์ ์ฒ๋ฆฌํ๋ ค๋ฉด
"time"
์ ์ฌ์ฉํ๋ผ - ์๋ฌ ํ(Error Types)
- ์ค๋ฅ ๋ํ(Error Wrapping)
- ํ์ ์ ์ด์ค์ ์คํจ ๋ค๋ฃจ๊ธฐ (Handle Type Assertion Failures)
- ํจ๋์ ํผํ ๊ฒ (Don't Panic)
- go.uber.org/atomic์ ์ฌ์ฉ
- ๋ณ๊ฒฝ ๊ฐ๋ฅํ(mutable) ์ ์ญ๋ณ์ ํผํ๊ธฐ
- ๊ณต๊ฐ ๊ตฌ์กฐ์ฒด(public struct)์์ ๋ด์ฅ ํ์ ๋ค(Embedding Types) ์ฌ์ฉํ์ง ์๊ธฐ
- ๋ด์ฅ๋(built-in) ์ด๋ฆ ์ฌ์ฉ์ ํผํด๋ผ
init()
์ฌ์ฉ์ ํผํด๋ผ
- ์ฑ๋ฅ(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)
์คํ์ผ(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
์ธํฐํ์ด์ค์ ๋ํ ํฌ์ธํฐ๋ ๊ฑฐ์ ํ์ํ์ง ์๋ค. ์ธํฐํ์ด์ค๋ ๊ฐ(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,
) {
// ...
}
๊ฐ ๋ฆฌ์๋ฒ๊ฐ ์๋ ๋ฉ์๋๋ ๊ฐ ๋ฟ๋ง ์๋๋ผ ํฌ์ธํฐ์์๋ ํธ์ถํ ์ ์์ต๋๋ค. ํฌ์ธํฐ ๋ฆฌ์๋ฒ ์๋ ๋ฉ์๋๋ ํฌ์ธํฐ ๋๋ ์ฃผ์ ์ง์ ๊ฐ๋ฅํ ๊ฐ(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์ ๋ํ ์ข์ ๊ธ์ด ์์ผ๋ ์ฐธ๊ณ ํ๊ธฐ ๋ฐ๋๋ค.
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() |
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
๋ณด๋ค ๋ ์ค์ํ, ๋จ์ํ ๋ฉ๋ชจ๋ฆฌ ์ก์ธ์ค ์ด์์ ๋๊ท๋ชจ ๋ฉ์๋์ ํนํ ํด๋นํ๋ค.
์ฑ๋์ ํฌ๊ธฐ๋ ์ผ๋ฐ์ ์ผ๋ก 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
์๊ฐ์ ๋ณต์กํ๋ค. ์๊ฐ์ ๋ํด ์ข ์ข ์๋ชป๋ ๊ฐ์ ๋ค ์ค์๋ ๋ค์๊ณผ ๋ด์ฉ์ด ์๋ค.
- ํ๋ฃจ๋ 24์๊ฐ
- ํ ์๊ฐ์ 60๋ถ
- ์ผ์ฃผ์ผ์ 7์ผ
- ์ผ๋ ์ 365์ผ
- ๋ ์ดํด๋ณด๊ธฐ
์๋ฅผ๋ค๋ฉด, 1 ์ ํน์ ์์ ์ 24์๊ฐ์ ๋ํ๋ค๊ณ ํด์ ํญ์ ์๋ก์ด ๋ ์ง๊ฐ ๋๋ ๊ฒ์ ์๋๋ผ๋ ๋ป์ด๋ค.
๊ทธ๋ฌ๋ฏ๋ก, ์๊ฐ์ ๋ค๋ฃฐ ๋์๋ "time"
ํจํค์ง๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
์๋ชป ๋ ๊ฐ์ ๋ค์ ๋ ์์ ํ๊ณ ์ ํํ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ๋๋ฐ ๋์์ ์ฃผ๊ธฐ ๋๋ฌธ์
๋๋ค.
์๊ฐ์ ์๊ฐ(instants of time)์ ์ฒ๋ฆฌํ ๋๋ time.Time
ํจํค์ง๋ฅผ ์ฌ์ฉํ๊ณ ,
์๊ฐ์ ๋น๊ตํ๊ฑฐ๋ ๋ํ๊ฑฐ๋ ๋นผ๋ ์์
์ ํ ๋๋ time.Time
์ ๋ฉ์๋๋ฅผ ์ฌ์ฉํด๋ผ.
Bad | Good |
---|---|
func isActive(now, start, stop int) bool {
return start <= now && now < stop
} |
func isActive(now, start, stop time.Time) bool {
return (start.Before(now) || start.Equal(now)) && now.Before(stop)
} |
์๊ฐ์ ๊ธฐ๊ฐ(periods of time)์ ์ฒ๋ฆฌํ ๋๋ time.Duration
์ ์ฌ์ฉํ๋ผ.
Bad | Good |
---|---|
func poll(delay int) {
for {
// ...
time.Sleep(time.Duration(delay) * time.Millisecond)
}
}
poll(10) // ์ด ๊ฐ์ ์ด(seconds) ์ธ๊ฐ ๋ฐ๋ฆฌ์ด(milliseconds) ์ธ๊ฐ? |
func poll(delay time.Duration) {
for {
// ...
time.Sleep(delay)
}
}
poll(10*time.Second) |
ํน์ ์์ ์ 24์๊ฐ์ ๋ํ๋ ์์๋ก ๋์๊ฐ๋ฉด, ์๊ฐ์ ๋ํ๋ ๋ฐฉ๋ฒ์ ์๋์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ์ฌ์ฉ๋๋ค.
ํ๋ฃจ ์ค ๊ฐ์ ๋ฎ ์๊ฐ์ ์ ์งํ๋ ๋ค์ ๋ ์ง๋ก ๋์ด๊ฐ๊ธธ ์ํ๋ค๋ฉด Time.AddDate
๋ฅผ ์ฌ์ฉํด์ผํ๋ค.
๊ทธ๋ฌ๋, ์ด์ ์๊ฐ์ผ๋ก๋ถํฐ ์ ํํ 24์๊ฐ์ด ์ง๋ ์๊ฐ์ ์ป๊ณ ์ถ๋ค๋ฉด Time.Add
๋ฅผ ์ฌ์ฉํด์ผํ๋ค.
newDay := t.AddDate(0 /* years */, 0 /* months */, 1 /* days */)
maybeNewDay := t.Add(24 * time.Hour)
๊ฐ๋ฅํ ๊ฒฝ์ฐ ์ธ๋ถ ์์คํ
๊ณผ ์ํธ์์ฉ ํ ๋๋ time.Duration
๊ณผ time.Time
์ ์ฌ์ฉํด๋ผ.
์๋ฅผ ๋ค๋ฉด:
- Command-line flags:
flag
๋time.ParseDuration
๋ฅผ ํตํดtime.Duration
์ ์ง์ํ๋ค. - JSON:
encoding/json
์ [UnmarshalJSON
๋ฉ์๋]๋ฅผ ํตํดtime.Time
์ RFC 3339 ๋ฌธ์์ด๋ก ์ธ์ฝ๋ฉํ๋ ๊ฒ์ ์ง์ํฉ๋๋ค. - SQL:
database/sql
์DATETIME
๋๋TIMESTAMP
์ด์time.Time
์ผ๋ก ๋ณํํ๊ณ ๊ธฐ๋ณธ ๋๋ผ์ด๋ฒ๊ฐ ์ง์ํ๋ ๊ฒฝ์ฐ ๊ทธ ๋ฐ๋๋ก ๋ณํํ๋ ๊ฒ์ ์ง์ํฉ๋๋ค. - YAML:
gopkg.in/yaml.v2
๋ RFC 3339 ๋ฌธ์์ด๋กtime.Time
์ ์ง์ํ๊ณtime.ParseDuration
์ ํตํดtime.Duration
์ ์ง์ํฉ๋๋ค.
time.Duration
์ ์ฌ์ฉํ ์ ์๋ ๊ฒฝ์ฐ,
int
๋ float64
๋ฅผ ์ฌ์ฉํ๊ณ ํ๋ ์ด๋ฆ์ ๋จ์๋ฅผ ํฌํจํด๋ผ.
์๋ฅผ ๋ค์ด, encoding/json
์ด time.Duration
์ ์ง์ํ์ง ์๊ธฐ ๋๋ฌธ์
ํ๋ ์ด๋ฆ์ ๋จ์๋ฅผ ํฌํจํด์ผ ํ๋ค.
Bad | Good |
---|---|
// {"interval": 2}
type Config struct {
Interval int `json:"interval"`
} |
// {"intervalMillis": 2000}
type Config struct {
IntervalMillis int `json:"intervalMillis"`
} |
time.Time
์ ์ฌ์ฉํ ์ ์๋ ๊ฒฝ์ฐ, ๋์์ด ํฉ์๋์ง ์์๋ค๋ฉด RFC 3339์ ์ ์๋
ํ์์คํฌํ ํ์์ผ๋ก string
์ฌ์ฉํด๋ผ.
์ด ํ์์ Time.UnmarshalText
์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉ๋๋ฉฐ, time.RFC3339
๋ฅผ ํตํด
time.format
๋ฐ time.Parse
์์ ์ฌ์ฉ ํ ์ ์๋ค.
์ค์ ๋ก๋ ๋ฌธ์ ๊ฐ ๋์ง ์๋ ๊ฒฝํฅ์ด ์์ง๋ง, "time"
ํจํค์ง๋ ์ค์ด(leap seconds)๊ฐ
ํฌํจ๋ ํ์์คํฌํ ๊ตฌ๋ฌธ ๋ถ์์ ์ง์ํ์ง ์์ผ๋ฉฐ(8728), ๊ณ์ฐ ์ ์ค์ด(leap seconds)๋ฅผ
๊ณ ๋ คํ์ง๋ ์์ต๋๋ค(15190). ๋ง์ฝ ๋ ์๊ฐ์ ์๊ฐ(instants of time)์ ๋น๊ตํ๋ค๋ฉด,
๊ทธ ์ฌ์ด์ ๋ฐ์ ํ ์ ์๋ ์ค์ด(leap seconds)๋ ์ฐจ์ด์ ๋ฐ์ ๋์ง ์์ ๊ฒ ์ด๋ค.
์๋ฌ๋ฅผ ์ ์ธํ๋๋ฐ ์์ด์ ๋ค์ํ ์ต์ ๋ค์ด ์กด์ฌํ๋ค:
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()
} |
๋ณ๊ฒฝ ๊ฐ๋ฅํ(mutable) ์ ์ญ๋ณ์๋ฅผ ํผํ๊ณ , ๋์ ์์กด์ฑ ์ฃผ์ ์ ์ ํํด๋ผ. ์ด ์ฌํญ์ ํจ์ ํฌ์ธํฐ๋ฟ๋ง ์๋๋ผ ๋ค๋ฅธ ์ข ๋ฅ์ ๊ฐ์๋ ์ ์ฉ๋๋ค.
Bad | Good |
---|---|
// sign.go
var _timeNow = time.Now
func sign(msg string) string {
now := _timeNow()
return signWithTime(msg, now)
} |
// sign.go
type signer struct {
now func() time.Time
}
func newSigner() *signer {
return &signer{
now: time.Now,
}
}
func (s *signer) Sign(msg string) string {
now := s.now()
return signWithTime(msg, now)
} |
// sign_test.go
func TestSign(t *testing.T) {
oldTimeNow := _timeNow
_timeNow = func() time.Time {
return someFixedTime
}
defer func() { _timeNow = oldTimeNow }()
assert.Equal(t, want, sign(give))
} |
// sign_test.go
func TestSigner(t *testing.T) {
s := newSigner()
s.now = func() time.Time {
return someFixedTime
}
assert.Equal(t, want, s.Sign(give))
} |
์ด๋ฌํ ๋ด์ฅ๋(embedded) ํ์ ๋ค์ ๊ตฌํ ์ธ๋ถ์ฌํญ์ ๋ ธ์ถ์ํค๊ณ , ํ์ ๊ตฌ์กฐ๋ฅผ ๋ฐ์ ์ํค๋ ๊ฒ์ ์ด๋ ต๊ฒ ํ๋ฉฐ, ๋ฌธ์ํ๋ฅผ ์ด๋ ต๊ฒ ํ๋ค.
์ฌ๋ฌ ์ข
๋ฅ์ ๋ฆฌ์คํธ ์ ํ์ ๊ณต์ ๋ AbstractList
๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํํ๋ค๊ณ ๊ฐ์ ํ๋ฉด,
๊ตฌ์ฒด์ ์ธ ๊ตฌํ์ฒด์ AbstractList
๋ฅผ ๋ด์ฅ(embedding)ํ๋ ๊ฒ์ ํผํ๋ผ.
๋์ , ์ถ์ ๋ชฉ๋ก์ ์์ํ ๊ตฌ์ฒด์ ์ธ ๋ชฉ๋ก์ ๋ฉ์๋(method)๋ง ์ง์ ์์ฑํ๋ผ.
type AbstractList struct {}
// Add adds an entity to the list.
func (l *AbstractList) Add(e Entity) {
// ...
}
// Remove removes an entity from the list.
func (l *AbstractList) Remove(e Entity) {
// ...
}
Bad | Good |
---|---|
// ConcreteList is a list of entities.
type ConcreteList struct {
*AbstractList
} |
// ConcreteList is a list of entities.
type ConcreteList struct {
list *AbstractList
}
// Add adds an entity to the list.
func (l *ConcreteList) Add(e Entity) {
l.list.Add(e)
}
// Remove removes an entity from the list.
func (l *ConcreteList) Remove(e Entity) {
l.list.Remove(e)
} |
Go๋ ์์(inheritance)๊ณผ ํฉ์ฑ(composition) ์ฌ์ด์ ํํ์ผ๋ก ํ์ ๋ด์ฅ(type embedding)์ ํ์ฉํ๋ค. ์ธ๋ถ ํ์ ์ ๋ด์ฅ๋ ํ์ ์ ๋ฉ์๋๋ฅผ ์์์ ์ผ๋ก ๋ณต์ฌํ๋ค. ์ด๋ฌํ ๋ฉ์๋๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ด์ฅ๋ ์ธ์คํด์ค์ ๋์ผํ ๋ฉ์๋์ ์์๋๋ค.
๋ํ ๊ตฌ์กฐ์ฒด๋ ๊ฐ์ ์ด๋ฆ์ ํ๋๋ฅผ ํ๋ํ๋ค. ๋ฐ๋ผ์, ๋ด์ฅ๋ ํ์ (embedded type)์ด ๊ณต๊ฐ๋๋ฉด, ํด๋น ํ๋๋ ๊ณต๊ฐ ๋๋ค. ์ด์ ๋ฒ์ ๊ณผ ํธํ์ฑ์ ์ ์งํ๊ธฐ ์ํด, ์ธ๋ถ ํ์ ์ ํฅํ ๋ฒ์ ์ ๋ด์ฅ๋ ํ์ (embedded type)์ ๊ณ์ ์ ์ง ํด์ผ ํ๋ค.
๋ด์ฅ๋ ํ์ (embedded type)์ ๊ฑฐ์ ํ์ํ์ง ์๋ค. ์ด๊ฒ์ ๋ฒ๊ฑฐ๋ก์ด ๋๋ฆฌ์ ๋ฉ์๋(delegate method)๋ค์ ์์ฑํ๋ ๊ฒ์ ํผํ ์ ์๋ ํธ์ ๊ธฐ๋ฅ์ด๋ค.
๊ตฌ์กฐ์ฒด ๋์ ์ ํธํ๊ฐ๋ฅํ AbstractList interface ๋ด์ฅํ๋๊ฒ ๊ฐ๋ฐ์์๊ฒ ํฅํ ๋ณ๊ฒฝ์ ๋ํ ๋ ๋ง์ ์ ์ฐ์ฑ์ ์ ๊ณตํ์ง๋ง, ์ฌ์ ํ ๊ตฌ์ฒด์ ์ธ ๋ฆฌ์คํธ๊ฐ ์ถ์์ ์ธ ๊ตฌํ(abstract implementation)์ ์ฌ์ฉํ๋ค๋ ์ธ๋ถ์ฌํญ์ ๋ ธ์ถ ์ํฌ ์ ์๋ค.
Bad | Good |
---|---|
// AbstractList is a generalized implementation
// for various kinds of lists of entities.
type AbstractList interface {
Add(Entity)
Remove(Entity)
}
// ConcreteList is a list of entities.
type ConcreteList struct {
AbstractList
} |
// ConcreteList is a list of entities.
type ConcreteList struct {
list AbstractList
}
// Add adds an entity to the list.
func (l *ConcreteList) Add(e Entity) {
l.list.Add(e)
}
// Remove removes an entity from the list.
func (l *ConcreteList) Remove(e Entity) {
l.list.Remove(e)
} |
๋ด์ฅ๋ ๊ตฌ์กฐ์ฒด(embedded struct)์ ๋ด์ฅ๋ ์ธํฐํ์ด์ค(embedded interface) ๋ชจ๋, ๋ด์ฅ๋ ํ์ ์ ํ์ ์ ๋ฐ์ (evolution)์ ์ ์ฝ์ ๊ฐํ๋ค.
- ๋ด์ฅ๋ ์ธํฐํ์ด์ค์ ๋ฉ์๋๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ํธํ์ฑ์ ๊บ ๋ ๋ณ๊ฒฝ์ฌํญ์ด๋ค.
- ๋ด์ฅ๋ ๊ตฌ์กฐ์ฒด์์ ๋ฉ์๋๋ฅผ ์ ๊ฑฐํ๋ ๊ฒ์ ํธํ์ฑ์ ๊นจ๋ ๋ณ๊ฒฝ์ฌํญ์ด๋ค.
- ๋ด์ฅ๋ ํ์ ์์ ์ ๊ฑฐํ๋ ๊ฒ์ ํธํ์ฑ์ ๊นจ๋ ๋ณ๊ฒฝ์ฌํญ์ด๋ค.
- ๋์ผํ ์ธํฐํ์ด์ค๋ฅผ ์ถฉ์กฑํ๋ ๋์์ผ๋ก ๋ด์ฅ๋ ํ์ ์ ๋์ฒดํ๋ ๊ฒ์กฐ์ฐจ ํธํ์ฑ์ ๊นจ๋ ๋ณ๊ฒฝ์ด๋ค.
์ด๋ฌํ ์์ ๋ฉ์๋(delegate method)๋ค์ ์์ฑํ๋ ๊ฒ์ ๋ฒ๊ฑฐ๋ก์ธ ์ ์์ง๋ง, ์ด ์ถ๊ฐ์ ์ธ ๋ ธ๋ ฅ์ผ๋ก ์ธํด ๊ตฌํ ์ธ๋ถ์ฌํญ์ด ์จ๊ฒจ์ง๊ณ , ๋ณ๊ฒฝํ ์ ์๋ ๊ธฐํ๋ฅผ ๋ ๋ง์ด ์ ๊ณตํ๋ฉฐ, ๋ํ ๋ฌธ์์์ List ์ธํฐํ์ด์ค ์ ์ฒด๋ฅผ ์ฐพ์๊ฐ๋ ๊ฐ์ ์ ์ธ ๋ฐฉ๋ฒ์ ์ ๊ฑฐํ๋ค.
Go ์ธ์ด ๋ช ์ธ(language specification)์๋ Go ํ๋ก๊ทธ๋จ ๋ด์์ ์ด๋ฆ์ผ๋ก ์ฌ์ฉ ํด์๋ ์๋๋ ๋ฏธ๋ฆฌ ์ ์ธ๋ ์๋ณ์(predeclared identifiers)๋ค์ด ๋ช ์ ๋์ด ์๋ค.
์ํฉ์ ๋ฐ๋ผ, ์ด๋ฌํ ์๋ณ์(identifier)๋ค์ ์ด๋ฆ์ผ๋ก ์ฌ์ฌ์ฉํ๋ฉด ํ์ฌ ์ดํ์ ์ค์ฝํ(lexical scope) ๋ฐ ๋ชจ๋ ์ค์ฒฉ ์ค์ฝํ(nested scope)๋ด์์ ์๋ณธ์ ๊ฐ๋ฆฌ๊ฒ ๋๊ฑฐ๋ ์ํฅ์ ๋ฐ๋ ์ฝ๋๋ฅผ ํผ๋์ค๋ฝ๊ฒ ๋ง๋ค ์ ์๋ค. ๊ฐ์ฅ ์ข์ ๊ฒฝ์ฐ์๋ ์ปดํ์ผ๋ฌ๊ฐ ๊ฒฝ๊ณ ๋ฅผ ํ์ํ ์ ์์ง๋ง; ์ต์ ์ ๊ฒฝ์ฐ, ์ด๋ฌํ ์ฝ๋๋ ์ ์ฌ์ ์ผ๋ก ์ฐพ๊ธฐ ์ด๋ ค์ด ๋ฒ๊ทธ๋ฅผ ๋ง๋ค ์ ์๋ค.
Bad | Good |
---|---|
var error string
// `error` shadows the builtin
// or
func handleErrorMessage(error string) {
// `error` shadows the builtin
} |
var errorMessage string
// `error` refers to the builtin
// or
func handleErrorMessage(msg string) {
// `error` refers to the builtin
} |
type Foo struct {
// ์ด๋ฌํ ํ๋๋ ๊ธฐ์ ์ ์ผ๋ก ์๋์(shadowing)์
// ๊ตฌ์ฑํ์ง๋ ์์ง๋ง
// `error` ๋๋ `string` ๋ฌธ์์ด์ ์ด์
// ๋ชจํธํด์ก๋ค.
error error
string string
}
func (f Foo) Error() error {
// `error` ์ `f.error`๋
// ์๊ฐ์ ์ผ๋ก ์ ์ฌํ๋ค.
return f.error
}
func (f Foo) String() string {
// `string` ๊ณผ `f.string`์
// ์๊ฐ์ ์ผ๋ก ์ ์ฌํ๋ค.
return f.string
} |
type Foo struct {
// `error` ์ `string` ๋ฌธ์์ด์
// ์ด์ ๋ชจํธํ์ง ์๋ค.
err error
str string
}
func (f Foo) Error() error {
return f.err
}
func (f Foo) String() string {
return f.str
} |
์ปดํ์ผ๋ ๋ฏธ๋ฆฌ ์ ์ธ๋ ์๋ณ์(predeclared identifier)๋ค์ ์ฌ์ฉ ํ ๋ ์ค๋ฅ๋ฅผ ์์ฑํ์ง ์์ง๋ง, go vet
๊ณผ ๊ฐ์ ๋๊ตฌ๋ ์ด์ ๊ฐ์ ์๋์(shadowing) ๊ฒฝ์ฐ์ ๋ค๋ฅธ ๊ฒฝ์ฐ๋ค์ ์ ํํ๊ฒ ์ง์ ํด ์ค ๊ฒ์ด๋ค.
๊ฐ๋ฅํ๋ค๋ฉด init()
์ฌ์ฉ์ ํผํด๋ผ. init()
์ ํผํ ์ ์๊ฑฐ๋ ์ํ๋ ๊ฒฝ์ฐ์๋ ์ฝ๋๋ ๋ค์ ์ฌํญ์ ์๋ํด์ผ ํ๋ค.
- ํ๋ก๊ทธ๋จ์ด ์คํ๋๋ ํ๊ฒฝ์ด๋ ํธ์ถ ๋ฐฉ์์ ๊ด๊ณ์์ด, ์ฝ๋ ๋์์ด ์์ธก๊ฐ๋ฅํ๊ณ ์ผ๊ด๋์ด์ผ ํ๋ค(Be completely deterministic).
- ๋ค๋ฅธ
init()
ํจ์๋ค์ ์์ ๋๋ ๋ถ์์ฉ(side-effect)์ ์์กด์ฑ์ ํผํด์ผํ๋ค.init()
์ ์์๋ ์ ์๋ ค์ ธ ์์ง๋ง, ์ฝ๋๊ฐ ๋ณ๊ฒฝ ๋ ์ ์์ผ๋ฏ๋กinit()
ํจ์๋ค ๊ฐ์ ๊ด๊ณ๋ ์ฝ๋๋ฅผ ๋ง๊ฐ์ง๊ธฐ ์ฝ๊ณ ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ธฐ ์ฝ๊ฒ ๋ง๋ค ์ ์๋ค. - ๊ธฐ๊ณ์ ๋ณด(machine information), ํ๊ฒฝ๋ณ์(enviroment variables), ์์ ๋๋ ํ ๋ฆฌ(working directory), ํ๋ก๊ทธ๋จ ์ธ์/์ ๋ ฅ(argument/input)๋ฑ๊ณผ ๊ฐ์ ์ ์ญ ๋๋ ํ๊ฒฝ ์ํ์ ์ ๊ทผํ๊ฑฐ๋ ์กฐ์ํ์ง ์๋๋ก ํด์ผํ๋ค.
- ํ์ผ์์คํ , ๋คํธ์ํฌ, ์์คํ ํธ์ถ์ ํฌํจํ I/O๋ฅผ ํผํด์ผ ํ๋ค.
์ด๋ฌํ ์๊ตฌ์ฌํญ์ ์ถฉ์กฑ์ํค๊ธฐ ์ด๋ ค์ด ์ฝ๋๋ main()
(๋๋ ํ๋ก๊ทธ๋จ ์๋ช
์ฃผ๊ธฐ์ ๋ค๋ฅธ ๊ณณ)์์ ํธ์ถ ๋๋ ๋ถ์์ ์ธ
๋์ฐ๋ฏธ(helper)๊ฐ ๋๊ฑฐ๋, ํน์ main()
๋ด๋ถ์์ ์ง์ ์์ฑ ๋ ์ ์๋ค.
ํนํ, ๋ค๋ฅธ ํ๋ก๊ทธ๋จ์์ ์ฌ์ฉํ ๋ชฉ์ ์ผ๋ก ์ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์์ ํ ๊ฒฐ์ ๋ก ์ (deterministic)์ด๊ณ
init magic
์ ํํ์ง ์๋๋ก ํด์ผํ๋ค.
Bad | Good |
---|---|
type Foo struct {
// ...
}
var _defaultFoo Foo
func init() {
_defaultFoo = Foo{
// ...
}
} |
var _defaultFoo = Foo{
// ...
}
// ๋๋, ํ
์คํธ๋ฅผ ์ ์ฉํ๊ฒ ํ๊ธฐ ์ํ ๋์ ๋ฐฉ๋ฒ:
var _defaultFoo = defaultFoo()
func defaultFoo() Foo {
return Foo{
// ...
}
} |
type Config struct {
// ...
}
var _config Config
func init() {
// Bad: ํ์ฌ ๋๋ ํ ๋ฆฌ ๊ธฐ์ค(based on current directory)
cwd, _ := os.Getwd()
// Bad: I/O
raw, _ := os.ReadFile(
path.Join(cwd, "config", "config.yaml"),
)
yaml.Unmarshal(raw, &_config)
} |
type Config struct {
// ...
}
func loadConfig() Config {
cwd, err := os.Getwd()
// handle err
raw, err := os.ReadFile(
path.Join(cwd, "config", "config.yaml"),
)
// handle err
var config Config
yaml.Unmarshal(raw, &config)
return config
} |
์๋ฅผ ๊ณ ๋ คํ ๋, init()
์ด ์ ํธ๋๊ฑฐ๋ ํ์ํ ๋ช๊ฐ์ง ์ํฉ์ ๋ค์๊ณผ ๊ฐ์ ์ ์๋ค.
- ๋จ์ผ ๋์ (single assignment)์ผ๋ก ํํํ ์ ์๋ ๋ณต์กํ ํํ์
database/sql
๋ฐฉ์ธ(dialect), ์ธ์ฝ๋ฉ ์ ํ ๋ ์ง์คํธ๋ฆฌ ๋ฑ๊ณผ ๊ฐ์ ์ฐ๊ฒฐ๊ฐ๋ฅํ(pluggable) ํ- Google Cloud Functions ๋ฐ ๊ฒฐ์ ๋ก ์ ์ฌ์ ๊ณ์ฐ(precomputation)์ ๋ค๋ฅธ ํํ์ ๋ํ ์ต์ ํ
์ฑ๋ฅ-ํน์ ์(performance-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 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"` |
๊ตฌ์กฐ์ฒด ์ฐธ์กฐ(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),
) |
๋ํ, ์๋์ ์๋ฃ๋ฅผ ์ฐธ๊ณ ํ๊ธฐ ๋ฐ๋๋ค: