Strconv #

Setiap aplikasi Go yang membaca input dari luar — form web, argumen CLI, file konfigurasi, query parameter URL, atau pesan antrian — pasti perlu mengonversi string menjadi tipe data yang bisa diolah: integer untuk ID dan angka, float untuk nilai desimal, bool untuk flag aktif/nonaktif. Package strconv adalah alat untuk semua konversi ini. Ia lebih cepat dan lebih eksplisit dari fmt.Sprintf karena dirancang khusus untuk satu tujuan: mengonversi antara string dan tipe primitif Go. Memahami strconv dengan baik berarti memahami cara menangani input yang tidak bisa dipercaya dengan benar — karena setiap konversi yang bisa gagal mengembalikan error yang harus diperiksa, bukan diabaikan.

Gambaran Besar Package strconv #

flowchart LR
    subgraph Parse["String → Tipe Lain"]
        P1["strconv.Atoi\nstring → int"]
        P2["strconv.ParseInt\nstring → int64 (basis apapun)"]
        P3["strconv.ParseFloat\nstring → float64"]
        P4["strconv.ParseBool\nstring → bool"]
        P5["strconv.ParseUint\nstring → uint64"]
    end

    subgraph Format["Tipe Lain → String"]
        F1["strconv.Itoa\nint → string"]
        F2["strconv.FormatInt\nint64 → string (basis apapun)"]
        F3["strconv.FormatFloat\nfloat64 → string"]
        F4["strconv.FormatBool\nbool → string"]
        F5["strconv.FormatUint\nuint64 → string"]
    end

    subgraph Quote["String Escaping"]
        Q1["strconv.Quote\ntambahkan tanda kutip & escape"]
        Q2["strconv.Unquote\nhapus tanda kutip & unescape"]
        Q3["strconv.AppendQuote\nappend ke []byte"]
    end

    subgraph Errors["Error yang Mungkin"]
        E1["*strconv.NumError\n  .Err: ErrSyntax\n  .Err: ErrRange\n  .Num: string input"]
    end

    Parse --> Errors
    Format --> Str["string"]
    Parse --> Val["nilai Go"]

    style Parse fill:#e8f5e9
    style Format fill:#e3f2fd
    style Quote fill:#fff3e0
    style Errors fill:#fce4ec

Konversi Integer #

Atoi dan Itoa — Shortcut Paling Umum #

Atoi (ASCII to Integer) dan Itoa (Integer to ASCII) adalah fungsi yang paling sering dipakai dari package strconv — konversi langsung antara string dan int.

package main

import (
    "fmt"
    "strconv"
)

func main() {
    // Itoa — int ke string, tidak pernah gagal
    s := strconv.Itoa(42)
    fmt.Println(s)        // "42"
    fmt.Printf("%T\n", s) // string

    s2 := strconv.Itoa(-100)
    fmt.Println(s2) // "-100"

    // Atoi — string ke int, bisa gagal
    n, err := strconv.Atoi("42")
    if err != nil {
        fmt.Println("error:", err)
        return
    }
    fmt.Println(n)        // 42
    fmt.Printf("%T\n", n) // int

    // Error saat input bukan angka
    _, err = strconv.Atoi("abc")
    fmt.Println(err) // strconv.Atoi: parsing "abc": invalid syntax

    // Error saat angka terlalu besar untuk int
    _, err = strconv.Atoi("99999999999999999999999")
    fmt.Println(err) // strconv.Atoi: parsing "99999999999999999999999": value out of range
}

ParseInt — Kontrol Penuh #

ParseInt memberikan kontrol atas basis bilangan (desimal, heksadesimal, oktal, biner) dan ukuran bit hasil:

// ParseInt(s string, base int, bitSize int) (int64, error)
// base: 0 (deteksi otomatis), 2, 8, 10, 16
// bitSize: 0 (int), 8, 16, 32, 64

// Basis 10 — desimal biasa
n, _ := strconv.ParseInt("255", 10, 64)
fmt.Println(n) // 255

// Basis 16 — heksadesimal
n, _ = strconv.ParseInt("ff", 16, 64)
fmt.Println(n) // 255

n, _ = strconv.ParseInt("FF", 16, 64)
fmt.Println(n) // 255

// Basis 2 — biner
n, _ = strconv.ParseInt("11111111", 2, 64)
fmt.Println(n) // 255

