Clean Code dengan Go — Part 2: Naming — Kode yang Berbicara Sendiri
Nama yang baik adalah dokumentasi terbaik. Pelajari konvensi naming di Go dan cara menulis kode yang mudah dibaca.
Bayangkan kamu baru bergabung di sebuah tim dan diminta mereview sebuah bug. Kamu membuka file-nya, dan ini yang kamu lihat:
func proc(d *D, tmp int) (interface{}, error) {
x := getStuff(d.id)
if x == nil || tmp > x.v {
return nil, fmt.Errorf("err")
}
data2 := h(x)
return data2, nil
}
Dua jam kemudian — setelah buka-tutup empat file lain, tanya dua rekan, dan minum dua cangkir kopi — kamu baru paham bahwa fungsi 20 baris ini melakukan validasi pembayaran. Waktu dua jam hanya untuk membaca kode, bukan untuk memperbaiki bug-nya.
Ini bukan cerita fiksi. Ini terjadi setiap hari di tim-tim yang meremehkan pentingnya penamaan. Dan kabar baiknya: ini sepenuhnya bisa dicegah.
Nama yang baik adalah dokumentasi terbaik yang tidak pernah basi.
Peta Konsep: Naming di Go
mindmap
root((Naming))
Variables
Deskriptif dan kontekstual
Hindari singkatan ambigu
Panjang sesuai scope
Functions
Kata kerja + kata benda
Satu tujuan, satu nama
Boolean: isX / hasX
Struct & Interface
Gunakan kata benda
Interface pakai suffix -er
Package
Lowercase, tanpa underscore
Pendek tapi jelas
Konvensi Naming di Go
Go punya filosofi yang sederhana tapi kuat soal penamaan. Berikut hal-hal yang wajib kamu pahami:
1. Exported vs Unexported
Di Go, huruf kapital pertama menentukan visibilitas. User bisa diakses dari luar package, user hanya untuk internal.
type User struct { ... } // ✅ Exported — bisa dipakai package lain
type internalCache struct { ... } // ✅ Unexported — hanya di dalam package ini
2. Akronim Ditulis Konsisten
Untuk akronim seperti URL, ID, HTTP — Go convention menggunakan semua huruf kapital (bukan Url, Id, Http).
// ❌ BAD:
func getUserUrl(userId int) string { ... }
// ✅ GOOD:
func getUserURL(userID int) string { ... }
3. Interface Pakai Suffix -er
Interface di Go idealnya dinamai berdasarkan perilakunya, dengan akhiran -er.
type Reader interface { Read(p []byte) (n int, err error) }
type Storer interface { Store(key string, value any) error }
type PaymentProcessor interface { ProcessPayment(amount float64) error }
4. Panjang Nama Sesuai Scope
Variabel yang hanya hidup dalam 2 baris boleh pendek. Variabel yang hidup sepanjang fungsi atau struct harus deskriptif.
// ✅ GOOD: `i` wajar untuk loop counter
for i := 0; i < len(users); i++ { ... }
// ✅ GOOD: nama panjang untuk scope lebar
cachedUser, err := userCache.GetByID(userID)
❌ Implementasi yang Salah
Berikut contoh kode dengan penamaan yang buruk — persis seperti yang kamu temui di awal cerita tadi:
// ❌ BAD: Nama-nama yang tidak bermakna
type D struct { // D itu apa? Data? Domain? Dokter?
id int
v float64 // v = value? version? volume?
ts int64 // ts = timestamp? test score?
}
// proc = process apa?
func proc(d *D, tmp int) (interface{}, error) {
x := getStuff(d.id) // getStuff mendapatkan "stuff" apa?
if x == nil || tmp > x.v {
return nil, fmt.Errorf("err") // error apa?
}
data2 := h(x) // h() = handler? hash? helper?
return data2, nil
}
Mengapa ini salah?
D,x,tmp,data2tidak memberikan konteks apapunproc(),h(),getStuff()memaksa pembaca menebak-nebak tujuannyafmt.Errorf("err")adalah error message yang tidak berguna sama sekali- Satu nama ambigu sudah merepotkan — bayangkan jika seluruh codebase seperti ini
✅ Implementasi yang Benar
Sekarang kita tulis ulang kode yang sama dengan nama yang bermakna:
// ✅ GOOD: Nama yang jelas dan berbicara sendiri
type Payment struct {
id int
amount float64
expiresAt int64
}
// processPayment memvalidasi dan memproses pembayaran user.
// Mengembalikan error jika pembayaran tidak ditemukan atau sudah kedaluwarsa.
func processPayment(payment *Payment, currentUnixTime int64) (*PaymentResult, error) {
existingPayment, err := getPaymentByID(payment.id)
if err != nil {
return nil, fmt.Errorf("payment not found: %w", err)
}
isExpired := currentUnixTime > existingPayment.expiresAt
if isExpired {
return nil, fmt.Errorf("payment %d has expired", payment.id)
}
result := buildPaymentResult(existingPayment)
return result, nil
}
Mengapa ini lebih baik?
Payment,expiresAt,currentUnixTime— langsung paham tanpa dokumentasi tambahanprocessPayment(),getPaymentByID(),buildPaymentResult()— kata kerja + kata benda yang jelasisExpired— boolean dengan prefixisyang konvensional- Error message yang informatif dengan
%wuntuk wrapping
Use Case: User Authentication Service
Mari kita lihat bagaimana naming yang baik diaplikasikan di sebuah auth service nyata:
package auth
import (
"context"
"fmt"
"time"
)
// UserAuthenticator mendefinisikan kontrak untuk autentikasi user.
type UserAuthenticator interface {
Authenticate(ctx context.Context, credentials Credentials) (*AuthToken, error)
RevokeToken(ctx context.Context, tokenID string) error
}
// Credentials menyimpan informasi login dari user.
type Credentials struct {
Email string
Password string
}
// AuthToken adalah token hasil autentikasi yang berhasil.
type AuthToken struct {
TokenID string
UserID int
ExpiresAt time.Time
IssuedAt time.Time
}
// authService adalah implementasi internal dari UserAuthenticator.
type authService struct {
userRepo UserRepository
tokenRepo TokenRepository
hasher PasswordHasher
}
// Authenticate memverifikasi kredensial dan mengembalikan token jika valid.
func (s *authService) Authenticate(ctx context.Context, creds Credentials) (*AuthToken, error) {
foundUser, err := s.userRepo.FindByEmail(ctx, creds.Email)
if err != nil {
return nil, fmt.Errorf("user lookup failed: %w", err)
}
isPasswordValid := s.hasher.Compare(creds.Password, foundUser.HashedPassword)
if !isPasswordValid {
return nil, ErrInvalidCredentials
}
token, err := s.tokenRepo.Create(ctx, foundUser.ID)
if err != nil {
return nil, fmt.Errorf("token creation failed: %w", err)
}
return token, nil
}
Perhatikan betapa mudahnya membaca kode ini. Tanpa satu komentar penjelasan pun, kamu sudah tahu apa yang terjadi di setiap baris.
📋 Ringkasan: 5 Aturan Naming untuk Go Developer
1. Gunakan nama yang mengungkap niat, bukan implementasi
calculateTotalPrice()lebih baik daricalc()ataudoThing()2. Ikuti konvensi akronim Go
userID,httpClient,getURL()— bukanuserId,httpClient,getUrl()3. Beri nama interface dengan suffix
-erReader,Storer,PaymentProcessor,UserAuthenticator4. Boolean harus punya prefix
is,has, ataucanisExpired,hasPermission,canRetry5. Panjang nama sebanding dengan jangkauan scope-nya Loop counter boleh
i. Field struct harusexpiresAt, bukanea
🎯 Challenge untuk Kamu
Buka PR atau commit terakhir yang kamu kerjakan. Temukan 3 variabel atau fungsi yang namanya bisa diperbaiki, lalu rename dengan aturan di atas.
Kemudian tunjukkan ke rekan tim kamu — tanpa penjelasan. Apakah mereka langsung paham apa yang dilakukan kode itu?
Kalau ya, kamu sudah menulis kode yang berbicara sendiri. 🎉
🇮🇩 Versi Indonesia | 🇬🇧 English version