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:

PraktikSaranAlasan
Prefer val non-nullableMulai dengan val bertipe non-nullableLebih sedikit cabang null, lebih mudah dirawat
Elvis untuk defaultx ?: default menggantikan if verboseSintaks ringkas, hasil non-nullable
Hindari !!Ganti dengan ?: error(...) atau requireNotNullPesan galat jelas, tidak tersembunyi
lateinit var untuk inisialisasi tertundaSaat var wajib non-nullable namun baru diisi belakanganMenghindari pola var x: T? = null
requireNotNull untuk pre-conditionMemverifikasi input wajib di awal functionPesan galat kontekstual
filterNotNull untuk membersihkan listlist.filterNotNull() menghapus elemen nullMengubah List<T?> menjadi List<T>
takeIf untuk validasi kondisionalx.takeIf { it > 0 } menyaring nilaiEkspresi ringkas tanpa if

Contoh Penerapan #

kotlin
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:

text
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 selesai

Mari kita uraikan baris-baris penting pada contoh di atas:

  1. Baris 1-12 (class Layanan): penggunaan lateinit var untuk property yang wajib non-nullable namun baru dapat diinisialisasi setelah objek dibuat. Properti nama bertipe String (bukan String?) sehingga tidak perlu ?. saat diakses. Pengecekan ::nama.isInitialized memastikan kita tidak mengakses property sebelum diisi — yang akan melempar UninitializedPropertyAccessException bila dilanggar.
  2. Baris 17 (nama ?: "Anonim"): pola default-value dengan Elvis. Kapan pun data nullable perlu diubah menjadi non-nullable, Elvis adalah pilihan pertama.
  3. Baris 23 (daftar ?: emptyList()): pola aman untuk koleksi nullable. Mengembalikan list kosong alih-alih null menyederhanakan kode pemanggil yang dapat mengiterasi tanpa pengecekan.
  4. Baris 28 (requireNotNull(idPengguna) { "..." }): pre-condition yang melempar IllegalArgumentException dengan pesan kontekstual bila nilai ternyata null. Lebih informatif daripada !! yang menghasilkan NullPointerException tanpa pesan.
  5. Baris 35 (input?.trim()?.takeIf { it.isNotEmpty() }): rangkaian safe call dengan takeIf. takeIf mengembalikan penerima bila lambda mengembalikan true, atau null bila false. Pola ini membersihkan input sekaligus memvalidasi.
  6. Baris 39 (bahasa ?: "id"): penggunaan default sederhana untuk konfigurasi opsional.
  7. Baris 42-43 (requireNotNull(nilaiAngka?.takeIf { it > 0 }) { "..." }): kombinasi takeIf dan requireNotNull. takeIf menyaring nilai yang valid, dan requireNotNull memastikan hasilnya non-nullable. Bila nilai asli null atau tidak memenuhi kondisi, IllegalArgumentException dilempar dengan pesan jelas.
  8. Baris 47-52 (try { ... } catch (e: IllegalArgumentException)): demonstrasi bahwa requireNotNull memang melempar IllegalArgumentException (bukan NullPointerException) saat gagal, sehingga lebih mudah ditangani spesifik.
  9. Baris 54-55 (daftarPengguna.filterNotNull()): method extension pada Iterable<T?> yang mengembalikan List<T> non-nullable. Berguna untuk membersihkan data dari sumber yang mungkin mengandung elemen null.
  10. Baris 58 (idOpsional ?: error("...")): pola alternatif menggunakan error() function bawaan. Function ini melempar IllegalStateException dengan 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:

  1. Pilih tipe non-nullable sebagai default. Tambahkan ? hanya bila ada alasan konkret.
  2. Pilih val sebagai default. Gunakan var hanya bila nilai harus berubah.
  3. Gunakan lateinit var bila perlu var non-nullable yang baru diisi belakangan dalam siklus hidup objek.
  4. Pilih operator Elvis ?: untuk nilai pengganti ringkas.
  5. Pilih requireNotNull untuk pre-condition dengan pesan galat kontekstual.
  6. Pilih ?: return atau ?: throw bila null mengindikasikan kondisi yang harus menghentikan alur.
  7. 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.