// Basis 8 — oktal
n, _ = strconv.ParseInt("377", 8, 64)
fmt.Println(n) // 255

// Basis 0 — deteksi otomatis dari prefix
n, _ = strconv.ParseInt("0xff", 0, 64)  // 0x prefix → hex
fmt.Println(n) // 255

n, _ = strconv.ParseInt("0377", 0, 64)  // 0 prefix → oktal
fmt.Println(n) // 255

n, _ = strconv.ParseInt("0b11111111", 0, 64) // 0b prefix → biner
fmt.Println(n) // 255

n, _ = strconv.ParseInt("255", 0, 64)   // tanpa prefix → desimal
fmt.Println(n) // 255

// bitSize membatasi rentang yang valid
n32, err := strconv.ParseInt("32768", 10, 16) // max int16 adalah 32767
fmt.Println(n32, err)
// 32767 strconv.ParseInt: parsing "32768": value out of range
// Perhatikan: nilai yang dikembalikan adalah batas atas (clamped), bukan 0!

FormatInt — Integer ke String dengan Basis #

// FormatInt(i int64, base int) string

n := int64(255)

fmt.Println(strconv.FormatInt(n, 10)) // "255"   — desimal
fmt.Println(strconv.FormatInt(n, 16)) // "ff"    — heksadesimal huruf kecil
fmt.Println(strconv.FormatInt(n, 2))  // "11111111" — biner
fmt.Println(strconv.FormatInt(n, 8))  // "377"   — oktal
fmt.Println(strconv.FormatInt(n, 36)) // "73"    — basis 36 (0-9, a-z)

// Untuk int biasa (bukan int64), konversi dulu
x := 42
fmt.Println(strconv.FormatInt(int64(x), 16)) // "2a"

// Atau gunakan Itoa untuk basis 10
fmt.Println(strconv.Itoa(x)) // "42"

ParseUint dan FormatUint — Unsigned Integer #

// Untuk nilai yang tidak pernah negatif (ID, ukuran, port)
u, err := strconv.ParseUint("65535", 10, 16) // uint16 max
fmt.Println(u, err) // 65535 <nil>

// Port number — uint16
port, err := strconv.ParseUint("8080", 10, 16)
if err != nil {
    fmt.Println("port tidak valid:", err)
    return
}
fmt.Printf("Port: %d\n", port) // Port: 8080

// Format unsigned
fmt.Println(strconv.FormatUint(uint64(255), 16)) // "ff"
fmt.Println(strconv.FormatUint(uint64(255), 2))  // "11111111"

Konversi Float #

ParseFloat — String ke Float #

// ParseFloat(s string, bitSize int) (float64, error)
// bitSize: 32 untuk float32, 64 untuk float64

// Parsing float64
f, err := strconv.ParseFloat("3.14159", 64)
if err != nil {
    fmt.Println("error:", err)
    return
}
fmt.Println(f)         // 3.14159
fmt.Printf("%T\n", f)  // float64

// Notasi ilmiah
f, _ = strconv.ParseFloat("1.5e10", 64)
fmt.Println(f) // 1.5e+10

f, _ = strconv.ParseFloat("2.5E-3", 64)
fmt.Println(f) // 0.0025

// Nilai khusus
f, _ = strconv.ParseFloat("Inf", 64)
fmt.Println(f) // +Inf

f, _ = strconv.ParseFloat("-Inf", 64)
fmt.Println(f) // -Inf

f, _ = strconv.ParseFloat("NaN", 64)
fmt.Println(f) // NaN

// bitSize 32 — presisi float32 tapi dikembalikan sebagai float64
f32, _ := strconv.ParseFloat("3.14159265358979", 32)
fmt.Println(f32)           // 3.1415927410125732 — presisi float32
fmt.Println(float32(f32))  // 3.1415927 — cast ke float32

FormatFloat — Float ke String #

FormatFloat memberikan kontrol penuh atas format dan presisi output — ini yang membedakannya dari fmt.Sprintf("%.2f", f).

// FormatFloat(f float64, fmt byte, prec, bitSize int) string
// fmt: 'f' (desimal), 'e' (ilmiah), 'g' (terpendek), 'b' (biner), 'x' (hex)
// prec: presisi (-1 untuk presisi minimum yang mewakili nilai dengan tepat)
// bitSize: 32 atau 64

