1 poin oleh GN⁺ 2 jam lalu | 1 komentar | Bagikan ke WhatsApp
  • assert adalah mekanisme untuk menyatakan prasyarat, pascakondisi, dan invarian di dalam kode, dan jika sebuah batasan bisa dipaksakan oleh sistem tipe, lebih baik mengekspresikannya dengan fitur bahasa
  • std.debug.assert di Zig adalah fungsi biasa, bukan makro; ia menandai jalur yang mustahil dicapai melalui unreachable dan juga dapat dimanfaatkan untuk optimisasi
  • Dalam Debug dan ReleaseSafe, assert yang gagal akan menyebabkan panic dan crash, tetapi dalam ReleaseFast dan ReleaseSmall, hal itu dapat menjadi unchecked illegal behavior yang membuat program berperilaku salah
  • Mematikan assert di production membuat kita kehilangan kesempatan menemukan asumsi yang salah lebih cepat, dan setelah itu kode dapat bergantung pada assert yang keliru hingga berujung pada kerentanan
  • Pilihan antara ReleaseSafe dan ReleaseFast bergantung pada prioritas program, tetapi intinya adalah jangan menonaktifkan assert untuk menutupinya — assert yang salah harus diperbaiki

Peran assert dan perilaku dasar Zig

  • assert adalah mekanisme untuk mengekspresikan dalam kode bahwa kondisi seperti “argumen ini tidak mungkin null” atau “bilangan bulat ini tidak mungkin genap” harus selalu benar
    • Contoh: assert(my_arg != null);, assert(my_num % 2 != 0);
    • Jika sebuah batasan dapat dipaksakan lewat sistem tipe, lebih baik memakai fitur bahasa daripada assert
    • Di Zig, pointer biasa *Foo tidak bisa bernilai null, sedangkan pointer opsional ?*Foo bisa null tetapi mewajibkan pengecekan sebelum nilainya diakses
  • assert cocok untuk menyatakan prasyarat, pascakondisi, dan invarian
    • assert yang baik bisa lebih kuat daripada unit test dalam menangkap kesalahan pemrograman
    • Jika dipakai bersama fuzzing, efek assert bisa menjadi lebih besar

unreachable di Zig dan assert

  • assert di Zig dibangun di atas unreachable, fitur bahasa untuk menandai jalur kode yang salah
    • Dalam switch, cabang yang tidak mungkin tercapai dapat ditandai seperti .a => unreachable
    • unreachable dapat digunakan sebagai statement maupun di tempat yang memerlukan ekspresi dari tipe apa pun
    • Tidak perlu memaksakan pembuatan nilai sementara untuk kasus yang mustahil dicapai
  • std.debug.assert di pustaka standar Zig diimplementasikan seperti ini
    pub fn assert(ok: bool) void {
      if (!ok) unreachable; // assertion failure
    }
    
  • Informasi unreachable dapat dimanfaatkan untuk optimisasi
    • Kompiler dapat menghapus jalur yang tak mungkin dicapai, dan informasi ini bisa dipropagasikan sehingga memungkinkan optimisasi nonlokal
    • Tidak semua assert akan menghasilkan peningkatan performa, tetapi optimisasi yang sulit diprediksi programmer pun bisa terjadi

Mode build dan keamanan runtime

  • Zig memiliki mode build Debug, ReleaseSafe, ReleaseFast, dan ReleaseSmall
    • Pengaturan ini tidak harus selalu diterapkan secara global untuk seluruh program
    • Tiap dependensi dapat dibangun dengan mode berbeda, dan dengan @setRuntimeSafety, keamanan runtime juga bisa diatur per blok di dalam fungsi
  • Kegagalan assert di Zig dianggap sebagai “illegal behavior”
    • Dalam mode checked seperti Debug, ReleaseSafe, dan @setRuntimeSafety(true), program akan crash dengan panic
    • Dalam mode unchecked seperti ReleaseFast, ReleaseSmall, dan @setRuntimeSafety(false), akan terjadi “unchecked illegal behavior” sehingga program berperilaku salah
  • Hasil dari unchecked illegal behavior tidak dijamin
    • Dalam contoh switch, berdasarkan karakteristik machine code yang saat ini dihasilkan, perilakunya bisa tampak seperti lompat ke cabang lain
    • Di versi kompiler lain, bisa muncul perilaku salah yang sama sekali berbeda
    • Perilaku terkait bisa dilihat di contoh godbolt
  • Perbedaan perilaku assert dan switch sesudahnya antara ReleaseSafe dan ReleaseFast dapat dilihat di contoh godbolt lain
    • Dalam ReleaseFast, fungsi tampak melewati semua perbandingan dan langsung mengembalikan true
    • Optimisasi semacam ini adalah jenis perilaku yang sangat diandalkan oleh video game dan aplikasi media real-time lainnya

