1 puan yazan GN⁺ 5 시간 전 | 1 yorum | WhatsApp'ta paylaş
  • Rust geliştirirken tüm hata yollarını ve sahiplik sorunlarını hemen çözmeye çalışmak, uygulama akışını kolayca bölebilir; bu yüzden önce happy path'i kurup kalan işleri uyarılar üzerinden takip etme stratejisi faydalıdır
  • Yalnızca standart Rust ile bile unwrap, clone, todo!, temel derleyici uyarıları ve /// dokümantasyon yorumu hilesi birleştirilerek tamamlanmamış kod işaretlenebilir
  • Mevcut yöntemlerde unwrap ve clone uyarı üretmez, todo! ise dolaylı uyarılara bağlıdır; /// hilesi de bulunduğu yere göre derleme hatalarına veya istenmeyen dokümantasyona yol açabilir
  • wip crate'i, wip!, unwrap_wip, clone_wip, fixme! ile geçici implementasyonları, geçici unwrap/clone kullanımını ve sonradan düzeltilecek notları her zaman uyarı olarak görünür kılar
  • Meilisearch, CI içinde -D warning ile uyarıları hataya çevirerek WIP kodunun sızmasını engelliyor; geliştirme sırasında eklenen wip bağımlılığı ise inceleme öncesi geçmiş temizliği sırasında kaldırılıyor

Rust geliştirmede neden uyarılar gerekir?

  • Rust, doğruluğa önem veren bir dildir; ancak geliştirme sırasında tüm hata yollarını anında ele almak, uygulama hızını ve odağı düşürebilir
  • rustc, Result döndürme, hata işleme veya uç durumları uygulama gibi nedenlerle derlemeyi durdurduğunda genelde iki seçenek ortaya çıkar
    • Tam hata işlemesini hemen uygulamak ve PR hazır olana kadar birkaç kez refaktör etmek
    • Sorunu uyarı seviyesine indiren, TODO later niteliğinde bir işaret bırakıp mevcut işe devam etmek
  • Uyarılar, geliştirmeyi engellemeden PR birleştirilmeden önce temizlenmesi gereken bir sinyal olarak kalır
  • Meilisearch, CI'da derleyiciyi -D warning bayrağıyla çalıştırarak tüm uyarıları hataya çeviriyor ve WIP kodunun yanlışlıkla dağıtıma çıkmasını önlüyor
  • Kod incelemesi, hataları önlemek ve ekip içinde bilgiyi paylaşmak için temel bir pratiktir; ancak yalnızca insanların hafızasına ve dikkatine güvenmek sonunda hatalara yol açabileceğinden ek koruma rayları gerekir

Standart Rust ile tamamlanmamış işleri erteleme yöntemleri

  • unwrap ile hata işlemeyi ertelemek

    • Result veya Option üzerinde unwrap kullanmak, hata yolunda panic üretme pahasına iç değeri çıkarıp mevcut implementasyona devam etmeyi sağlar
    • Kodun geri kalanı hazır olduktan sonra geçici olarak eklenen unwrap kullanımları seçilip kaldırılabilir ve uygun hata işleme ile değiştirilebilir
    • Bu yaklaşım, Rust doğru hata işleme için yapı sunsa da bunun hemen tamamlanmasının gerekmediği varsayımına dayanır
  • clone ile sahiplik sorunlarını ertelemek

    • Sahiplik sorunları, hata işlemeye göre daha seyrek görülür; ancak doğru sahipliği korumak tasarım değişikliği gerektirebilir
    • Böyle durumlarda değeri taşımak yerine clone ederek yaşam süresi sorunları geçici olarak atlatılabilir
    • Daha sonra uygun değişkenleri çağrı yığınının daha üst seviyelerine taşıyarak clone kullanımının kaldırılabileceği durumlar deneyimle ayırt edilebilir
    • Ancak bu düzeltmeler kod yapısında değişiklik gerektirebildiği için, hata işlemeye göre ertelemek daha zordur
  • todo!() ile yalnızca implementasyon yerini ayırmak

    • Rust standart kütüphanesindeki todo!() makrosu, tamamlanmamış kodu gösterir ve prototipleme sırasında tür analizinden geçmek için yer tutucu olarak kullanışlıdır
    • todo!() panic ile diverge ettiği için çoğu tür konumunda yer tutucu ifade olarak kullanılabilir
    • Arayüz şekillenirken fonksiyon tanımlarını kullanılabilir kılar, gerçek implementasyonu ise sonraya bırakır
    • todo!(), programın kullanıcılarına özelliğin eksik olduğunu bildirmekten çok, kod yazarı ve inceleyiciler için bir WIP kolaylığı gibidir
    • Kullanıcıya özelliğin henüz uygulanmadığını bildirmek için ayrıca unimplemented!() makrosu vardır
  • Temel derleyici uyarıları ve dokümantasyon yorumu hilesi

    • Rust derleyicisinin temel uyarıları da geliştirme sırasında tamamlanmamış durumları görünür kılmak için yararlıdır
      • Kullanılmayan bağlamalar
      • Gereksiz mut
      • Kullanılmayan Result ve Option
    • Geliştirme sırasında bu uyarıları _ öneki veya başka hilelerle geçici olarak bastırmak, tamamlanmamış kodun olduğu gibi geçmesine yol açabilir
    • Tür sistemiyle izlenmeyen uç durumlar veya bozuk mantık bazen yorumlarda bırakılır
    • Yorumu /// dokümantasyon yorumu gibi yazarsanız, Rust dokümante edilecek bir öğe bulunmayan dokümantasyon yorumları için uyarı verdiğinden, o konumdaki sorun bir uyarı olarak izlenebilir

