Rust'ta WIP kodunu uyarı olarak bırakmak
(blog.dureuill.net)- 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
unwrapvecloneuyarı ü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 wipcrate'i,wip!,unwrap_wip,clone_wip,fixme!ile geçici implementasyonları, geçiciunwrap/clonekullanımını ve sonradan düzeltilecek notları her zaman uyarı olarak görünür kılar- Meilisearch, CI içinde
-D warningile uyarıları hataya çevirerek WIP kodunun sızmasını engelliyor; geliştirme sırasında eklenenwipbağı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,Resultdö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 laterniteliğ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 warningbayrağı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
-
unwrapile hata işlemeyi ertelemekResultveyaOptionüzerindeunwrapkullanmak, 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
unwrapkullanı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
-
cloneile 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
cloneederek 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
clonekullanı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
- Rust standart kütüphanesindeki
-
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
ResultveOption
- 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
- Rust derleyicisinin temel uyarıları da geliştirme sırasında tamamlanmamış durumları görünür kılmak için yararlıdır
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 gelirunwrapveclonehiç uyarı üretmez; bu yüzden son PR'dan önce yeniden okunup kaldırılmaları gerekirunwrap, ü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
wipcrate'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!makrosutodo!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ı
todoidi; ancak bağımlılıkstdmakrosunu shadow etmeye çalıştığında Rust bunu tercih etmediği için prelude kullanımında sorun çıktı
-
unwrap_wipveclone_wipunwrap_wipveclone_wip,ResultveOptioniçin extension trait olarak uygulanır- Scope içinde olduklarında normal
unwrap,clonegibi çalışırlar ama kullanım anında uyarı üretirler - Böylece son kodda
unwrapveyaclonebı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çmezTODOyorumu 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
wipcrate'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
wipbağı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
fixmeeklerken 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
wipbelgelerinde görülebilir
1 yorum
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 veriyorAraç zinciri geliştiricileri neyin
rustcuyarısı, neyin Clippy lint’i olacağını ayırt edegeldi;wipise mevcut işlevleri yeni bir adla yeniden yapıpcargo clippydeki lint’leri zorlacargo checkvecargo buildtarafı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)]nuncargo clippydışında da görünmesini sağlamak;unwrap_wipde#![warn(clippy::unwrap_used)]a benziyorBana 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_wipvefixme!konusunda emin değilim; geliştiricilerin bu tür vakaları nasıl ele almayı amaçladığını yanıtlayacak kadar üzerine düşünmüş değilimYine 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 geliyorClippy 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
-Deklemek 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 olarakrustcya da Clippy’de uyarı vermesi gerektiğini düşünüyorum; böyle olursawipin faydasının azalacağına katılıyorumunwrap_wipin#![warn(clippy::unwrap_used)]olduğu sözüne iki açıdan katılmıyorum. Birincisi, niyet açısındanunwrapü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; hepsiniexpecte ç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 gerekirfixme!çok kullanışlı ve standart kütüphaneye vb. girmesini isterdim. Bu crate’te gerçekten en çok kullandığım özellik oYazıda bu ayrıntılara değinilmedi, ama bu crate standart değil, özelleşmiş bir kütüphane olduğu için
wip_itergibi özellikler de var. Yineleyicinin dönüş konumundakiimpl Traitfonksiyonlarındatodobenzeri makroları kullanmayı sağlıyor;wip_futureda aynı şekilde. Dönüş tipi çıkarımı iletodobirlikte ç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ğızYeni 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
unwrapile 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ğarHata tipi olarak
Box<dyn Error>, string,anyhowvb. kullanıpResultile 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 sonradanResultdöndürecek şekilde refactor etmek daha zordurAyrı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!()veunimplemented!()yorumunda belgelerle aynı fikirde değilim. Gerçi atlanan sonraki paragraf bu duruşu bir ölçüde dengeliyorBelgelerde
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ıyortodo!1.40.0’da eklendiğinde sırf daha kısa bir isimdi; adı dışındaunimplemented!tan farkı yoktuAncak 1.42.0’da
unimplementedmesajından “yet” çıkarıldı. Hatırladığım kadarıyla iki makroya bu anlamların verilmesi de biraz tartışmalıydı;todo!nununimplemented!ın kısa yazımı olarak yayımlanması da öyleydiGü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çimdeGeliştirme sırasında workspace’teki crate’e
wipbağı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 ediyorumPR’ı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 geliyorwip::fixme!gibi panik üretmeyen not alma makrosu gerçekten iyi bir fikir. Doc comment hilesi de akılda tutulacak temiz bir yöntemrustctarafında değişikliklere yol açsa iyi olacak ilginç fikirler var.todo!()gerçekten varsayılan uyarı vermeliGeliş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 hafifletebilirAyrı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 olurGereken 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 serbestYeni 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