Konstruktor

Fungsi NewX dan desain zero value.

Daftar isi

Go tidak memiliki konstruktor (constructor), yaitu fungsi khusus yang dipanggil otomatis ketika sebuah objek dibuat. Struct dapat dibuat kapan saja melalui literal, dan nilainya selalu dimulai dari zero value yang terdefinisi. Sebagai gantinya, Go menyediakan konvensi fungsi NewX yang membuat, memvalidasi, dan menyiapkan nilai sebelum nilai itu dipakai. Topik ini membahas pola fungsi konstruktor tersebut beserta desain zero value yang membuat struct tetap layak dipakai tanpa konstruktor.

Pola fungsi NewX#

Fungsi konstruktor di Go hanyalah fungsi biasa yang namanya diawali New dan diikuti nama tipenya, misalnya NewKartuAnggota. Fungsi ini menerima data yang diperlukan, memeriksa kesahannya, lalu mengembalikan struct yang siap dipakai. Apabila pemeriksaan gagal, nilai struct kosong dikembalikan bersama error, pola yang sama dengan konvensi (nilai, error) pada pustaka standar:

go kartu-baru.go
package main

import (
	"errors"
	"fmt"
)

// KartuAnggota menyimpan data kartu anggota perpustakaan.
type KartuAnggota struct {
	Nama     string
	Nomor    string
	Pinjaman []string
}

// NewKartuAnggota memvalidasi data dan menyiapkan kartu yang layak dipakai.
func NewKartuAnggota(nama, nomor string) (KartuAnggota, error) {
	if nama == "" {
		return KartuAnggota{}, errors.New("nama tidak boleh kosong")
	}
	if nomor == "" {
		return KartuAnggota{}, errors.New("nomor tidak boleh kosong")
	}
	return KartuAnggota{
		Nama:     nama,
		Nomor:    nomor,
		Pinjaman: []string{},
	}, nil
}

func main() {
	kartu, err := NewKartuAnggota("Dewi", "P-2024-115")
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Printf("%+v\n", kartu)

	_, err = NewKartuAnggota("", "P-2024-116")
	fmt.Println("error:", err)
}
text Keluaran
{Nama:Dewi Nomor:P-2024-115 Pinjaman:[]}
error: nama tidak boleh kosong

Pemanggilan kedua pada main menolak kartu tanpa nama, dan errornya dilaporkan tanpa struct parsial yang terpakai secara keliru. Pengembalian KartuAnggota{} bersama error menjaga pola tetap seragam, yaitu penerima wajib memeriksa error sebelum memakai nilai, dan nilai nol menandakan nilai itu tidak sah. Perhatikan juga bahwa Pinjaman diisi []string{}, bukan dibiarkan nil, sehingga kartu hasil konstruktor siap dipakai tanpa persiapan tambahan.

Bentuk kembalian konstruktor mengikuti kebutuhan tipe, bukan kebiasaan dari bahasa lain. Struct sederhana dikembalikan sebagai nilai, sebagaimana NewKartuAnggota mengembalikan KartuAnggota, sedangkan tipe yang identitasnya perlu dibagikan kepada banyak pemakai dikembalikan sebagai *T. Yang menetap adalah pola kembalian konstruktor, yaitu (T, error) bila konstruksi dapat gagal, sehingga pemanggil selalu memeriksa error sebelum memakai hasilnya.

Zero value dan desain tipe#

Setiap struct di Go selalu punya zero value, yaitu nilai yang terbentuk ketika field dibiarkan tanpa inisialisasi, dengan aturan 0 untuk bilangan, "" untuk string, false untuk bool, dan nil untuk peta, slice, serta pointer. Desain tipe yang baik mengejar zero value yang layak dipakai, karena struct dapat langsung digunakan tanpa langkah persiapan. Field bertipe peta adalah pengecualiannya, karena peta nil tidak dapat ditulisi, sehingga penyiapannya perlu dilakukan oleh fungsi konstruktor:

go catatan-suhu.go
package main

import "fmt"

// CatatanSuhu menyimpan pengukuran suhu yang dikelompokkan per kode sensor.
type CatatanSuhu struct {
	Entri map[string]float64
}

// NewCatatanSuhu menyiapkan peta kosong agar struct langsung layak dipakai.
func NewCatatanSuhu() CatatanSuhu {
	return CatatanSuhu{Entri: make(map[string]float64)}
}

// Catat merekam satu pengukuran suhu.
func (c *CatatanSuhu) Catat(kode string, suhu float64) {
	c.Entri[kode] = suhu
}

