Checked vs Unchecked vs No Exception… Aksine inançların en iyi uygulaması


10

Bir sistemin istisnaları düzgün bir şekilde iletmesi ve işlemesi için birçok gereksinim vardır. Bir dilin konsepti uygulamak için seçebileceği birçok seçenek de vardır.

İstisnalar için gereklilikler (belirli bir sırada değil):

  1. Belgeler : Bir dil, bir API'nın atabileceği istisnaları belgelemek için bir araç içermelidir. İdeal olarak bu dokümantasyon aracı, derleyicilerin ve IDE'lerin programcıya destek sağlamasına izin vermek için makinede kullanılabilir olmalıdır.

  2. İstisnai Durumlar İletin : Bu, bir işlevin, çağrılan işlevin beklenen eylemi gerçekleştirmesini engelleyen durumları iletmesine izin vermek için açıktır. Bence bu tür durumların üç büyük kategorisi var:

    2.1 Bazı verilerin geçersiz olmasına neden olan koddaki hatalar.

    2.2 Yapılandırma veya diğer harici kaynaklardaki sorunlar.

    2.3 Doğası gereği güvenilir olmayan kaynaklar (ağ, dosya sistemleri, veritabanları, son kullanıcılar vb.). Güvenilmez nitelikleri ara sıra başarısızlıklarını beklememizi gerektireceğinden, bunlar bir köşe olayıdır. Bu durumda bu durumlar istisnai kabul edilir mi?

  3. Kod ile başa çıkmak için yeterli bilgi sağlayın : İstisnalar, çağrıya tepki göstermesi ve durumu ele alması için yeterli bilgi sağlamalıdır. bilgiler de, bu istisnaların günlüğe kaydedildiğinde, rahatsız edici ifadeleri tanımlamak ve izole etmek ve bir çözüm sunmak için bir programcıya yeterli bağlam sağlaması için yeterli olmalıdır.

  4. Programcıya kodunun yürütme durumunun mevcut durumu hakkında güven sağlama : Bir yazılım sisteminin istisna işleme yetenekleri, programcının yolundan uzak dururken gerekli önlemleri sağlayacak kadar mevcut olmalıdır; el.

Bunları kapsamak için çeşitli dillerde çeşitli yöntemler uygulanmıştır:

  1. Kontrol Edilen İstisnalar İstisnaları belgelemek için harika bir yol sağlar ve teorik olarak doğru uygulandığında her şeyin iyi olduğuna dair geniş bir güvence sağlamalıdır. Bununla birlikte, maliyet, birçok kişinin ya istisnaları yutması ya da denetlenmeyen istisnalar olarak yeniden atması için daha verimli olduğunu düşünecek şekildedir. Uygun olmayan şekilde kontrol edilen istisnalar kullanıldığında, tüm kullanışlılığı hemen hemen kaybeder. Ayrıca, kontrol edilen istisnalar, zaman içinde kararlı olan bir API oluşturmayı zorlaştırır. Belirli bir etki alanı içindeki genel bir sistemin uygulamaları, yalnızca denetlenen istisnalar kullanılarak sürdürülmesi zor olacak istisnai durum yükünü getirecektir.

  2. Denetlenmeyen Özel Durumlar - işaretli özel durumdan çok daha çok yönlüdür ve belirli bir uygulamanın olası istisnai durumlarını düzgün bir şekilde belgeleyemezler. Hiç değilse geçici belgelere güvenirler. Bu, bir ortamın güvenilir olmayan doğasının güvenilirlik görünümü veren bir API tarafından maskelentiği durumlar yaratır. Ayrıca bu istisnalar atıldığında, soyutlama katmanları boyunca geri giderken anlamlarını kaybederler. Kötü bir şekilde belgelendirildiklerinden, bir programcı bunları özel olarak hedefleyemez ve genellikle ikincil sistemlerin, başarısız olmaları durumunda, tüm sistemi düşürmemelerini sağlamak için gerekenden çok daha geniş bir ağ kullanmaları gerekir. Bu da bize sağlanan yutma sorun kontrol istisnalar geri getiriyor.

  3. Çok aşamalı döndürme türleri Burada beklenen sonucu veya istisnayı temsil eden bir nesneyi döndürmek için ayrık bir kümeye, tuple veya benzer bir konsepte güvenmek gerekir. Burada yığın çözme yok, kod kesme yok, her şey normal çalışıyor, ancak devam etmeden önce hata için dönüş değeri doğrulanmalıdır. Bu konuda henüz çalışmadım, bu yüzden deneyimden yorum yapamıyorum, normal akışı atlayarak bazı sorunları istisnaları çözdüğünü kabul ediyorum, ancak yine de yorucu ve sürekli "yüzünüzde" olmak üzere kontrol edilen istisnalarla aynı sorunlardan muzdarip olacak.