f := 3.14159265358979

// Format 'f' — desimal tetap
fmt.Println(strconv.FormatFloat(f, 'f', 2, 64))  // "3.14"
fmt.Println(strconv.FormatFloat(f, 'f', 5, 64))  // "3.14159"
fmt.Println(strconv.FormatFloat(f, 'f', -1, 64)) // "3.14159265358979"

// Format 'e' — notasi ilmiah
fmt.Println(strconv.FormatFloat(f, 'e', 2, 64))  // "3.14e+00"
fmt.Println(strconv.FormatFloat(f, 'e', -1, 64)) // "3.14159265358979e+00"

// Format 'g' — terpendek (ilmiah atau desimal, tergantung mana lebih pendek)
fmt.Println(strconv.FormatFloat(f, 'g', -1, 64)) // "3.14159265358979"
fmt.Println(strconv.FormatFloat(1e10, 'g', -1, 64)) // "1e+10"

// ANTI-PATTERN: Sprintf untuk round-trip float
pi := 3.14159265358979323846
s := fmt.Sprintf("%f", pi)   // "3.141593" — kehilangan presisi!
f2, _ := strconv.ParseFloat(s, 64)
fmt.Println(f2 == pi) // false — tidak sama dengan aslinya

// BENAR: FormatFloat dengan prec -1 untuk round-trip yang tepat
s2 := strconv.FormatFloat(pi, 'f', -1, 64)
f3, _ := strconv.ParseFloat(s2, 64)
fmt.Println(f3 == pi) // true — nilai persis sama

Konversi Boolean #

// ParseBool — string ke bool
// Menerima: "1", "t", "T", "TRUE", "true", "True" → true
// Menerima: "0", "f", "F", "FALSE", "false", "False" → false

b, err := strconv.ParseBool("true")
fmt.Println(b, err) // true <nil>

b, _ = strconv.ParseBool("1")
fmt.Println(b) // true

b, _ = strconv.ParseBool("T")
fmt.Println(b) // true

b, _ = strconv.ParseBool("false")
fmt.Println(b) // false

b, _ = strconv.ParseBool("0")
fmt.Println(b) // false

_, err = strconv.ParseBool("yes") // tidak valid!
fmt.Println(err) // strconv.ParseBool: parsing "yes": invalid syntax

// FormatBool — bool ke string
fmt.Println(strconv.FormatBool(true))  // "true"
fmt.Println(strconv.FormatBool(false)) // "false"

Pola: Membaca Flag dari Environment #

// Environment variable sering direpresentasikan sebagai bool
func getEnvBool(key string, defaultVal bool) bool {
    val, ada := os.LookupEnv(key)
    if !ada || val == "" {
        return defaultVal
    }

    b, err := strconv.ParseBool(val)
    if err != nil {
        // Log warning — nilai tidak valid, pakai default
        fmt.Fprintf(os.Stderr, "warning: %s=%q bukan bool valid, pakai %v\n",
            key, val, defaultVal)
        return defaultVal
    }
    return b
}

// Penggunaan
debugMode := getEnvBool("APP_DEBUG", false)
tlsEnabled := getEnvBool("TLS_ENABLED", true)

Memahami NumError #

Semua fungsi Parse* mengembalikan *strconv.NumError saat gagal. Memahami strukturnya memungkinkan penanganan error yang lebih spesifik.

flowchart TD
    Err["*strconv.NumError"] --> Func["Func: nama fungsi\n('Atoi', 'ParseInt', dll)"]
    Err --> Num["Num: string input\nyang gagal diparse"]
    Err --> ErrType["Err: jenis error"]

    ErrType --> Syntax["strconv.ErrSyntax\ninput bukan format angka valid\ncontoh: 'abc', '12.3' untuk int"]
    ErrType --> Range["strconv.ErrRange\nangka valid tapi di luar rentang\ncontoh: '999' untuk uint8 (max 255)"]

    Syntax --> Handle1["Tampilkan pesan\n'format tidak valid'"]
    Range --> Handle2["Tampilkan pesan\n'angka terlalu besar/kecil'"]

    style Err fill:#fce4ec
    style Syntax fill:#fff3e0
    style Range fill:#ffebee
import (
    "errors"
    "strconv"
)