func main() {
	catatan := NewCatatanSuhu()
	fmt.Println(catatan.Entri == nil) // false, karena peta sudah disiapkan
	catatan.Catat("SN-01", 27.5)
	catatan.Catat("SN-02", 29.0)
	fmt.Println(catatan.Entri["SN-01"])
	fmt.Println(catatan.Entri["SN-02"])
	fmt.Println(catatan.Entri["SN-99"]) // kunci yang tidak ada menghasilkan zero value
}
text Keluaran
false
27.5
29
0

Nilai false pada baris pertama membuktikan bahwa peta sudah disiapkan oleh NewCatatanSuhu, bukan peta nil bawaan struct. Baris terakhir memperlihatkan sifat peta lain yang deterministik, yaitu pembacaan kunci yang tidak ada menghasilkan zero value dari tipe nilainya, sehingga tidak ada error yang perlu ditangani saat membaca. Konstruktor dengan demikian memastikan satu jaminan sejak awal, yaitu setiap CatatanSuhu hasil NewCatatanSuhu siap merekam pengukuran tanpa syarat.

Contoh pustaka standar memperlihatkan pola yang sama. bytes.Buffer dapat dipakai segera setelah dideklarasikan karena buffer kosong adalah keadaan yang sah, sedangkan sync.Mutex siap mengunci tanpa inisialisasi apa pun. Keduanya membuktikan bahwa banyak tipe tidak memerlukan konstruktor sama sekali, dan hanya tipe dengan keadaan awal yang rumit yang benar-benar membutuhkannya.

Kapan zero value cukup#

Konstruktor bukan keharusan pada setiap tipe, dan menambahkannya tanpa alasan justru menambah kode yang tidak berfungsi apa pun. Pedoman pemilihannya dapat dirangkum sebagai berikut.

  • Struct dengan field sederhana dan tanpa aturan khusus, misalnya Sensor{Kode: "SN-01", Suhu: 25.5}, cukup dibuat melalui literal.
  • Struct dengan field peta, slice yang harus tidak nil, atau aturan keabsahan tertentu, misalnya nomor yang tidak boleh kosong, layak diberi fungsi NewX.
  • Struct yang perlu menyembunyikan field tidak terekspor agar invariannya terjaga wajib dibuat melalui konstruktor, karena hanya kode dalam package yang sama yang dapat mengisinya.
  • Tipe pustaka standar seperti bytes.Buffer dan sync.Mutex dirancang dengan zero value yang layak dipakai, dan pola itu menjadi teladan ketika struct baru dirancang.

Pedoman di atas bergerak dari yang paling sederhana. Mulailah dari literal struct, dan tambahkan fungsi NewX pada saat keabsahan atau persiapan mulai menuntut langkah yang mudah terlewat. Penambahan itu biasanya terjadi bersamaan dengan munculnya aturan keabsahan, misalnya nomor yang tidak boleh kosong atau peta yang harus siap diisi.

Kesalahan umum#

  • Menulisi peta nil hasil zero value. Baris var catatan CatatanSuhu menyisakan Entri berupa nil, dan pemanggilan catatan.Catat(...) memicu panic: assignment to entry in nil map. Perbaikannya, buat struct melalui fungsi konstruktor yang menyiapkan peta kosong sejak awal.
  • Mengembalikan struct terisi meskipun validasi gagal. Pemanggil tidak memiliki cara mengetahui bahwa nomor kartunya kosong, sehingga struct tidak sah tetap dipakai. Perbaikannya, kembalikan KartuAnggota{} bersama errornya, pola yang menandai nilai sebagai tidak layak pakai.
  • Mengira fungsi bawaan new adalah konstruktor yang mengisi nilai. Pemanggilan new(KartuAnggota) hanya mengalokasikan memori dan mengembalikan *KartuAnggota yang seluruh fieldnya masih zero value, tanpa validasi apa pun. Perbaikannya, gunakan fungsi NewX untuk nilai yang perlu disiapkan dan divalidasi.

Ringkasan#

  • Go tidak memiliki konstruktor, sehingga fungsi NewX mengambil peran pembuatan, validasi, dan penyiapan struct.
  • Konstruktor yang dapat gagal mengembalikan (T, error) dengan struct kosong sebagai pasangan error.
  • Zero value yang layak dipakai mengurangi kebutuhan konstruktor, sedangkan field peta dan aturan keabsahan menuntut persiapan.
  • Field peta nil tidak dapat ditulisi, sehingga konstruktor menyiapkannya sebelum struct dipakai.

Referensi#