- Memperkenalkan kebiasaan menulis kode untuk mencegah bug sejak awal dengan memanfaatkan secara aktif sistem tipe dan compiler Rust
- Menunjukkan contoh bau kode (Code Smell) yang rapuh seperti pengindeksan vektor, penyalahgunaan
Default, match yang tidak lengkap, dan parameter boolean yang tidak perlu, serta menjelaskan alternatifnya
- Prinsip utamanya adalah merancang struktur agar compiler memaksa invarian, dengan memanfaatkan pattern matching, field privat, dan atribut
#[must_use]
- Menyajikan secara konkret teknik defensif di tingkat kode nyata seperti penggunaan
TryFrom, pembongkaran struct secara lengkap, mutabilitas sementara, dan validasi di konstruktor
- Pola-pola ini penting untuk menjaga stabilitas saat refactoring dan meningkatkan maintainability jangka panjang
Gambaran umum pemrograman defensif
- Titik yang diberi komentar
// this should never happen adalah lokasi ketika invarian implisit rusak
- Dalam banyak kasus, pengembang tidak mempertimbangkan semua kondisi batas atau perubahan kode di masa depan
- Compiler Rust menjamin keamanan memori, tetapi kesalahan logika bisnis tetap bisa terjadi
- Pola kebiasaan kecil (idiom) yang diperoleh dari pengalaman kerja bertahun-tahun dapat sangat meningkatkan kualitas kode
Code Smell: pengindeksan vektor
- Bentuk
if !vec.is_empty() { let x = &vec[0]; } memiliki risiko panic saat runtime karena pemeriksaan panjang dan pengindeksan terpisah
- Dengan menggunakan pattern matching pada slice (
match vec.as_slice()), compiler memaksa pemeriksaan semua keadaan
- Semua kasus seperti vektor kosong, satu elemen, atau elemen duplikat dapat ditangani secara eksplisit
- Ini adalah contoh representatif dari desain yang membuat compiler menjamin invarian
Code Smell: penggunaan Default yang sembarangan
..Default::default() menimbulkan masalah risiko field terlewat saat field baru ditambahkan dan penetapan nilai implisit
- Jika semua field diinisialisasi secara eksplisit, compiler akan memaksa field baru untuk ikut diatur
- Dengan bentuk
let Foo { field1, field2, .. } = Foo::default();, dimungkinkan membongkar struct default lalu menimpa sebagian field secara selektif
- Ini menjaga keseimbangan antara mempertahankan nilai default dan override yang eksplisit
Code Smell: implementasi trait yang rapuh
- Saat membandingkan dengan membongkar seluruh field struct, penambahan field baru akan memunculkan error kompilasi sebagai peringatan
- Contoh: saat mengimplementasikan
PartialEq, gunakan let Self { size, toppings, .. } = self;
- Jika field baru seperti
extra_cheese ditambahkan, logika perbandingan dipaksa untuk ditinjau ulang
- Prinsip yang sama juga dapat diterapkan pada trait lain seperti
Hash, Debug, dan Clone
Code Smell: perlu TryFrom alih-alih From
- Jika konversi tidak selalu berhasil, gunakan
TryFrom untuk menyatakan kemungkinan kegagalan alih-alih From
- Penggunaan
unwrap_or_else adalah sinyal bahwa kegagalan potensial sedang disembunyikan, dan pendekatan fail fast lebih aman
Code Smell: match yang tidak lengkap
- Pola catch-all seperti
_ => {} berisiko membuat kasus baru terlewat saat variant baru ditambahkan
- Jika semua variant dicantumkan secara eksplisit, compiler akan memperingatkan ketika ada kasus baru yang belum ditangani
- Logika yang sama dapat dikelompokkan dalam bentuk
Variant3 | Variant4
Code Smell: penyalahgunaan placeholder _
- Jika hanya menggunakan
_, tidak jelas variabel mana yang dihilangkan
- Nama eksplisit seperti
has_fuel: _, has_crew: _ meningkatkan keterbacaan
Pattern: mutabilitas sementara (Temporary Mutability)
- Saat data hanya perlu mutable selama inisialisasi, gunakan bentuk
let mut data = ...; data.sort(); let data = data;
- Dengan memanfaatkan block scope, variabel sementara tidak terekspos ke luar
- Contoh:
let data = { let mut d = get_vec(); d.sort(); d };
- Ini memungkinkan pemisahan cakupan yang jelas dalam proses inisialisasi yang memakai beberapa variabel sementara
Pattern: memaksa validasi konstruktor
- Saat membuat struct, pastikan logika validasi wajib dilalui
- Jika field
_private: () ditambahkan, pembuatan langsung dari luar menjadi tidak mungkin
- Atribut
#[non_exhaustive] mencegah konstruksi dari luar crate dan memberi sinyal adanya perluasan di masa depan
- Jika ingin memaksanya juga di modul internal, gunakan struktur modul bertingkat dengan tipe privat (
Seal)
- Karena
Seal hanya ada di internal, pembuatan langsung selain melalui new() menjadi tidak mungkin
- Dengan menjadikan field privat dan menyediakan getter, keadaan immutable dapat dipertahankan
- Kriteria penerapan
- Memblokir kode eksternal:
_private atau #[non_exhaustive]
- Memblokir kode internal: modul privat +
Seal
- Mengubah logika validasi menjadi jaminan di tingkat compiler
Pattern: memanfaatkan atribut #[must_use]
#[must_use] mencegah nilai return penting diabaikan
- Contoh:
#[must_use = "Configuration must be applied to take effect"]
- Jika pengguna mengabaikan nilai return, compiler akan mengeluarkan peringatan
- Ini adalah mekanisme defensif yang sederhana tetapi kuat, dan juga banyak dipakai di standard library seperti
Result
Code Smell: parameter boolean
- Bentuk
fn process_data(..., compress: bool, encrypt: bool, validate: bool) memiliki makna yang tidak jelas dan risiko salah urutan
- Gunakan
enum Compression, enum Encryption, dan sebagainya untuk mengekspresikan maksud secara eksplisit
- Jika ada banyak opsi, gunakan parameter struct (Params struct)
- Metode preset seperti
ProcessDataParams::production() meningkatkan reusabilitas
- Saat opsi baru ditambahkan, dampak pada titik pemanggilan yang ada dapat diminimalkan
Otomatisasi dengan Clippy lints
- Pola defensif utama dapat diperiksa secara otomatis dengan lint Clippy
indexing_slicing: melarang pengindeksan langsung
fallible_impl_from: menyarankan TryFrom alih-alih From
wildcard_enum_match_arm: melarang pola _
fn_params_excessive_bools: peringatan untuk parameter boolean yang terlalu banyak
must_use_candidate: menyarankan kandidat #[must_use]
- Dapat diterapkan ke seluruh proyek melalui
#![deny(clippy::...)] atau pengaturan di Cargo.toml
Kesimpulan
- Inti pemrograman defensif adalah memanfaatkan sistem tipe dan compiler Rust secara aktif untuk membuat invarian menjadi eksplisit dan dapat diverifikasi
- Pola-pola ini membantu menjaga stabilitas saat refactoring, meminimalkan kemungkinan bug, dan memperkuat maintainability jangka panjang
- Ini adalah pendekatan yang menjalankan prinsip: “bug yang tidak bisa dikompilasi adalah bug terbaik”
Belum ada komentar.