Soru şu:

Bu konudaki deneyiminiz nedir ve size göre bir dile sahip olmak için iyi bir istisna yönetim sistemi yapmak için en iyi aday hangisidir?


EDIT: Bu soruyu yazdıktan birkaç dakika sonra bu yazı rastladım ürkütücü!


2
"yorucu ve sürekli yüzünüzde kontrol edilen istisnalarla aynı problemlerden muzdarip olacak": Gerçekten değil: uygun dil desteği ile sadece "başarı yolunu" programlamanız gerekir, temel dil makineleri yayılmaya özen gösterir hatalar.
Giorgio

"Bir dil, bir API'nin atabileceği istisnaları belgelemek için bir araç içermelidir." - weeeel. C ++ 'da bunun gerçekten işe yaramadığını öğrendik. Eğer tüm edebilir gerçekten faydalı bir do bir API atabilir edip devlete olan herhangi istisna. (Bu gerçekten uzun bir hikaye kısa kesmek, ama bence noexceptC ++ hikayesine bakmak C # ve Java da EH için çok iyi bilgiler verebilir.)
Martin Ba

Yanıtlar:


10

C ++ 'ın ilk günlerinde, bir çeşit jenerik programlama olmadan, güçlü bir şekilde yazılmış dillerin son derece hantal olduğunu keşfettik. Ayrıca, kontrol edilen istisnaların ve genel programlamanın birlikte iyi çalışmadığını ve kontrol edilen istisnaların temelde terk edildiğini keşfettik.

Çoklu ayar dönüş türleri mükemmeldir, ancak istisnaların yerini almaz. İstisnasız kod hata kontrol gürültüsü ile doludur.

Denetlenmiş istisnalarla ilgili diğer sorun, düşük düzeyli bir işlev tarafından atılan istisnalardaki bir değişikliğin tüm arayanlar ve arayanlar vb. Bunu önlemenin tek yolu, her kod seviyesinin daha düşük seviyeler tarafından atılan istisnaları yakalaması ve yeni bir istisnaya sarmasıdır. Yine, çok gürültülü bir kod ile sonuçlanır.


2
Jenerikler, çoğunlukla dilin OO paradigmasına desteğinin sınırlandırılmasından kaynaklanan bir hata sınıfının çözülmesine yardımcı olur. yine de, alternatifleri ya çoğunlukla hata denetimi yapan bir kod var ya da hiç bir şey yanlış gidiyor umuduyla çalışan kod gibi görünüyor. Ya yüzünüzde sürekli olarak olağanüstü durumlar var ya da ortada büyük bir kötü kurt bıraktığınızda gerçek çirkin hale gelen kabarık beyaz tavşanların rüya ülkesinde yaşıyoruz!
Newtopian

3
Basamaklı sayı için +1. Değişimi zorlaştıran herhangi bir sistem / mimari, yazarların ne kadar iyi tasarlanmış olduklarına bakılmaksızın, sadece maymun yama ve dağınık sistemlere yol açar.
Matthieu M.30

