Instansiasi Struct Pada Go

Di bahasa pemrograman Go, terdapat dua cara untuk menginisialisasi sebuah struct. Kita dapat melakukan inisialisasi langsung dengan mengisi nilai fieldnya atau menggunakan constructor.

Sebagai contoh, kita memiliki struct dengan skema berikut:

type Car struct {
	Manufacturer string
	Color        string
}

Kita dapat melakukan inisialisasi langsung dengan mengisi nilai fieldnya:

car := Car{Manufacturer: "BMW", Color: "Red"}

Atau kita juga bisa menggunakan constructor:

func NewCar(manufacturer string, color string) *Car {
	return &Car{
		Manufacturer: manufacturer,
		Color:        color,
	}
}

Untuk menggunakan constructor:

car := NewCar("BMW", "Red")

Kedua cara tersebut sebenarnya tidak ada masalah karena menghasilkan hal yang sama. Namun, permasalahannya terletak pada desain dan cara penggunaannya.

Biasanya, sebuah struct memberikan rekomendasi, baik secara implisit maupun eksplisit, mengenai cara terbaik untuk menginisialisasinya.

Jika constructor tersedia, maka sebaiknya kita menggunakan constructor tersebut. Jika constructor tidak tersedia, maka kita dapat melakukan inisialisasi field per field.

Pros and Cons

Keuntungan dan kerugian dari masing-masing cara tersebut adalah sebagai berikut:

Inisialisasi field per field membuat lebih eksplisit bagaimana nilai yang berbeda digunakan dan bagaimana variabel bercampur dan saling berhubungan. Namun, ini tidak mencegah pengisian field dengan nilai kosong atau tidak diisi.

Sebagai contoh, jika kita melakukan inisialisasi field per field seperti ini:

type Car struct {
	Manufacturer string
	Color        string
}

func main() {
	car := Car{Manufacturer: "BMW"}
	fmt.Println(car)
}

Output yang dihasilkan adalah:

{BMW }

Dapat dilihat bahwa hanya field “Manufacturer” yang terisi dan field “Color” terisi dengan nilai kosong karena tidak diberikan nilai.

Jika kita menggunakan constructor, maka seluruh field harus diisi jika diperlukan. Pada saat kompilasi, akan terjadi error jika tidak semua field diisi. Ini karena constructor tidak menerima nilai nol (zero value) untuk field-field tersebut.

Sebagai contoh, menggunakan constructor:

type Car struct {
	Manufacturer string
	Color        string
}

func NewCar(manufacturer string, color string) *Car {
	return &Car{
		Manufacturer: manufacturer,
		Color:        color,
	}
}

func main() {
	car := NewCar("BMW") // field "Color" tidak diisi
	fmt.Println(car)
}

Output yang dihasilkan adalah:

./main.go:20:16: not enough arguments in call to NewCar
	have (string)
	want (string, string)

Terjadi error saat kompilasi karena constructor tidak menerima nilai nol (zero value) untuk field-field tersebut. Selain itu, penggunaan constructor juga memberikan antarmuka yang lebih ergonomis dalam pemrograman.

Opini pribadi saya adalah saat mendesain sebuah struct, saya selalu mempertimbangkan apakah perlu membuat constructor atau tidak.

Jika nilai nol (zero value) pada struct adalah hal yang masuk akal dan aman untuk digunakan ke depannya, maka tidak perlu membuat constructor. Kita bisa melakukan inisialisasi langsung, mengisi field-field yang diperlukan secara eksplisit.

Namun, jika nilai nol (zero value) pada struct tidak masuk akal atau ada kekhawatiran terhadap penggunaan yang salah, maka disarankan untuk menggunakan constructor.

Kesimpulan dari permasalahan ini adalah untuk memberikan antarmuka yang lebih ergonomis dalam pemrograman.