Swift, 2.0 sürümünde yapılandırılmış hata yönetimini tanıttı ve Objective-C'nin hata işaretçi kalıplarını yerel throw ve catch semantiği ile değiştirdi. rethrows anahtar kelimesi, map veya filter gibi genel yüksek seviyeli fonksiyonların çağırıcıları, hata verme potansiyeli olmayan closure'lar geçirirken bile try kullanmaya zorladığı özel sürtüşmeyi çözmek için ortaya çıktı, bu da gereksiz hata yönetimi törenleri yarattı.
Sorun, fonksiyon etkisi polimorfizmi ve alt tiplenme etrafında döner. Swift'in tip sisteminde, hata vermeyen bir closure, asla hata vermediği için "hata verebilir" sözleşmesini yerine getirdiği için hata veren bir closure'ın alt türüdür. rethrows olmadan, bir hata veren closure kabul eden bir fonksiyon, ister istemez hata yaymak zorundadır; bu durum tüm çağrı noktalarının, gerçek argümanın davranışına bakılmaksızın hataları yönetmesini zorlar.
Çözüm, yalnızca closure parametresi hata verirse hataları propagat eden koşullu bir sözleşme oluşturan rethrows anotasyonudur. Swift derleyicisi, bu durumu derleme zamanında closure argümanlarının hata verme durumunu takip ederek uygular. Hata vermeyen bir closure geçirildiğinde, fonksiyon çağrı noktasında hata vermeyen olarak değerlendirilir; hata veren bir closure geçirildiğinde, fonksiyon hata verme etkisini miras alır.
Bir iOS uygulaması için kullanıcıların JSON ayrıştırma, görüntü boyutlandırma ve kriptografik hashleme gibi işlemleri zincirleme yapabilecekleri modüler bir veri dönüşüm hattı oluşturuyorduk. Temel pipeline fonksiyonu, (Data) throws -> Data olarak tanımlanan bir dizi dönüşümü kabul etti. Başlangıçta, pipeline üzerinde standart bir throws anotasyonu kullandık, bu da her çağrı noktasının basit dönüşümleri bile do-catch blokları ile sarmasını zorunlu kıldı, birçok işlemin hata verme durumu olmadan saf işlevler olmasıyla çelişiyordu.
İlk yaklaşım, tam fonksiyonu ikiye bölmekti: bir versiyon hata vermeyen dönüşümler için pipeline, diğeri ise hata verenler için pipelineThrowing adını aldı. Bu ayrım temiz çağrı noktalarına izin verdi ancak her hata düzeltmesi için iki konumu da düzenlemeyi gerektiren bir bakım kabusu yarattı ve her yeni yapılandırma seçeneği ile API yüzeyi iki katına çıktı. Ayrıca, kullanıcılar doğru yöntemi seçmek için uygulama detaylarını bilmek zorunda kaldı ve encapsulation ilkelerini ihlal etti.
İkinci yaklaşım, tek bir throws imzasını korudu ama try? kullanmayı teşvik etti, bu da uyarıları susturmak için hata bilgilerini etkili bir şekilde göz ardı etti ve gerçek hatalar oluştuğunda hata ayıklamayı imkansız hale getirdi. Bu, güvenlik garantilerini ihlal etti ve kodu kırılgan hale getirdi, çünkü geliştiriciler, hem güvenli hem de güvensiz işlemleri içeren karışık hatlarda gerçek hata durumlarını yönetmeyi unuttular.
Sonunda rethrows çözümünü benimsedik ve func pipeline(_ transforms: [(Data) throws -> Data]) rethrows -> Data şeklinde ilan ettik. Bu, derleyicinin yalnızca closure dizisinin hata veren işlemler içerdiğinde try zorunlu kılmasını sağlarken, saf hesaplamalar için doğrudan çağrılara izin verdi. Sonuç, 40% oranında bir boilerplate kod azalması, tekrar eden fonksiyon imzalarının ortadan kaldırılması ve API ergonomisinin iyileşmesi oldu; burada tip sistemi belirli kullanım durumlarının gerçek hata alanlarını doğru bir şekilde yansıtıyordu.
Neden Swift, bir rethrows fonksiyon gövdesi içinde doğrudan hata atmayı yasaklar, sadece closure parametresi üzerinden değil?
rethrows anahtar kelimesi, fonksiyonun yalnızca argümanları tarafından üretilen hataları propagat ettiğini belirten katı bir şeffaflık sözleşmesi oluşturur. Fonksiyon gövdesinde throw CustomError() denemesi yaparsanız, Swift derleyicisi bunu reddeder, çünkü bu koşulsuz bir atış temsil eder, "sadece closure hata verirse" garantisini ihlal eder. Fonksiyon, ya içsel olarak kendi hatalarını do-catch kullanarak ele almalıdır, ya da bunları dönüş değerlerine dönüştürmeli veya imzayı koşulsuz throws'a yükseltmelidir; böylece çağıranlar, fonksiyonun kendisinden yeni hata alanlarının oluşmadığını güvenle varsayabilir.
rethrows, birden fazla closure parametresi ile nasıl etkileşir ve etki yayılımı üzerindeki sonuçları nelerdir?
Bir fonksiyon, hata veren olarak işaretlenmiş birden fazla closure parametresine sahipse ve fonksiyon kendisi rethrows olarak işaretlenmişse, fonksiyon herhangi bir closure hata verirse hata verir, bu da etkilerin birleşimini yaratır. Swift'in derleyicisi, çağrı zinciri boyunca bu etkileri bireysel olarak takip eder, bu nedenle rethrows fonksiyonlarının birleştirilmesi koşullu doğayı korur, manuel müdahale olmaksızın. Ancak, closure'ları geçmeden önce dönüştürür veya sararsanız, sarmalayıcıda hata verme imzasını korumanız gerekir, yoksa derleyici argümanı hata vermeyen olarak değerlendirir ve dış fonksiyonun koşullu hata verme yeteneğini kaybettirir.
rethrows ile @autoclosure arasındaki ilişki nedir ve bu desen neden assertion API'lerinde ortaya çıkar?
@autoclosure ve rethrows kombinasyonu, gecikmeli değerlendirme ile koşullu hata yayılımını sağlar; burada autoclosure, ihtiyaç duyulana kadar değerlendirmeyi geciktirir ve fonksiyon yalnızca o geciktirilmiş değerlendirme hata verdiğinde hata verir. Bu desen, Swift'in assert ve precondition fonksiyonlarını çalıştırır ve hata veren ifadelerin assertion'lara iletilmesini sağlar, bu da assertion çağrısını try ile işaretlemeyi gerektirmez. Adaylar sıklıkla, autoclosure'ın () throws -> T olarak açıkça tanımlanması gerektiğini atlar, böylece rethrows sözleşmesine katılır ve bu mekanizmanın (tembel) değerlendirme zamanlamasını hata yayılımı semantiğinden (koşullu) ayırdığını, bu durumun, serbest derleme sürümlerinde devre dışı bırakılan assertion'larda performans odaklı kod yolları için kritik olduğunu vurgular.