func parseIDPengguna(s string) (int64, error) {
    id, err := strconv.ParseInt(s, 10, 64)
    if err != nil {
        // Periksa jenis error untuk pesan yang lebih informatif
        var numErr *strconv.NumError
        if errors.As(err, &numErr) {
            switch numErr.Err {
            case strconv.ErrSyntax:
                return 0, fmt.Errorf("ID pengguna %q bukan angka valid", s)
            case strconv.ErrRange:
                return 0, fmt.Errorf("ID pengguna %q terlalu besar", s)
            }
        }
        return 0, fmt.Errorf("parseIDPengguna: %w", err)
    }
    if id <= 0 {
        return 0, fmt.Errorf("ID pengguna harus positif, dapat: %d", id)
    }
    return id, nil
}

// Penggunaan
id, err := parseIDPengguna("abc")
// error: ID pengguna "abc" bukan angka valid

id, err = parseIDPengguna("99999999999999999999")
// error: ID pengguna "99999999999999999999" terlalu besar

id, err = parseIDPengguna("42")
// id: 42, err: nil

Quote dan Unquote — Escaping String #

Fungsi Quote dan Unquote berguna untuk debugging, logging, dan menangani string yang mungkin mengandung karakter spesial atau tidak bisa dicetak.

// Quote — tambahkan tanda kutip ganda dan escape karakter spesial
s := "Halo\tDunia\n"
fmt.Println(strconv.Quote(s))
// "Halo\tDunia\n"  — tampil dengan escape sequence literal

s2 := `Ini "dikutip" dan ini\tbackslash`
fmt.Println(strconv.Quote(s2))
// "Ini \"dikutip\" dan ini\\tbackslash"

// Karakter Unicode
s3 := "Bahasa Indonesia: é à ü"
fmt.Println(strconv.Quote(s3))
// "Bahasa Indonesia: é à ü"  — karakter printable tidak di-escape

s4 := string([]byte{0x00, 0x01, 0x1f}) // karakter kontrol
fmt.Println(strconv.Quote(s4))
// "\x00\x01\x1f"

// QuoteToASCII — escape semua non-ASCII
fmt.Println(strconv.QuoteToASCII("Héllo"))
// "H\u00e9llo"

// Unquote — kebalikan dari Quote
original, err := strconv.Unquote(`"Halo\tDunia\n"`)
fmt.Println(original, err)
// Halo	Dunia
// <nil>

// IsPrint — apakah rune bisa dicetak?
fmt.Println(strconv.IsPrint('A'))    // true
fmt.Println(strconv.IsPrint('\t'))   // false — tab bukan printable
fmt.Println(strconv.IsPrint('é'))    // true

// CanBackquote — apakah string bisa diwakili sebagai raw string literal?
fmt.Println(strconv.CanBackquote("Hello World"))  // true
fmt.Println(strconv.CanBackquote("Hello\nWorld")) // false — ada newline
fmt.Println(strconv.CanBackquote("Hello`World"))  // false — ada backtick

Append Variants — Zero Allocation #

Package strconv menyediakan varian Append* untuk semua fungsi Format — ini memungkinkan konversi langsung ke slice byte yang sudah ada tanpa alokasi string baru.

// Append variants — berguna untuk membangun output tanpa alokasi ekstra
buf := make([]byte, 0, 64)

// AppendInt — append representasi integer ke slice
buf = strconv.AppendInt(buf, 255, 16)      // ff
buf = append(buf, ' ')
buf = strconv.AppendInt(buf, 255, 2)       // 11111111
buf = append(buf, ' ')
buf = strconv.AppendInt(buf, -42, 10)      // -42
fmt.Println(string(buf)) // "ff 11111111 -42"

// AppendFloat
buf = buf[:0] // reset tanpa realokasi
buf = strconv.AppendFloat(buf, 3.14159, 'f', 2, 64)
fmt.Println(string(buf)) // "3.14"

// AppendBool
buf = buf[:0]
buf = strconv.AppendBool(buf, true)
buf = append(buf, '/')
buf = strconv.AppendBool(buf, false)
fmt.Println(string(buf)) // "true/false"

// AppendQuote
buf = buf[:0]
buf = strconv.AppendQuote(buf, "Hello\tWorld")
fmt.Println(string(buf)) // "Hello\tWorld" (dengan tanda kutip)

