이전에 했던 style guide보다는 좀 더 서술적이고 예시가 많은 문서이다.
참고 문서: https://google.github.io/styleguide/go/decisions
Naming
- Underscore를 피하자. 다음 케이스만 예외로 둔다.
- *_test.go 안의 Test, Benchmark and Example function
- OS와 직접 통신하는 system library
- util, utility, common, helper와 같은 무의미한 패키지 이름을 피하자.
- Receiver 이름은 짧고, 축약어를 많이 사용하라.
- func (tray Tray) → func (t Tray)
- func (info *ResearchInfo) → func (ri *ResearchInfo)
- 상수 이름은 다른 이름들처럼 MixedCaps로 하자.
- // Good: const MaxPacketSize = 512 const ( ExecuteBit = 1 << iota WriteBit ReadBit )
- 축약어는 모두 대문자로 사용하자.
- ex: XMLAPI, IOS(public), ios(private), GRPC(public), gRPC(private)
- Getter에는 get prefix를 사용하지 말자.
- ex) GetCounts 대신 Counts
- 더 복잡한 computation이 필요한 함수라면 Compute나 Fetch같은 다른 단어를 사용하자.
- ex) GetCounts 대신 Counts
- 변수 이름
- 변수 이름 길이는 사용되는 scope에 비례해야 한다는 일반적인 규칙이 있다.
- A small scope is one in which one or two small operations are performed, say 1-7 lines.
- A medium scope is a few small or one large operation, say 8-15 lines.
- A large scope is one or a few large operations, say 15-25 lines.
- A very large scope is anything that spans more than a page (say, more than 25 lines).
- 일반적으로:
- count나 option같은 single word는 좋은 시작점이다.
- 모호함을 줄이기 위해 단어가 추가될 수 있다. ex) userCount, projectCount
- Typing을 줄이기 위해 글자를 빼지 말자. ex) Sbx보다는 Sandbox가 낫다.
- Type과 Type을 내포한 단어를 사용하지 말자.
- userCount가 numUsers나 usersInt보다 낫다.
- users가 userSlice보다 낫다.
- 한 scope에 값에 대한 2개의 버전이 있다면 type-like qualifier도 가능하다. ex) ageString을 입력으로 받고, age를 parse된 값으로 가질 수 있다.
- 컨텍스트 내에서 의미가 명확하면 단어를 빼라. UserCount 메소드에서, userCount 로컬 변수보다는 count, users, 또는 c가 낫다.
- 한 글자 변수
- Method receiver에서는 한두 글자 이름이 선호된다.
- 친숙한 이름은 괜찮다.
- io.Reader나 *http.Request 에 r
- Loop에서 indices(i)나 coordinates(x, y)를 가리키는 경우
- Loop 안에서만 사용되는 변수 ex) for _, n := range nodes { ... }
- 변수 이름 길이는 사용되는 scope에 비례해야 한다는 일반적인 규칙이 있다.
- 반복
- context를 통해 알 수 있는 정보는 최대한 줄이자.
- // Bad: // In package "ads/targeting/revenue/reporting" type AdsTargetingRevenueReport struct{} func (p *Project) ProjectName() string // Good: // In package "ads/targeting/revenue/reporting" type Report struct{} func (p *Project) Name() string
Commentary
- 좁은 화면에서도 보일 수 있을 정도로 작성하자. 60~70자 정도로 줄이고, punctuation 단위로 line break를 하자.
- Top level 메소드나 변수들은 doc comment가 있어야 한다. 이 comment는 완전한 문장으로 이루어져야 한다.
- Package는 의도된 사용법을 문서화해야 한다. Runnable example을 제공해보자.
- Named result parameter는 같은 타입이 여러 개 있는 등 꼭 필요한 경우에만 사용하자.
- // Good: func (n *Node) Parent1() *Node func (n *Node) Parent2() (*Node, error) // Good: func (n *Node) Children() (left, right *Node, err error) // Bad: func (n *Node) Parent1() (node *Node) func (n *Node) Parent2() (node *Node, err error)
- Naked return(named variable이 존재할 때, return에 argument를 포함하지 않는 것)는 작은 함수에서만 하자. 규모가 커지면 명확성을 잃을 수 있기 때문이다.
- func split(sum int) (x, y int) { x = sum * 4 / 9 y = sum - x return }
Imports
- Import는 name collision을 피하기 위해서만 rename되어야 한다. 단, generated protocol buffer packages는 underscore를 피하기 위해 rename될 수 있다.
- // Good: import ( fspb "path/to/package/foo_service_go_proto" ) // Good: import ( core "github.com/kubernetes/api/core/v1" meta "github.com/kubernetes/apimachinery/pkg/apis/meta/v1beta1" )
- 만약 common local name과 충돌하는 package를 import하고 싶다면, pkg suffix를 붙여보자. ex) urlpkg
- Import는 2개의 그룹으로 구성되어야 한다.
- Standard library packages
- Other (project and vendored) packages
// Good: package main import ( "fmt" "hash/adler32" "os" "github.com/dsnet/compress/flate" "golang.org/x/text/encoding" "google.golang.org/protobuf/proto" foopb "myproj/foo/proto/proto" _ "myproj/rpc/protocols/dial" _ "myproj/security/auth/authhooks" )
- Blank import(import _)는 main package에서만 사용하자. Dependency control을 어렵게 한다.
- Dot import(import .)는 사용하지 말자.
Errors
- 함수가 fail할 수 있음을 error를 통해 나타내자. 컨벤션은, error를 result parameter의 마지막에 넣는 것이다.
- Error string은 capitalize하지 말고, punctuation으로 끝나서는 안된다. 다른 error와 concat 될 수 있기 때문이다.
- // Bad: err := fmt.Errorf("Something bad happened.") // Good: err := fmt.Errorf("something bad happened")
- Error를 리턴하는 함수에서 error를 _로 처리하는 것은 일반적으로 좋지 않다. 3가지 중 하나를 하자:
- 에러를 적절히 처리
- 호출자에게 다시 error 리턴
- 예외적인 상황에서만) log.Fatal이나 panic 호출
- Error는 다른 코드를 처리하기 전에 처리하자.
- // Good: if err != nil { // error handling return // or continue, etc. } // normal code // Bad: if err != nil { // error handling } else { // normal code that looks abnormal due to indentation }
Language
- Nil slice
- 대부분의 경우에는 nil과 empty slice간에 차이는 없다. Empty slice를 선언할 때에는 nil initialization을 하자.
- // Good: var t []string // Bad: t := []string{}
- Interface 작성시에는 nil slice와 zero-length slice 간에 차이를 만드는 것을 피하자.
- // Good: // describeInts describes s with the given prefix, unless s is empty. func describeInts(prefix string, s []int) { if len(s) == 0 { return } fmt.Println(prefix, s) } // Bad: func maybeInts() []int { /* ... */ } // describeInts describes s with the given prefix; pass nil to skip completely. func describeInts(prefix string, s []int) { // The behavior of this function unintentionally changes depending on what // maybeInts() returns in 'empty' cases (nil or []int{}). if s == nil { return } fmt.Println(prefix, s) } describeInts("Here are some ints:", maybeInts())
- Function argument가 너무 많으면 structure로 묶자.
- // Good: good := foo.Call(long, CallOptions{ Names: list, Of: of, The: parameters, Func: all, Args: on, Now: separate, Visible: lines, }) // Bad: bad := foo.Call( long, list, of, parameters, all, on, separate, lines, )
- Function과 Method 호출은 line break가 없어야 한다.
- // Good: good := foo.Call(long, list, of, parameters, all, on, one, line) // Bad: bad := foo.Call(long, list, of, parameters, with, arbitrary, line, breaks)
- If 문도 line break가 없어야 한다. 너무 길어진다면, 조건문을 변수화하자.
- // Bad: // The second if statement is aligned with the code within the if block, causing // indentation confusion. if db.CurrentStatusIs(db.InTransaction) && db.ValuesEqual(db.TransactionKey(), row.Key()) { return db.Errorf(db.TransactionError, "query failed: row (%v): key does not match transaction key", row) } // Good: inTransaction := db.CurrentStatusIs(db.InTransaction) keysMatch := db.ValuesEqual(db.TransactionKey(), row.Key()) if inTransaction && keysMatch { return db.Error(db.TransactionError, "query failed: row (%v): key does not match transaction key", row) }
- Yoda style 조건문을 지양하라.
- // Bad: if "foo" == result { // ... } // Good: if result == "foo" { // ... }
- Struct를 복사할 때에는 주의하라. 예를 들어, Buffer를 복사하면 복사본의 slice는 원본의 alias이므로, 예상치 못한 결과를 일으킬 수 있다.
- Don’t Panic! Panic 사용을 지양해라.
- Goroutine을 만들때, 언제 종료될지 명확히 해라. Close된 channel로 데이터를 보내는 것은 panic을 발생시킨다. 이는 Context를 이용해서 제어될 수 있다.
- // Good: func (w *Worker) Run(ctx context.Context) error { // ... for item := range w.q { // process returns at latest when the context is cancelled. go process(ctx, item) } // ... } // Bad: func (w *Worker) Run() { // ... for item := range w.q { // process returns when it finishes, if ever, possibly not cleanly // handling a state transition or termination of the Go program itself. go process(item) } // ... }
- 몇 바이트를 줄이기 위해 pointer value 를 함수로 넘기지 말자. 단, large struct같은 경우에는 적용되지 않는다.
- Receiver Type
- Receiver가 slice면 value를 사용하라.
- // Good: type Buffer []byte func (b Buffer) Len() int { return len(b) }
- Receiver를 수정하면 pointer를 사용하라.
- // Good: type Counter int func (c *Counter) Inc() { *c++ }
- Receiver가 안전하게 복사될 수 없는 field를 포함하고 있다면, pointer를 사용하라. ex) mutex
- Receiver가 “large” struct나 array이면 pointer를 사용하라.
- Receiver가 struct나 array이고 element가 변경된다면 pointer를 사용하라. 독자에게 명확하게 의도를 전달할 수 있다.
- Receiver가 integer나 string처럼 built-in type이면 수정될 수 없으므로 value를 사용하라.
- // Good: type User string func (u User) String() { return string(u) }
- Receiver가 map, function, channel이면 value를 사용하라.
- 어떤 것을 사용할지 잘 모르겠으면 pointer를 사용하라.
- Switch문에서 break를 사용하지 마라.
- Async function보다 Sync function을 선호해야 한다.
- Type alias(type T1 = T2)를 새 소스코드로 migrate하는 경우가 아니라면 지양해라.
- %q를 애용하자. String을 쌍따옴표 사이에 출력해준다.
- // Good: fmt.Printf("value %q looks like English text", someText)
- Go 1.18부터 any 가 추가되었다. interface{} 보다 any를 선호해야 한다.
Common libraries
- Function이나 method를 호출할 때, context.Context는 언제나 첫 파라미터여야 한다.예외는:
- HTTP handler에서는 req.Context()를 사용한다.
- Streaming RPC method는 stream의 context를 사용한다.
- Entrypoint function에서는 context.Background()를 사용한다.
- ex) main, init,
- func F(ctx context.Context /* other arguments */) {}
- Custom context를 사용하지 마라.
- Key 생성시 math/rand 대신 crypto/rand 를 사용하라. math/rand 는 predictable하다.
- Slice같은 복잡한 자료구조를 비교할 때에는, cmp.Equal을 사용하자. cmp.Diff는 object간 차이를 human readable format으로 얻을 수 있다.
'golang' 카테고리의 다른 글
[TIL] golang promauto metric register시 panic (0) | 2023.12.21 |
---|---|
[TIL] golang buffer 복사 (1) | 2023.12.21 |
Go google style guide - overview & guide (1) | 2023.11.19 |
Go garbage collection (1) | 2023.10.30 |
[TIL] Go buffer read (0) | 2023.10.27 |