Sebagian kode Go sebenarnya bukan hasil pemikiran manusia: method String() untuk tiap konstanta enum, pembungkus protobuf, binding database, hingga daftar kode kesalahan. Menulis semuanya dengan tangan membosankan dan rawan salah sinkron. Perintah go generate menjembatani kedua dunia: penanda //go:generate di dalam kode mendeklarasikan perintah pembangkit, dan go generate menjalankan seluruh penanda itu satu kali atas permintaan. Pada chapter ini kita akan menjalankan directive nyata pada module kecil, membangkitkan berkas versi otomatis, lalu menyaring directive mana yang boleh jalan.
Directive go:generate #
Directive ditulis sebagai komentar dengan bentuk kaku //go:generate perintah argumen... — tanpa spasi setelah garis miring ganda dan tanpa spasi di awal baris. Tiga aturan mainnya:
| Aturan | Akibat |
|---|---|
go build, go test, go run tidak pernah menjalankan directive | Program tetap bisa dikompilasi tanpa generator terpasang |
Hanya go generate yang menjalankan directive, secara eksplisit | Developer mengendalikan kapan kode dibangkitkan ulang |
| Argumen dipisah spasi; string berpetik ganda mengikuti sintaks Go | "1.2.3" diteruskan sebagai satu argumen tanpa tanda petiknya |
Perintah directive dijalankan di direktori package pemilik berkasnya, dan keluaran langsungnya diteruskan ke terminal.
Demo: Directive echo dan Keluarannya #
Kita siapkan module kecil example.com/gendemo dengan satu berkas main.go. Directive pertamanya memakai echo — generator paling sederhana yang ada di setiap shell:
package main
import "fmt"
//go:generate echo const versi = "1.2.3"
//go:generate sh -c "printf 'package main\n\n// Code generated by go generate. DO NOT EDIT.\nconst Versi = \"1.2.3\"\n' > versi.go"
func main() {
fmt.Println("Aplikasi kasir versi", Versi)
}Mari kita telaah berkas di atas baris demi baris:
- Baris 5 (
//go:generate echo const versi = "1.2.3"): directive pertama hanya mencetak usulan nilai versi ke terminal — keluarannya akan kita lihat pada eksekusigo generate. - Baris 6 (
//go:generate sh -c "printf ..."): directive kedua membangkitkan berkasversi.golewatprintfdengan pengalihan keluaran.go generatemenjalankan program secara langsung tanpa shell, sehingga tanda>harus dibungkussh -c "..."agar diartikan sebagai pengalihan berkas. - Baris 9 (
fmt.Println("Aplikasi kasir versi", Versi)): program memakai konstantaVersiyang baru dideklarasikan diversi.go— sebelum berkas itu dibangkitkan,go run .gagal dengan galatundefined: Versi.
Jalankan seluruh directive pada module:
go generate ./...Output yang Dihasilkan:
const versi = 1.2.3Baris tersebut adalah keluaran echo yang diteruskan go generate ke terminal. Tanda petik pada "1.2.3" hilang karena string berpetik ganda pada directive dievaluasi dengan sintaks Go, lalu diteruskan sebagai argumen tunggal. Sementara itu directive kedua bekerja diam-diam: berkas versi.go kini tercipta di direktori yang sama.
Berkas Hasil Bangkitan #
Isi versi.go yang dibentuk directive kedua:
package main
// Code generated by go generate. DO NOT EDIT.
const Versi = "1.2.3"Baris // Code generated by go generate. DO NOT EDIT. bukan hiasan — baris yang cocok dengan pola ^// Code generated .* DO NOT EDIT\.$ memberi tahu manusia dan tool bahwa berkas ini hasil mesin dan tidak boleh diedit manual. Setelah berkas hadir, program menjadi utuh dan bisa dijalankan:
go run .Output yang Dihasilkan:
Aplikasi kasir versi 1.2.3Menyaring Directive dengan -run #
Module yang matang bisa memuat banyak directive dengan generator berbeda — sebagian cepat, sebagian berat. Flag -run menerima pola dan hanya menjalankan directive yang baris perintahnya cocok:
rm versi.go
go generate -run echo ./...Output yang Dihasilkan:
const versi = 1.2.3Directive echo tetap mencetak keluarannya, tetapi versi.go tidak tercipta karena directive sh -c dilewati. Menjalankan go generate -run sh ./... melakukan kebalikannya: versi.go dibuat ulang tanpa keluaran terminal. Flag -n menyediakan mode simulasi — seluruh directive dicetak tanpa dijalankan.
Mengapa Generate, Bukan Tulis Manual #
Alasan fundamentalnya adalah satu sumber kebenaran. Nilai versi, nama konstanta, dan daftar error cukup dideklarasikan sekali, lalu seluruh turunannya dibangkitkan — ketika sumber berubah, hasil regenerasi tidak bisa salah sinkron. Generator populer di ekosistem Go antara lain stringer yang membuat method String() untuk tipe konstanta:
type Hari int
const (
Senin Hari = iota
Selasa
Rabu
)
//go:generate stringer -type=HariSetelah directive itu dijalankan, berkas hari_string.go tercipta berisi pemetaan Senin → "Senin" lengkap dengan penanda DO NOT EDIT. Pola serupa dipakai protobuf dan mockery. Kompilasi generator besar memang lambat, tetapi hasilnya dipakai berulang tanpa menjalankan ulang.
Berkas contoh.go pada bagian bawah halaman memuat directive yang aman untuk dicoba — program tetap bisa dijalankan tanpa pembangkitan karena Versi dideklarasikan manual, dan directive hanya mencatat prosedur pembaruan. Seluruh berkas berbunyi:
package main
import "fmt"
// Versi adalah nomor versi aplikasi kasir.
const Versi = "1.2.3"
//go:generate echo perbarui versi ke 1.2.3
func main() {
fmt.Println("Aplikasi kasir versi", Versi)
}Program tersebut berjalan normal meski go generate belum pernah dipanggil:
go run contoh.goOutput yang Dihasilkan:
Aplikasi kasir versi 1.2.3Sekarang jalankan directive di dalamnya dari direktori chapter:
go generate ./...Output yang Dihasilkan:
perbarui versi ke 1.2.3Dengan directive yang tepat, kode berulang menulis dirinya sendiri. Chapter berikutnya menyusun langkah-langkah manual yang tersisa — menjalankan test, build, dan lint secara berurutan — ke dalam satu Makefile.
Referensi #
- Generate Go files by processing source — perintah go
— spesifikasi lengkap directive dan flag
go generate. - Effective Go — bagian generate — motivasi dan filosofi go generate dari dokumen resmi.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contoh.go
Unduh berkaspackage main
import "fmt"
// Versi adalah nomor versi aplikasi kasir.
const Versi = "1.2.3"
//go:generate echo perbarui versi ke 1.2.3
func main() {
fmt.Println("Aplikasi kasir versi", Versi)
}