Setelah memahami semua alat null-safety — tipe nullable, safe call, Elvis, smart cast, let, dan platform types — langkah terakhir adalah menyusun praktik terbaik yang menyatukan semuanya menjadi kode yang aman dan mudah dirawat. Praktik terbaik tidak berdiri sendiri; setiap pilihan bergantung pada konteks data dan alur program. Pada chapter ini kita akan memetakan tujuh praktik utama yang dianjurkan, lengkap dengan contoh kode yang menerapkannya sekaligus menunjukkan penolakannya.
Tujuh Praktik Utama #
Berikut ringkasan praktik yang akan kita terapkan pada contoh kode:
| Praktik | Saran | Alasan |
|---|---|---|
Prefer val non-nullable | Mulai dengan val bertipe non-nullable | Lebih sedikit cabang null, lebih mudah dirawat |
| Elvis untuk default | x ?: default menggantikan if verbose | Sintaks ringkas, hasil non-nullable |
Hindari !! | Ganti dengan ?: error(...) atau requireNotNull | Pesan galat jelas, tidak tersembunyi |
lateinit var untuk inisialisasi tertunda | Saat var wajib non-nullable namun baru diisi belakangan | Menghindari pola var x: T? = null |
requireNotNull untuk pre-condition | Memverifikasi input wajib di awal function | Pesan galat kontekstual |
filterNotNull untuk membersihkan list | list.filterNotNull() menghapus elemen null | Mengubah List<T?> menjadi List<T> |
takeIf untuk validasi kondisional | x.takeIf { it > 0 } menyaring nilai | Ekspresi ringkas tanpa if |
Contoh Penerapan #
class Layanan {
lateinit var nama: String
fun inisialisasi(namaBaru: String) {
nama = namaBaru
}
fun tampilkanNama(): String {
if (!::nama.isInitialized) {
return "<belum diinisialisasi>"
}
return nama
}
}
fun main() {
val nama: String? = null
val namaAman: String = nama ?: "Anonim"
println("Nama aman: $namaAman")
val umur: Int? = null
val umurFinal: Int = umur ?: 0
println("Umur final: $umurFinal")
val daftar: List<String>? = null
val daftarAman: List<String> = daftar ?: emptyList()
println("Ukuran daftar aman: ${daftarAman.size}")
val layanan = Layanan()
println("Sebelum inisialisasi: ${layanan.tampilkanNama()}")
layanan.inisialisasi("Sistem Produksi")
println("Setelah inisialisasi: ${layanan.tampilkanNama()}")
val idPengguna = ambilIdPengguna()
val idWajib: Long = requireNotNull(idPengguna) { "ID pengguna wajib ada di konteks ini" }
println("ID pengguna wajib: $idWajib")
val token = ambilToken()
val tokenWajib: String = requireNotNull(token) { "Token otentikasi wajib ada" }
println("Token wajib: $tokenWajib")
val input: String? = " Budi "
val inputBersih: String = input?.trim()?.takeIf { it.isNotEmpty() } ?: "Tanpa Nama"
println("Input bersih: '$inputBersih'")
val bahasa: String? = null
val bahasaFinal: String = bahasa ?: "id"
println("Bahasa final: $bahasaFinal")
val nilaiAngka: Int? = 5
val nilaiValid: Int = requireNotNull(nilaiAngka?.takeIf { it > 0 }) { "Nilai harus positif" }
println("Nilai valid: $nilaiValid")
try {
val tidakValid = requireNotNull((-1 as Int?)?.takeIf { it > 0 }) { "Nilai harus positif" }
println("Tidak tercetak: $tidakValid")
} catch (e: IllegalArgumentException) {
println("IllegalArgumentException tertangkap: ${e.message}")
}
val daftarPengguna: List<String?> = listOf("Budi", null, "Siti")
val hanyaValid: List<String> = daftarPengguna.filterNotNull()
println("Daftar pengguna valid: $hanyaValid")
val idOpsional: Long? = 1001L
val idTerverifikasi: Long = idOpsional ?: error("ID tidak boleh null di sini")
println("ID terverifikasi: $idTerverifikasi")
println("Program best-practices selesai")
}
fun ambilIdPengguna(): Long? = 12345L
fun ambilToken(): String? = "YOUR_API_TOKEN"Output yang Dihasilkan:
Nama aman: Anonim
Umur final: 0
Ukuran daftar aman: 0
Sebelum inisialisasi: <belum diinisialisasi>
Setelah inisialisasi: Sistem Produksi
ID pengguna wajib: 12345
Token wajib: YOUR_API_TOKEN
Input bersih: 'Budi'
Bahasa final: id
Nilai valid: 5
IllegalArgumentException tertangkap: Nilai harus positif
Daftar pengguna valid: [Budi, Siti]
ID terverifikasi: 1001
Program best-practices selesaiMari kita uraikan baris-baris penting pada contoh di atas:
- Baris 1-12 (
class Layanan): penggunaanlateinit varuntuk property yang wajib non-nullable namun baru dapat diinisialisasi setelah objek dibuat. PropertinamabertipeString(bukanString?) sehingga tidak perlu?.saat diakses. Pengecekan::nama.isInitializedmemastikan kita tidak mengakses property sebelum diisi — yang akan melemparUninitializedPropertyAccessExceptionbila dilanggar. - Baris 17 (
nama ?: "Anonim"): pola default-value dengan Elvis. Kapan pun data nullable perlu diubah menjadi non-nullable, Elvis adalah pilihan pertama. - Baris 23 (
daftar ?: emptyList()): pola aman untuk koleksi nullable. Mengembalikan list kosong alih-alihnullmenyederhanakan kode pemanggil yang dapat mengiterasi tanpa pengecekan. - Baris 28 (
requireNotNull(idPengguna) { "..." }): pre-condition yang melemparIllegalArgumentExceptiondengan pesan kontekstual bila nilai ternyatanull. Lebih informatif daripada!!yang menghasilkanNullPointerExceptiontanpa pesan. - Baris 35 (
input?.trim()?.takeIf { it.isNotEmpty() }): rangkaian safe call dengantakeIf.takeIfmengembalikan penerima bila lambda mengembalikantrue, ataunullbilafalse. Pola ini membersihkan input sekaligus memvalidasi. - Baris 39 (
bahasa ?: "id"): penggunaan default sederhana untuk konfigurasi opsional. - Baris 42-43 (
requireNotNull(nilaiAngka?.takeIf { it > 0 }) { "..." }): kombinasitakeIfdanrequireNotNull.takeIfmenyaring nilai yang valid, danrequireNotNullmemastikan hasilnya non-nullable. Bila nilai aslinullatau tidak memenuhi kondisi,IllegalArgumentExceptiondilempar dengan pesan jelas. - Baris 47-52 (
try { ... } catch (e: IllegalArgumentException)): demonstrasi bahwarequireNotNullmemang melemparIllegalArgumentException(bukanNullPointerException) saat gagal, sehingga lebih mudah ditangani spesifik. - Baris 54-55 (
daftarPengguna.filterNotNull()): method extension padaIterable<T?>yang mengembalikanList<T>non-nullable. Berguna untuk membersihkan data dari sumber yang mungkin mengandung elemen null. - Baris 58 (
idOpsional ?: error("...")): pola alternatif menggunakanerror()function bawaan. Function ini melemparIllegalStateExceptiondengan pesan — cocok untuk menandai pelanggaran kontrak internal yang tidak seharusnya terjadi.
Referensi Lanjutan #
- Null-safety — ringkasan resmi praktik terbaik menangani tipe nullable
Ringkasan Pedoman #
Saat menulis kode Kotlin yang melibatkan null, ikuti urutan prioritas berikut:
- Pilih tipe non-nullable sebagai default. Tambahkan
?hanya bila ada alasan konkret. - Pilih
valsebagai default. Gunakanvarhanya bila nilai harus berubah. - Gunakan
lateinit varbila perluvarnon-nullable yang baru diisi belakangan dalam siklus hidup objek. - Pilih operator Elvis
?:untuk nilai pengganti ringkas. - Pilih
requireNotNulluntuk pre-condition dengan pesan galat kontekstual. - Pilih
?: returnatau?: throwbila null mengindikasikan kondisi yang harus menghentikan alur. - Hindari
!!kecuali tidak ada alternatif yang masuk akal.
Dengan rangkaian praktik ini, kita menutup section Null Safety. Anda kini memiliki peranti lengkap untuk menangani nullable secara aman di Kotlin: dari pemilihan tipe, operator safe call dan Elvis, smart cast dan let, hingga penanganan platform types dari Java. Bab-bab selanjutnya akan membahas struktur kontrol dan koleksi yang memanfaatkan fondasi ini.