golangci-lint & gerbang CI
gofmt sudah menyelesaikan urusan format. Yang tersisa untuk linter adalah hal yang lebih berharga: error yang diabaikan, context yang tidak diteruskan, kerentanan yang diketahui, dan dependensi yang dilarang.
Intisari
- Jalankan puluhan linter dalam satu proses dengan cache โ jauh lebih cepat daripada memanggilnya satu per satu.
- Yang paling berharga:
errcheck,govet,staticcheck,bodyclose,rowserrcheck,gosec,depguard. govulncheckmemeriksa kerentanan yang benar-benar bisa dijangkau kodemu โ bukan sekadar ada di dependensi.- Untuk repo lama, pakai
new-from-rev: kode baru harus bersih, yang lama tidak menghalangi. - Gerbang CI yang lengkap: fmt, vet, lint, vulncheck,
go mod tidybersih, tes-race.
Konfigurasi yang layak dipakai
# .golangci.yml
version: "2"
linters:
default: standard
enable:
# Bug nyata
- errcheck # error yang tidak diperiksa
- bodyclose # resp.Body yang tidak ditutup (Fase 3)
- rowserrcheck # rows.Err() yang tidak diperiksa (Fase 4)
- sqlclosecheck # rows/stmt yang tidak ditutup
- nilerr # "return nil" padahal err != nil
- contextcheck # context yang tidak diteruskan
- noctx # permintaan HTTP tanpa context
- errorlint # perbandingan error tanpa errors.Is/As (Fase 1)
- copyloopvar
- makezero
# Keamanan
- gosec # G112 timeout, G402 TLS, G404 math/rand (Fase 6)
# Kualitas
- staticcheck
- revive
- unconvert
- unparam
- prealloc
- misspell
# Batas arsitektur (Fase 5)
- depguard
linters-settings:
errcheck:
check-type-assertions: true
exclude-functions:
- (*database/sql.Rows).Close
depguard:
rules:
domain:
files: ["**/internal/produk/**", "**/internal/pesanan/**"]
deny:
- pkg: "net/http"
desc: "paket domain tidak boleh tahu HTTP"
- pkg: "database/sql"
desc: "akses database lewat interface penyimpan"
issues:
exclude-rules:
- path: _test\.go
linters: [errcheck, gosec, dupl]
Aturan depguard di atas mengubah aturan arsitektur Fase 5 dari kesepakatan jadi
kegagalan CI. Ini satu-satunya cara batas lapisan bertahan setelah tim bertambah โ dokumen yang
menjelaskan aturan akan dilanggar; linter tidak bisa dilanggar tanpa terlihat di diff.
Linter yang paling sering menangkap bug sungguhan
| Linter | Menangkap | Akibat kalau lolos |
|---|---|---|
bodyclose | resp.Body tidak ditutup | Kebocoran koneksi dan file descriptor |
rowserrcheck | rows.Err() tidak diperiksa | Data tidak lengkap tanpa error (Fase 4) |
sqlclosecheck | rows tidak ditutup | Pool database habis; aplikasi membeku |
errorlint | err == ErrX pada error terbungkus | Penanganan error diam-diam berhenti bekerja |
nilerr | return nil saat err != nil | Kegagalan yang ditelan |
contextcheck | context.Background() di tengah rantai | Pembatalan tidak sampai; timeout jadi hiasan |
gosec G404 | math/rand untuk hal rahasia | Token yang bisa ditebak (Fase 6) |
gosec G112 | Server tanpa ReadHeaderTimeout | Rentan Slowloris (Fase 3) |
Memasangnya di repo lama
# Hanya periksa kode yang berubah sejak main โ
# supaya ribuan temuan lama tidak menghalangi.
golangci-lint run --new-from-rev=origin/main
# Atau bekukan temuan yang ada, lalu perbaiki bertahap
golangci-lint run --issues-exit-code=0 > baseline.txt
Menyalakan semua linter sekaligus di repo yang sudah berjalan adalah cara tercepat membuat tim membenci linter. Mulai dari lima linter penangkap bug, buat gerbangnya wajib untuk kode baru, lalu tambahkan satu per satu. Yang penting bukan jumlah linter, melainkan bahwa gerbangnya tidak pernah dilewati.
govulncheck
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
Vulnerability #1: GO-2025-1234
Kerentanan pada net/http saat mem-parse header
Fixed in: go1.26.5
Example trace found:
#1: internal/http/server.go:42 memanggil http.ListenAndServe
Yang membedakan govulncheck dari pemindai dependensi biasa: ia menganalisis
jalur panggilan, dan hanya melaporkan kerentanan yang benar-benar bisa dijangkau kodemu. Itu
mengubah laporan dari "80 temuan di dependensi transitif" jadi "3 yang benar-benar memengaruhimu" โ
perbedaan antara laporan yang ditindaklanjuti dan laporan yang diabaikan.
Gerbang CI yang lengkap
name: ci
on: [push, pull_request]
jobs:
periksa:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.26"
cache: true
- name: format
run: test -z "$(gofmt -l .)"
- name: go.mod rapi
run: |
go mod tidy
git diff --exit-code go.mod go.sum
- name: vet
run: go vet ./...
- name: lint
uses: golangci/golangci-lint-action@v6
- name: kerentanan
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
- name: tes
run: go test -race -coverprofile=cover.out ./...
- name: build
run: go build ./...
| Gerbang | Menjamin |
|---|---|
gofmt -l kosong | Format seragam |
go mod tidy tidak mengubah apa pun | Dependensi jujur (Fase 0) |
go vet | Kesalahan yang lolos compiler |
golangci-lint | Bug, keamanan, batas arsitektur |
govulncheck | Tidak ada CVE yang bisa dijangkau |
go test -race | Tidak ada data race (Fase 2) |
Latihan: pasang .golangci.yml di atas dan jalankan pada proyekmu. Perbaiki semua
temuan errcheck dan bodyclose โ dua yang paling sering menemukan bug nyata.
Lalu tambahkan aturan depguard yang melarang paket domainmu mengimpor net/http,
dan lihat apakah kodemu sudah sebersih yang kamu kira.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.