Komentar #
Komentar adalah bagian dari kode yang diabaikan compiler tapi dibaca manusia. Di Go, komentar punya peran lebih dari sekadar penjelasan: sistem godoc menggunakan komentar untuk menghasilkan dokumentasi API secara otomatis, dan beberapa komentar khusus (disebut direktif) bahkan mempengaruhi perilaku compiler dan toolchain. Memahami cara menulis komentar yang baik di Go berarti memahami konvensi yang membuat seluruh ekosistem Go bisa terdokumentasi secara konsisten.
Dua Jenis Komentar #
Go mendukung dua sintaks komentar yang identik dengan C:
// Ini adalah komentar satu baris.
// Diawali dengan dua garis miring dan berlaku sampai akhir baris.
/*
Ini adalah komentar multi-baris.
Bisa mencakup banyak baris sekaligus.
Sering digunakan untuk "mematikan" blok kode sementara.
*/
Dalam praktiknya, komentar satu baris // jauh lebih umum digunakan di Go — termasuk untuk komentar yang mencakup beberapa baris berurutan. Komentar /* */ lebih sering dipakai untuk mematikan blok kode sementara saat debugging.
package main
import "fmt"
func main() {
// Hitung total harga setelah diskon
harga := 100000
diskon := 0.1
total := harga - int(float64(harga)*diskon) // kurangi 10%
fmt.Println("Total:", total)
/*
// Kode ini sedang dinonaktifkan sementara untuk debugging
if total < 0 {
fmt.Println("Error: total negatif")
}
*/
}
Doc Comment — Dokumentasi yang Dibaca godoc #
Go mempunyai sistem dokumentasi built-in: godoc membaca komentar yang ditulis tepat di atas declaration dan menggunakannya sebagai dokumentasi resmi. Konvensi ini sangat penting karena seluruh standard library Go — dan hampir semua library Go yang baik — mengikutinya.
Aturan doc comment:
- Ditulis dengan
//(bukan/* */) - Diletakkan tepat di atas declaration tanpa baris kosong di antaranya
- Dimulai dengan nama identifier yang didokumentasikan
- Diakhiri dengan tanda titik
// Package mathutil menyediakan fungsi-fungsi matematika tambahan
// yang tidak tersedia di standard library.
// Package ini tidak memiliki dependency eksternal.
package mathutil
// MaxInt adalah nilai integer terbesar yang bisa direpresentasikan
// pada platform ini. Nilainya bergantung pada apakah sistem 32-bit atau 64-bit.
const MaxInt = int(^uint(0) >> 1)
// ErrDivisionByZero dikembalikan ketika operasi pembagian
// mencoba membagi dengan nol.
var ErrDivisionByZero = errors.New("pembagian dengan nol")
// Add mengembalikan penjumlahan dari a dan b.
// Fungsi ini aman untuk semua nilai int, termasuk negatif.
func Add(a, b int) int {
return a + b
}
// Divide mengembalikan hasil pembagian a dibagi b.
// Jika b adalah nol, Divide mengembalikan 0 dan ErrDivisionByZero.
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, ErrDivisionByZero
}
return a / b, nil
}
// Calculator menyimpan state kalkulasi dan menyediakan operasi aritmatika dasar.
// Gunakan NewCalculator untuk membuat instance yang sudah terinisialisasi.
type Calculator struct {
// Memory menyimpan nilai yang tersimpan di memori kalkulator.
Memory float64
// Precision menentukan jumlah digit desimal dalam output.
// Default: 2.
Precision int
result float64 // hasil kalkulasi saat ini (tidak diekspos)
}
// NewCalculator membuat Calculator baru dengan precision default 2.
func NewCalculator() *Calculator {
return &Calculator{Precision: 2}
}
Setelah menulis komentar di atas, jalankan go doc untuk melihat hasilnya:
go doc mathutil.Add
# Output:
# func Add(a, b int) int
# Add mengembalikan penjumlahan dari a dan b. Fungsi ini aman untuk semua
# nilai int, termasuk negatif.
go doc mathutil.Calculator
# Output:
# type Calculator struct {
# Memory float64
# Precision int
# }
# Calculator menyimpan state kalkulasi...
Konvensi Doc Comment yang Lebih Kaya (Go 1.19+) #
Sejak Go 1.19, godoc mendukung markup ringan dalam doc comment:
Paragraf #
Pisahkan dengan baris kosong (dengan // kosong):
// Connect membuat koneksi ke database menggunakan parameter yang diberikan.
//
// Fungsi ini mencoba koneksi sebanyak maxRetry kali dengan jeda eksponensial
// sebelum mengembalikan error. Setiap percobaan di-log ke logger yang diberikan.
//
// Contoh penggunaan:
//
// db, err := Connect("postgres://localhost/myapp", 3, logger)
// if err != nil {
// log.Fatal(err)
// }
// defer db.Close()
func Connect(dsn string, maxRetry int, logger *zap.Logger) (*DB, error) {
// ...
}
Daftar dalam Doc Comment #
// Status merepresentasikan kondisi dari sebuah order.
// Ada lima status yang valid:
//
// - Pending: order dibuat, menunggu pembayaran
// - Paid: pembayaran diterima, menunggu pemrosesan
// - Processing: sedang diproses
// - Shipped: sudah dikirim
// - Cancelled: dibatalkan, tidak bisa diubah
type Status int
Kode dalam Doc Comment #
Blok kode dalam doc comment diindentasi dengan satu tab:
// ParseConfig membaca konfigurasi dari file JSON.
// Format file JSON yang diharapkan:
//
// {
// "host": "localhost",
// "port": 8080,
// "debug": false
// }
//
// Jika field tidak ada, nilai default akan digunakan.
func ParseConfig(path string) (*Config, error) {
// ...
}
Komentar Package #
Komentar package adalah komentar yang mendokumentasikan keseluruhan package. Ia harus berada di atas deklarasi package di salah satu file dalam package tersebut (biasanya file utama atau file bernama doc.go):
// Package validator menyediakan validasi input untuk aplikasi web.
//
// Package ini mendukung validasi berbasis tag struct dan validasi
// kustom melalui interface Validator. Semua fungsi validasi thread-safe
// dan bisa digunakan bersamaan dari banyak goroutine.
//
// Contoh penggunaan dasar:
//
// type User struct {
// Name string `validate:"required,min=3,max=50"`
// Email string `validate:"required,email"`
// Age int `validate:"min=0,max=150"`
// }
//
// v := validator.New()
// user := User{Name: "Budi", Email: "[email protected]", Age: 28}
// if err := v.Validate(user); err != nil {
// fmt.Println("Validasi gagal:", err)
// }
package validator
Untuk package dengan dokumentasi panjang, biasanya dibuat file doc.go terpisah:
mypackage/
├── doc.go ← hanya berisi package comment
├── validator.go
├── rules.go
└── errors.go
Komentar yang Baik: “Mengapa”, Bukan “Apa” #
Ini adalah prinsip terpenting dalam menulis komentar. Kode yang baik sudah menjelaskan apa yang dilakukan — nama variabel, fungsi, dan tipe harus cukup deskriptif. Komentar yang bernilai menjelaskan mengapa sesuatu dilakukan dengan cara tertentu:
// ANTI-PATTERN: komentar yang mengulangi kode
// Tambahkan 1 ke counter
counter++
// Buat slice kosong
items := make([]Item, 0)
// Cek apakah err tidak nil
if err != nil {
return err
}
// BENAR: komentar yang menjelaskan MENGAPA
// Rate limiter bucket diisi ulang setiap detik, bukan setiap request,
// untuk memungkinkan burst traffic hingga maxBurst request sekaligus
// sementara tetap menjaga rata-rata sesuai limit.
token := time.Now().Unix() / int64(refillInterval.Seconds())
// Gunakan int64 bukan int untuk ID agar aman di platform 32-bit
// di mana int hanya 32-bit dan tidak cukup untuk ID database besar.
var userID int64
// Skip validasi untuk admin — mereka bisa mengakses resource apapun
// tanpa perlu pemeriksaan ownership. Keputusan ini sudah didiskusikan
// di ADR-042 dan disetujui oleh tim security.
if user.IsAdmin() {
return resource, nil
}
// Kurangi dari belakang untuk menghindari index shifting saat delete.
// Jika iterate dari depan, setiap delete menggeser semua elemen setelahnya
// sehingga kita bisa melewatkan elemen.
for i := len(items) - 1; i >= 0; i-- {
if items[i].Expired() {
items = append(items[:i], items[i+1:]...)
}
}
Komentar TODO, FIXME, dan HACK #
Komentar bertag adalah konvensi umum untuk menandai pekerjaan yang tertunda:
// TODO: tambahkan validasi email sebelum menyimpan ke database
// TODO(budi): implementasikan retry logic setelah diskusi dengan tim
// FIXME: fungsi ini tidak aman untuk concurrent access
// Perlu ditambahkan mutex sebelum di-deploy ke production
func updateCache(key string, val interface{}) {
cache[key] = val // ← race condition!
}
// HACK: bypass validasi karena bug di library pihak ketiga (issue #234)
// Hapus ini setelah library diupdate ke v2.1.0
value := strings.TrimRight(input, "\x00")
// NOTE: urutan di bawah sangat penting — jangan diubah tanpa memahami alasannya.
// Step 1 harus selesai sebelum Step 2 karena Step 2 bergantung pada state
// yang diinisialisasi Step 1.
Tag ini berguna karena mudah di-grep:
grep -rn "TODO\|FIXME\|HACK" ./...
Direktif Compiler — Komentar Spesial #
Beberapa komentar di Go bukan sekadar dokumentasi — mereka adalah instruksi ke compiler atau toolchain. Formatnya adalah //go:direktif tanpa spasi setelah //.
//go:generate — Mengotomatiskan Code Generation
#
// Jalankan go generate ./... untuk mengeksekusi perintah di bawah
//go:generate mockgen -source=./repository.go -destination=./mock/repository_mock.go
//go:generate stringer -type=Status
//go:generate protoc --go_out=. api.proto
Kemudian jalankan:
go generate ./... # eksekusi semua go:generate dalam package
//go:build — Build Constraints
#
Menentukan kondisi di mana file ini dikompilasi:
//go:build linux || darwin
// +build linux darwin ← format lama (sebelum Go 1.17), masih didukung
package main
// Kode ini hanya dikompilasi di Linux dan macOS
//go:build !windows
package fileutil
// Implementasi Unix-style file permissions
//go:build integration
package db_test
// Test ini hanya dijalankan dengan: go test -tags=integration ./...
//go:noinline, //go:nosplit, dll — Optimasi Compiler
#
// Directive untuk mencegah fungsi di-inline oleh compiler.
// Berguna untuk benchmarking yang akurat.
//
//go:noinline
func computeHash(data []byte) uint64 {
// ...
}
_ "embed" dan //go:embed
#
import _ "embed"
//go:embed templates/email.html
var emailTemplate string
//go:embed static/*
var staticFiles embed.FS
Mematikan Kode dengan Komentar #
Saat debugging atau development, sering perlu “mematikan” blok kode sementara:
// Gaya 1: gunakan /* */ untuk blok kode
/*
func lama() {
// implementasi lama yang belum dihapus
fmt.Println("ini tidak akan dieksekusi")
}
*/
// Gaya 2: gunakan // untuk setiap baris (lebih umum di Go modern)
// func lama() {
// fmt.Println("ini tidak akan dieksekusi")
// }
// Gaya 3: gunakan konstanta boolean (untuk conditional yang sering diubah)
const debug = false
if debug {
fmt.Println("debug info") // compiler menghapus blok ini jika debug=false
}
Editor modern (VS Code dengan Go extension, GoLand) punya shortcut untuk toggle comment:Ctrl+/(Windows/Linux) atauCmd+/(macOS). Ini secara otomatis menambah atau menghapus//di setiap baris yang dipilih.
godoc dan pkgsite — Membaca Dokumentasi
#
Go menyediakan dua cara untuk membaca dokumentasi dari komentar:
go doc — Baris Perintah
#
# Dokumentasi package
go doc fmt
# Dokumentasi fungsi spesifik
go doc fmt.Println
go doc fmt.Sprintf
# Dokumentasi tipe
go doc net/http.Request
# Semua yang exported dari package
go doc -all strings
# Dokumentasi dengan source code
go doc -src strings.Builder
godoc — Server Web Lokal
#
# Install godoc
go install golang.org/x/tools/cmd/godoc@latest
# Jalankan server di port 6060
godoc -http=:6060
# Buka di browser: http://localhost:6060/pkg/
pkgsite — Versi Modern pkgsite.go.dev
#
# Install pkgsite
go install golang.org/x/pkgsite/cmd/pkgsite@latest
# Jalankan di proyek kamu
pkgsite
# Buka di browser: http://localhost:8080
Anti-Pattern Komentar yang Harus Dihindari #
// ANTI-PATTERN 1: Komentar yang sudah usang — lebih berbahaya dari tidak ada komentar
// Fungsi ini mengembalikan nilai string dari integer
func formatPrice(price float64) string { // ← tipe sudah berubah, komentar tidak diupdate
return fmt.Sprintf("Rp%.2f", price)
}
// ANTI-PATTERN 2: Komentar yang menjelaskan hal yang sudah jelas
// Assign nilai 10 ke x
x := 10
// Kembalikan true jika nama kosong
return name == ""
// ANTI-PATTERN 3: Commented-out code yang dibiarkan lama
// func oldImplementation() { ← kode lama yang tidak pernah dihapus
// ...
// }
// Hapus kode mati — gunakan version control untuk sejarah
// ANTI-PATTERN 4: Komentar sebagai pengganti nama yang baik
// p adalah pemroses permintaan HTTP
func p(w http.ResponseWriter, r *http.Request) {
// Lebih baik: beri nama yang deskriptif
func handleUserProfile(w http.ResponseWriter, r *http.Request) {
// ANTI-PATTERN 5: Komentar TODO yang tidak pernah diselesaikan
// TODO: perbaiki ini (ditulis 3 tahun lalu, tidak pernah disentuh)
// Jika TODO tidak akan dikerjakan dalam waktu dekat, buat issue di tracker
Contoh Program Lengkap #
Program berikut mendemonstrasikan semua konvensi komentar dalam konteks package yang lengkap:
// Package currency menyediakan tipe dan fungsi untuk menangani
// nilai mata uang dengan presisi yang tepat.
//
// Package ini menghindari penggunaan float64 untuk kalkulasi keuangan
// dengan menyimpan semua nilai dalam unit terkecil (sen/rupiah terkecil).
//
// Contoh penggunaan:
//
// harga := currency.NewIDR(150000) // Rp 150.000
// pajak := harga.Percent(11) // 11% PPN
// total := harga.Add(pajak)
// fmt.Println(total.Format()) // Rp 166.500,00
package currency
import (
"fmt"
"strings"
)
// IDR merepresentasikan nilai dalam Rupiah Indonesia.
// Nilai disimpan dalam sen (unit terkecil) sebagai integer
// untuk menghindari floating-point rounding error.
//
// IDR adalah value type — operasi seperti Add dan Sub
// mengembalikan nilai baru tanpa memodifikasi receiver.
type IDR struct {
// sen menyimpan nilai dalam satuan sen (1/100 Rupiah)
// Nilai negatif merepresentasikan hutang atau kredit.
sen int64
}
// NewIDR membuat nilai IDR dari jumlah rupiah.
// Contoh: NewIDR(150000) membuat nilai Rp 150.000,00.
func NewIDR(rupiah int64) IDR {
return IDR{sen: rupiah * 100}
}
// NewIDRFromSen membuat nilai IDR dari jumlah sen.
// Berguna untuk presisi penuh, misalnya NewIDRFromSen(15050)
// membuat Rp 150,50.
func NewIDRFromSen(sen int64) IDR {
return IDR{sen: sen}
}
// Add mengembalikan penjumlahan dari dua nilai IDR.
// Operasi ini tidak memodifikasi receiver.
func (a IDR) Add(b IDR) IDR {
return IDR{sen: a.sen + b.sen}
}
// Sub mengembalikan pengurangan dari dua nilai IDR.
// Hasilnya bisa negatif jika b lebih besar dari a.
func (a IDR) Sub(b IDR) IDR {
return IDR{sen: a.sen - b.sen}
}
// Percent menghitung persentase dari nilai IDR.
// Hasilnya dibulatkan ke sen terdekat menggunakan pembulatan normal.
//
// Contoh: IDR{15000000}.Percent(11) = Rp 1.650.000 (11% dari Rp 15.000.000)
func (a IDR) Percent(pct int) IDR {
// Gunakan perkalian integer sebelum pembagian untuk menghindari
// kehilangan presisi. Tambahkan 50 sebelum bagi 100 untuk pembulatan.
result := (a.sen*int64(pct) + 50) / 100
return IDR{sen: result}
}
// IsZero melaporkan apakah nilai IDR adalah nol.
func (a IDR) IsZero() bool {
return a.sen == 0
}
// IsNegative melaporkan apakah nilai IDR negatif (hutang/kredit).
func (a IDR) IsNegative() bool {
return a.sen < 0
}
// Rupiah mengembalikan nilai dalam satuan Rupiah (bukan sen).
// Nilai desimal dibulatkan ke bawah.
func (a IDR) Rupiah() int64 {
return a.sen / 100
}
// Format mengembalikan representasi string dari nilai IDR
// dalam format standar Indonesia: "Rp 1.500.000,00".
func (a IDR) Format() string {
// Tangani nilai negatif
prefix := "Rp "
sen := a.sen
if sen < 0 {
prefix = "-Rp "
sen = -sen
}
rupiah := sen / 100
sisa := sen % 100
// Format angka rupiah dengan pemisah ribuan
rupiahStr := formatWithThousands(rupiah)
return fmt.Sprintf("%s%s,%02d", prefix, rupiahStr, sisa)
}
// String mengimplementasikan interface fmt.Stringer.
// Output sama dengan Format().
func (a IDR) String() string {
return a.Format()
}
// formatWithThousands memformat integer dengan pemisah ribuan (titik).
// Contoh: 1500000 → "1.500.000"
func formatWithThousands(n int64) string {
s := fmt.Sprintf("%d", n)
if len(s) <= 3 {
return s
}
var result strings.Builder
start := len(s) % 3
if start > 0 {
result.WriteString(s[:start])
}
for i := start; i < len(s); i += 3 {
if i > 0 || start > 0 {
result.WriteByte('.')
}
result.WriteString(s[i : i+3])
}
return result.String()
}
func main() {
// Demonstrasi penggunaan package currency
harga := NewIDR(15000000) // Rp 15.000.000
diskon := harga.Percent(10) // Diskon 10%
setelahDiskon := harga.Sub(diskon)
pajak := setelahDiskon.Percent(11) // PPN 11%
total := setelahDiskon.Add(pajak)
fmt.Printf("Harga awal : %s\n", harga)
fmt.Printf("Diskon 10%% : %s\n", diskon)
fmt.Printf("Setelah diskon: %s\n", setelahDiskon)
fmt.Printf("PPN 11%% : %s\n", pajak)
fmt.Printf("Total : %s\n", total)
// TODO: tambahkan dukungan untuk mata uang lain (USD, EUR, dll)
// Diskusikan desain API di issue #15 sebelum implementasi
}
Output:
Harga awal : Rp 15.000.000,00
Diskon 10% : Rp 1.500.000,00
Setelah diskon: Rp 13.500.000,00
PPN 11% : Rp 1.485.000,00
Total : Rp 14.985.000,00
Ringkasan #
- Dua jenis komentar:
//untuk satu baris (lebih umum) dan/* */untuk multi-baris atau mematikan blok kode sementara.- Doc comment ditulis dengan
//tepat di atas declaration tanpa baris kosong, diawali nama identifier, diakhiri titik.- Urutan doc comment: package → const/var → type → fungsi/metode.
- Komentar package biasanya di file
doc.gountuk package besar, atau di file utama.- Jelaskan “mengapa”, bukan “apa” — kode yang baik sudah menjelaskan apa yang dilakukan; komentar menjelaskan konteks, keputusan desain, dan trade-off.
- Tag konvensi:
TODO,FIXME,HACK,NOTE— mudah di-grep dan mengkomunikasikan status kode.- Direktif
//go:generateuntuk code generation;//go:builduntuk build constraints.go docuntuk membaca dokumentasi dari terminal;godocuntuk server web lokal.- Jangan biarkan komentar usang — komentar yang tidak akurat lebih berbahaya dari tidak ada komentar.
- Hapus commented-out code dari production code — gunakan version control untuk sejarah kode.