Fmt #
Package fmt adalah salah satu package yang paling sering diimpor dalam kode Go — hampir tidak ada program Go yang tidak menyentuhnya. Ia menangani dua kebutuhan mendasar: mencetak output ke konsol atau stream manapun, dan memformat nilai menjadi string dengan kontrol penuh atas tampilannya. Yang membuat fmt menarik bukan sekadar kemampuan Println-nya, tapi sistem verb yang memungkinkan kamu mengontrol representasi setiap nilai secara presisi — dari integer dalam format heksadesimal, hingga struct dengan nama field-nya, hingga nilai apapun dengan representasi Go syntax yang valid. Artikel ini membahas keseluruhan package fmt: cara kerjanya, semua verb penting, perbedaan antara fungsi-fungsinya, dan bagaimana menggunakannya secara efektif di kode produksi.
Tiga Keluarga Fungsi #
Package fmt mengorganisir fungsinya dalam tiga keluarga berdasarkan tujuan: print (cetak ke stdout), fprint (cetak ke io.Writer manapun), dan sprint (kembalikan sebagai string). Ketiganya punya pola nama yang konsisten.
| Keluarga | Fungsi | Tujuan |
|---|---|---|
Print, Println, Printf | Cetak ke os.Stdout | |
| Fprint | Fprint, Fprintln, Fprintf | Cetak ke io.Writer (file, buffer, stderr, dll) |
| Sprint | Sprint, Sprintln, Sprintf | Kembalikan sebagai string |
| Scan | Scan, Scanln, Scanf | Baca input dari os.Stdin |
| Fscan | Fscan, Fscanln, Fscanf | Baca input dari io.Reader |
| Sscan | Sscan, Sscanln, Sscanf | Baca input dari string |
| Errorf | Errorf | Buat error dengan format string |
Pola penamaannya mudah dipahami: suffix f berarti menerima format string, suffix ln berarti menambahkan newline dan spasi antar argumen secara otomatis. Tanpa suffix, spasi ditambahkan hanya jika kedua operand bukan string.
flowchart LR
Input["Nilai Go\n(int, string, struct, ...)"]
subgraph fmt ["package fmt"]
direction TB
P["Print / Println / Printf"]
FP["Fprint / Fprintln / Fprintf"]
SP["Sprint / Sprintln / Sprintf"]
SC["Scan / Scanln / Scanf"]
ERR["Errorf"]
end
Stdout["os.Stdout"]
Writer["io.Writer\n(file, buffer, HTTP, stderr)"]
Str["string"]
StdinSrc["os.Stdin / io.Reader / string"]
ErrOut["error"]
Input --> P --> Stdout
Input --> FP --> Writer
Input --> SP --> Str
StdinSrc --> SC --> Input
Input --> ERR --> ErrOut
style fmt fill:#f0f4ff,stroke:#4f86c6
style Stdout fill:#e8f5e9,stroke:#4caf50
style Writer fill:#e8f5e9,stroke:#4caf50
style Str fill:#e8f5e9,stroke:#4caf50
style ErrOut fill:#fce4ec,stroke:#e91e63package main
import (
"fmt"
"os"
)
func main() {
nama := "Gopher"
umur := 15
// Print — tidak ada newline otomatis
fmt.Print("Hello, ")
fmt.Print(nama)
fmt.Print("\n")
// Println — tambah newline, spasi antar argumen
fmt.Println("Hello,", nama, "— umur:", umur)
// Printf — format dengan verb
fmt.Printf("Nama: %s, Umur: %d tahun\n", nama, umur)
// Sprintf — kembalikan string (tidak cetak)
pesan := fmt.Sprintf("Selamat datang, %s!", nama)
fmt.Println(pesan)
// Fprintf — cetak ke io.Writer (stderr dalam contoh ini)
fmt.Fprintf(os.Stderr, "Error: pengguna %s tidak ditemukan\n", nama)
}
Verb Format — Inti dari fmt #
Verb adalah kode format yang dimulai dengan % dan diikuti satu atau lebih karakter yang menentukan bagaimana nilai direpresentasikan. Memahami verb adalah kunci penggunaan fmt yang efektif.
flowchart TD
V["Verb fmt"] --> General["Umum — semua tipe"]
V --> Int["Integer"]
V --> Flt["Float"]
V --> Str["String & Byte"]
V --> Misc["Lainnya"]
General --> vv["%v — representasi default"]
General --> vpv["%+v — struct dengan nama field"]
General --> vhv["%#v — Go syntax lengkap"]
General --> vT["%T — nama tipe"]
Int --> vd["%d — desimal"]
Int --> vb["%b — biner"]
Int --> vx["%x / %X — heksadesimal"]
Int --> vo["%o — oktal"]
Int --> vc["%c — karakter Unicode"]
Int --> vq["%q — karakter dengan kutip"]
Flt --> vf["%f — desimal tetap"]
Flt --> ve["%e / %E — notasi ilmiah"]
Flt --> vg["%g — format terpendek"]
Str --> vs["%s — string biasa"]
Str --> vsq["%q — dengan tanda kutip & escape"]
Str --> vsx["%x — hex dari bytes"]
Misc --> vt["%t — boolean"]
Misc --> vp["%p — pointer / alamat"]
style V fill:#4f86c6,color:#fff
style General fill:#e3f2fd
style Int fill:#e8f5e9
style Flt fill:#fff3e0
style Str fill:#f3e5f5
style Misc fill:#fce4ecVerb Umum untuk Semua Tipe #
nilai := 42
pi := 3.14159
nama := "Go"
aktif := true
// %v — representasi default, pilihan aman untuk semua tipe
fmt.Printf("%v\n", nilai) // 42
fmt.Printf("%v\n", pi) // 3.14159
fmt.Printf("%v\n", nama) // Go
fmt.Printf("%v\n", aktif) // true
// %+v — untuk struct, tambahkan nama field
type Pengguna struct {
Nama string
Email string
Umur int
}
p := Pengguna{"Budi", "[email protected]", 30}
fmt.Printf("%v\n", p) // {Budi [email protected] 30}
fmt.Printf("%+v\n", p) // {Nama:Budi Email:[email protected] Umur:30}
// %#v — representasi Go syntax (berguna untuk debugging)
fmt.Printf("%#v\n", p) // main.Pengguna{Nama:"Budi", Email:"[email protected]", Umur:30}
fmt.Printf("%#v\n", []int{1, 2, 3}) // []int{1, 2, 3}
// %T — tipe dari nilai
fmt.Printf("%T\n", nilai) // int
fmt.Printf("%T\n", pi) // float64
fmt.Printf("%T\n", p) // main.Pengguna
fmt.Printf("%T\n", &p) // *main.Pengguna
Verb untuk Integer #
n := 255
// Representasi numerik
fmt.Printf("%d\n", n) // 255 — desimal (paling umum)
fmt.Printf("%b\n", n) // 11111111 — biner
fmt.Printf("%o\n", n) // 377 — oktal
fmt.Printf("%x\n", n) // ff — heksadesimal huruf kecil
fmt.Printf("%X\n", n) // FF — heksadesimal huruf besar
fmt.Printf("%#x\n", n) // 0xff — hex dengan prefix 0x
fmt.Printf("%#o\n", n) // 0377 — oktal dengan prefix 0
// Karakter Unicode
fmt.Printf("%c\n", 65) // A
fmt.Printf("%c\n", 9829) // ♥
fmt.Printf("%U\n", 65) // U+0041 — format Unicode code point
fmt.Printf("%q\n", 65) // 'A' — karakter dengan tanda kutip
// Width dan padding
fmt.Printf("%5d\n", 42) // 42 — rata kanan, lebar 5
fmt.Printf("%-5d|\n", 42) // 42 | — rata kiri, lebar 5
fmt.Printf("%05d\n", 42) // 00042 — padding nol
fmt.Printf("%+d\n", 42) // +42 — selalu tampilkan tanda
fmt.Printf("%+d\n", -42) // -42
Verb untuk Float #
f := 3.14159265358979
// Format dasar
fmt.Printf("%f\n", f) // 3.141593 — default 6 desimal
fmt.Printf("%e\n", f) // 3.141593e+00 — notasi ilmiah huruf kecil
fmt.Printf("%E\n", f) // 3.141593E+00 — notasi ilmiah huruf besar
fmt.Printf("%g\n", f) // 3.14159265358979 — format terpendek
fmt.Printf("%G\n", f) // 3.14159265358979
// Kontrol presisi: %[lebar].[presisi]f
fmt.Printf("%.2f\n", f) // 3.14 — 2 angka desimal
fmt.Printf("%.5f\n", f) // 3.14159 — 5 angka desimal
fmt.Printf("%8.2f\n", f) // 3.14 — lebar 8, 2 desimal
fmt.Printf("%08.2f\n", f) // 00003.14 — padding nol
fmt.Printf("%-8.2f|\n", f) // 3.14 | — rata kiri
// Nilai khusus
fmt.Printf("%f\n", math.Inf(1)) // +Inf
fmt.Printf("%f\n", math.Inf(-1)) // -Inf
fmt.Printf("%f\n", math.NaN()) // NaN
Verb untuk String dan Byte #
s := "Halo, Dunia!"
b := []byte{72, 101, 108, 108, 111}
// String
fmt.Printf("%s\n", s) // Halo, Dunia! — string biasa
fmt.Printf("%q\n", s) // "Halo, Dunia!" — dengan tanda kutip dan escape
fmt.Printf("%x\n", s) // 48616c6f2c204475... — hex dari bytes string
// Byte slice
fmt.Printf("%s\n", b) // Hello — interpretasi sebagai string
fmt.Printf("%x\n", b) // 48656c6c6f
fmt.Printf("%X\n", b) // 48656C6C6F
fmt.Printf("% x\n", b) // 48 65 6c 6c 6f — spasi antar byte
// Width dan alignment
fmt.Printf("%10s\n", "Go") // Go — rata kanan
fmt.Printf("%-10s|\n", "Go") // Go | — rata kiri
fmt.Printf("%.3s\n", "Golang") // Gol — potong ke 3 karakter
Verb untuk Boolean dan Pointer #
// Boolean
fmt.Printf("%t\n", true) // true
fmt.Printf("%t\n", false) // false
// Pointer — alamat memori dalam heksadesimal
x := 42
fmt.Printf("%p\n", &x) // 0xc0000b4008 (alamat bervariasi)
slice := []int{1, 2, 3}
fmt.Printf("%p\n", slice) // 0xc0000b4020 — pointer ke elemen pertama
Formatting Struct dan Tipe Kustom #
fmt mendukung dua cara untuk mengkustomisasi tampilan tipe buatan kamu: mengimplementasikan interface fmt.Stringer untuk tampilan default, dan interface fmt.GoStringer untuk representasi Go syntax.
Interface Stringer #
type Koordinat struct {
Lat float64
Lon float64
}
// ANTI-PATTERN: tidak ada Stringer — output tidak informatif
k := Koordinat{-6.2088, 106.8456}
fmt.Println(k) // {-6.2088 106.8456} — susah dibaca
// BENAR: implementasikan Stringer untuk tampilan yang bermakna
func (k Koordinat) String() string {
arahLat := "N"
if k.Lat < 0 {
arahLat = "S"
}
arahLon := "E"
if k.Lon < 0 {
arahLon = "W"
}
return fmt.Sprintf("%.4f°%s, %.4f°%s",
math.Abs(k.Lat), arahLat,
math.Abs(k.Lon), arahLon)
}
fmt.Println(k) // 6.2088°S, 106.8456°E
fmt.Printf("%v\n", k) // 6.2088°S, 106.8456°E
fmt.Printf("%s\n", k) // 6.2088°S, 106.8456°E
Interface GoStringer #
// GoStringer — untuk %#v
func (k Koordinat) GoString() string {
return fmt.Sprintf("Koordinat{Lat: %g, Lon: %g}", k.Lat, k.Lon)
}
fmt.Printf("%#v\n", k) // Koordinat{Lat: -6.2088, Lon: 106.8456}
Interface Formatter untuk Kontrol Penuh #
Untuk kebutuhan formatting yang sangat spesifik, implementasikan fmt.Formatter:
type Matriks struct {
data [][]float64
rows int
cols int
}
func (m Matriks) Format(f fmt.State, verb rune) {
switch verb {
case 'v', 's':
for i, row := range m.data {
if i > 0 {
fmt.Fprint(f, "\n")
}
fmt.Fprint(f, "[")
for j, val := range row {
if j > 0 {
fmt.Fprint(f, " ")
}
fmt.Fprintf(f, "%6.2f", val)
}
fmt.Fprint(f, "]")
}
case 'q':
// format khusus lainnya
fmt.Fprintf(f, "Matriks(%dx%d)", m.rows, m.cols)
}
}
Errorf dan Error Wrapping #
fmt.Errorf adalah cara idiomatis untuk membuat error dengan konteks tambahan. Sejak Go 1.13, ia mendukung error wrapping menggunakan verb %w yang memungkinkan errors.Is dan errors.As bekerja pada error yang dibungkus.
import (
"errors"
"fmt"
)
var ErrTidakDitemukan = errors.New("tidak ditemukan")
// ANTI-PATTERN: buat error baru yang kehilangan konteks
func cariPengguna(id int) error {
return fmt.Errorf("pengguna tidak ditemukan") // informasi id hilang
}
// JUGA ANTI-PATTERN: gunakan %v — tidak bisa di-unwrap
func cariPengguna2(id int) error {
return fmt.Errorf("cariPengguna %d: %v", id, ErrTidakDitemukan)
// errors.Is(err, ErrTidakDitemukan) akan mengembalikan false!
}
// BENAR: gunakan %w untuk wrapping — bisa di-unwrap
func cariPengguna3(id int) error {
if id <= 0 {
return fmt.Errorf("cariPengguna: id %d tidak valid", id)
}
if id > 1000 {
return fmt.Errorf("cariPengguna %d: %w", id, ErrTidakDitemukan)
}
return nil
}
// Penggunaan
err := cariPengguna3(9999)
if err != nil {
fmt.Println(err) // cariPengguna 9999: tidak ditemukan
// errors.Is bekerja karena menggunakan %w
if errors.Is(err, ErrTidakDitemukan) {
fmt.Println("handle: data tidak ada di database")
}
}
Pola penamaan error yang baik mengikuti konvensi: namaFungsi: detail error. Konvensi ini membuat stack error mudah dibaca saat error di-wrap beberapa lapisan:
sequenceDiagram
participant H as handler
participant S as service
participant R as repository
participant DB as database
H->>S: service(id)
S->>R: repository(id)
R->>DB: query(id)
DB-->>R: ErrTidakDitemukan
R-->>S: fmt.Errorf("repository %d: %w", id, err)
S-->>H: fmt.Errorf("service: %w", err)
H-->>H: fmt.Errorf("handler: %w", err)
Note over H: errors.Is(err, ErrTidakDitemukan) → true
Note over H: err.Error() → "handler: service: repository 42: tidak ditemukan"// Error yang terbentuk saat di-wrap berlapis
// "handler: service: repository: tidak ditemukan"
func repository(id int) error {
return fmt.Errorf("repository %d: %w", id, ErrTidakDitemukan)
}
func service(id int) error {
if err := repository(id); err != nil {
return fmt.Errorf("service: %w", err)
}
return nil
}
func handler(id int) error {
if err := service(id); err != nil {
return fmt.Errorf("handler: %w", err)
}
return nil
}
err := handler(42)
fmt.Println(err)
// handler: service: repository 42: tidak ditemukan
Fprintf — Menulis ke io.Writer #
fmt.Fprintf adalah versi paling fleksibel dari Printf karena menerima io.Writer apapun sebagai tujuan output — file, buffer, koneksi jaringan, response HTTP, atau implementasi io.Writer kustom.
flowchart LR
FP["fmt.Fprintf\n(w io.Writer, format, args)"]
FP --> Stderr["os.Stderr\nlog error"]
FP --> File["os.File\ntulis ke file"]
FP --> Buf["bytes.Buffer\nbuat string bertahap"]
FP --> SB["strings.Builder\nstring efisien"]
FP --> HTTP["http.ResponseWriter\nHTTP response"]
FP --> Net["net.Conn\nkomunikasi jaringan"]
FP --> Custom["io.Writer kustom\nimplementasi sendiri"]
style FP fill:#4f86c6,color:#fffimport (
"bytes"
"fmt"
"os"
"strings"
)
// Menulis ke os.Stderr — untuk log error
fmt.Fprintf(os.Stderr, "[ERROR] %s: %v\n", "koneksi gagal", err)
// Menulis ke file
file, _ := os.Create("output.txt")
defer file.Close()
fmt.Fprintf(file, "Laporan tanggal %s\n", time.Now().Format("2006-01-02"))
// Menulis ke bytes.Buffer — untuk membangun string secara inkremental
var buf bytes.Buffer
for i := 1; i <= 5; i++ {
fmt.Fprintf(&buf, "item %d\n", i)
}
hasil := buf.String()
// Menulis ke strings.Builder — lebih efisien dari bytes.Buffer untuk string
var sb strings.Builder
for i := 0; i < 3; i++ {
fmt.Fprintf(&sb, "baris %d\n", i+1)
}
fmt.Print(sb.String())
Pola: HTTP Response Writer #
http.ResponseWriter mengimplementasikan io.Writer, sehingga fmt.Fprintf bisa digunakan langsung untuk menulis response HTTP:
import "net/http"
func handler(w http.ResponseWriter, r *http.Request) {
nama := r.URL.Query().Get("nama")
if nama == "" {
nama = "Dunia"
}
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "Halo, %s!\n", nama)
fmt.Fprintf(w, "Waktu server: %s\n", time.Now().Format(time.RFC3339))
}
Sprintf — Membangun String Terformat #
fmt.Sprintf mengembalikan string terformat tanpa mencetaknya. Ini berguna untuk membangun string dinamis, log message, query, atau nilai yang akan diproses lebih lanjut.
// Membangun pesan dinamis
func pesanSelamat(nama string, level int) string {
return fmt.Sprintf("Selamat datang kembali, %s! Level kamu: %d", nama, level)
}
// Format tanggal kustom
func formatTanggalIndonesia(t time.Time) string {
bulan := []string{
"", "Januari", "Februari", "Maret", "April", "Mei", "Juni",
"Juli", "Agustus", "September", "Oktober", "November", "Desember",
}
return fmt.Sprintf("%d %s %d", t.Day(), bulan[t.Month()], t.Year())
}
// Membangun key untuk cache/map
func cacheKey(userID int, resource string) string {
return fmt.Sprintf("user:%d:%s", userID, resource)
}
// ANTI-PATTERN: konkatenasi string dalam loop — alokasi berulang
func buatCSVBuruk(data [][]string) string {
hasil := ""
for _, row := range data {
for j, cell := range row {
if j > 0 {
hasil += ","
}
hasil += cell
}
hasil += "\n"
}
return hasil
}
// BENAR: gunakan strings.Builder dengan Fprintf untuk performa lebih baik
func buatCSVBaik(data [][]string) string {
var sb strings.Builder
for _, row := range data {
for j, cell := range row {
if j > 0 {
sb.WriteByte(',')
}
sb.WriteString(cell)
}
sb.WriteByte('\n')
}
return sb.String()
}
Scan — Membaca Input #
Keluarga Scan membaca nilai dari input. Penggunaannya lebih jarang dibanding Print karena aplikasi Go modern biasanya membaca input dari file, HTTP request, atau database — bukan stdin interaktif. Tapi untuk tools CLI, ia berguna.
var nama string
var umur int
// Scanln — baca satu baris, pisahkan dengan spasi
fmt.Print("Masukkan nama dan umur: ")
n, err := fmt.Scanln(&nama, &umur)
fmt.Printf("Berhasil baca %d nilai: nama=%s, umur=%d\n", n, nama, umur)
// Scanf — baca dengan format spesifik
var x, y float64
fmt.Print("Masukkan koordinat (x,y): ")
fmt.Scanf("%f,%f", &x, &y)
fmt.Printf("Koordinat: (%.2f, %.2f)\n", x, y)
// Sscan — baca dari string (berguna untuk parsing)
input := "Jakarta -6.2088 106.8456"
var kota string
var lat, lon float64
fmt.Sscan(input, &kota, &lat, &lon)
fmt.Printf("Kota: %s, Koordinat: %.4f, %.4f\n", kota, lat, lon)
// Sscanf — baca dari string dengan format
tanggal := "2024-03-15"
var tahun, bulan, hari int
fmt.Sscanf(tanggal, "%d-%d-%d", &tahun, &bulan, &hari)
fmt.Printf("Tahun: %d, Bulan: %d, Hari: %d\n", tahun, bulan, hari)
fmt.Scandanfmt.Scanlntidak cocok untuk input yang mengandung spasi karenaScanmemisahkan nilai berdasarkan whitespace. Untuk membaca satu baris penuh termasuk spasi, gunakanbufio.Scannerataubufio.Reader.ReadString('\n').
Verb %w dan Multiple Wrapping (Go 1.20+) #
Sejak Go 1.20, fmt.Errorf mendukung multiple error wrapping — satu error bisa membungkus beberapa error sekaligus menggunakan beberapa %w dalam satu format string:
import "errors"
var (
ErrKoneksi = errors.New("koneksi gagal")
ErrTimeout = errors.New("timeout")
)
// Go 1.20+: multiple error wrapping
func prosesData() error {
return fmt.Errorf("prosesData: %w dan %w", ErrKoneksi, ErrTimeout)
}
err := prosesData()
fmt.Println(err) // prosesData: koneksi gagal dan timeout
// errors.Is bekerja untuk keduanya
fmt.Println(errors.Is(err, ErrKoneksi)) // true
fmt.Println(errors.Is(err, ErrTimeout)) // true
// Untuk mengakses semua wrapped errors
var joinedErr interface{ Unwrap() []error }
if errors.As(err, &joinedErr) {
for _, e := range joinedErr.Unwrap() {
fmt.Printf(" - %v\n", e)
}
}
Pola Penggunaan di Aplikasi Produksi #
Logger Sederhana dengan Fprintf #
import (
"fmt"
"os"
"time"
)
type Level int
const (
DEBUG Level = iota
INFO
WARN
ERROR
)
func (l Level) String() string {
switch l {
case DEBUG:
return "DEBUG"
case INFO:
return "INFO"
case WARN:
return "WARN"
case ERROR:
return "ERROR"
default:
return "UNKNOWN"
}
}
func log(level Level, format string, args ...any) {
timestamp := time.Now().Format("2006-01-02 15:04:05")
writer := os.Stdout
if level >= ERROR {
writer = os.Stderr
}
fmt.Fprintf(writer, "[%s] %s — "+format+"\n",
append([]any{timestamp, level}, args...)...)
}
// Penggunaan
log(INFO, "server berjalan di port %d", 8080)
log(ERROR, "gagal koneksi ke database: %v", err)
Template String untuk Notifikasi #
type Notifikasi struct {
Pengguna string
Aksi string
Objek string
Waktu time.Time
}
func (n Notifikasi) PesanSingkat() string {
return fmt.Sprintf("%s %s %s", n.Pengguna, n.Aksi, n.Objek)
}
func (n Notifikasi) PesanLengkap() string {
return fmt.Sprintf(
"Pengguna %q melakukan %s terhadap %q pada %s",
n.Pengguna,
n.Aksi,
n.Objek,
n.Waktu.Format("02 Jan 2006 pukul 15:04"),
)
}
notif := Notifikasi{
Pengguna: "Budi",
Aksi: "mengedit",
Objek: "dokumen proposal",
Waktu: time.Now(),
}
fmt.Println(notif.PesanSingkat())
// Budi mengedit dokumen proposal
fmt.Println(notif.PesanLengkap())
// Pengguna "Budi" melakukan mengedit terhadap "dokumen proposal" pada 15 Mar 2024 pukul 14:30
Debugging dengan %#v dan %+v #
type Config struct {
Host string
Port int
Debug bool
MaxConn int
Timeout time.Duration
}
cfg := Config{
Host: "localhost",
Port: 5432,
Debug: true,
MaxConn: 10,
Timeout: 30 * time.Second,
}
// Untuk logging config saat startup
fmt.Printf("Config: %+v\n", cfg)
// Config: {Host:localhost Port:5432 Debug:true MaxConn:10 Timeout:30s}
// Untuk debugging — tampilkan dengan tipe
fmt.Printf("Config detail: %#v\n", cfg)
// Config detail: main.Config{Host:"localhost", Port:5432, Debug:true, MaxConn:10, Timeout:30000000000}
Tabel Output di Terminal #
func cetakTabel(headers []string, rows [][]string) {
// Hitung lebar kolom
widths := make([]int, len(headers))
for i, h := range headers {
widths[i] = len(h)
}
for _, row := range rows {
for i, cell := range row {
if i < len(widths) && len(cell) > widths[i] {
widths[i] = len(cell)
}
}
}
// Format string untuk setiap kolom
formatStr := ""
separator := ""
for _, w := range widths {
formatStr += fmt.Sprintf("%%-%ds ", w)
separator += strings.Repeat("-", w+2)
}
formatStr += "\n"
// Cetak header
headerArgs := make([]any, len(headers))
for i, h := range headers {
headerArgs[i] = h
}
fmt.Printf(formatStr, headerArgs...)
fmt.Println(separator)
// Cetak baris
for _, row := range rows {
args := make([]any, len(row))
for i, cell := range row {
args[i] = cell
}
fmt.Printf(formatStr, args...)
}
}
// Penggunaan
cetakTabel(
[]string{"Nama", "Email", "Role"},
[][]string{
{"Budi Santoso", "[email protected]", "Admin"},
{"Ani", "[email protected]", "User"},
{"Charlie Brown", "[email protected]", "Moderator"},
},
)
Performa: Kapan Tidak Menggunakan fmt #
Package fmt nyaman tapi bukan yang paling cepat. Untuk kode yang sangat performa-sensitif, ada alternatif yang lebih efisien.
flowchart TD
A{Apa yang ingin\ndilakukan?} --> B{Tujuan output?}
B -- "Cetak ke layar / stderr" --> P["fmt.Print*\nfmt.Fprintf(os.Stderr, ...)"]
B -- "Cetak ke file / HTTP / buffer" --> FP["fmt.Fprintf\n(io.Writer)"]
B -- "Buat string terformat" --> C{Seberapa kompleks\nformatnya?}
C -- "Banyak komponen\natau format khusus" --> SP["fmt.Sprintf"]
C -- "Konversi tipe saja\n(int→string, dll)" --> SC["strconv.Itoa\nstrconv.FormatFloat\nstrconv.FormatBool"]
C -- "Gabungkan banyak\nstring dalam loop" --> SB["strings.Builder\ndengan sb.WriteString"]
A --> D{Membuat error?}
D -- "Dengan konteks,\nbisa di-unwrap" --> EW["fmt.Errorf\ndengan %w"]
D -- "Pesan saja,\ntanpa wrapping" --> EN["errors.New"]
style P fill:#e8f5e9,stroke:#4caf50
style FP fill:#e8f5e9,stroke:#4caf50
style SP fill:#e3f2fd,stroke:#2196f3
style SC fill:#fff3e0,stroke:#ff9800
style SB fill:#fff3e0,stroke:#ff9800
style EW fill:#fce4ec,stroke:#e91e63
style EN fill:#fce4ec,stroke:#e91e63import (
"strconv"
"strings"
)
// ANTI-PATTERN: Sprintf untuk konversi sederhana — ada alokasi ekstra
func intKeStringLambat(n int) string {
return fmt.Sprintf("%d", n) // alokasi format string + konversi
}
// BENAR: strconv untuk konversi tipe — lebih cepat dan zero-allocation
func intKeStringCepat(n int) string {
return strconv.Itoa(n) // atau strconv.FormatInt(int64(n), 10)
}
// ANTI-PATTERN: Sprintf untuk konkatenasi string sederhana
func buatKey1(prefix string, id int) string {
return fmt.Sprintf("%s:%d", prefix, id)
}
// BENAR: strings.Builder untuk konkatenasi banyak string
func buatKey2(prefix string, id int) string {
var sb strings.Builder
sb.WriteString(prefix)
sb.WriteByte(':')
sb.WriteString(strconv.Itoa(id))
return sb.String()
}
Panduan praktis kapan menggunakan apa:
| Kebutuhan | Gunakan |
|---|---|
| Debugging, logging, output konsol | fmt.Printf / fmt.Sprintf |
| Konversi int/float ke string | strconv.Itoa / strconv.FormatFloat |
| Konversi string ke int/float | strconv.Atoi / strconv.ParseFloat |
| Konkatenasi banyak string | strings.Builder |
| Membuat error dengan konteks | fmt.Errorf dengan %w |
| Output ke file/HTTP | fmt.Fprintf |
Benchmark menunjukkanstrconv.Itoasekitar 3-5× lebih cepat darifmt.Sprintf("%d", n)untuk konversi integer ke string. Untuk hot path dalam aplikasi dengan throughput tinggi (misalnya handler HTTP yang dipanggil ribuan kali per detik), gunakanstrconvbukanfmt.
Kapan Beralih ke Alternatif #
Tetap gunakan fmt jika:
✓ Output ke konsol, stderr, atau io.Writer
✓ Membuat string dengan format kompleks dan banyak komponen
✓ Debugging dengan %+v, %#v, %T
✓ Membuat error dengan konteks menggunakan Errorf + %w
✓ Implementasi Stringer untuk tipe kustom
Pertimbangkan strconv jika:
✗ Konversi tipe sederhana (int↔string, float↔string, bool↔string)
✗ Kode di hot path yang butuh performa maksimal
✗ Parsing string angka dari input atau file
Pertimbangkan strings.Builder jika:
✗ Konkatenasi banyak string dalam loop
✗ Membangun string besar secara inkremental
Pertimbangkan text/template atau html/template jika:
✗ Template yang dipisahkan dari kode (file .html, .txt)
✗ Output HTML yang butuh auto-escaping untuk keamanan
✗ Template yang bisa diubah tanpa recompile
Ringkasan #
- Tiga keluarga utama:
Print*(ke stdout),Fprint*(ke io.Writer),Sprint*(ke string) — pola nama yang konsisten membuatnya mudah diprediksi.- Verb
%vadalah pilihan aman untuk semua tipe;%+vuntuk struct dengan nama field;%#vuntuk representasi Go syntax yang sangat berguna saat debugging.- Kontrol lebar dan presisi dengan format
%[lebar].[presisi][verb]— misalnya%8.2funtuk float lebar 8 dengan 2 desimal,%-10suntuk string rata kiri lebar 10.- Implementasikan
Stringer(methodString() string) untuk tipe kustom agar tampil informatif saat di-print — ini adalah konvensi Go yang sangat dianjurkan.fmt.Errorfdengan%wadalah cara idiomatis untuk membuat error dengan konteks — gunakan%w(bukan%v) agarerrors.Isdanerrors.Asbisa bekerja.- Konvensi penamaan error:
"namaFungsi: detail"atau"namaFungsi argumen: %w"— konsistensi ini membuat error message mudah ditelusuri saat terjadi di production.fmt.Fprintfkeio.Writermembuatnya sangat fleksibel — file, buffer, HTTP response, koneksi jaringan, semuanya bisa jadi tujuan output tanpa mengubah kode formatting.- Untuk performa tinggi, hindari
fmt.Sprintfuntuk konversi sederhana — gunakanstrconvuntuk konversi tipe danstrings.Builderuntuk konkatenasi string yang banyak.