2
@Newtopian: Şablonlar, genel kaplar için statik tür güvenliği sağlamak gibi katı nesne yönüyle gerçekleştirilemeyen şeyler yapar.
David Thornley

2
"Denetlenmiş istisnalar" kavramına sahip bir istisna sistemi görmek istiyorum, ancak Java'dan çok farklı. Denetlenen durum bir istisna türünün niteliği olmamalı , aksine siteler atmalı, siteleri yakalamalı ve istisna örnekleri olmalıdır; bir yöntemin işaretli bir istisna atması olarak bildirilmesi durumunda, bunun iki etkisi olmalıdır: (1) işlev, iade edilen özel bir şey yaparak (örn. taşıma bayrağını ayarlama vb. tam platform) hangi arama kodunun hazırlanması gerektiği.
supercat

7
"İstisnalar olmadan kod hata kontrol gürültüsü ile doludur.": Bundan emin değilim: Haskell'de bunun için monad kullanabilirsiniz ve tüm hata kontrol gürültüsü kayboldu. "Çok aşamalı geri dönüş türleri" ile ortaya çıkan gürültü, programlama dilinin kendi içindeki çözümden çok bir sınırlamadır.
Giorgio

9

Uzun zamandır OO dillerinde, istisnaların kullanımı, hataları iletmek için fiili standart olmuştur. Ancak fonksiyonel programlama dilleri farklı bir yaklaşım, örneğin monadlar kullanma (kullanmadığım) veya Scott Wlaschin tarafından tarif edildiği gibi daha hafif "Demiryolu Odaklı Programlama" olanağı sağlar.