Pola Append sangat berguna saat membangun response HTTP, serialisasi data, atau situasi lain di mana kamu ingin menghindari alokasi memori yang tidak perlu dalam hot path.


Perbandingan: strconv vs fmt #

Ini adalah pertanyaan yang sangat sering muncul: kapan harus pakai strconv dan kapan pakai fmt?

flowchart TD
    Q{"Apa yang ingin\ndilakukan?"} --> Conv["Konversi tipe\n(int↔string, float↔string, bool↔string)"]
    Q --> Rich["Format kompleks\n(multiple values, padding, width)"]
    Q --> Debug["Debugging atau\nlogging ke konsol"]
    Q --> Err["Membuat error\ndengan konteks"]

    Conv --> C2{"Seberapa\npenting performa?"}
    C2 -- "Hot path / banyak panggilan" --> SC["strconv\n3-5× lebih cepat\ntidak ada alokasi ekstra"]
    C2 -- "Biasa saja" --> FMT["fmt.Sprintf\nlebih mudah dibaca"]

    Rich --> FMT2["fmt.Sprintf\n'%05d', '%-10s', dll"]
    Debug --> FMT3["fmt.Printf / fmt.Println"]
    Err --> FMT4["fmt.Errorf dengan %w"]

    style SC fill:#e8f5e9
    style FMT fill:#e3f2fd
    style FMT2 fill:#e3f2fd
    style FMT3 fill:#e3f2fd
    style FMT4 fill:#e3f2fd
import (
    "strconv"
    "fmt"
    "testing"
)

// Benchmark sederhana untuk ilustrasi
func BenchmarkItoa(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = strconv.Itoa(12345)
    }
}
// BenchmarkItoa: ~15 ns/op, 0 allocs/op

func BenchmarkSprintfInt(b *testing.B) {
    for i := 0; i < b.N; i++ {
        _ = fmt.Sprintf("%d", 12345)
    }
}
// BenchmarkSprintfInt: ~70 ns/op, 1 allocs/op

// Kesimpulan: strconv.Itoa sekitar 4-5x lebih cepat untuk konversi int→string

Kapan Masing-masing Lebih Tepat #

// GUNAKAN strconv untuk konversi tunggal
id := 42
idStr := strconv.Itoa(id)            // ✓ cepat, jelas
idStr2 := fmt.Sprintf("%d", id)      // ✗ overhead tidak perlu

harga := 99.99
hargaStr := strconv.FormatFloat(harga, 'f', 2, 64) // ✓
hargaStr2 := fmt.Sprintf("%.2f", harga)             // ✗ untuk konversi saja

// GUNAKAN fmt untuk format yang lebih kaya
label := fmt.Sprintf("ID: %05d | Harga: Rp%,.2f", id, harga) // ✓ fmt lebih cocok
// strconv tidak bisa handle ini dalam satu panggilan

// GUNAKAN strconv untuk parsing input
func parseQueryParam(params url.Values) (*Filter, error) {
    filter := &Filter{}

    if halStr := params.Get("hal"); halStr != "" {
        hal, err := strconv.Atoi(halStr)
        if err != nil {
            return nil, fmt.Errorf("parameter 'hal' tidak valid: %w", err)
        }
        filter.Halaman = hal
    }

    if limitStr := params.Get("limit"); limitStr != "" {
        limit, err := strconv.ParseInt(limitStr, 10, 32)
        if err != nil {
            return nil, fmt.Errorf("parameter 'limit' tidak valid: %w", err)
        }
        if limit < 1 || limit > 100 {
            return nil, fmt.Errorf("limit harus antara 1-100, dapat: %d", limit)
        }
        filter.Limit = int(limit)
    }

    return filter, nil
}

Pola Penggunaan di Produksi #

Parser Query Parameter HTTP #

import (
    "fmt"
    "net/http"
    "strconv"
)

type PaginasiParam struct {
    Halaman int
    Limit   int
    Urutan  string
}

