Go는 언어 차원에서 코드 포맷팅 도구(gofmt)를 제공하고, 표준 도구 체인에 정적 분석(go vet)이 포함되어 있습니다.
이런 도구들을 프로젝트 초기에 설정해두면 코드 리뷰에서 스타일 논쟁을 줄이고, 흔한 실수를 빌드 전에 잡을 수 있습니다.
이 글에서는 Go로 개발하는 프로젝트에서 기본적으로 갖추면 좋은 도구들과 설정 방법을 정리합니다.
1. gofmt: 코드 포맷팅
Go 커뮤니티에서는 gofmt 이 표준 포맷터입니다.
탭/스페이스, 중괄호 위치 같은 스타일 논쟁 없이 모든 Go 코드가 동일한 포맷을 따르게 됩니다.
gofmt -w .
-w 플래그는 파일을 직접 수정합니다. -l 플래그를 쓰면 포맷이 맞지 않는 파일 목록만 출력합니다.
gofmt -l .
포맷이 맞지 않는 파일이 있으면 파일 경로가 출력되고, 모두 맞으면 아무것도 출력되지 않습니다. CI에서는 이 출력을 검사해서 포맷팅되지 않은 코드가 머지되는 것을 막을 수 있습니다.
2. goimports: import 정리
goimports는 `gofmt`의 기능에 더해 import 문을 자동으로 정리합니다.
-
사용하지 않는 import를 제거
-
필요한 import를 자동 추가
-
import를 표준 라이브러리 / 그 외 패키지 2그룹으로 정렬 (
-local플래그를 쓰면 로컬 패키지를 별도 그룹으로 분리 가능)
go install golang.org/x/tools/cmd/goimports@latest
goimports -w .
gofmt 대신 `goimports`만 실행해도 포맷팅까지 함께 처리됩니다.
3. go vet: 정적 분석
`go vet`은 Go 표준 도구 체인에 포함된 정적 분석 도구입니다. 컴파일러가 잡지 못하는 의심스러운 코드를 검출합니다.
go vet ./...
대표적으로 잡아주는 문제들은 다음과 같습니다.
-
fmt.Printf`의 포맷 문자열과 인자 불일치 (`printf) -
도달할 수 없는 코드 (
unreachable) -
sync.Mutex등 잠금을 값으로 복사하는 코드 (copylocks) -
context.WithCancel`의 cancel 함수를 호출하지 않는 경로 (`lostcancel)
별도 설치 없이 바로 사용할 수 있으므로, 모든 Go 프로젝트에서 기본으로 실행하는 것이 좋습니다.
4. golangci-lint: 통합 린터
golangci-lint는 여러 린터를 하나의 도구로 통합해서 실행해주는 린터 러너입니다. 개별 린터를 따로 설치하고 실행할 필요 없이, 설정 파일 하나로 원하는 린터를 선택해서 쓸 수 있습니다.
## 바이너리 설치 (go install은 공식적으로 권장되지 않음)
curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b $(go env GOPATH)/bin
golangci-lint run ./...
4.1. 설정 파일
프로젝트 루트에 .golangci.yml 파일을 만들어 설정합니다.
아래는 golangci-lint v2 기준 설정 예시입니다.
version: "2"
linters:
default: standard
enable:
- misspell
formatters:
enable:
- gofmt
- goimports
v2에서는 `version: "2"`를 반드시 명시해야 합니다. `default: standard`로 설정하면 errcheck, govet, ineffassign, staticcheck, unused 5개 린터가 기본으로 활성화됩니다. 추가로 필요한 린터만 `enable`에 넣으면 됩니다.
v2의 또 다른 변경점은 gofmt, goimports 같은 포맷터가 linters`가 아닌 `formatters 섹션으로 분리된 것입니다.
기존 v1 설정 파일이 있다면 golangci-lint migrate 명령으로 자동 변환할 수 있습니다.
4.2. 기본 활성화 린터 (default: standard)
| 린터 | 설명 |
|---|---|
errcheck |
에러 반환값을 무시하는 코드 검출 |
govet |
`go vet`과 동일한 정적 분석 |
ineffassign |
이후에 사용되지 않는 변수 할당 검출 |
staticcheck |
Go 코드의 버그, 성능 문제, 스타일 문제를 종합적으로 검사 (gosimple, stylecheck 포함) |
unused |
사용되지 않는 변수, 함수, 타입 검출 |
이 중 `errcheck`은 특히 중요합니다. Go에서 에러를 반환값으로 처리하는 관례상, 에러를 무시하는 코드는 런타임에 예상치 못한 동작을 일으킬 수 있습니다.
5. Makefile로 통합
위 도구들을 개별로 실행하는 것은 번거로우므로, Makefile에 통합하면 편리합니다.
.PHONY: fmt lint test check ci
fmt:
goimports -w .
lint:
golangci-lint run ./...
test:
go test ./...
check: fmt lint test
ci: lint test
check`와 `ci 두 가지 타겟을 나눈 이유가 있습니다.
fmt 타겟은 파일을 직접 수정하므로 로컬에서 커밋 전에 실행하기에 편리하지만, CI 환경에서는 파일을 수정하면 안 됩니다.
ci 타겟은 `golangci-lint`의 gofmt, goimports 린터가 포맷 위반을 검출만 하고 파일을 수정하지 않으므로, CI에서 안전하게 쓸 수 있습니다.
-
로컬:
make check(포맷 자동 수정 + 린트 + 테스트) -
CI:
make ci(린트 + 테스트, 파일 수정 없음)
`go vet`을 별도 타겟으로 두지 않은 이유는, golangci-lint에 govet 린터가 포함되어 있어서 `make lint`에서 이미 실행되기 때문입니다.
코드 수정 후 커밋하기 전에 `make check`를 실행하는 습관을 들이면, CI에서 실패하는 상황을 줄일 수 있습니다.
6. 참고 자료
(이 글은 개인 라이센스로 구매한 Claude Code의 도움을 받아 작성되었습니다.)
Twitter
Facebook
Reddit
LinkedIn
Email