Gerçekten çok aşamalı sonuç türünün bir varyantıdır.

  • Bir işlev ya bir başarı ya da hata döndürür. Her ikisini de döndüremez (bir tupleda olduğu gibi).
  • Tüm olası hatalar kısa bir süre için belgelenmiştir (En azından F #'da ayrımcı sendikalar olarak sonuç türleriyle).
  • Arayan, sonucun başarılı mı yoksa başarısız mı olduğunu dikkate almadan sonucu kullanamaz.

Sonuç türü şöyle bildirilebilir

type Result<'TSuccess,'TFail> =
| Success of 'TSuccess
| Fail of 'TFail

Dolayısıyla, bu türü döndüren bir işlevin sonucu a Successveya bir Failtür olur. İkisi de olamaz.

Daha zorunlu yönlendirmeli programlama dillerinde, bu tarz bir stil, arayan sitede büyük miktarda kod gerektirebilir. Ancak fonksiyonel programlama, çoklu fonksiyonları birbirine bağlamak için ciltleme fonksiyonları veya operatörleri oluşturmanıza izin verir, böylece hata kontrolü kodun yarısını kaplamaz. Örnek olarak:

// Create an updateUser function that takes an id, and new state
// as input, and updates an existing user.
let updateUser id input =
    validateInput input
    >>= loadUser id
    >>= updateUser input
    >>= saveUser id
    >>= notifyAboutUserUpdated

updateUserFonksiyon arkaya bu fonksiyonların her biri arar ve bunların her başarısız olabilir. Hepsi başarılı olursa, son çağrılan işlevin sonucu döndürülür. İşlevlerden biri başarısız olursa, o işlevin sonucu genel updateUserişlevin sonucu olacaktır . Tüm bunlar özel >> = operatörü tarafından gerçekleştirilir.

Yukarıdaki örnekte, hata türleri

type UserValidationErrorType =
| InvalidEmail of string
| MissingFirstName of string
... etc

type DbErrorType =
| RecordNotFound of int
| ConcurrencyError of int

type UpdateUserErrorType =
| InvalidInput of UserValidationErrorType
| DbError of DbErrorType

Arayanın updateUserişlevdeki tüm olası hataları açıkça işlememesi durumunda, derleyici bir uyarı verir. Böylece her şey belgelenir.

Haskell'de, dokodu daha da temiz hale getirebilecek bir gösterim var.


2
Çok iyi cevap ve referanslar (demiryoluna yönelik programlama), +1. doOrtaya çıkan kodu daha da temiz hale getiren Haskell'in notasyonundan bahsetmek isteyebilirsiniz .
Giorgio

1
@Giorgio - Şimdi yaptım, ama Haskell ile çalışmadım, sadece F #, bu yüzden gerçekten çok fazla yazamadım. Ama isterseniz cevaba ekleyebilirsiniz.
Pete

Teşekkürler, küçük bir örnek yazdım ama cevabınıza eklenebilecek kadar küçük olmadığından tam bir cevap yazdım (bazı ekstra arka plan bilgileriyle).
Giorgio

2
Railway Oriented ProgrammingTam olarak monadic davranıştır.
Daenyth

5

Pete'nin cevabını çok iyi buluyorum ve biraz dikkate almak ve bir örnek vermek istiyorum. Özel hata değerlerinin döndürülmesine karşı istisnaların kullanımı ile ilgili çok ilginç bir tartışma , Standart ML'de Programlama'da , Bölüm 29.3, sayfa 243, 244'ün sonunda Robert Harper tarafından bulunabilir .

Sorun, fbir tür değer döndüren kısmi bir işlevi uygulamaktır t. Bir çözüm, işlevin tipine sahip olmaktır

f : ... -> t

ve olası bir sonuç olmadığında bir istisna atar. İkinci çözüm, türü olan bir işlev uygulamaktır

f : ... -> t option

SOME vbaşarıya ve başarısızlığa dönüş NONE.

İşte kitabın metni, metni daha genel hale getirmek için kendim tarafından küçük bir uyarlamayla (kitap belirli bir örneğe atıfta bulunuyor). Değiştirilen metin italik yazılmıştır .

İki çözüm arasındaki ödünleşimler nelerdir?

  1. Seçenek türlerine dayalı çözüm, işlevin türünde fhata olasılığını açık hale getirir . Bu, programcıyı, çağrının sonucu üzerinde bir vaka analizi kullanarak açıkça hata testi yapmaya zorlar. Tip denetleyicisi, a'nın beklendiği t optionyerdet kullanılamayacağını garanti eder . İstisnalara dayalı çözüm, türünde açıkça bir hata olduğunu göstermez. Bununla birlikte, programcı yine de hatayı işlemek zorunda kalır, aksi takdirde yakalanmayan bir istisna hatası derleme zamanı yerine çalışma zamanında ortaya çıkar.
  2. Seçenek türlerine dayalı çözüm, her çağrının sonucu üzerinde açık bir vaka analizi gerektirir. “En” sonuçları başarılı olursa, kontrol gereksizdir ve bu nedenle aşırı maliyetlidir. İstisnalara dayalı çözüm bu ek yükten muaftır: sonuçt döndürmeme “başarısızlığı” durumundan ziyade “normal” bir geri dönüş davasına yönelmiştir . İstisnaların uygulanması, başarısızlığın başarıya kıyasla nadir olması durumunda, bir işleyici kullanımının açık bir vaka analizinden daha verimli olmasını sağlar.

Genel olarak, verimlilik çok önemliyse, başarısızlık nadirse istisnaları tercih etme ve başarısızlık nispeten yaygınsa seçenekleri tercih etme eğilimindeyiz. Öte yandan, statik kontrol çok önemliyse, seçeneklerin kullanılması avantajlıdır, çünkü tür denetleyicisi, hatanın yalnızca çalışma zamanında ortaya çıkmasından ziyade programlayıcının arızayı kontrol etmesi gereksinimini uygulayacaktır.

İstisnalar ve opsiyon iade türleri arasındaki seçim söz konusu olduğunda bu durum söz konusudur.

Dönüş türünde bir hatayı temsil etme fikriyle ilgili olarak, kodun her tarafına yayılmış hata denetimlerine yol açar: durum böyle değildir. İşte Haskell'de bunu gösteren küçük bir örnek.

İki sayıyı ayrıştırmak ve sonra ilkini ikinciye bölmek istediğimizi varsayalım. Bu nedenle, her sayıyı ayrıştırırken veya bölerken bir hata olabilir (sıfıra bölme). Bu nedenle, her adımdan sonra bir hata olup olmadığını kontrol etmeliyiz.

import Text.Read

parseInt :: String -> Maybe Int
parseInt s = readMaybe s :: Maybe Int

safeDiv :: Int -> Int -> Maybe Int
safeDiv n d = if d /= 0 then Just (n `div` d) else Nothing

toString :: Maybe Int -> String
toString (Just i) = show i
toString Nothing  = "error"

main = do
         -- Get two lines from the terminal.
         nStr <- getLine
         dStr <- getLine

         -- Parse each string and divide.
         let r = do n <- parseInt nStr
                    d <- parseInt dStr
                    safeDiv n d

         -- Print the result.
         putStrLn $ toString r

Ayrıştırma ve bölme let ...blokta gerçekleştirilir . MaybeMonad ve dogösterimi kullanarak yalnızca başarı yolunun belirtildiğini unutmayın: Maybemonad'ın anlambilimi, dolaylı olarak hata değerini ( Nothing) yayar . Programcı için ek yük yok.


2
Böyle bir tür yararlı hata mesajı yazdırmak istediğiniz gibi durumlarda, Eithertür daha uygun olacağını düşünüyorum. NothingBuraya gelirsen ne yaparsın ? Sadece "hata" mesajını alıyorsunuz. Hata ayıklama için çok yararlı değil.
sara

1

Checked Exceptions'ın büyük bir hayranı oldum ve bunları ne zaman kullanacağım konusundaki genel kuralımı paylaşmak istiyorum.

Temelde benim kod ile uğraşmak zorunda hataların 2 tür olduğu sonucuna vardım. Kod yürütülmeden önce test edilebilir hatalar var ve kod yürütülmeden önce test edilemeyen hatalar var. Kod bir NullPointerException içinde yürütülmeden önce test edilebilir bir hataya basit bir örnek.

//... bad code below.  the runnable variable
// tries to call the run() method before the variable
// is instantiated.  Running the code below will cause
// a NullPointerException.
Runnable runnable = null;
runnable.run();

Basit bir test hatayı önleyebilirdi ...

Runnable runnable = null;
...
if (runnable != null)
{   runnable.run(); }

Bilgisayarda, güvenli olduğundan emin olmak için kodu çalıştırmadan önce 1 veya daha fazla test çalıştırabileceğiniz zamanlar vardır VE İSTİSNA EDİLECEKTİR. Örneğin, verilerinizi sürücüye yazmadan önce sabit sürücüde yeterli disk alanı bulunduğundan emin olmak için bir dosya sistemini test edebilirsiniz. Bugün kullanılanlar gibi çok işlemcili bir işletim sisteminde, işleminiz disk alanını test edebilir ve dosya sistemi yeterli alan olduğunu belirten bir değer döndürür, daha sonra başka bir işleme bağlam anahtarı, kullanılabilir kalan baytları yazabilir sistemi. İşletim sistemi içeriği, içeriğinizi diske yazdığınız çalışan işleminize geri döndüğünde, dosya sisteminde yeterli disk alanı olmadığından bir Özel Durum oluşur.

Yukarıdaki senaryoyu Kontrol Edilen İstisna için mükemmel bir durum olarak görüyorum. Kodunuz mükemmel bir şekilde yazılabilse bile, sizi kötü bir şeyle uğraşmaya zorlayan kodda bir İstisna. 'İstisna yutmak' gibi kötü şeyler yapmayı seçerseniz, kötü programcı sizsiniz. Bu arada, istisnayı yutmanın makul olduğu durumlar buldum, ancak lütfen istisnanın neden yutulduğuna ilişkin kodda bir yorum bırakın. İstisna yönetim mekanizması sorumlu değildir. Sıklıkla kalp pili kontrol ettiğimde Kontrol Edilmiş İstisnalar olan bir dille yazılmasını tercih ediyorum.

Kodun test edilebilir olup olmadığına karar vermenin zorlaştığı zamanlar vardır. Örneğin, bir yorumlayıcı yazıyorsanız ve kod sözdizimsel bir nedenle yürütülemediğinde bir SyntaxException oluşturulursa, SyntaxException bir Checked Exception mı yoksa (Java'da) bir RuntimeException mı olmalı? Yorumlayıcı kod yürütülmeden önce kodun sözdizimini denetler, sonra özel durum bir RuntimeException olmalıdır yanıt verir. Tercüman sadece 'hot' kodunu çalıştırırsa ve bir Sözdizimi hatasına vurursa, istisnanın Kontrol Edilmiş İstisna olması gerektiğini söyleyebilirim.

Bir Checked Exception'ı yakalamak veya atmak zorunda kaldığım için her zaman mutlu olmadığımı itiraf edeceğim çünkü ne yapacağımdan emin olmadığım zaman var. İşaretli İstisnalar, bir programcıyı oluşabilecek olası problemi dikkate almaya zorlamanın bir yoludur. Java'da programlamamın nedenlerinden biri, Checked Exceptions (Denetlenmiş İstisnalar) olmasıdır.


1
Kalp pilimin istisnaları olmayan bir dilde yazılmasını ve tüm kod satırlarının dönüş kodları aracılığıyla hataları ele almasını tercih ederim. Bir istisna attığınızda "her şey yanlış gitti" diyorsunuz ve işlemeye devam etmenin tek güvenli yolu durup yeniden başlatmaktır. Geçersiz duruma kolayca ulaşabilen bir program, kritik yazılımlar için istediğiniz bir şey değildir (ve Java,
EULA'daki

İstisna kullanarak ve dönüş kodunu kullanarak kontrol etmemek ve sonunda kontrol etmemek aynı kalp durmasına neden olur.
Newtopian

-1

Şu anda oldukça büyük bir OOP tabanlı proje / API ortasındayım ve istisnaların bu düzenini kullandım. Ama her şey gerçekten istisna işleme ve benzerleri ile ne kadar derin gitmek istediğinize bağlıdır.

ExpectedException
- AuthorisedException
- EmptySetException
- NoRemainingException
- NoRowsException
- NotFoundException
- ValidationException

UnexpectedException
- ConnectivityException
- EnvironmentException
- ProgrammerException
- SQLException

MİSAL

   $valid_types = array('mysql', 'oracle', 'sqlite');
       if (!in_array($type, $valid_types)) {
           throw new ecProgrammerException(
        'The database type specified, %1$s, is invalid. Must be one of: %2$s.',
    $type,
    join(', ', $valid_types)
    );
}

11
İstisna bekleniyorsa, bu gerçekten bir istisna değildir. "NoRowsException"? Bana kontrol akışı gibi geliyor ve bu nedenle bir istisnanın zayıf kullanımı.
quentin-starin

1
@qes: Bir işlev bir değer hesaplayamadığında, örneğin double Math.sqrt (double v) veya User findUser (long id) için bir istisna oluşturmak mantıklıdır. Bu, arayan kişiye, her çağrıdan sonra kontrol etmek yerine, uygun olan yerlerde hataları yakalama ve işleme özgürlüğü verir.
kevin cline

1
Beklenen = kontrol akışı = istisna karşıtı model. Kontrol akışı için istisna kullanılmamalıdır. Belirli bir giriş için hata üretmesi bekleniyorsa, o zaman dönüş değerinin bir kısmından geçirilir. Yani NANya var ya NULL.
Eonil

1
@Eonil ... veya Seçenek <T>
Maarten Bodewes
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.