Standart yaklaşımın sınırları

  • En büyük sorun, bu tekniklerin hiçbirinin çözülmesi gereken sorunla doğrudan bağlantılı uyarıları güvenilir biçimde üretmemesidir
  • todo!() sık sık uyarı tetikler, ancak bunlar çoğu zaman fonksiyon parametrelerinin kullanılmaması veya gerekli mutation'ın yapılmaması gibi dolaylı nedenlerden gelir
  • unwrap ve clone hiç uyarı üretmez; bu yüzden son PR'dan önce yeniden okunup kaldırılmaları gerekir
  • unwrap, üretim kodunda da meşru biçimde kalabilir; dolayısıyla geçici WIP kullanımı ile normal kullanım birbirine karışabilir
  • /// dokümantasyon yorumu hilesi daha çok bir hack gibidir ve ifade konumlarında dokümantasyon yorumu yasak olduğundan derleme hatasına yol açabilir
  • Gerçekten dokümantasyon üretilen bir yerde /// kullanılırsa beklenen uyarı çıkmayabilir ve dağıtılan kodda istenmeyen belgeler kalabilir

wip crate'inin sunduğu araçlar

  • wip crate'i, Rust geliştirirken “sonradan ele alınacak uyarı” stratejisini daha rahat kullanmak için hazırlanmış bir araç setidir
  • Normal bir crate gibi eklenebilir
    • cargo add wip
  • Gereken dosyalarda prelude içe aktarılabilir
    • use wip::prelude::*;
  • wip, Rust'ın deprecation warning mekanizmasına dayandığı için, editörün deprecated fonksiyon çağrılarını üstü çizili göstermesi kapatıldığında daha az rahatsız edici olabilir
  • wip::wip!

    • wip! makrosu todo! gibi çalışır, ancak kullanıldığında her zaman uyarı üretir
    • Tamamlanmamış kodu işaretlemek için todo! kullanılan yerlerde tercih edilebilir
    • İlk sürümlerde adı todo idi; ancak bağımlılık std makrosunu shadow etmeye çalıştığında Rust bunu tercih etmediği için prelude kullanımında sorun çıktı
  • unwrap_wip ve clone_wip

    • unwrap_wip ve clone_wip, Result ve Option için extension trait olarak uygulanır
    • Scope içinde olduklarında normal unwrap, clone gibi çalışırlar ama kullanım anında uyarı üretirler
    • Böylece son kodda unwrap veya clone bırakmanın amaçlanmadığı açıkça ifade edilebilir
  • wip::fixme!

    • fixme! makrosu, wip!in panic üretmeyen varyantıdır ve bir ifadenin yerine geçmez
    • TODO yorumu gibi kullanılabilir, ancak /// hilesi olmadan güvenilir biçimde uyarı üretir
    • Sıklıkla sonradan düzeltilmesi gereken notlar bırakmak için kullanılır

Kullanım akışı ve dikkat edilmesi gerekenler

  • wip crate'i yakın zamanda bazı PR'larda kullanıldı ve bunlardan biri de büyük değişiklik içeren Meilisearch PR #6484 oldu
  • Genel akış, geliştirme sırasında workspace içindeki crate'e wip bağımlılığı eklemek ve inceleme öncesinde geçmişi yeniden yazarken bunu kaldırmak şeklindedir
  • wip, happy path'e odaklanırken sonradan yapılacak işleri unutmamaya yardımcı olur
  • Bunun bazı dezavantajları da vardır
    • fixme eklerken yeterli bağlam yazılmazsa sonradan düzeltmek zorlaşabilir
    • Büyük PR'larda wipin ürettiği uyarı sayısı artıp yük oluşturabilir
  • Güncel API, docs.rs üzerindeki wip belgelerinde görülebilir