Zig assert bukan makro

  • std.debug.assert di Zig adalah fungsi biasa, bukan makro
    • Zig tidak memiliki makro
    • Ini sering menjadi hal yang mengejutkan terutama bagi developer C/C++ yang baru mendekati Zig
  • Dalam C/C++, jika assert dinonaktifkan, lazimnya seluruh pemanggilan assert dan ekspresi yang diberikan akan diperlakukan seolah-olah ikut dikomentari
    • Karena itu, di C/C++ tidak boleh memasukkan ekspresi yang punya efek samping ke dalam assert
    • Saat assert dinonaktifkan, operasi itu sendiri bisa hilang
  • Di Zig, sesuai aturan pemanggilan fungsi, argumen dievaluasi sebelum fungsi dipanggil
    • Terlepas dari logika internal std.debug.assert, ekspresi argumennya tetap dievaluasi
    • Karena itu, ekspresi dengan efek samping seperti berikut pun bisa dimasukkan ke assert
    // assert that the remove operation is not a noop:
    assert(my_map.remove("expected-to-exist"));
    
  • Sebaliknya, jika menghitung kondisi assert memerlukan operasi yang kompleks, perhitungan itu mungkin tidak selalu dieliminasi dalam mode unchecked
    • Dalam kasus seperti ini, kode harus dijaga dengan comptime if
    const builtin = @import("builtin");
    
    if (builtin.mode == .Debug) {
      var condition = ...;
      // whatever bookkeeping is necessary
      // to compute the condition
      assert(condition == .ok);
    }
    
  • Jika terbiasa dengan semantik C/C++, ini mungkin terasa asing, tetapi di Zig terdapat asumsi bahwa assert pada umumnya tidak dinonaktifkan

Masalah mematikan assert di production

  • Secara garis besar, ada tiga pilihan untuk assert
    • Mempertahankannya sebagai runtime check dan membiarkan proses crash dengan panic saat gagal
    • Memakai assert untuk optimisasi performa sambil menerima risiko program salah berperilaku ketika assert tersebut keliru
    • Menonaktifkan assert sepenuhnya
  • std.debug.assert tidak menyediakan dukungan bawaan untuk menonaktifkan assert sepenuhnya
    • Jika membuat assert sendiri yang memeriksa flag build pada saat kompilasi, kita bisa membuat perilaku yang lebih mirip gaya C/C++
  • Alasan orang ingin mematikan assert biasanya merupakan gabungan dari dua hal
    • Mereka tidak ingin mempertahankan runtime check karena tak suka biaya performa atau crash aplikasi
    • Mereka juga sulit percaya bahwa assert selalu benar, sehingga takut pada perilaku salah yang bisa terjadi saat assert dipakai untuk optimisasi
  • Seperti diingatkan matklad dalam diskusi terkait, memang ada situasi dengan alasan engineering yang sah untuk menghindari crash
    • Tetapi dalam software umum, menjadikan penghindaran crash sebagai default dinilai sebagai pilihan yang buruk
  • Jika assert dinonaktifkan, kondisi yang diasumsikan mustahil tetap dapat terjadi dan program akan terus berjalan
    • Program terus berjalan di bawah asumsi yang salah, dan itu sendiri merupakan bentuk salah perilaku meskipun bukan unchecked illegal behavior
  • Alasan unchecked illegal behavior atau undefined behavior di C berbahaya adalah karena hal itu bisa menjadi jalan yang mengubah program menjadi weird machine
    • Dalam software yang cukup kompleks, bahkan tanpa UIB pun program bisa terpelintir dengan cara yang tidak dimaksudkan
    • Assert yang bernilai false saat runtime berarti keluar dari spesifikasi, dan itu sendiri dapat membuat program melakukan tindakan yang tak dimaksudkan
    • SQL injection adalah contoh konkret dan sangat umum dari salah perilaku setingkat weird machine tanpa melibatkan UIB
  • Jika biaya salah perilaku program terlalu tinggi, maka assert sebaiknya tetap dinyalakan
    • Jika performa sangat penting sehingga risiko salah perilaku bisa diterima, maka assert sebaiknya dipakai sebagai peluang optimisasi
    • Menonaktifkan assert membuat kita kehilangan performa sekaligus mudah salah mengira sistem lebih aman daripada kenyataannya

