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:

go
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:

bash
go vet ./...

Output yang Dihasilkan:

text
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.Mutex

Perintah keluar dengan status galat setelah mencetak lima baris temuan. Mari kita bedah arti setiap temuan:

  1. Baris 23 (fmt.Printf("Umur: %d\n", p.Nama)): analyzer printf menemukan verb %d yang seharusnya menerima bilangan bulat, tetapi argumen yang diberikan bertipe string. Keluaran program akan rusak di tempat ini.
  2. Baris 10 (tag json:nama): analyzer structtag menandai tag yang tidak dibungkus tanda kutip ganda. Bentuk yang benar adalah json:"nama"; tag salah tetap terbaca oleh reflect, tetapi perilakunya tidak terdefinisi dengan baik.
  3. Baris 16 dan 17 (fungsi salinPengguna): analyzer copylocks melaporkan parameter bertipe Pengguna diterima per nilai. Menyalin struct yang memuat sync.Mutex menduplikasi keadaan kunci yang sedang terkunci — sumber deadlock yang sulit dilacak.
  4. Baris 22 (pemanggilan di main): titik pemanggilan salinPengguna(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:

bash
go run masalah.go

Output yang Dihasilkan:

text
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:

go
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:

bash
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:

bash
go run contoh.go

Output yang Dihasilkan:

text
Umur: 25

go 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 #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

contoh.go

Unduh berkas
go
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)
}