1 yorum

 
GN⁺ 5 시간 전
Lobste.rs yorumları
  • Açıkçası wipin gerekçesine bakınca, araçların aslında nasıl kullanılması gerektiğini anlamadığı için Clippy’yi yeniden icat etmiş izlenimi veriyor
    Araç zinciri geliştiricileri neyin rustc uyarısı, neyin Clippy lint’i olacağını ayırt edegeldi; wip ise mevcut işlevleri yeni bir adla yeniden yapıp cargo clippydeki lint’leri zorla cargo check ve cargo build tarafına aktarıyor gibi
    Örneğin wip! açıklaması, yeni bir bağımlılık ve standart dışı sözdizimi ekleme pahasına #![warn(clippy::todo)]nun cargo clippy dışında da görünmesini sağlamak; unwrap_wip de #![warn(clippy::unwrap_used)]a benziyor
    Bana göre .unwrap() prototipleme içindir, bu yüzden #![warn(clippy::unwrap_used)]ı açarım; .expect("description of invariant") ise .unwrap_wip() yerine .unwrap() kullanabileceğiniz üretim kodu içindir, bu yüzden #![warn(clippy::expect_used)]ı açmam. Örn: .expect("parse regex from const")
    clone_wip ve fixme! konusunda emin değilim; geliştiricilerin bu tür vakaları nasıl ele almayı amaçladığını yanıtlayacak kadar üzerine düşünmüş değilim

    • Dürüst bir söz olabilir ama pek de nazik bir ifade değil :-) Bu alanı 10 yıldır kullanıyorum; elbette her şeyi bildiğim anlamına gelmez ama iyi niyetli bir yorum yapılabilir diye düşünüyorum
      Yine de yazıda Clippy gibi mevcut linter’lara ilişkin tartışmanın eksik olduğu doğru. Bu biraz kişisel kullanım biçimimle de ilgili: eskiden kaydettiğimde Clippy çalıştırırdım, ama projemizde çok yavaş olduğundan kayıtlar arasında dakikalar aldığı için bıraktım; artık yalnızca CI’da ve push öncesi hook’ta çalıştırıyorum
      Uyarıları rustcye çekmek çok daha hızlı ve yineleme sırasında satır içi uyarıları görebildiğim için bu iş akışının özü haline geliyor
      Clippy yapılandırması da en azından son baktığımda oldukça zahmetliydi. Bir workspace’teki tüm crate’lerde lint’leri tek seferde kodla belirtmenin bir yolu yoktu; bu yüzden workspace’te reddetmek istediğimiz her lint için CI’a açık bir -D eklemek ya da tüm crate’lere sürekli global attribute koymak gerekiyordu
      #![warn(clippy::todo)] tüm crate’lerde gerektiğinden, geliştirme sırasında yalnızca bir crate ekleyip kaldırmaya kıyasla bizim için büyük bir avantaj değil. todo!nun varsayılan olarak rustc ya da Clippy’de uyarı vermesi gerektiğini düşünüyorum; böyle olursa wipin faydasının azalacağına katılıyorum
      unwrap_wipin #![warn(clippy::unwrap_used)] olduğu sözüne iki açıdan katılmıyorum. Birincisi, niyet açısından unwrap üretim dışı, expect üretim içi ayrımı yaygın olsa da evrensel değil. İkincisi, mevcut kod tabanında yaklaşık 4600 unwrap var; hepsini expecte çevirebilirim ama o zaman her yere geçici mesajlar koymak ya da “bu mutex poison edilmedi”, “bu işlem hatasını çağırana iletmek istiyorum” gibi önkoşulları yeniden kurmak için çok fazla zaman harcamam gerekir
      fixme! çok kullanışlı ve standart kütüphaneye vb. girmesini isterdim. Bu crate’te gerçekten en çok kullandığım özellik o
      Yazıda bu ayrıntılara değinilmedi, ama bu crate standart değil, özelleşmiş bir kütüphane olduğu için wip_iter gibi özellikler de var. Yineleyicinin dönüş konumundaki impl Trait fonksiyonlarında todo benzeri makroları kullanmayı sağlıyor; wip_future da aynı şekilde. Dönüş tipi çıkarımı ile todo birlikte çalışmadığı için zorla tip üretmek zorunda kaldığım epey durum oldu. Birileri yayın modunda varsayılan olarak derleme hatası vermeyi de önerdi; diğer iyileştirme fikirleriyle birlikte bakacağız
      Yeni bağımlılık ve standart dışı sözdizimi maliyetine biraz dengeli bakarsak, bu yalnızca geliştirme sırasında kullanılan ve sonunda genelde kaldırılan tek dosyalık bir bağımlılık
  • unwrap ile prototipleme yaparken sorun şu: daha sonra hataları düzgün biçimde eklemek istediğinizde dönüş tipinin şekli değişebilir; özellikle bu fonksiyonlar fonksiyonel tarzda kullanılıyorsa zincirleme etkiler doğar
    Hata tipi olarak Box<dyn Error>, string, anyhow vb. kullanıp Result ile başlamayı öneririm. Daha sonra gerçek bir hata enum’una geçmek mümkün, ama hiç başarısız olmayacakmış gibi görünen kodu sonradan Result döndürecek şekilde refactor etmek daha zordur
    Ayrıca LLM kullanıyorsanız dikkatli olun. LLM’ler örüntü eşleme makineleridir; “bilerek kaba taslak yaptım ve sonra düzeltmek istiyorum” ile “bu proje hataları böyle ele almalı”yı ayırt edemezler. Zamanla “şimdilik böyle” kararlarını çoğaltabilirler; bu yüzden main’de gevşek hata işlemenin nerede/ne zaman kabul edilebilir olduğuna dair bir iki yorum bırakmak yardımcı olur

  • todo!() ve unimplemented!() yorumunda belgelerle aynı fikirde değilim. Gerçi atlanan sonraki paragraf bu duruşu bir ölçüde dengeliyor
    Belgelerde todo!nun daha sonra uygulanacağı niyetini ilettiği ve mesajının “not yet implemented” olduğu, unimplemented!ın ise böyle bir vaat vermediği ve mesajının “not implemented” olduğu yazıyor
    todo! 1.40.0’da eklendiğinde sırf daha kısa bir isimdi; adı dışında unimplemented!tan farkı yoktu
    Ancak 1.42.0’da unimplemented mesajından “yet” çıkarıldı. Hatırladığım kadarıyla iki makroya bu anlamların verilmesi de biraz tartışmalıydı; todo!nun unimplemented!ın kısa yazımı olarak yayımlanması da öyleydi
    Günümüzde unimplemented!() teknik nedenlerle bir metod gövdesi sağlanması gereken ama asla çağrılmaması gereken trait implementasyonları gibi yerler için öneriliyor. Belgelerdeki örnek de tam olarak bu biçimde

  • Geliştirme sırasında workspace’teki crate’e wip bağımlılığını ekleyip inceleme öncesinde geçmişi yeniden yazarak kaldırma akışının ek araçlarla daha pürüzsüz hale getirilip getirilemeyeceğini merak ediyorum
    PR’ın ortasını düzeltirken lock dosyasını yeniden yazmak zorunda kalmak biraz çekindirici. Bu arada bağımlılık ağacı başka yerlerde de değiştiyse gereksiz çakışmalar çıkabilir. jj fixi kötüye kullanmak mümkün olabilir mi bilmiyorum. Her hâlükârda proje fikri çok iyi ve bir yerde kullanacakmışım gibi geliyor

  • wip::fixme! gibi panik üretmeyen not alma makrosu gerçekten iyi bir fikir. Doc comment hilesi de akılda tutulacak temiz bir yöntem

  • rustc tarafında değişikliklere yol açsa iyi olacak ilginç fikirler var. todo!() gerçekten varsayılan uyarı vermeli
    Geliştirici hack modundayken bu uyarıları kapatabilse daha da iyi olurdu. Çok fazla uyarı bunaltıcı oluyor; genellikle IDE’nin altındaki 19 satırlık konsolda cargo çalıştırıyoruz. Geliştirme sırasında uyarılar gerçek hataları ve daha önemli uyarıları gömüyor. Hataları her zaman en sona sıralama disiplini bile bu sorunu hafifletebilir
    Ayrıca .unwrap_wip() ve .clone_wip() adları özgün adlardan uzun olduğu için fazla uzun. “dev unwrap” ve “dev clone”un kısaltması olarak .du() ve .dc() gibi adlar öneririm. Hızlı programlamada kullanımı daha kolay olur

  • Gereken yerlere .clone() ve .unwrap()u otomatik olarak eklemek mümkün olamaz mı? Örneğin #[wip(autoclone,autounwrap)] gibi bir bayrak ya da özellikle. Muhtemelen olmaz ama hayal etmek serbest

  • Yeni bir projeye ya da prototipe her başladığımda main.rsye şu satırları koyarım:
    #![allow(dead_code)]
    #![allow(unused_variables)]
    #![allow(unreachable_code)]
    İlk aşamada çok fazla gürültüyü azaltıyor