I veya bu cevap diğer dillerden programcılar Pythonistas gazabı (Ben çok Python kullanmayın olarak bilmiyorum) çağırabilir, ancak bence en fonksiyonları gerektiğini değil bir var catch
blok, ideal olarak konuşan. Nedenini göstermek için, bunu 80'lerin sonunda ve 90'ların başında Turbo C ile çalışırken yapmak zorunda olduğum türdeki manuel hata kodu yayılımı ile karşılaştırmama izin verin.
Öyleyse, bir görüntüyü yüklemek üzere bir işleve sahip olduğumuzu ve kullanıcının yüklenecek görüntü dosyasını seçen bir kullanıcıya yanıt olarak böyle bir şey olduğunu varsayalım ve bu, C ve montaj ile yazılmıştır:
Bazı düşük seviyeli işlevleri atladım, ancak hata işleme konusunda ne gibi sorumluluklara sahip olduklarına bağlı olarak, renk kodlu farklı işlev kategorileri tanımladığımı görebiliyoruz.
Arıza ve Kurtarma Noktası
Artık fonksiyon kategorilerini yazmak hiç zor olmadı "olası başarısızlık noktaları" ( throw
yani olanlar ) ve "hata kurtarma ve raporlama" fonksiyonları ( catch
yani bunlar ).
Bu işlevler, istisna işlemeye başlamadan önce doğru yazmak için her zaman önemsizdi, çünkü harici bir arızaya neden olan, bellek ayırmamak gibi bir işlev, yalnızca bir NULL
ya 0
da -1
ya da ya da genel bir hata kodu ya da bunun için bir şey ayarlayabilir. Ve hata kurtarma / raporlama her zaman kolaydı, çünkü çağrı yığınının aşağısında, arızaları kurtarmanın ve rapor etmenin mantıklı olduğu bir noktaya geldiğinizde, sadece hata kodunu ve / veya mesajı alıp kullanıcıya rapor edin. Ve doğal olarak, bu hiyerarşinin yaprağında, gelecekte nasıl değiştiğine bakılmaksızın asla başarısız olamayacak bir işlev ( Convert Pixel
), doğru bir şekilde (en azından hata işleme ile ilgili olarak) doğru şekilde yazmak için ölüdür.
Hata Yayılımı
Ancak, insan hata eğilimli sıkıcı fonksiyonları vardı hata prapagatör doğrudan başarısızlık ama bir yerde daha derin hiyerarşi içinde başarısız olabilir çağrılan fonksiyonlar girmek yoktu olanlar. Bu noktada, Allocate Scanline
bir başarısızlık işlemek gerekebilir malloc
ve daha sonra aşağı bir hata döndürür Convert Scanlines
, daha sonra Convert Scanlines
bu hata için kontrol edin ve onu aşağı geçmek zorunda Decompress Image
ardından, Decompress Image->Parse Image
ve, Parse Image->Load Image
ve Load Image
hata nihayet bildirilen kullanıcı tarafında bir komutuna .
Bu, birçok insanın hata yaptığı yerdir, çünkü hataların tam anlamıyla ele alınması söz konusu olduğunda, fonksiyonların tüm hiyerarşisi için hatayı kontrol etmek ve yok etmek sadece bir hata üreticisini alır.
Dahası, eğer hata kodları fonksiyonlar tarafından döndürülürse, kod tabanımızın% 90'ını başarı ile ilgilenen değerleri döndürme kabiliyetini kaybederiz, çünkü birçok fonksiyonun bir hata kodu döndürmek için geri dönüş değerlerini ayırması gerekir . başarısızlık .
İnsan Hatasını Azaltma: Global Hata Kodları
Peki insan hatası olasılığını nasıl azaltabiliriz? Burada bazı C programcılarının öfkesini bile çağırabilirim, ancak bence derhal bir gelişme OpenGL gibi küresel hata kodlarını kullanmak glGetError
. Bu, en azından başarıya anlamlı bir ilgi değeri döndürme işlevlerini serbest bırakır. Hata kodunun bir iş parçacığına yerelleştirildiği durumlarda bu iş parçacığı güvenli ve verimli hale getirmek için yollar vardır.
Bir işlevin bir hatayla karşılaşabileceği bazı durumlar da vardır, ancak önceki bir hatanın keşfi sonucunda erken dönmeden önce biraz daha uzun süre çalışmaya devam etmesi göreceli olarak zararsızdır. Bu, her bir işlevde yapılan işlev çağrılarının% 90'ına karşı hataları kontrol etmek zorunda kalmadan böyle bir şeyin gerçekleşmesini sağlar, böylece bu kadar titiz olmadan yine de doğru hata işlemesine izin verebilir.
İnsan Hatasını Azaltma: İstisna İşleme
Bununla birlikte, yukarıdaki çözüm, manüel if error happened, return error
kod yayılımının kontrol akış yönü ile uğraşmak için hala çok sayıda fonksiyon gerektirir, manüel kod türü satır sayısını azaltmış olsa bile . Her zaman bir hatayı kontrol eden ve hemen hemen her bir hata yayma fonksiyonuna geri dönen en az bir yer olması gerektiğinden, bu durumu tamamen ortadan kaldıramazdı. Bu, istisnasızlık, günü kurtarmak için resmin üzerine geldiğinde (sorta).
Ancak buradaki istisnaların ele alınmasının değeri, manuel hata yayılımının kontrol akış yönü ile başa çıkma ihtiyacını ortadan kaldırmaktır. Bu, değerinin catch
kod tabanınız boyunca bir bot dolusu blok yazmak zorunda kalmamaya bağlı olduğu anlamına gelir . Yukarıdaki diyagramda, bir catch
bloğa sahip olması gereken tek yer Load Image User Command
, hatanın rapor edildiği yerdir. Başka hiçbir şey ideal olarak bir catch
şey yapmak zorunda değildir, çünkü aksi halde hata kodu işleme kadar sıkıcı ve hataya açık hale gelmeye başlar.
Öyleyse, bana sorarsanız, istisnai durumdan zarif bir şekilde faydalanmayı gerçekten sağlayan bir kod temeli varsa, minimumcatch
blok sayısına sahip olmalıdır (en azından sıfırı kastetmiyorum, fakat her benzersiz yüksek değer için bir tane gibi). başarısız olabilecek son kullanıcı işlemi ve tüm üst düzey kullanıcı işlemlerinin merkezi bir komut sistemi aracılığıyla başlatılması durumunda muhtemelen daha da az).
Kaynak Temizleme
Bununla birlikte, istisnaların ele alınması yalnızca, normal uygulama akışlarından ayrı istisnai yollarda hata yayılımının kontrol akışı yönleriyle manuel olarak uğraşmaktan kaçınma ihtiyacını çözer. Genelde hata yayıcısı olarak işlev gören bir işlev, bunu şimdi otomatik olarak EH ile yapsa bile, imha etmesi gereken bazı kaynakları edinmeye devam edebilir. Örneğin, böyle bir işlev, ne olursa olsun, işlevden dönmeden önce kapatması gereken geçici bir dosyayı açabilir veya ne olursa olsun kilidini açmak için gereken bir muteksi kilitleyebilir.
Bunun için her türlü dilden birçok programcının gazabını çağırabilirim, ancak C ++ yaklaşımının ideal olduğunu düşünüyorum. Dil , bir nesnenin kapsam dışına çıktığı anda belirleyici bir biçimde çağrılan yıkıcıları tanıtır . Bu nedenle, bir mutex'i bir imha edici ile kapsamlaştırılmış bir mutex nesnesi üzerinden kilitleyen bir C ++ kodu, manuel olarak kilidini açmaya gerek duymaz, çünkü ne olursa olsun nesne kapsam dışına çıktığında otomatik olarak açılacaktır (bir istisna olsa bile) ) karşılaştı. Bu nedenle, yerel kaynak temizliği ile uğraşmak zorunda kalmadan iyi yazılmış C ++ kodlarına gerçekten gerek yok.
Yıkıcı olmayan dillerde, finally
yerel kaynakları el ile temizlemek için bir blok kullanmaları gerekebilir . Bununla birlikte , hilkat garibesi yerindeki bütün istisnalar dışında olmak kaydıyla , kodunuzu manuel hata yayma ile değiştirmek zorunda kalmaya devam ediyor catch
.
Dış Yan Etkileri Tersine Çevirme
Bu çözülmesi en güç kavramsal sorunu. Herhangi bir işlev, bir hata oluşturucu ya da başarısızlık noktası olsun, dış yan etkilere neden olursa, o zaman sistemi geri almak yerine, bir işlem yerine bir daha gerçekleşmemiş gibi bir duruma geri döndürmek için bu yan etkileri geri almak ya da "geri almak" gerekir. yarı geçerli "işlemin yarı yarıya başarılı olduğu durum". Değişmezlik ve kalıcı veri yapıları etrafında dönen işlevsel diller gibi, çoğu fonksiyonun ilk başta dış yan etkilere neden olma ihtiyacını azaltan diller dışında, bu kavramsal sorunu çok kolaylaştıran diller olmadığını biliyorum.
İşte finally
sık sık mantık bu tip belirli bir işleve çok özeldir ve "kaynak temizleme kavramına çok iyi eşleşmiyor çünkü tartışmasız orada değişebilirlik ve yan etkileri etrafında döner dilde sorununa en zarif çözümleri arasında yer alıyor ". Ayrıca finally
, bu durumlarda, bir catch
bloğun gerekip gerekmediğine bakılmaksızın, fonksiyonunuzun onu destekleyen dillerde yan etkileri tersine çevirdiğinden emin olmak için liberal bir şekilde kullanmanızı öneririm (ve yine, eğer bana sorarsanız, iyi yazılmış kodun catch
bloklar ve tüm catch
bloklar yukarıdaki diyagramdaki gibi en anlamlı olduğu yerlerde olmalıdır Load Image User Command
).
Hayal Dili
Bununla birlikte, IMO finally
yan etkilerin tersine çevrilmesi için idealdir ancak tam olarak değil. boolean
Erken çıkış durumunda (atılmış bir istisnadan veya başka şekilde) yan etkileri etkili bir şekilde geri almak için bir değişkeni tanımlamamız gerekir , şöyle ki:
bool finished = false;
try
{
// Cause external side effects.
...
// Indicate that all the external side effects were
// made successfully.
finished = true;
}
finally
{
// If the function prematurely exited before finishing
// causing all of its side effects, whether as a result of
// an early 'return' statement or an exception, undo the
// side effects.
if (!finished)
{
// Undo side effects.
...
}
}
Eğer bir dil tasarlayabilseydim, bu problemi çözme hayalimdeki yol yukarıdaki kodu otomatikleştirmek gibidir:
transaction
{
// Cause external side effects.
...
}
rollback
{
// This block is only executed if the above 'transaction'
// block didn't reach its end, either as a result of a premature
// 'return' or an exception.
// Undo side effects.
...
}
... yerel kaynakların temizlenmesini otomatikleştiren yıkıcılarla birlikte, sadece ihtiyacımız transaction
olan şeyi yapıyoruz ( rollback
ve catch
yine finally
de, kendilerini temizlemeyen C kaynaklarıyla çalışmak için hala eklemek isteyebilirim ). Bununla birlikte, finally
bir boolean
değişkenle şu ana kadar rüya dilimden yoksun bulduğum şeyi basitleştiren en yakın şey budur. Bunun için bulduğum en basit ikinci çözüm, C ++ ve D gibi dillerdeki kapsam koruyucularıdır , ancak “kaynak temizleme” ve “yan etkilerin tersine çevrilmesi” fikrini bulanıklaştırdığı için kapsam korumalarını her zaman biraz garip buldum. Bence bunlar farklı bir şekilde ele alınması gereken çok farklı fikirler.
Benim küçük bir dil hayalim de, değişmezlik ve kalıcı veri yapıları etrafında yoğun bir şekilde dönüp, gerekli olmasa da, büyük veri yapılarını bütünüyle derinlemesine kopyalamak zorunda kalmayan, verimli fonksiyonlar yazmasına neden oluyor. yan efektleri olmayan.
Sonuç
Her neyse, başım ağrıyorken, try/finally
Python'un yıkıcıların C ++ eşdeğeri olmadığını göz önüne alarak soketi kapatma kodunuzun gayet iyi ve harika olduğunu düşünüyorum ve şahsen bunu yan etkilerini tersine çevirmesi gereken yerler için liberal olarak kullanmanız gerektiğini düşünüyorum. ve catch
en anlamlı olduğu yerlere gitmek zorunda olduğunuz yer sayısını en aza indirin .