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:

AturanAkibat
go build, go test, go run tidak pernah menjalankan directiveProgram tetap bisa dikompilasi tanpa generator terpasang
Hanya go generate yang menjalankan directive, secara eksplisitDeveloper 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:

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

  1. Baris 5 (//go:generate echo const versi = "1.2.3"): directive pertama hanya mencetak usulan nilai versi ke terminal — keluarannya akan kita lihat pada eksekusi go generate.
  2. Baris 6 (//go:generate sh -c "printf ..."): directive kedua membangkitkan berkas versi.go lewat printf dengan pengalihan keluaran. go generate menjalankan program secara langsung tanpa shell, sehingga tanda > harus dibungkus sh -c "..." agar diartikan sebagai pengalihan berkas.
  3. Baris 9 (fmt.Println("Aplikasi kasir versi", Versi)): program memakai konstanta Versi yang baru dideklarasikan di versi.go — sebelum berkas itu dibangkitkan, go run . gagal dengan galat undefined: Versi.

Jalankan seluruh directive pada module:

bash
go generate ./...

Output yang Dihasilkan:

text
const versi = 1.2.3

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

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

bash
go run .

Output yang Dihasilkan:

text
Aplikasi kasir versi 1.2.3

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

bash
rm versi.go
go generate -run echo ./...

Output yang Dihasilkan:

text
const versi = 1.2.3

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

go
type Hari int

const (
	Senin Hari = iota
	Selasa
	Rabu
)

//go:generate stringer -type=Hari

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

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

bash
go run contoh.go

Output yang Dihasilkan:

text
Aplikasi kasir versi 1.2.3

Sekarang jalankan directive di dalamnya dari direktori chapter:

bash
go generate ./...

Output yang Dihasilkan:

text
perbarui versi ke 1.2.3

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

Berkas Sumber

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

contoh.go

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