Format yang rapi dan import yang bersih belum menjamin kode bebas bug. Sebagian kesalahan lolos dari kompilator karena tetap sah secara tipe — misalnya format verb Printf yang tidak cocok dengan argumennya, atau struct yang disalin lengkap dengan kunci sync.Mutex di dalamnya. Perintah go vet mengisi celah ini: pemeriksa statis bawaan toolchain yang memindai kode untuk pola mencurigakan tanpa menjalankan program. Pada chapter ini kita akan menulis kode yang sengaja memuat tiga kategori masalah, melihat bagaimana go vet menangkap semuanya, lalu memverifikasi versi perbaikannya lolos pemeriksaan.
Pemeriksa Statis Bawaan Toolchain #
go vet bekerja pada level syntax tree seperti gofmt, tetapi tujuannya berbeda: bukan menata tampilan, melainkan mencari konstruksi yang hampir pasti salah. Contoh klasiknya adalah pemanggilan fmt.Printf dengan verb %d untuk argumen string — kode seperti ini dikompilasi tanpa keluhan karena tipe argumennya sah, tetapi keluaran programnya rusak. vet menjalankan sekumpulan analyzer (printf, structtag, copylocks, lostcancel, dan lainnya) yang masing-masing mengawasi satu pola bahaya.
Berbeda dengan linter pihak ketiga, vet dibundel bersama Go: tidak ada instalasi tambahan, dan hasilnya distandarkan lintas versi. Karena itu vet menjadi pemeriksa pertama yang layak dijalankan di setiap proyek.
Demo: Empat Masalah dalam Satu Berkas #
Kita siapkan modul kecil example.com/vetdemo berisi satu berkas masalah.go. Berkas ini memuat masalah dari tiga kategori: struct tag tanpa tanda kutip, struct dengan sync.Mutex yang disalin per nilai, dan format verb yang salah:
package main
import (
"fmt"
"sync"
)
// Pengguna menyimpan data profil seorang pengguna.
type Pengguna struct {
Nama string `json:nama`
Umur int `json:"umur"`
kunci sync.Mutex
nilai map[string]int
}
func salinPengguna(p Pengguna) Pengguna {
return p
}
func main() {
p := Pengguna{Nama: "Budi", Umur: 25, nilai: map[string]int{}}
salinPengguna(p)
fmt.Printf("Umur: %d\n", p.Nama)
}Jalankan pemeriksaan pada seluruh package modul:
go vet ./...Output yang Dihasilkan:
masalah.go:23:20: fmt.Printf format %d has arg p.Nama of wrong type string
masalah.go:10:2: struct field tag `json:nama` not compatible with reflect.StructTag.Get: bad syntax for struct tag value
masalah.go:16:22: salinPengguna passes lock by value: example.com/vetdemo.Pengguna contains sync.Mutex
masalah.go:17:9: return copies lock value: example.com/vetdemo.Pengguna contains sync.Mutex
masalah.go:22:16: call of salinPengguna copies lock value: example.com/vetdemo.Pengguna contains sync.MutexPerintah keluar dengan status galat setelah mencetak lima baris temuan. Mari kita bedah arti setiap temuan:
- Baris 23 (
fmt.Printf("Umur: %d\n", p.Nama)): analyzer printf menemukan verb%dyang seharusnya menerima bilangan bulat, tetapi argumen yang diberikan bertipe string. Keluaran program akan rusak di tempat ini. - Baris 10 (tag
json:nama): analyzer structtag menandai tag yang tidak dibungkus tanda kutip ganda. Bentuk yang benar adalahjson:"nama"; tag salah tetap terbaca olehreflect, tetapi perilakunya tidak terdefinisi dengan baik. - Baris 16 dan 17 (fungsi
salinPengguna): analyzer copylocks melaporkan parameter bertipePenggunaditerima per nilai. Menyalin struct yang memuatsync.Mutexmenduplikasi keadaan kunci yang sedang terkunci — sumber deadlock yang sulit dilacak. - Baris 22 (pemanggilan di
main): titik pemanggilansalinPengguna(p)dihitung sebagai penyalinan kunci juga, sehingga satu desain keliru menghasilkan tiga temuan sekaligus.
Kompilasi Lulus, Bug Tetap Bersembunyi #
Bukti bahwa masalah-masalah di atas tidak tertangkap kompilator: program tersebut tetap bisa dijalankan:
go run masalah.goOutput yang Dihasilkan:
Umur: %!d(string=Budi)Baris %!d(string=Budi) adalah bentuk fmt ketika verb tidak cocok dengan argumen — program berjalan, tetapi keluarannya jelas salah. Kasus struct tag dan penyalinan kunci bahkan tidak menampakkan gejala langsung; keduanya hanya meledak dalam situasi tertentu, misalnya saat tag dibaca encoding/json atau saat dua goroutine memakai salinan kunci yang berbeda.
Versi yang Sudah Diperbaiki #
Perbaikan atas seluruh masalah tersebut: tag dibungkus tanda kutip, sync.Mutex tidak lagi tersimpan dalam struct yang disalin, fungsi menerima pointer, dan verb %d tetap dipakai tetapi argumennya diganti p.Umur yang memang bilangan. Berkas contoh.go pada bagian bawah halaman memuat versi perbaikan tersebut. Seluruh berkas berbunyi:
package main
import (
"fmt"
"sync"
)
// Pengguna menyimpan data profil seorang pengguna.
type Pengguna struct {
Nama string `json:"nama"`
Umur int `json:"umur"`
mu sync.Mutex
}
// tampilkan mencetak umur pengguna dengan kunci terlindungi.
func tampilkan(p *Pengguna) {
p.mu.Lock()
defer p.mu.Unlock()
fmt.Printf("Umur: %d\n", p.Umur)
}
func main() {
p := Pengguna{Nama: "Budi", Umur: 25}
tampilkan(&p)
}Dalam modul uji, versi perbaikan yang identik menghasilkan pemeriksaan yang bersih:
go vet ./...Perintah tidak mencetak satu baris pun dan keluar dengan status 0 — dua tanda itulah pertanda go vet puas; bila ada temuan, keluaran berisi baris galat dan status menjadi 1. Program perbaikan kemudian berjalan normal:
go run contoh.goOutput yang Dihasilkan:
Umur: 25go vet dalam Keseharian #
go vet paling efektif dijalankan otomatis, bukan diingat-ingat manual. Tiga titik integrasi yang umum: go test sebenarnya sudah menjalankan sebagian analyzer berkeyakinan tinggi (termasuk printf) sebelum mengeksekusi test, editor seperti gopls menandai temuan vet langsung saat mengetik, dan pipeline CI menjalankan go vet ./... pada setiap push. Penutup rangkaian pemeriksaan statis ini adalah staticcheck yang mengawasi pola jauh lebih luas — chapter berikutnya membahasnya.
Referensi #
- Dokumentasi perintah go vet — daftar resmi seluruh analyzer beserta penjelasannya.
- Diagnostics — dokumentasi resmi Go — posisi vet di antara alat diagnostik Go.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contoh.go
Unduh berkaspackage main
import (
"fmt"
"sync"
)
// Pengguna menyimpan data profil seorang pengguna.
type Pengguna struct {
Nama string `json:"nama"`
Umur int `json:"umur"`
mu sync.Mutex
}
// tampilkan mencetak umur pengguna dengan kunci terlindungi.
func tampilkan(p *Pengguna) {
p.mu.Lock()
defer p.mu.Unlock()
fmt.Printf("Umur: %d\n", p.Umur)
}
func main() {
p := Pengguna{Nama: "Budi", Umur: 25}
tampilkan(&p)
}