Cara assert yang salah menipu codebase

  • Risiko utamanya adalah assert yang keliru bisa tidak terlihat saat testing dan baru gagal di production
    • Jika dapat dijamin bahwa semua assert selalu benar, pemakaian assert untuk optimisasi tidak akan menjadi kontroversi
    • Jika testing dapat dijamin menangkap semua assert yang salah, optimisasi di production juga menjadi aman
    • Pada kenyataannya, kita bisa menulis assert yang salah, dan testing pun tidak selalu menangkapnya
  • Jika assert dimatikan di production, kita kehilangan kesempatan menemukan assert yang salah secepat mungkin
    • Masalah yang lebih serius adalah kode berikutnya terus ditulis dengan bergantung pada assert yang salah tersebut
  • Dalam contoh kode, diasumsikan processThing hanya boleh dipanggil pada thing yang sudah dimulai, dan asumsi itu ditaruh sebagai assert
    fn processThing(thing: Thing) void {
       // this function must always be invoked on
       // a thing that has already been started
       assert(thing.is_started);
    
       // ...
    }
    
  • Bisa saja assert ini tidak pernah gagal saat testing, lalu tanpa disadari dinonaktifkan di production padahal dalam kenyataan bisa bernilai false
    • Jika tidak ada salah perilaku yang terlihat oleh pengguna, semuanya bisa tampak baik-baik saja dan pengembangan terus berlanjut
  • Setelah itu, seseorang bisa menambahkan kode dengan asumsi bahwa thing sudah dimulai, sehingga baz aman dipanggil tanpa persiapan tambahan
    fn processThing(thing: Thing) void {
       // this function must always be invoked on
       // a thing that has already been started
       assert(thing.is_started);
    
       // ...
    
       // Since thing is already started, we don't
       // need to foo the bar before bazzing the qux.
       // It would be really bad to baz the qux otherwise,
       // so we add an assert for good measure.
       assert(thing.is_fooed);
       thing.baz(qux);
    }
    
  • Meskipun assert kedua secara logika benar, tetap muncul bahaya jika assert pertama sebenarnya dapat bernilai false
    • Dalam testing, assert pertama tidak gagal, sehingga assert kedua pun tidak gagal
    • Di production, karena assert dinonaktifkan, kita mungkin tidak menyadari saat kerentanan masuk ke codebase
  • Jika assert di dalam kode sudah menipu developer, maka menulis kode yang benar menjadi jauh lebih sulit daripada seharusnya

Pilihan bergantung pada prioritas program

  • Tiap program memiliki prioritas yang berbeda, dan untuk sebagian program, memprioritaskan performa daripada meminimalkan risiko salah perilaku bisa saja masuk akal
    • Dalam kasus ini, mengubah assert menjadi peluang optimisasi adalah pilihan yang wajar
  • Menonaktifkan assert di production secara kebiasaan dinilai sebagai pilihan yang lebih buruk daripada membiarkannya tetap aktif, bahkan juga lebih buruk daripada secara aktif memanfaatkan optimisasi performa
    • Bersikap sangat kritis terhadap ReleaseFast tetapi menerima penonaktifan assert tanpa kritik adalah sikap yang kontradiktif
  • Zine adalah static site generator, dan saat ini terutama dipakai untuk membangun blog pribadi
    • Threat model-nya belum terdefinisi dan itu juga bukan prioritas utamanya
    • Karena disukai bisa berjalan satu orde magnitudo lebih cepat daripada Hugo, build ReleaseFast yang didistribusikan
  • Awebo adalah alternatif Discord self-hosted yang masih berada di tahap pre-alpha
    • Sudah jelas bahwa ini software yang akan menangani data pribadi dan terekspos ke internet
    • Saat distribusi nanti, rencananya build ReleaseSafe yang akan disediakan
    • Namun, beberapa dependensi inti seperti FFmpeg, Xiph Opus, dan SQLite tetap akan dibangun dengan ReleaseFast
    • Pada dependensi tersebut, peningkatan performa dinilai jelas lebih penting daripada mengurangi risiko salah perilaku program lebih jauh