func parsePaginasi(r *http.Request) (*PaginasiParam, error) {
    q := r.URL.Query()

    param := &PaginasiParam{
        Halaman: 1,   // default
        Limit:   20,  // default
        Urutan:  "asc",
    }

    if s := q.Get("page"); s != "" {
        hal, err := strconv.Atoi(s)
        if err != nil || hal < 1 {
            return nil, fmt.Errorf("parameter 'page' tidak valid: %q", s)
        }
        param.Halaman = hal
    }

    if s := q.Get("limit"); s != "" {
        limit, err := strconv.Atoi(s)
        if err != nil || limit < 1 || limit > 100 {
            return nil, fmt.Errorf("parameter 'limit' tidak valid: %q (harus 1-100)", s)
        }
        param.Limit = limit
    }

    if s := q.Get("order"); s == "asc" || s == "desc" {
        param.Urutan = s
    }

    return param, nil
}

func handlerDaftarProduk(w http.ResponseWriter, r *http.Request) {
    param, err := parsePaginasi(r)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    // gunakan param...
    fmt.Fprintf(w, "Halaman %d, Limit %d, Urutan %s\n",
        param.Halaman, param.Limit, param.Urutan)
}

Serialisasi Manual ke CSV #

import (
    "strings"
    "strconv"
)

type Produk struct {
    ID    int
    Nama  string
    Harga float64
    Aktif bool
    Stok  int
}

// Konversi produk ke baris CSV tanpa library tambahan
func produkKeCSV(p Produk) string {
    var sb strings.Builder

    sb.WriteString(strconv.Itoa(p.ID))
    sb.WriteByte(',')
    sb.WriteString(strconv.Quote(p.Nama)) // handle nama yang mengandung koma
    sb.WriteByte(',')
    sb.WriteString(strconv.FormatFloat(p.Harga, 'f', 2, 64))
    sb.WriteByte(',')
    sb.WriteString(strconv.FormatBool(p.Aktif))
    sb.WriteByte(',')
    sb.WriteString(strconv.Itoa(p.Stok))

    return sb.String()
}

// Parse baris CSV kembali ke Produk
func csvKeProduk(baris string) (Produk, error) {
    bagian := strings.SplitN(baris, ",", 5)
    if len(bagian) != 5 {
        return Produk{}, fmt.Errorf("format CSV tidak valid: %q", baris)
    }

    id, err := strconv.Atoi(bagian[0])
    if err != nil {
        return Produk{}, fmt.Errorf("ID tidak valid: %w", err)
    }

    nama, err := strconv.Unquote(bagian[1])
    if err != nil {
        nama = bagian[1] // fallback jika tidak ada kutip
    }

    harga, err := strconv.ParseFloat(bagian[2], 64)
    if err != nil {
        return Produk{}, fmt.Errorf("harga tidak valid: %w", err)
    }

    aktif, err := strconv.ParseBool(bagian[3])
    if err != nil {
        return Produk{}, fmt.Errorf("status aktif tidak valid: %w", err)
    }

    stok, err := strconv.Atoi(bagian[4])
    if err != nil {
        return Produk{}, fmt.Errorf("stok tidak valid: %w", err)
    }

    return Produk{
        ID:    id,
        Nama:  nama,
        Harga: harga,
        Aktif: aktif,
        Stok:  stok,
    }, nil
}

Parsing Konfigurasi dari File .env #

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

type AppConfig struct {
    Port     int
    Debug    bool
    MaxConn  int
    Timeout  float64 // dalam detik
    AppName  string
}

func muatDotEnv(path string) (map[string]string, error) {
    f, err := os.Open(path)
    if err != nil {
        return nil, fmt.Errorf("muatDotEnv: %w", err)
    }
    defer f.Close()

    hasil := make(map[string]string)
    scanner := bufio.NewScanner(f)
    nomorBaris := 0

    for scanner.Scan() {
        nomorBaris++
        baris := strings.TrimSpace(scanner.Text())

        // Skip baris kosong dan komentar
        if baris == "" || strings.HasPrefix(baris, "#") {
            continue
        }

        bagian := strings.SplitN(baris, "=", 2)
        if len(bagian) != 2 {
            return nil, fmt.Errorf("baris %d: format tidak valid: %q", nomorBaris, baris)
        }

        key := strings.TrimSpace(bagian[0])
        val := strings.TrimSpace(bagian[1])
        // Hapus tanda kutip jika ada
        if unquoted, err := strconv.Unquote(val); err == nil {
            val = unquoted
        }

        hasil[key] = val
    }

    return hasil, scanner.Err()
}

