2025 04 08 Go Style Guide

Go Style Guide

Go 언어는 상대적으로 단순한 언어이지만, minimalism이라는 철학을 유지하기 위해서는 나름의 코딩 컨벤션이 필요하다. Go 언어를 처음 사용하는 사람이라면 주요 문서들을 읽어보며 style convention도 함께 익히는 게 좋다.

Google의 Go Style Decisions는 문서 내용이 길고 매우 자세한데, Go 개발자라면 지키면 좋을 Guideline이 매우 자세히 정리되어 있다.

물론 가장 좋은 방법은 이런 가이드를 직접 읽고 스스로 체화하는 것이나, 자료들이 매우 방대하기 때문에 관심 갖고 볼만한 요소들을 정리해 보았다.

글의 말미에는 style 규칙을 linter로 강제할 때 많이 사용되는 golangci-lint에 대해서도 간략히 소개한다.

Effective Go

Go 언어 입문자들이라면 90%는 이 글을 읽을 텐데, 여기서도 몇몇 style 관련 팁을 제시한다.

https://go.dev/doc/effective_go

명명규칙

기본 규칙

Packages

Getter

Interface

코드 스타일

Control flow

기타

Google Go Style Guide

https://google.github.io/styleguide/go/guide

https://google.github.io/styleguide/go/decisions

Line Length

Least mechanism

패키지

Receiver

상수

Initialisms

English UsageScopeCorrectIncorrect
XML APIExportedXMLAPIXmlApiXMLApiXmlAPIXMLapi
XML APIUnexportedxmlAPIxmlapixmlApi
iOSExportedIOSIosIoS
iOSUnexportediOSios
gRPCExportedGRPCGrpc
gRPCUnexportedgRPCgrpc
DDoSExportedDDoSDDOSDdos
DDoSUnexportedddosdDoSdDOS
IDExportedIDId
IDUnexportedidiD
DBExportedDBDb
DBUnexporteddbdB
TxnExportedTxnTXN

Getter

Variable names

Doc comments

// Options configure the group management service.
type Options struct {
    // General setup:
    Name  string
    Group *FooGroup

    // Dependencies:
    DB *sql.DB

    // Customization:
    LargeGroupThreshold int // optional; default: 10
    MinimumMembers      int // optional; default: 2
}

Error

Don’t panic

Must functions

func MustParse(version string) *Version {
    v, err := Parse(version)
    if err != nil {
        panic(fmt.Sprintf("MustParse(%q) = _, %v", version, err))
    }
    return v
}

Goroutine lifetimes

Interfaces

Generics

Use %q

문자열을 formatting하는 함수를 사용하면서 내부에 따옴표로 감싸진 문자열을 넣고 싶다면 %q를 사용하라.

// Good:
fmt.Printf("value %q looks like English text", someText)

// Bad:
fmt.Printf("value \"%s\" looks like English text", someText)

Use any

context

Test

Uber Go Style Guide

zero value mutex

// Bad
mu := new(sync.Mutex)
mu.Lock()

// Good
var mu sync.Mutex
mu.Lock()

Copy Slices and Maps at Boundaries

slice와 map은 내부 데이터를 가리키는 pointer를 내장하고 있기 때문에 copy를 해야하는 케이스에 주의해야 한다.

유저가 map이나 slice에 의도하지 않은 변형을 할 수 있다는 점을 늘 고려해야 한다.

// Good
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] = ...

Clean up을 위해 defer 사용

// Bad: 락 해제를 까먹을 수 있음
p.Lock()
if p.count < 10 {
  p.Unlock()
  return p.count
}

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

return newCount

// Good
p.Lock()
defer p.Unlock()

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

p.count++
return p.count

Channel Size

Errors

변경 가능한전역변수

Public 구조체의 embedding type

// BAD
type ConcreteList struct {
  *AbstractList
}

//GOOD
type ConcreteList struct {
  list *AbstractList
}

func (l *ConcreteList) Add(e Entity) {
  l.list.Add(e)
}

func (l *ConcreteList) Remove(e Entity) {
  l.list.Remove(e)
}

init() 사용 지양

os.Exit

Exit Once

Goroutine을 fire-and-forget 방식으로 사용하지 말기

성능

코드 스타일

패키지 작명

함수 순서

export되지 않는 전역변수

nil은 valid한 slice다

Variable의 scope 줄이기

if err := os.WriteFile(name, data, 0644); err != nil {
 return err
}

var for zero-value struct

// BAD
user := User{}

// GOOD
var user User

Map 생성

Golangci-lint로 Style Convention을 강제하기

golangci-lint를 이용하면 팀에서 원하는 style convention을 어느 정도 강제할 수 있다.

Introduction | golangci-lint

예를 들어, export되는 함수들에는 반드시 주석이 있어야 한다는 룰을 적용하고 싶다면, 아래처럼 적용하면 된다.

linters:
  enable:
    - revive
  settings:
    revive:
      rules:
         - name: exported
          severity: warning
          disabled: false
          exclude: [""]
          arguments:
            - "checkPublicInterface" # public 인터페이스 타입의 public method 주석 강제

그 밖에도 주요 style guide들에서 명시하는 패턴/안티패턴을 규제하는 linter들이 많으므로 쭉 읽어보고 사용할 만한 linter를 추가해 사용하면 좋다.

여러 linter들이 제공되는 데, 니즈에 맞는 linter를 잘 설정 한다면 꽤나 디테일한 적용도 가능하다.

예를 들면 아래처럼 regex 패턴기반으로 log.Print 를 사용금지할 수도 있다.

linters:
  enable:
    - forbidigo
  settings:
    forbidigo:
      forbid:
        - pattern: log\.Print
          msg: "log.Print() 사용 금지"
          pkg: "^log$"
      analyze-types: true

golangci-lint는 로컬에서도 golangci-lint run 명령어로 실행할 수 있지만, github action이나 gitlab ci와도 잘 연동되며 CI에서 사용하기에도 무리가 없다.

Install | golangci-lint

아래는 유명 Go 기반 오픈소스 라이브러리들의 golangci-lint 사용 여부를 정리한 것이다.

라이브러리.golangci.yml 여부
kubernetes
gin
moby
prometheus
ollama
traefik
zap
open-match
grafana
elastic/beats(filebeat, etc)

자주 사용되는 linter

golangci.yml을 사용하는 9개 라이브러리들에서 주요 linter의 사용 빈도 정리

린터사용한 라이브러리 수목적
misspell6자주 스펠링 틀리는 영어 단어 체크
goimports5imort 추가/미사용 제거
nolintlint5잘못 작성된 nolint 선언 체크
revive5golint 대체제
unconvert5불필요한 type conversion 제거
depguard4특정 패키지 import를 허용/금지 가능
errorlint4error 비교시 errors.Is 사용 강제 등
gosec4소스코드의 보안 이슈 체크
nakedret4길이가 긴 함수의 naked return을 방지
wastedassign4불필요하게 낭비되는 변수 할당(할당 후 사용 안하는 케이스) 체크

Ref

Back To Top