โ† Semua pembelajaran / Go Nol โ†’ Enterprise
Fase 8 ยท Testing & Kualitas Kode

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.
  • govulncheck memeriksa 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 tidy bersih, 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

LinterMenangkapAkibat kalau lolos
bodycloseresp.Body tidak ditutupKebocoran koneksi dan file descriptor
rowserrcheckrows.Err() tidak diperiksaData tidak lengkap tanpa error (Fase 4)
sqlclosecheckrows tidak ditutupPool database habis; aplikasi membeku
errorlinterr == ErrX pada error terbungkusPenanganan error diam-diam berhenti bekerja
nilerrreturn nil saat err != nilKegagalan yang ditelan
contextcheckcontext.Background() di tengah rantaiPembatalan tidak sampai; timeout jadi hiasan
gosec G404math/rand untuk hal rahasiaToken yang bisa ditebak (Fase 6)
gosec G112Server tanpa ReadHeaderTimeoutRentan 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 ./...
GerbangMenjamin
gofmt -l kosongFormat seragam
go mod tidy tidak mengubah apa punDependensi jujur (Fase 0)
go vetKesalahan yang lolos compiler
golangci-lintBug, keamanan, batas arsitektur
govulncheckTidak ada CVE yang bisa dijangkau
go test -raceTidak 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.