func parseConfig(env map[string]string) (*AppConfig, error) {
    cfg := &AppConfig{
        Port:    8080,
        Debug:   false,
        MaxConn: 10,
        Timeout: 30.0,
        AppName: "MyApp",
    }

    if v, ok := env["PORT"]; ok {
        port, err := strconv.Atoi(v)
        if err != nil {
            return nil, fmt.Errorf("PORT tidak valid: %w", err)
        }
        if port < 1 || port > 65535 {
            return nil, fmt.Errorf("PORT harus 1-65535, dapat: %d", port)
        }
        cfg.Port = port
    }

    if v, ok := env["DEBUG"]; ok {
        debug, err := strconv.ParseBool(v)
        if err != nil {
            return nil, fmt.Errorf("DEBUG harus boolean: %w", err)
        }
        cfg.Debug = debug
    }

    if v, ok := env["MAX_CONN"]; ok {
        maxConn, err := strconv.Atoi(v)
        if err != nil {
            return nil, fmt.Errorf("MAX_CONN tidak valid: %w", err)
        }
        cfg.MaxConn = maxConn
    }

    if v, ok := env["TIMEOUT"]; ok {
        timeout, err := strconv.ParseFloat(v, 64)
        if err != nil {
            return nil, fmt.Errorf("TIMEOUT tidak valid: %w", err)
        }
        cfg.Timeout = timeout
    }

    if v, ok := env["APP_NAME"]; ok && v != "" {
        cfg.AppName = v
    }

    return cfg, nil
}

Kapan Beralih ke Alternatif #

Tetap gunakan strconv jika:
  ✓ Konversi antara string dan int, float, bool
  ✓ Parsing input dari form, query param, file konfigurasi
  ✓ Serialisasi nilai primitif ke string untuk CSV atau format teks
  ✓ Hot path yang butuh konversi dengan performa tinggi
  ✓ Escaping dan unescaping string dengan Quote/Unquote

Pertimbangkan fmt.Sprintf jika:
  ✗ Format yang lebih kaya: padding, lebar kolom, multiple values
  ✗ Kode lebih mengutamakan keterbacaan daripada performa
  ✗ Satu kali format untuk banyak nilai sekaligus

Pertimbangkan encoding/json jika:
  ✗ Konversi struct ke string (serialisasi JSON)
  ✗ Data yang kompleks dengan nested structure
  ✗ Interoperabilitas dengan API atau sistem lain

Pertimbangkan encoding/csv jika:
  ✗ Membaca atau menulis file CSV yang kompleks
  ✗ CSV dengan quoting, newline di dalam field, atau escape yang rumit

Ringkasan #

  • strconv.Atoi dan strconv.Itoa adalah shortcut paling umum — konversi langsung antara string dan int tanpa overhead format string.
  • Selalu periksa error dari fungsi Parse* — parse yang gagal bukan panik, tapi mengembalikan zero value dan error yang harus ditangani secara eksplisit.
  • *strconv.NumError memiliki dua jenis: ErrSyntax (format salah) dan ErrRange (angka valid tapi di luar rentang) — bedakan keduanya untuk pesan error yang lebih informatif.
  • ParseInt dengan base 0 mendeteksi basis secara otomatis dari prefix: 0x untuk hex, 0b untuk biner, 0 untuk oktal — berguna untuk input yang mungkin dalam berbagai format.
  • FormatFloat dengan prec -1 menghasilkan representasi minimum yang bisa di-parse kembali ke nilai yang sama persis — gunakan ini untuk round-trip float yang akurat.
  • Varian Append* (AppendInt, AppendFloat, dll) menghindari alokasi string baru dengan menambahkan langsung ke []byte — penting untuk hot path dengan volume tinggi.
  • strconv 3-5× lebih cepat dari fmt.Sprintf untuk konversi tipe tunggal — gunakan strconv di handler HTTP dan loop yang sering dipanggil.
  • strconv.Quote dan strconv.Unquote berguna untuk logging dan debugging string yang mungkin mengandung karakter tak terlihat atau spesial.
  • Validasi setelah parsing — jangan hanya periksa error parsing, validasi juga rentang nilai (misalnya port 1-65535, halaman > 0) sebelum menggunakan nilai yang diparsing.

← Sebelumnya: Time   Berikutnya: Errors →

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