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.

KeluargaFungsiTujuan
PrintPrint, Println, PrintfCetak ke os.Stdout
FprintFprint, Fprintln, FprintfCetak ke io.Writer (file, buffer, stderr, dll)
SprintSprint, Sprintln, SprintfKembalikan sebagai string
ScanScan, Scanln, ScanfBaca input dari os.Stdin
FscanFscan, Fscanln, FscanfBaca input dari io.Reader
SscanSscan, Sscanln, SscanfBaca input dari string
ErrorfErrorfBuat 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:#e91e63
package 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:#fce4ec

Verb 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:#fff
import (
    "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.Scan dan fmt.Scanln tidak cocok untuk input yang mengandung spasi karena Scan memisahkan nilai berdasarkan whitespace. Untuk membaca satu baris penuh termasuk spasi, gunakan bufio.Scanner atau bufio.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:#e91e63
import (
    "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:

KebutuhanGunakan
Debugging, logging, output konsolfmt.Printf / fmt.Sprintf
Konversi int/float ke stringstrconv.Itoa / strconv.FormatFloat
Konversi string ke int/floatstrconv.Atoi / strconv.ParseFloat
Konkatenasi banyak stringstrings.Builder
Membuat error dengan konteksfmt.Errorf dengan %w
Output ke file/HTTPfmt.Fprintf
Benchmark menunjukkan strconv.Itoa sekitar 3-5× lebih cepat dari fmt.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), gunakan strconv bukan fmt.

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 %v adalah pilihan aman untuk semua tipe; %+v untuk struct dengan nama field; %#v untuk representasi Go syntax yang sangat berguna saat debugging.
  • Kontrol lebar dan presisi dengan format %[lebar].[presisi][verb] — misalnya %8.2f untuk float lebar 8 dengan 2 desimal, %-10s untuk string rata kiri lebar 10.
  • Implementasikan Stringer (method String() string) untuk tipe kustom agar tampil informatif saat di-print — ini adalah konvensi Go yang sangat dianjurkan.
  • fmt.Errorf dengan %w adalah cara idiomatis untuk membuat error dengan konteks — gunakan %w (bukan %v) agar errors.Is dan errors.As bisa bekerja.
  • Konvensi penamaan error: "namaFungsi: detail" atau "namaFungsi argumen: %w" — konsistensi ini membuat error message mudah ditelusuri saat terjadi di production.
  • fmt.Fprintf ke io.Writer membuatnya sangat fleksibel — file, buffer, HTTP response, koneksi jaringan, semuanya bisa jadi tujuan output tanpa mengubah kode formatting.
  • Untuk performa tinggi, hindari fmt.Sprintf untuk konversi sederhana — gunakan strconv untuk konversi tipe dan strings.Builder untuk konkatenasi string yang banyak.

← Sebelumnya: Math   Berikutnya: Os →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact