1 puan yazan GN⁺ 1 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • assert, önkoşulları, sonkoşulları ve değişmezleri kodda açıkça belirtme aracıdır; tür sistemiyle zorunlu kılınabilen kısıtların dil özellikleriyle ifade edilmesi tercih edilir
  • Zig’de std.debug.assert, makro değil normal bir fonksiyondur; unreachable ile ulaşılamaz yolları işaretler ve optimizasyonda da kullanılır
  • Debug ve ReleaseSafe’te başarısız assert bir panic ile çöküşe yol açarken, ReleaseFast ve ReleaseSmall’da unchecked illegal behavior nedeniyle yanlış çalışmaya neden olabilir
  • Prodüksiyonda assert’i kapatmak, yanlış varsayımları erken fark etme fırsatını ortadan kaldırır ve sonrasında kodun hatalı assert’lere dayanarak güvenlik açıklarına dönüşmesine yol açabilir
  • ReleaseSafe ile ReleaseFast arasında seçim yapmak programın önceliklerine bağlıdır; ancak asıl nokta assert’i örtbas edip kapatmak değil, hatalı assert’leri düzeltmektir

assert’in rolü ve Zig’in varsayılan davranışı

  • assert, “bu argüman null olamaz”, “bu tamsayı tek olamaz” gibi koşulların her zaman doğru olması gerektiğini kodla ifade etme aracıdır
    • Örnek: assert(my_arg != null);, assert(my_num % 2 != 0);
    • Eğer tür sistemiyle kısıtlar zorunlu kılınabiliyorsa, assert yerine dil özelliklerini kullanmak daha iyidir
    • Zig’de normal işaretçi *Foo null olamaz; isteğe bağlı işaretçi ?*Foo null olabilir ama değere erişmeden önce kontrol zorunludur
  • assert; önkoşulları, sonkoşulları ve değişmezleri belirtmek için uygundur
    • İyi yazılmış assert’ler, programlama hatalarını yakalamada birim testlerinden daha güçlü olabilir
    • Fuzzing ile birlikte kullanıldığında assert’in etkisi daha da artabilir

Zig’de unreachable ve assert

  • Zig’de assert, hatalı kod yolunu işaretleyen bir dil özelliği olan unreachable üzerine kuruludur
    • switch içinde ulaşılamaz bir dal .a => unreachable şeklinde işaretlenebilir
    • unreachable hem bir ifade (statement) olarak hem de herhangi bir türden ifade gereken yerlerde kullanılabilir
    • Ulaşılamaz bir durumda zorla geçici bir değer üretmek gerekmez
  • Zig standart kütüphanesindeki std.debug.assert şu şekilde uygulanır
    pub fn assert(ok: bool) void {
      if (!ok) unreachable; // assertion failure
    }
    
  • unreachable bilgisi optimizasyon için kullanılabilir
    • Derleyici ulaşılamaz yolları kaldırabilir, bu bilgi yayılabilir ve yerel olmayan optimizasyonlar mümkün hale gelir
    • Her assert performans artışı sağlamaz ama programcının kolayca öngöremeyeceği optimizasyonlar da mümkün olabilir

Derleme modları ve çalışma zamanı güvenliği

  • Zig’de Debug, ReleaseSafe, ReleaseFast ve ReleaseSmall derleme modları vardır
    • Bu ayarların programın tamamına yalnızca global olarak uygulanması gerekmez
    • Her bağımlılık farklı modlarla derlenebilir ve @setRuntimeSafety kullanılırsa çalışma zamanı güvenliği fonksiyon içindeki blok düzeyinde de ayarlanabilir
  • assert başarısızlığı Zig’de “illegal behavior” sayılır
    • Checked modlar olan Debug, ReleaseSafe ve @setRuntimeSafety(true) altında panic ile program çöker
    • Unchecked modlar olan ReleaseFast, ReleaseSmall ve @setRuntimeSafety(false) altında “unchecked illegal behavior” oluşur ve program yanlış çalışır
  • unchecked illegal behavior sonucunda ne olacağı garanti edilmez
    • Örnek switchte, şu an üretilen makine kodunun özellikleri nedeniyle başka bir dala atlıyormuş gibi görünebilir
    • Farklı bir derleyici sürümünde bambaşka bir yanlış davranış ortaya çıkabilir
    • İlgili davranış godbolt örneğinde görülebilir
  • assert ve sonrasındaki switchin ReleaseSafe ile ReleaseFast’te nasıl farklılaştığı başka bir godbolt örneğinde görülebilir
    • ReleaseFast’te fonksiyonun tüm karşılaştırmaları atlayıp true döndürdüğü bir yapı ortaya çıkar
    • Bu tür optimizasyonlar, video oyunları ve diğer gerçek zamanlı medya uygulamalarının yoğun biçimde dayandığı davranışlardır

Zig assert bir makro değildir

  • Zig’de std.debug.assert, makro değil normal bir fonksiyondur
    • Zig’de makro yoktur
    • Bu özellikle C/C++ geliştiricilerinin Zig’e yaklaşırken şaşırdığı bir noktadır
  • C/C++’ta assert devre dışı bırakıldığında, assert çağrısının tamamı ve ona verilen ifade yorum satırı haline gelmiş gibi davranması yaygındır
    • Bu yüzden C/C++’ta yan etkili ifadeler assert içine konmamalıdır
    • Çünkü assert kapatıldığında o işlemin kendisi de ortadan kalkabilir
  • Zig’de fonksiyon çağrısı kurallarına göre argümanlar fonksiyon çağrısından önce değerlendirilir
    • std.debug.assert iç mantığından bağımsız olarak argüman ifadesi değerlendirilir
    • Bu nedenle aşağıdaki gibi yan etkili ifadeler de assert içine konabilir
    // assert that the remove operation is not a noop:
    assert(my_map.remove("expected-to-exist"));
    
  • Buna karşılık assert koşulunu hesaplamak için karmaşık işlemler gerekiyorsa, unchecked modda bile bu hesaplamanın mutlaka kaldırılacağı garanti değildir
    • Böyle durumlarda kod comptime if ile korunmalıdır
    const builtin = @import("builtin");
    
    if (builtin.mode == .Debug) {
      var condition = ...;
      // whatever bookkeeping is necessary
      // to compute the condition
      assert(condition == .ok);
    }
    
  • C/C++ anlam bilimlerine alışkın olanlara yabancı gelebilir ama Zig’de genel varsayım assert’in normalde kapatılmadığıdır

Prodüksiyonda assert kapatmanın sorunu

  • assert için kabaca üç seçenek vardır
    • Onu çalışma zamanı kontrolü olarak tutup başarısız olduğunda sürecin panic ile çökmesine izin vermek
    • assert’i performans optimizasyonu için kullanıp assert yanlışsa programın yanlış çalışmasını göze almak
    • assert’i tamamen devre dışı bırakmak
  • std.debug.assert, assert’i tamamen devre dışı bırakmayı varsayılan olarak desteklemez
    • Derleme anındaki bayrağı içeride kontrol eden özel bir assert uygulanırsa C/C++ tarzına daha yakın bir davranış üretilebilir
  • assert’i kapatma isteği genelde iki nedenin birleşiminden doğar
    • Performans maliyetini veya uygulamanın çökmesini istememek nedeniyle çalışma zamanı kontrolünü sürdürmek istenmez
    • assert’in her zaman doğru olduğuna güvenmek zor olduğu için, optimizasyonda kullanıldığında ortaya çıkabilecek yanlış çalışmadan korkulur
  • matklad’ın ilgili tartışmada hatırlattığı gibi, çöküşten kaçınmak için meşru mühendislik gerekçelerinin bulunduğu durumlar vardır
    • Ancak genel yazılım dünyasında çöküşten kaçınmayı varsayılan yapmak kötü bir tercih olarak değerlendirilir
  • assert devre dışı bırakılırsa, imkânsız kabul edilen bir koşul gerçekten oluştuğunda bile program çalışmayı sürdürür
    • Program yanlış bir varsayım altında çalışmaya devam eder; bu da unchecked illegal behavior olmasa bile bir yanlış çalışma biçimidir
  • unchecked illegal behavior veya C’deki undefined behavior tehlikelidir çünkü programı bir weird machine haline getiren bir yol olabilir
    • Yeterince karmaşık yazılımlarda UIB olmasa bile program istenmeyen şekillerde bükülebilir
    • Çalışma anında assert’in false olması, tanımın dışına çıkmak demektir ve bu tek başına istenmeyen işler yaptırabilir
    • SQL injection, UIB olmadan da weird-machine düzeyinde yanlış çalışmaya yol açabilen somut ve yaygın bir örnektir
  • Programın yanlış çalışma maliyeti çok yüksekse, assert’i açık bırakmak daha doğrudur
    • Performans çok kritikse ve yanlış çalışma riski göze alınabiliyorsa, assert’i optimizasyon fırsatı olarak kullanmak daha uygundur
    • assert’i devre dışı bırakmak, hem performans kazancını kaçırmaya hem de gerçekte olduğundan daha güvenli sanmaya yol açabilir

Hatalı assert’in kod tabanını kandırma biçimi

  • Asıl risk, yanlış assert’in testlerde ortaya çıkmayıp yalnızca prodüksiyonda başarısız olabilmesidir
    • Eğer tüm assert’lerin her zaman doğru olduğu garanti edilebilseydi, assert’i optimizasyon için kullanmak tartışmalı olmazdı
    • Testlerin tüm yanlış assert’leri yakalayacağı garanti edilebilseydi, prodüksiyon optimizasyonu da güvenli olurdu
    • Gerçekte yanlış assert yazılabilir ve testlerin bunu mutlaka yakalaması da garanti değildir
  • Prodüksiyonda assert’i kapatmak, yanlış assert’i olabildiğince erken fark etme fırsatını ortadan kaldırır
    • Daha ciddi sorun ise, sonrasında kodun bu yanlış assert’e dayanarak yazılmaya devam etmesidir
  • Örnek kodda processThing fonksiyonunun yalnızca zaten başlatılmış bir thing üzerinde çağrılması gerektiği varsayımı assert ile belirtilir
    fn processThing(thing: Thing) void {
       // this function must always be invoked on
       // a thing that has already been started
       assert(thing.is_started);
    
       // ...
    }
    
  • Bu assert testlerde başarısız olmazken prodüksiyonda devre dışı olduğundan gerçekte false olabileceği gözden kaçabilir
    • Kullanıcının görebildiği bir yanlış davranış yoksa, sorun yokmuş gibi görünüp geliştirme devam edebilir
  • Sonrasında biri, thing zaten başlatılmış olduğu için ek hazırlık olmadan baz çağrılabileceğini varsayarak koda ekleme yapabilir
    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);
    }
    
  • İkinci assert kendi başına mantıksal olarak doğru olsa bile, ilk assert gerçekte false olabiliyorsa tehlike doğar
    • Testlerde ilk assert başarısız olmadığı için ikinci assert de başarısız olmaz
    • Prodüksiyonda assert devre dışı olduğundan, güvenlik açığının kod tabanına girdiği an fark edilmeyebilir
  • Koddaki assert’ler geliştiriciyi yanıltır hale geldiyse, doğru kod yazmak mantıksız derecede zorlaşır

Seçenekler programın önceliklerine göre değişir

  • Her programın öncelikleri farklıdır; bazı programlarda yanlış çalışma riskini en aza indirmektense performansa öncelik vermek meşru olabilir
    • Bu durumda assert’i optimizasyon fırsatına dönüştürmek doğal bir seçimdir
  • Prodüksiyonda assert’i alışkanlıkla devre dışı bırakmak; assert’i açık bırakmaktan da, performans optimizasyonlarını bilinçli şekilde kullanmaktan da daha kötü bir tercih olarak değerlendirilir
    • ReleaseFast’e karşı çok eleştirel olup assert devre dışı bırakmayı sorgusuz kabul etmek çelişkilidir
  • Zine bir statik site üreticisidir ve şu anda ağırlıklı olarak kişisel blog derlemeleri için kullanılır
    • Tehdit modeli tanımlı değildir ve bu en yüksek öncelik de değildir
    • Hugo’dan bir mertebe daha hızlı çalışmasını tercih ettiği için ReleaseFast derlemeleri dağıtır
  • Awebo pre-alpha aşamasında, kendi kendine barındırılabilen bir Discord alternatifidir
    • Kişisel veriler işleyeceği ve internete açık bir yazılım olacağı şimdiden açıktır
    • Dağıtım zamanı geldiğinde ReleaseSafe derlemeleri sunmayı planlamaktadır
    • Ancak FFmpeg, Xiph Opus ve SQLite gibi bazı çekirdek bağımlılıkları ReleaseFast ile derlemeyi planlamaktadır
    • Çünkü bu bağımlılıklarda performans artışının, programın yanlış çalışma riskini daha da azaltmaktan açıkça daha önemli olduğu düşünülmektedir

Gerçek projelerin tercihleri ve güvenlik örnekleri

Zig’de tamamen kaybolmayan örtük assert’ler

  • Özel assert’ler devre dışı bırakılabilse bile, Zig dilinin kod içine örtük biçimde eklediği assert’ler devre dışı bırakılamaz
    • Tamsayı taşması, sıfıra bölme ve dizi sınırının aşılması buna dahildir
    • Bu koşullar ya çalışma anında panic üretir ya da optimizasyon amacıyla kullanılır
  • Prodüksiyonda assert kapatma pratiği, hatalı assert’lerin kod tabanı içinde çürüyüp çoğalmasına yol açabilir
    • Bunun sonucu olarak UIB’ye karşı paranoya artabilir ve geliştiriciler assert’i yeniden açıp sonucu görmeye bilinçsizce korkar hale gelebilir
  • Kaçınılmaz sonuç, assert’i kapatarak üstünü örtmek değil, hatalı assert’leri düzeltmek gerektiğidir
    • Program doğruluğu bir alt küme için değil, bütün olarak hedeflenmelidir

1 yorum

 
GN⁺ 1 시간 전
Lobste.rs görüşleri
  • assert içinde doğrudan çökme yaratmanın ya da Rust’taki panic gibi yalnızca işi çökertmenin genelde en iyi seçenek olduğuna katılıyorum. Ama asserti optimizasyon ipucu olarak kullanmanın, onu basitçe kaldırmaktan her zaman daha iyi olduğuna katılmak zor.
    Birincisi, rastgele bir assert çoğu zaman optimizasyona pek yardımcı olmaz ve optimizasyon aracının hemen kullanamayacağı birçok koşul vardır. “Bu dala asla girilmez” gibi doğrudan bir varsayım koymak yerine kodun her tarafına rastgele varsayımlar serpiştirmenin getireceği performans kazancı muhtemelen büyük olmayacaktır.
    İkincisi, asserti varsayıma dönüştürmek bir hatanın etki alanını ciddi biçimde büyütür. Örneğin proje ya da kullanıcı bazında ayrılmış verileri işleyen bir sistemde, aslında imkânsız olması gereken bir durumu yakalayan assertin bir hesaplama fonksiyonunun ortasında bulunduğunu düşünün. Release derlemesinde maliyeti yüksek olduğu için kapatıldığında, basit devre dışı bırakma durumunda etki tek bir proje ya da kullanıcıyla sınırlı kalabilir ve sonraki kontrollerde yakalanabilir. Buna karşılık bunu tanımsız davranışa çevirirseniz hesaplama alakasız bir koda sıçrayabilir, belleği rastgele bozabilir ve tüm projelerin verilerini bozabilir.
    Sonuç olarak release derlemesinde varsayılan olarak güvensiz assert seçmek, bir sorun çıktığında hasarı yerelleştirme ihtimalini azaltma pahasına kodun rastgele noktalarını aceleyle optimize etmek anlamına gelir. Bana göre Rust bu konuda iyi tasarlanmış: assert!() her zaman panic üretir, debug_assert!() yalnızca debug modunda panic üretir, assert_unchecked() ise debug’da panic üretip release’te optimizasyon ipucu olur.

    • Hataların etki alanı endişe veriyorsa ReleaseFast yerine ReleaseSafe kullanılmalı.
    • Tek tek assertleri kapatmaya karşı değilim; karşı olduğum şey, genel tavsiye gibi hepsini topluca kapatmak.
      Performans etkisi çok büyük olduğu için release derlemesinde tutulamayacağı sonucuna varmak tamamen makul. Üstelik hesaplama maliyeti yüksek assertler, daha önce söylendiği gibi, performans iyileşmesine yol açma ihtimali en düşük olanlardır.
      Zine’da buna birkaç örnek de var:
      https://github.com/kristoff-it/zine/…
      https://github.com/kristoff-it/zine/…
      Zig’de “varsayılan release modu” yok. assertlerin nasıl ele alınacağını her zaman kendiniz seçmeniz gerekir ve tümüne uygulanacak seçenek ya çökme ya da optimizasyondur; ikisinden biri daha varsayılan sayılmaz.
  • Ghostty’de şu ana kadar açıklanmış görece ciddi iki CVE’nin de bellek bozulması olmadan keyfi komut çalıştırmaya yol açmış olması bana çok tuhaf geliyor. Bunun ReleaseFast ile dağıtılmış olmasına rağmen böyle olması, dünyanın işleyişine dair anlayışıma bütünüyle ters düşüyor.

    • Bence o kadar da tuhaf değil. Ciddi güvenlik açıklarının %70’inin bellekle ilgili olduğuna dair rapora inansak bile bu C ve C++ için geçerli; Zig bellek güvenliği konusunda biraz daha iyi olabilir. Ayrıca örneklem büyüklüğü 2 ise, kabaca her 10 projeden birinde böyle bir sonuç görmek o kadar da şaşırtıcı olmaz.
      Terminal emülatörleriyle çalışmış biri olarak, bunlar tam da bekleyeceğiniz türden can sıkıcı açıklar. Geliştiricileri ya da araştırmacıları küçümsemek istemem ama böyle beklenmedik bir yerde ortaya çıkan komut enjeksiyonu, bu alanda neredeyse işin doğasında var; başka alanlarda başka enjeksiyon açıklarının peşinizden gelmesine benziyor.
  • Prodüksiyonda “performans yüzünden” assertleri ve sınır kontrollerini kapatma çağrısını neredeyse 40 yıldır duyuyor olmamız komik. Bu sürede bilgisayarlar birkaç büyüklük mertebesi hızlandı ve yazılım herkesin hayatına çok daha derinden girdi; dolayısıyla çalışma zamanındaki doğruluk hiç olmadığı kadar önemli hale geldi.
    Daha verimli bir konuşma için, eski Microsoft’ta sıradan assert, check vb. dışında başka yerlerde pek görmediğim bir şey vardı: raporlama amaçlı assert. Tam olarak kontrol etmediğim bir koşul var, doğru olduğunu varsayıyorum ama yanlış olursa savunmacı biçimde ele alıyorum ve bunun sahada gerçekten yanlış olup olmadığını loglar ya da telemetriyle bilmek istiyorum. Örneğin bir kullanıcının belli bir listeye 1000’den fazla öğe koymayacağını varsayıp ikinci dereceden bir algoritma kullanmak ya da ağ gecikmesinin 200 ms’nin altında olacağını varsayıp çok sayıda gidiş-dönüş gerektiren bir protokol kullanmak gibi.

    • Bunun checkten farkı ne?
  • Burada bağlantısı verilen kişilerden biri olarak, bunun assert hakkındaki görüşlerimi gülünç bir sahte ikileme ve karikatüre dönüştürdüğünü düşünüyorum. Başka bir yorumda da yazdığım gibi, tanımsız davranışa dönüştürülüp dönüştürülmeyeceğine assert bazında karar verilmesini tercih ediyorum. ReleaseFaste yönelik eleştirim, bu seçimi belirli bir kapsam içindeki tüm assertlerle ve hatta tüm güvenlik kontrolleriyle paket halinde bağlaması.
    Düzeltilmemiş assertin çökme yaratıyor diye kapatılmasının aptalca olduğu yönündeki kristoff yorumuna katılıyorum. Ancak “çökme ya da tanımsız davranış”ın tek makul alternatifler olduğu fikrine katılmıyorum. Kardeş yorumdaki goldstein’ın görüşü benimkine daha yakın.

  • assert_unchecked() davranışını küresel varsayılan yapmak savunulması zor bir şey, ama performans optimizasyon tekniği olarak makul olabilir. Tüm assertler varsayıma çevrildiğinde prodüksiyon derlemesi ciddi ölçüde hızlanıyorsa, performans artışının çoğunu üreten az sayıdaki varsayım, umarız tek bir varsayım olabilir ve bunu ikili arama benzeri yöntemlerle bulabilirsiniz.

    • Varsayılan yok; kullanıcı açıkça ReleaseSafe ile ReleaseFast/ReleaseSmall seçeneklerinden birini seçiyor.
  • Program analizi literatüründe, koddaki iddiaları ya da assertleri iki biçime ayıran bir ikilik vardır. Biri kodun çevresindeki bağlamla ilgilidir; bir fonksiyon için çağıranın sağlaması gereken koşuldur. Diğeri ise kodun kendisiyle ilgilidir; yine bir fonksiyon için fonksiyonun sağlaması gereken koşuldur.
    Bu ayrım, sözleşmeler ve kademeli tipler literatüründeki standart akademik kavram olan “sorumluluk (blame)” açısından bakıldığında netleşir. Bağlama ilişkin bir iddia başarısız olursa bu bizim hatamız değildir; sorumluluk bağlama ya da çağırana aittir, ancak çağıran doğru olup iddianın kendisi hatalı da olabilir. Kodun kendisine ilişkin bir iddia başarısız olursa sorumluluk bizdedir, ancak kod doğru olup iddianın kendisi hatalı da olabilir.
    Fonksiyon düzeyinde önkoşul, bağlama ilişkin bir iddiadır; sonkoşul ise kodun kendisine ilişkin bir iddiadır. Yine de ikisi de kodun ortasında yer alabilir. Bazı doğrulama çerçeveleri kodla ilgili iddialar için assert, bağlamla ilgili iddialar için assume kullanır. Bu, bazı test çerçevelerinin, özellikle de rastgele test çerçevelerinin bunları yorumlama biçimiyle de bağlantılıdır. assert başarısız olursa test başarısız olarak işaretlenir, assume başarısız olursa test atlanır.

    • BIND9, çağıranın sağlaması gereken önkoşulları REQUIRE() makrosuyla, fonksiyonun garanti ettiği sonkoşulları ise ENSURE() ile denetleyen, sözleşmeyle tasarım yaklaşımına yakın bir tarz izliyor. Ara denetimler için INSIST(), döngüler ya da veri yapıları için de INVARIANT() var. Fonksiyon belgelerinde önkoşul ve sonkoşullara karşılık gelen “requires” ve “ensures” notları bulunmalı.
  • Bu sanki Bun’a gönderme yapıyor gibi, o yüzden bağlantıyı biraz daha resmileştirmek istiyorum. Bun’ın geliştiricisi Jarred Sumner’ın 2024’te açtığı, unreachableın ReleaseFast modunda panic üretmesi gerektiğini öneren bir Zig meselesi var. O başlıktaki Andrew Kelley ve Matthew Lugg yorumları bu tartışmayla ilgili.
    => https://github.com/ziglang/zig/issues/19664
    Bun kendi assert fonksiyonlarını kullanıyor; bunlar release modunda panic üretir ya da kaldırılır, ama tanımsız davranış üretmez. Yine de Loris’in dipnotunu da akılda tutmak gerekir: “Zig, bir dil olarak, devre dışı bırakılamayan çok sayıda asserti koda örtük biçimde ekler.”
    Bun konusunu fazla uzatmak istemiyorum; sonuçta küçük bir ekibin tek projesi. Esas nokta şu: En ufak bir endişeniz varsa ReleaseSafe kullanın. ReleaseSafe’in yavaş olduğuna dair bir ünü var, ama benim küçük Zig projelerimde ReleaseSafe ile ReleaseFast arasında ölçülebilir bir benchmark farkı göremedim. Buna rağmen hâlâ birçok başka dilden daha hızlı olması muhtemel.

    • En ufak bir endişeniz varsa ReleaseSafe kullanmak doğru. Daha ilginç stratejiler de mümkün. Kodu değiştirirken, yani hata ekleme ihtimaliniz varken, ReleaseSafe kullanıp; kod oturup gerçek dünyada sınandıktan sonra performans artışı anlamlıysa ReleaseFast’e geçebilirsiniz.
      Ya da bağlama uygunsa, ReleaseFast derlemesini dağıttıktan sonra tanımsız davranış yüzünden belirlenimsiz hata raporları gelmeye başlarsa ReleaseSafe’e geri dönebilirsiniz. Böylece hangi assertin başarısız olduğunu ve sınır dışı erişim ya da taşma gibi durumları içeren, işe yarar hata raporları toplayıp kodu düzeltebilirsiniz. Hatta baştan ReleaseFast dağıtılmaması gereken bir bağlamda öyle bir karar vermiş olsanız bile bu yaklaşımı yine de öneririm :^)
      Bağımlılıkları ayarlayıp @setRuntimeSafety kullanarak aynı yaklaşımı projenin sadece bazı bölümlerine de uygulayabilirsiniz. Sonuçta akıllıca davranma niyetiniz varsa ihtiyaç duyduğunuz araçların hepsi mevcut.
  • assert çağrılarının içine yan etkili ifadeler koymanın kabul edilebilir olduğu şekilde yazmamak gerekir. Bu kötü bir pratiktir. Hata denetimi için assert kullanmaktan da kaçınılmalıdır. Adil olmak gerekirse, yazarın bunu savunduğu pek söylenemez.
    Tersine, assert karmaşık bir hesaba bağlıysa unchecked modda bile o hesabın kesin olarak ortadan kaldırılmayabileceği, bu yüzden comptime if ile korunması gerektiği de anlatılıyor.
    Umarım yazar, “makroların bıraktığı travmayı geride bırakıp sadeliği kabullenmek için iyi bir fırsat” sözündeki ironiyi gözden kaçırmamıştır. Çünkü bunun anlamı, “programın build modunu hesaba katıp her yere savunmacı comptime ifler serpiştirme sadeliğini” kabul etmek oluyor.

    • Neden kötü bir pratik?
  • C# ile biraz sayısal hesap kodu yazıyorum ve release’te kapatılan çok sayıda assert kullanıyorum. Bunlar sıkı döngülerin her iterasyonunda çalıştırmak için fazla pahalı, ama birim testlerinde rutinin ilk kez NaN girişi gördüğü anda hemen patlaması faydalı oluyor.
    Bu tür NaN’ler çoğu zaman kullanıcı girdisinden değil, örneğin optimize edicinin gitmemesi gereken bir yere gitmesi gibi kod hatalarından kaynaklanıyor ve daha iyi sınır kısıtları gerektiriyor. Elbette kullanıcı girdisinin temizlenmesi gerekebilir, ama bu algoritmanın derinlerinde değil, en dış sınırda yapılmalıdır. Kullanıcı girdisi temizlemesinin sonucunda algoritma içi değişmezleri assert olmadan kanıtlayabilen bir ispat sistemi güzel olurdu, ama bu bir yan proje ve bozulursa kimse ölmeyecek.

  • assert hakkındaki fikir ayrılıklarının %90’ı, bu kelimenin tanımının zayıf ve çok anlamlı olmasından kaynaklanıyor; bu da düşünmeyi ve iletişimi bulanıklaştırıyor. Bu yüzden kavramı şu üç ad altında ayırıp katı biçimde kullanmak gerekir.
    assert(bool) ya da Rust’taysa assert_unchecked(), programcının bunun her zaman doğru olduğuna inandığı ve derleyicinin de bunu her zaman doğru varsayarak optimizasyonda kullandığı şeydir. Eski dillerdeki denetim yapan assert çağrışımından kaçınmak için buna assume() demek daha iyi olabilir.
    check(bool), koşul yanlışsa panic üretir, doğruysa devam eder; her zaman böyle davranır.
    debug_check(bool), debug modunda check() ile aynıdır; release modunda ise her zaman devam eder. Pratikte bu, debug modunda varsayılan olarak açık olan --debug_checks bayrağıyla kontrol edilir.
    Buna ek olarak, assert()i check()e dönüştüren bir --check_asserts derleyici bayrağına da ihtiyaç var. Bu, kendi assertlerinden şüphe duyup doğrulamak istediğinizde kullanılır ve debug modunda varsayılan olarak açıktır. “Assert” derken neyi kastettiğiniz son derece açık değilse olgun bir tartışma yapmak mümkün değildir; sadece laf israfı olur.