Pilihan proyek nyata dan kasus keamanan

Assert implisit yang tidak benar-benar hilang di Zig

  • Sekalipun assert buatan sendiri dapat dinonaktifkan, assert implisit yang ditambahkan bahasa Zig sendiri ke dalam kode tidak bisa dinonaktifkan
    • Integer overflow, pembagian dengan 0, dan akses array di luar batas termasuk di dalamnya
    • Kondisi-kondisi ini akan menimbulkan panic runtime atau dipakai untuk tujuan optimisasi
  • Kebiasaan menonaktifkan assert di production dapat membuat assert yang salah membusuk dan bertambah di dalam codebase
    • Akibatnya, paranoia terhadap UIB bisa makin besar, dan developer bisa secara bawah sadar takut menyalakan kembali assert lalu menghadapi hasilnya
  • Kesimpulan yang tak terhindarkan adalah bahwa kita tidak boleh menutupi masalah dengan menonaktifkan assert, melainkan harus memperbaiki assert yang salah
    • Kebenaran program harus diupayakan untuk keseluruhan, bukan hanya untuk sebagian subset

1 komentar

 
GN⁺ 2 jam lalu
Opini Lobste.rs
  • Saya setuju bahwa pada assert, sekadar crash, atau perilaku seperti panic di Rust yang hanya membuat tugasnya crash, umumnya adalah pilihan terbaik. Namun, saya sulit setuju bahwa memakai assert sebagai petunjuk optimisasi selalu lebih baik daripada sekadar menghapusnya
    Pertama, assert yang arbitrer sering kali tidak banyak membantu optimisasi, dan banyak kondisi juga tidak bisa langsung dimanfaatkan oleh pengoptimal. Kecuali Anda memasukkan asumsi yang langsung seperti “cabang ini tidak akan pernah dilewati”, keuntungan performa dari menaburkan asumsi acak di seluruh kode kemungkinan tidak besar
    Kedua, mengubah assert menjadi asumsi sangat memperluas radius dampak dari sebuah kesalahan. Misalnya, dalam sistem yang memproses data yang dipisahkan per proyek atau per pengguna, bayangkan ada assert di tengah fungsi perhitungan yang menangkap keadaan yang semestinya mustahil terjadi. Jika dalam build rilis ia dimatikan karena mahal, maka jika hanya dinonaktifkan, kerusakannya bisa terbatas pada satu proyek atau pengguna dan mungkin tertangkap pada pemeriksaan berikutnya. Sebaliknya, jika itu dijadikan perilaku tak terdefinisi, perhitungan bisa lompat ke kode yang tidak semestinya, merusak memori secara sewenang-wenang, dan merusak data semua proyek
    Pada akhirnya, memilih assert yang tidak aman sebagai default untuk build rilis berarti mengoptimalkan secara prematur di titik-titik acak dalam kode, dengan konsekuensi berkurangnya peluang untuk melokalisasi dampak saat masalah terjadi. Menurut saya Rust dirancang dengan baik karena assert!() selalu panic, debug_assert!() hanya panic di mode debug, dan assert_unchecked() panic di debug lalu menjadi petunjuk optimisasi di rilis

    • Jika yang dikhawatirkan adalah radius dampak dari kesalahan, Anda seharusnya memakai ReleaseSafe alih-alih ReleaseFast
    • Saya bukan menentang mematikan assert tertentu, melainkan menentang mematikannya secara massal seolah itu praktik umum yang disarankan
      Menilai bahwa dampak performanya terlalu besar sehingga tidak bisa dipertahankan di build rilis adalah keputusan yang sepenuhnya masuk akal. Lagipula, assert yang biaya komputasinya besar juga, seperti disebut sebelumnya, hampir tidak mungkin menghasilkan peningkatan performa
      Ada beberapa contoh seperti itu di Zine:
      https://github.com/kristoff-it/zine/…
      https://github.com/kristoff-it/zine/…
      Zig tidak memiliki “mode rilis default”. Anda selalu harus memilih sendiri bagaimana assert diperlakukan, dan opsi globalnya adalah crash atau optimisasi, tanpa salah satunya bisa dibilang lebih default daripada yang lain
  • Fakta bahwa dua CVE yang relatif serius dan sejauh ini dipublikasikan di Ghostty sama-sama berujung pada eksekusi perintah arbitrer tanpa kerusakan memori terasa sangat aneh. Fakta bahwa itu terjadi meski didistribusikan dengan ReleaseFast benar-benar bertentangan dengan pemahaman saya tentang cara dunia bekerja

    • Menurut saya itu tidak terlalu aneh. Bahkan jika Anda percaya laporan bahwa 70% kerentanan serius terkait memori, itu berdasarkan C dan C++, dan Zig bisa sedikit lebih baik dalam keamanan memori. Ditambah lagi, dengan ukuran sampel 2, hasil seperti ini tetap tidak aneh terjadi pada kira-kira satu dari sepuluh proyek
      Dari pengalaman menangani terminal emulator, kerentanan ini adalah jenis masalah menyebalkan yang persis bisa diduga. Bukan untuk merendahkan pengembang atau peneliti, tetapi injeksi perintah di tempat yang tidak terduga seperti ini nyaris merupakan masalah bawaan di bidang ini, mirip dengan bagaimana kerentanan injeksi lain ikut muncul di bidang lain
  • Menarik bahwa saya sudah hampir 40 tahun mendengar argumen untuk mematikan assert dan pemeriksaan batas di produksi “demi performa”. Selama itu, komputer sudah menjadi beberapa orde magnitudo lebih cepat, dan perangkat lunak sudah jauh lebih masuk ke kehidupan semua orang, jadi ketepatan runtime lebih penting daripada sebelumnya
    Agar pembahasannya lebih produktif, dulu di Microsoft ada sesuatu yang, selain assert, check, dan sejenisnya yang umum, berupa assert untuk pelaporan yang jarang saya lihat di tempat lain. Ini dipakai saat ada kondisi yang tidak sepenuhnya saya kendalikan, saya mengasumsikan itu benar tetapi tetap menangani secara defensif jika ternyata salah, dan saya ingin tahu lewat log atau telemetri apakah itu benar-benar pernah salah di lapangan. Misalnya, saya mengasumsikan pengguna tidak akan memasukkan lebih dari 1000 item ke daftar tertentu sehingga saya memakai algoritme kuadratik, atau saya menganggap latensi jaringan akan di bawah 200 ms sehingga saya memakai protokol dengan banyak round-trip

    • Apa bedanya dengan check?
  • Sebagai salah satu orang yang ditautkan di sini, ini menjadikan pandangan saya tentang assert sebagai dikotomi palsu dan karikatur yang konyol. Seperti yang saya tulis di komentar lain, saya lebih memilih memutuskan per assert apakah akan diubah menjadi perilaku tak terdefinisi. Kritik saya terhadap ReleaseFast adalah bahwa pilihan itu dibundel bukan hanya untuk semua assert dalam cakupan tertentu, tetapi juga untuk semua pemeriksaan keamanan
    Saya setuju dengan kristoff bahwa mematikan assert yang belum diperbaiki hanya karena ia menyebabkan crash adalah tindakan bodoh. Namun, saya tidak setuju bahwa satu-satunya alternatif yang masuk akal hanyalah “crash atau perilaku tak terdefinisi”. Pandangan goldstein di komentar saudara lebih dekat dengan posisi saya

  • Menjadikan perilaku assert_unchecked() sebagai default global memang sulit dibela, tetapi itu bisa masuk akal sebagai teknik optimisasi performa. Jika mengubah semua assert menjadi asumsi membuat build produksi jauh lebih cepat, mungkin ada sejumlah kecil asumsi, semoga hanya satu, yang menghasilkan sebagian besar peningkatan performa itu, dan itu bisa ditemukan dengan metode seperti pencarian biner

    • Tidak ada default; pengguna secara eksplisit harus memilih antara ReleaseSafe dan ReleaseFast/ReleaseSmall
  • Dalam literatur analisis program, ada dualitas yang membagi pernyataan atau assert di dalam kode menjadi dua bentuk. Yang satu adalah konteks di sekitar kode—untuk fungsi, ini adalah kondisi yang harus dipenuhi pemanggil—dan yang lain adalah kode itu sendiri—untuk fungsi, ini adalah kondisi yang harus dipenuhi fungsi tersebut
    Pembedaan ini menjadi jelas jika dilihat melalui konsep akademis standar “blame” dalam literatur kontrak dan tipe gradual. Jika pernyataan tentang konteks gagal, itu bukan kesalahan kita melainkan tanggung jawab konteks atau pemanggil, meski bisa juga pemanggil benar dan justru pernyataannya yang bug. Jika pernyataan tentang kode itu sendiri gagal, itu tanggung jawab kita, meski bisa juga kodenya benar dan justru pernyataannya yang bug
    Pada tingkat fungsi, prasyarat adalah pernyataan tentang konteks, dan pascakondisi adalah pernyataan tentang kode itu sendiri. Namun, keduanya bisa ditempatkan juga di tengah-tengah kode. Beberapa framework verifikasi memakai assert untuk pernyataan tentang kode, dan assume untuk pernyataan tentang konteks. Ini juga terkait dengan cara beberapa framework pengujian—terutama framework pengujian acak—menafsirkannya. Jika assert gagal, itu ditandai sebagai kegagalan tes; jika assume gagal, tes dilewati

    • BIND9 mengikuti gaya yang dekat dengan design by contract, dengan makro REQUIRE() untuk memeriksa prasyarat yang harus dipenuhi pemanggil, dan ENSURE() untuk memeriksa pascakondisi yang dijamin fungsi. Ada juga INSIST() untuk pemeriksaan di tengah proses, dan INVARIANT() untuk loop atau struktur data. Dokumentasi fungsi seharusnya memiliki catatan “requires” dan “ensures” yang sesuai dengan prasyarat dan pascakondisi
  • Ini tampaknya menyindir Bun, jadi saya ingin membuat kaitannya sedikit lebih formal. Ada issue Zig pada 2024 di mana pembuat Bun, Jarred Sumner, mengusulkan agar unreachable melakukan panic di ReleaseFast. Komentar Andrew Kelley dan Matthew Lugg di thread itu relevan dengan pembahasan ini
    => https://github.com/ziglang/zig/issues/19664
    Bun memakai fungsi assert buatannya sendiri, dan dalam mode rilis itu akan panic atau dihapus, tetapi tidak memperkenalkan undefined behavior. Namun, catatan kaki dari Loris juga perlu diingat: “Sebagai bahasa, Zig secara implisit menambahkan banyak assert ke dalam kode yang tidak bisa dinonaktifkan”
    Saya tidak ingin terlalu panjang membahas Bun, karena itu proyek tunggal dari tim kecil. Intinya, jika ada sedikit saja kekhawatiran, gunakan ReleaseSafe. ReleaseSafe punya reputasi lambat, tetapi pada proyek-proyek Zig kecil saya, saya tidak bisa mengukur perbedaan benchmark antara ReleaseSafe dan ReleaseFast. Meski begitu, kemungkinan besar tetap lebih cepat daripada banyak bahasa lain

    • Benar bahwa jika ada sedikit saja kekhawatiran, pakailah ReleaseSafe. Strategi yang lebih menarik juga memungkinkan. Saat Anda sedang mengubah kode—yakni ketika ada kemungkinan memasukkan bug—biarkan tetap di ReleaseSafe, lalu setelah kode stabil dan sudah teruji di kondisi nyata, jika peningkatan performa memang layak, Anda bisa beralih ke ReleaseFast
      Atau, jika masuk akal dalam konteksnya, Anda bisa mendistribusikan biner ReleaseFast lalu, ketika mulai muncul laporan bug non-deterministik akibat undefined behavior, kembali ke ReleaseSafe. Dengan begitu Anda bisa mengumpulkan laporan bug yang bisa ditindaklanjuti tentang assert mana yang gagal, termasuk akses di luar batas atau overflow, lalu memperbaiki kodenya. Saya bahkan akan merekomendasikan pendekatan ini meskipun sejak awal Anda membuat keputusan untuk mendistribusikan ReleaseFast dalam konteks yang sebenarnya tidak seharusnya :^)
      Anda juga bisa menyesuaikan dependensi dan memakai @setRuntimeSafety untuk menerapkan pendekatan yang sama hanya pada sebagian proyek. Pada akhirnya, jika ada kemauan untuk melakukannya dengan cerdas, semua alat yang dibutuhkan sudah tersedia
  • Jangan menulis seolah-olah boleh memasukkan ekspresi yang punya efek samping ke dalam pemanggilan assert. Itu praktik buruk. Memakai assert untuk pengecekan error juga sebaiknya dihindari. Supaya adil, penulis tampaknya tidak sedang menganjurkan itu
    Sebaliknya, juga disebutkan bahwa jika assert bergantung pada perhitungan yang kompleks, perhitungan itu belum tentu dihapus dalam mode unchecked, sehingga perlu dijaga dengan comptime if
    Saya harap penulis tidak melewatkan ironi dari kalimat “kesempatan yang bagus untuk membuang trauma yang ditinggalkan makro dan menerima kesederhanaan”. Maksudnya, kita diminta menerima “kesederhanaan” berupa harus mempertimbangkan mode build program dan menaburkan comptime if defensif di mana-mana

    • Mengapa itu praktik buruk?
  • Saya menulis sedikit kode komputasi numerik dalam C#, dan banyak memakai assert yang dimatikan di mode rilis. Menjalankannya di setiap loop yang padat terlalu mahal, tetapi dalam unit test sangat berguna jika rutin langsung meledak pada saat pertama kali melihat input NaN
    NaN seperti ini sering bukan berasal dari input pengguna, melainkan dari bug kode—misalnya optimizer masuk ke jalur yang seharusnya tidak boleh—dan berarti dibutuhkan pembatasan batas yang lebih baik. Tentu saja input pengguna mungkin juga perlu disanitasi, tetapi itu harus dilakukan di batas paling luar, bukan jauh di dalam algoritme. Akan bagus jika ada sistem pembuktian yang bisa membuktikan invarian internal algoritme tanpa assert sebagai hasil dari sanitasi input pengguna, tetapi ini proyek sampingan, dan kalau sampai meledak tidak ada yang mati

  • Sebanyak 90% perbedaan pendapat tentang assert muncul karena definisi istilah itu buruk dan bermacam-macam, sehingga mengaburkan pemikiran dan komunikasi. Karena itu, konsepnya perlu dipecah menjadi tiga nama berikut dan dipakai secara ketat
    assert(bool) atau, dalam Rust, assert_unchecked() adalah sesuatu yang diyakini programmer selalu benar, dan compiler juga menganggapnya selalu benar untuk keperluan optimisasi. Untuk menghindari asosiasi dengan assert pemeriksaan dari bahasa lama, mungkin lebih baik menyebutnya assume()
    check(bool) akan panic jika kondisi salah dan lanjut jika benar, dan selalu berperilaku demikian
    debug_check(bool) sama dengan check() di mode debug, dan di mode rilis selalu lanjut. Dalam praktiknya, ini dikendalikan oleh flag --debug_checks yang aktif secara default di mode debug
    Selain itu juga dibutuhkan flag compiler --check_asserts yang mengubah assert() menjadi check(). Ini dipakai saat ingin memverifikasi assert milik sendiri karena terasa mencurigakan, dan di mode debug aktif secara default. Jika saat mengatakan “assert” tidak benar-benar jelas yang dimaksud adalah yang mana, diskusi yang matang tidak mungkin terjadi dan yang terbuang hanya kata-kata