Hata işleme konuları


31

Sorun:

Uzun zamandan beri, exceptionsmekanizma konusunda endişeliyim , çünkü gerçekten olması gerekeni çözmediğini hissediyorum.

ÖNEMLİ: Bu konu hakkında dışarıda uzun tartışmalar var ve bunların çoğu, exceptionsbir hata kodu döndürmekle karşılaştırmakta zorlanıyor . Bu kesinlikle burada konu değil.

Bir hata tanımlamaya çalışırken, Bjarne Stroustrup ve Herb Sutter'dan CppCoreGuidelines ile aynı fikirdeyim.

Bir hata, işlevin reklamı yapılan amacına ulaşamayacağı anlamına gelir.

CLAIM: exceptionMekanizma hataları ele almak için kullanılan bir dildir.

CLAIM: Bana göre, bir görevi yerine getirememe işlevine “mazeret yok”: Önceden / post koşullarını yanlış tanımladık, bu yüzden işlev sonuç elde edemez, ya da belirli bir istisnai durumun geliştirilmesi için zaman harcayacak kadar önemli olduğu düşünülmez. bir çözüm. IMO, normal kod ve hata kodu işleme arasındaki farkın (uygulamadan önce) çok öznel bir çizgi olduğunu düşünür.

ÖNEMLİ: Öncesi veya post koşulunun ne zaman saklanmadığını belirtmek için istisnalar kullanmak exception, esas olarak hata ayıklama amacıyla mekanizmanın başka bir amacıdır. exceptionsBurada bu kullanımını hedeflemiyorum .

Pek çok kitapta, öğreticide ve diğer kaynaklarda, hata yönetimi oldukça çözülmüş bir bilim olarak gösterilmeye meyillidir ve çözülmüş olan sağlam bir yazılıma sahip olmaları için bunlara exceptionsihtiyaç duyarsınız catch. Ancak geliştirici olarak geçirdiğim birkaç yıl, sorunu farklı bir yaklaşımdan görmemi sağlıyor:

  • Programcılar, belirli bir durumun dikkatle uygulanması için çok nadir göründüğü durumlarda istisnalar atarak görevlerini basitleştirme eğilimindedir. Bunun tipik durumları şunlardır: yetersiz bellek sorunları, disk dolu sorunları, bozuk dosya sorunları, vb. Bu yeterli olabilir, ancak her zaman mimari düzeyde kararlaştırılmaz.
  • Programcılar, kütüphanelerdeki istisnalar hakkındaki belgeleri dikkatlice okumama eğilimindedir ve genellikle bir fonksiyonun ne zaman ve ne zaman attığının farkında değildir. Ayrıca, bilseler bile, onları gerçekten yönetemezler.
  • Programcılar, istisnaları yeterince erken yakalama eğilimi göstermezler ve yaptıkları zaman, çoğunlukla daha fazla giriş yapmak ve daha fazla atmaktır. (birinci noktaya bakınız).

Bunun iki sonucu var:

  1. Sıkça meydana gelen hatalar gelişimde erken tespit edilir ve hata ayıklanır (bu iyidir).
  2. Nadir istisnalar yönetilmez ve kullanıcının evinde sistemin (güzel bir günlük mesajı ile) çökmesine neden olur. Bazı durumlarda hata bildirilir, hatta bildirilmez.

IMO'nun bir hata mekanizmasının temel amacı olduğu düşünülürse:

  1. Bazı özel durumların yönetilmediği kodda görünür olun.
  2. Bu durum gerçekleştiğinde, sorun çalışma zamanını ilgili koda (en azından arayan) iletin.
  3. Kurtarma mekanizmaları sağlar

exceptionAnlamsallığın hata işleme mekanizması olarak ana kusuru IMO'dur: a'nın throwkaynak kodunun neresinde olduğunu görmek kolaydır , ancak belirli bir işlevin bildirgeye bakarak atıp atmayacağını bilmek kesinlikle açık değildir. Bu yukarıda bahsettiğim tüm problemleri beraberinde getiriyor.

Dil, hata kodunu dilin diğer yönleri için olduğu gibi uygulamamakta ve kontrol etmemektedir (ör. Güçlü değişken türleri)

Çözüm için bir deneme

Bunu geliştirmek amacıyla, hata işlemeyi normal kodla aynı öneme sahip olan çok basit bir hata işleme sistemi geliştirdim.

Fikir şudur:

  • Her (ilgili) işlev, successçok hafif bir nesneye referans alır ve bu durumda bir hata durumuna getirebilir. Metinle ilgili bir hata kaydedilene kadar nesne çok hafiftir.
  • Sağlanan nesne zaten bir hata içeriyorsa, bir işlevi görevini atlamaya teşvik edilir.
  • Bir hata asla geçersiz kılmamalıdır.

Tam tasarım açıkça her bir konuyu (yaklaşık 10 sayfa) ve ayrıca OOP'ye nasıl uygulayacağınızı iyi düşünmektedir.

SuccessSınıfın örneği :

class Success
{
public:
    enum SuccessStatus
    {
        ok = 0,             // All is fine
        error = 1,          // Any error has been reached
        uninitialized = 2,  // Initialization is required
        finished = 3,       // This object already performed its task and is not useful anymore
        unimplemented = 4,  // This feature is not implemented already
    };

    Success(){}
    Success( const Success& v);
    virtual ~Success() = default;
    virtual Success& operator= (const Success& v);

    // Comparators
    virtual bool operator==( const Success& s)const { return (this->status==s.status && this->stateStr==s.stateStr);}
    virtual bool operator!=( const Success& s)const { return (this->status!=s.status || this->stateStr==s.stateStr);}

    // Retrieve if the status is not "ok"
    virtual bool operator!() const { return status!=ok;}

    // Retrieve if the status is "ok"
    operator bool() const { return status==ok;}

    // Set a new status
    virtual Success& set( SuccessStatus status, std::string msg="");
    virtual void reset();

    virtual std::string toString() const{ return stateStr;}
    virtual SuccessStatus getStatus() const { return status; }
    virtual operator SuccessStatus() const { return status; }

private:
    std::string stateStr;
    SuccessStatus status = Success::ok;
};

Kullanımı:

double mySqrt( Success& s, double v)
{
    double result = 0.0;
    if (!s) ; // do nothing
    else if (v<0.0) s.set(Error, "Square root require non-negative input.");
    else result = std::sqrt(v);
    return result;
}

Success s;
mySqrt(s, 144.0);
otherStuff(s);
saveStuff(s);
if (s) /*All is good*/;
else cout << s << endl;

Bunu (kendi) kodumun çoğunda kullandım ve programcıyı (ben) olası istisnai durumlar ve bunların nasıl çözüleceği (iyi) hakkında daha fazla düşünmeye zorladı. Bununla birlikte, bir öğrenme eğrisi vardır ve onu şimdi kullanan kodla iyi bir şekilde bütünleşmez.

Soru

Bir projede böyle bir paradigmanın kullanılmasının getirdiği sonuçları daha iyi anlamak istiyorum:

  • Sorunun öncülü doğru mu? veya alakalı bir şeyi mi kaçırdım?
  • Çözüm iyi bir mimari fikir midir? ya da fiyat çok mu yüksek?

DÜZENLE:

Yöntemler arası karşılaştırma:

//Exceptions:

    // Incorrect
    File f = open("text.txt"); // Could throw but nothing tell it! Will crash
    save(f);

    // Correct
    File f;
    try
    {
        f = open("text.txt");
        save(f);
    }
    catch( ... )
    {
        // do something 
    }

//Error code (mixed):

    // Incorrect
    File f = open("text.txt"); //Nothing tell you it may fail! Will crash
    save(f);

    // Correct
    File f = open("text.txt");
    if (f) save(f);

//Error code (pure);

    // Incorrect
    File f;
    open(f, "text.txt"); //Easy to forget the return value! will crash
    save(f);

    //Correct
    File f;
    Error er = open(f, "text.txt");
    if (!er) save(f);

//Success mechanism:

    Success s;
    File f;
    open(s, "text.txt");
    save(s, f); //s cannot be avoided, will never crash.
    if (s) ... //optional. If you created s, you probably don't forget it.

25
"Bu soru araştırma çabasını gösterir; faydalı ve açıktır" için oylandı, kabul etmiyorum çünkü: Düşüncelerin bir kısmının yanlış yönlendirildiğini düşünüyorum. (Detaylar bir cevapta olabilir.)
Martin Ba

2
Kesinlikle, anladım ve buna katılıyorum! Eleştirilmek bu sorunun amacı. Sorunun puanı, iyi / kötü soruları gösterir, OP'nin haklı olmadığını gösterir.
Adrian Maire,

2
Doğru anlarsam, istisnalar hakkındaki temel yakınlığınız, insanların bunları kullanmak yerine (c ++ 'da) görmezden gelebilecekleridir. Ancak, Başarı yapınız tasarımdaki aynı kusurlara sahiptir. İstisnalar gibi, bunu görmezden gelirler. Daha da kötüsü: daha ayrıntılı, basamaklı geri dönüşlere yol açıyor ve yukarı akışta "yakalayamıyorsunuz" bile.
dagnelies,

3
Neden sadece monadlar gibi bir şey kullanmıyorsun? Hatalarınızı örtük yaparlar ancak çalışma sırasında sessiz kalmazlar. Aslında, kodunuza bakarken ilk düşündüğüm şey "monads, nice" idi. Onlara bir bak.
bash0r

2
İstisnalardan hoşlanmamın ana nedeni, beklenmeyen tüm hataları bir kod bloğundan yakalamanıza ve bunları tutarlı bir şekilde ele almanıza izin vermeleridir. Evet, kodun görevini yerine getirmemesi için iyi bir neden yok - "bir hata vardı" kötü bir nedendir, ancak yine de gerçekleşir ve bu gerçekleştiğinde, nedeni günlüğe kaydetmek ve bir mesaj görüntülemek veya yeniden denemek istersiniz. (Uzak bir sistemle karmaşık, yeniden başlatılabilir bir etkileşim gerçekleştiren bazı kodlarım var; uzak sistem kapanırsa, günlüğe
kaydetmek

Yanıtlar:


32

Hata işleme belki de bir programın en zor kısmıdır.

Genel olarak, bir hata durumunun olduğunun farkına varmak kolaydır; bununla birlikte, atlanamayacak şekilde işaret etmek ve uygun şekilde ele almak (bkz. İbrahim'in İstisna Güvenliği seviyeleri ) gerçekten zordur.

C de, sinyal hataları, çözümünüze özgü olan bir dönüş kodu ile yapılır.

C ++ nedeniyle istisnalar tanıtıldı kısa geliyor böyle bir yaklaşımın; yani, sadece arayanlar bir hatanın meydana gelip gelmediğini kontrol etmeyi hatırladığında çalışır ve aksi halde korkunç bir şekilde başarısız olur. Kendinizi ne zaman bulursanız “Her zaman sorun değil…” derken bir sorunla karşılaşıyorsunuz; insanlar umrunda olsa bile, o kadar titiz değildir.

Ancak sorun, istisnaların kendi sorunları olması. Yani görünmez / gizli kontrol akışı. Bunun amacı şuydu: Hata durumunu gizlemek için kodun mantığı hata işleme kazan plakası tarafından engellenmemelidir. Hata yollarını hemen hemen yenilmez yapmak pahasına, "mutlu yolu" çok daha net (ve hızlı!) Yapar.


Başka dillerin bu konuya nasıl yaklaştığına bakmayı ilginç buluyorum:

  • Java istisnaları kontrol etti (ve işaretlenmeyenleri),
  • Go hata kodları / panik kullanır,
  • Pas toplam türleri / panik kullanır ).
  • Genel olarak FP dilleri.

C ++ bazı kontrol edilmiş istisnalar dışında kullanılıyordu, noexcept(<bool>)bunun yerine kullanımdan kaldırıldığını ve basitleştirildiğini farketmiş olabilirsiniz : ya bir fonksiyonun muhtemelen fırlattığı veya hiç yapılmadığı bildirildi. Kontrol edilen istisnalar, uygunsuz eşleşmelere / yuvalara neden olabilecek, genişletilebilirliklerinin eksik olması nedeniyle biraz problemlidir. Ve karmaşık istisna hiyerarşileri (sanal kalıtımın en önemli kullanım durumlarından biri istisnalardır ...).

Buna karşılık, Go ve Rust şöyle yaklaşıyor:

  • hatalar bantta işaretlenmeli,
  • istisna gerçekten istisnai durumlar için kullanılmalıdır.

İkincisi, (1) istisnaları panik olarak adlandırdıkları ve (2) burada bir tür hiyerarşi / karmaşık madde olmadığı şeklinde açıktır. Dil, bir "panik" in içeriğini denetlemek için imkanlar sunmuyor: tip hiyerarşisi yok, kullanıcı tanımlı içerik yok, sadece "hatalar, işler o kadar yanlış gitti ki" kurtarma mümkün değil ".

Bu, kullanıcıları istisnai durumlarda rahatça kurtarmanın kolay bir yolunu bırakırken ("bekle, henüz uygulamadım!" Gibi) uygun hata yönetimi kullanmalarını teşvik eder.

Tabii ki, Go yaklaşımı ne yazık ki sizinki gibi çok hata kontrol etmek kolayca unutabilirsiniz ...

... ancak Rust yaklaşımı çoğunlukla iki tip etrafında toplanmıştır:

  • Option, Benzer olan std::optional,
  • Result, bu iki olasılık varyantıdır: Tamam ve Err.

bu çok daha nettir, çünkü başarı için kontrol edilmeksizin bir sonucu yanlışlıkla kullanma imkanı yoktur: eğer yaparsanız, program paniğe kapılır.


FP dilleri üç katmana ayrılabilecek yapılardaki hata işlemlerini oluşturur: - Functor - Uygulamalı / Alternatif - Monadlar / Alternatif

Haskell'in Functoryazı sınıfına bir göz atalım:

class Functor m where
  fmap :: (a -> b) -> m a -> m b

Her şeyden önce, tip sınıfları biraz benzer ancak arayüzlere eşit değildir. Haskell'in işlev imzaları ilk bakışta biraz korkutucu görünüyor. Ama hadi onları deşifre edelim. İşlev fmap, biraz benzer olan ilk parametre olarak işlev görür std::function<a,b>. Sıradaki şey bir m a. mGibi std::vectorve m agibi bir şey hayal edebilirsiniz std::vector<a>. Fakat fark şu ki m a, açıkça olması gerektiği söylenmiyor std:vector. Yani bir de olabilir std::option. Dile ya da Functorgibi belirli bir tip için typeclass örneğine sahip olduğumuzu söyleyerek, bu tip için fonksiyonu kullanabiliriz . Aynı typeclasses için yapılmalıdır , vestd::vectorstd::optionfmapApplicativeAlternativeMonadBu durum, durumsal, olası başarısız hesaplamaları yapmanızı sağlar. AlternativeTypeclass uygular hata kurtarma soyutlamalar. Bununla bir a <|> bterim aya da terim anlamına gelen bir şey söyleyebilirsiniz b. Her iki hesaplamadan hiçbiri başarılı olmazsa, bu hala bir hatadır.

Haskell'in Maybetürüne bir göz atalım .

data Maybe a
  = Nothing
  | Just a

Bunun anlamı, beklediğiniz yerde bir Maybe aya Nothingda almanızdır Just a. fmapYukarıdan bakarken , bir uygulama gibi görünebilir

fmap f m = case m of
  Nothing -> Nothing
  Just a -> Just (f a)

case ... ofİfade desen eşleştirme ve nesne yönelimli programlama dünyasında bilinen yaptıklarını benzer denir visitor pattern. Çizgiyi case m ofşu şekilde hayal edin m.apply(...)ve noktalar, gönderme işlevlerini uygulayan bir sınıfın başlatılmasıdır. case ... ofİfadenin altındaki satırlar , sınıf alanlarını doğrudan ismiyle kapsamına getiren ilgili gönderme işlevlerdir. Yarattığımız Nothingdalda Nothingve Just adalda tek değerimizi adlandırır ave uygulanan Just ...dönüşüm işlevi ile bir tane daha yaratırız . Olarak oku: .fanew Just(f(a))

Bu, gerçek hata kontrollerini iptal ederken hatalı hesaplamalar yapabilir. Bu tür hesaplamaları çok güçlü yapan diğer arayüzler için uygulamalar var. Aslında, MaybeRust's Option-Type için ilham kaynağıdır .


Bunun yerine Successsınıfınızı tekrar elden geçirmenizi tavsiye ederim Result. Alexandrescu aslında expected<T>, standart tekliflerin yapıldığı , gerçekten çok yakın bir şey önerdi .

Pas adlandırma ve API'ya bağlı kalacağım çünkü ... belgelenmiş ve çalışıyor. Elbette, Rust ?kodu daha tatlı hale getirecek şık bir sonek operatörüne sahip; C ++ 'da TRYmakroyu ve GCC'nin ifadesini öykünmek için kullanacağız.

template <typename E>
struct Error {
    Error(E e): error(std::move(e)) {}

    E error;
};

template <typename E>
Error<E> error(E e) { return Error<E>(std::move(e)); }

template <typename T, typename E>
struct [[nodiscard]] Result {
    template <typename U>
    Result(U u): ok(true), data(std::move(u)), error() {}

    template <typename F>
    Result(Error<F> f): ok(false), data(), error(std::move(f.error)) {}

    template <typename U, typename F>
    Result(Result<U, F> other):
        ok(other.ok), data(std::move(other.data)),  error(std::move(other.error)) {}

    bool ok = false;
    T data;
    E error;
};

#define TRY(Expr_) \
    ({ auto result = (Expr_); \
       if (!result.ok) { return result; } \
       std::move(result.data); })

Not: Bu Resultbir yer tutucudur. Düzgün bir uygulama kapsülleme kullanır ve a union. Ancak bu noktaya dikkat çekmek yeterli.

Bu da yazmama izin veriyor ( eylem halinde gör ):

Result<double, std::string> sqrt(double x) {
    if (x < 0) {
        return error("sqrt does not accept negative numbers");
    }
    return x;
}

Result<double, std::string> double_sqrt(double x) {
    auto y = TRY(sqrt(x));
    return sqrt(y);
}

Hangi gerçekten temiz buluyorum:

  • Hata kodlarının (veya Successsınıfınızın) kullanımından farklı olarak, hataları kontrol etmeyi unutmak bazı rasgele davranışlardan ziyade çalışma zamanı hatası 1 ile sonuçlanır ,
  • İstisnaların kullanımından farklı olarak, çağrı sitesinde işlevlerin başarısız olabileceği açıktır , bu nedenle sürpriz olmaz.
  • C ++ - 2X standardında standartlara geçebiliriz concepts. Bu, bu tür bir programlamayı, hata türünden seçmeyi bırakabileceğimizden daha zevkli hale getirecektir. Örneğin, std::vectorsonuç olarak, olası tüm çözümleri bir kerede hesaplayabiliriz. Ya da önerdiğiniz gibi, hata işlemeyi iyileştirmeyi seçebiliriz.

1 Düzgün kapsüllenmiş bir Resultuygulama ile;)


Not: İstisnaların aksine, bu hafif Result, kütüğü daha az verimli yapan geri çekmecelere sahip değildir; en azından hata mesajının üretildiği dosya / satır numarasını kaydetmeyi ve genellikle zengin bir hata mesajı yazmayı yararlı bulabilirsiniz. Bu, TRYmakro her kullanıldığında dosyayı / satırı yakalayarak , temel olarak geri izlemeyi manuel olarak oluşturarak veya libbacktraceçağrı zincirindeki simgeleri listelemek için platforma özel kod ve kütüphaneleri kullanarak birleştirilebilir .


Yine de büyük bir uyarı var: mevcut C ++ kütüphaneleri ve hatta stdistisnalara dayanıyor. Herhangi bir 3. parti kütüphanenin API'sinin bir adaptöre sarılması gerektiğinden, bu stili kullanmak zorlu bir mücadele olacak ...


3
Bu makro görünüyor ... çok yanlış. Bir ({...})gcc uzantısı olduğunu varsayıyorum , ama öyle olsa bile, öyle değil mi if (!result.ok) return result;? Durumunuz geriye doğru görünüyor ve gereksiz bir kopyasını yapıyorsunuz.
Mooing Duck

@MooingDuck cevap açıklar ({...})gcc en olduğu ifadeleri ifadesi .
jamesdlin


1
C ++ 17 kullanıyorsanız std::variantuygulamak için kullanmanızı tavsiye ederim Result. Eğer bir hata görmezden Ayrıca, bir uyarı almak için, kullanmak[[nodiscard]]
Justin

2
@Justin: İstisna kullanımı konusundaki değişimlerden dolayı, kullanıp kullanmama std::variantbiraz zevkli bir meseledir. [[nodiscard]]Gerçekten de saf bir kazanç.
Matthieu M.

46

CLAIM: İstisna mekanizması, hataları ele almak için kullanılan bir dildir

istisnalar kontrol akışı mekanizmasıdır. Bu kontrol akışı mekanizması için motivasyon , hata işlemeyi, hata işlemenin, hata işlemenin çok tekrarlı olduğu ve mantığın ana bölümüyle çok az ilgisi olduğu durumlarda, hatalı işlem kodundan ayırmaktı .

ÖNEMLİ: Bana göre, bir görevi yerine getirememe işlevine "mazeret yok": Önceden / post koşullarını yanlış tanımladık, bu nedenle işlev sonuçları garanti edemez, ya da belirli bir istisnai durumun geliştirmek için zaman harcayacak kadar önemli olduğu düşünülmez. bir çözüm

Bir düşünün: Bir dosya oluşturmaya çalışıyorum. Depolama aygıtı dolu.

Şimdi, bu önkoşullarımı tanımlamakta bir başarısızlık değil: genel olarak önkoşul olarak "yeterli miktarda depolama alanı olması gerekir" ifadesini kullanamazsınız, çünkü paylaşılan saklama alanı bunu tatmin etmeyi imkansız kılan yarış koşullarına tabidir.

Öyleyse, programım bir şekilde boş yer açmalı ve daha sonra başarılı bir şekilde ilerlemeli mi, yoksa "çözüm geliştirmek" için çok tembel miyim? Bu açıkçası saçma görünüyor. Paylaşılan depolama yönetmek için "çözüm" dır benim program dışı ve kullanıcı ya biraz boşluk yayımlanan veya bazı daha fazla depolama eklediğinde benim program tekrar yayınlıyoruz incelikle başarısız ve olmamı sağlayan bir ince .


Başarı sınıfınızın yaptığı şey, program mantığınızla çok açık bir şekilde hata yönetimidir. Her bir işlevin, çalıştırmadan önce, bir hatanın oluşup oluşmadığını kontrol etmesi gerekir; bu, hiçbir şey yapmaması gerektiği anlamına gelir. Her kütüphane işlevi, tamamen aynı şeyi yapan bir tane daha argümanla (ve inşallah mükemmel bir yönlendirme ile) başka bir işleve sarılmalıdır.

Ayrıca, mySqrtişlevinizin başarısız olsa bile (veya önceki bir işlev başarısız olsaydı) bir değer döndürmesi gerektiğine dikkat edin . Öyleyse, ya sihirli bir değer (benzeri NaN) döndürüyorsunuz ya da programınıza belirsiz bir değer enjekte ediyorsunuz ve hiçbir şeyin, yürütmeniz sırasında girdiğiniz başarı durumunu kontrol etmeden kullandığını umuyorsunuz .

Doğruluk - ve performans için - herhangi bir ilerleme kaydedemediğinizde kontrolü tekrar kapsam dışına çıkarmak daha iyidir. Erken dönüşle istisnalar ve C tarzı açık hata kontrolü her ikisi de bunu başarır.


Karşılaştırma için, gerçekten işe yarayan fikrinize bir örnek Haskell'deki Error monad . Sisteminize göre avantajı, mantığınızın büyük bölümünü normal olarak yazmanız ve ardından bir adım başarısız olduğunda değerlendirmeyi durdurmaya özen gösteren monadın içine sarmanızdır. Bu yolla, doğrudan hata işleme sistemine dokunan tek kod, başarısız olabilecek koddur (bir hata atar) ve başarısızlıkla başa çıkmak için gereken koddur (bir istisna yakalar).

Monad tarzı ve tembel değerlendirmenin C ++ 'a iyi bir şekilde çevrildiğinden emin değilim.


1
Cevabınız sayesinde, konuya ışık katar. Sanırım, kullanıcı and allowing my program to fail gracefully, and be re-run2 saatlik çalışmasını kaybettiğinde buna katılmıyordu :
Adrian Maire

14
Çözümünüz, her yerde bir dosya oluşturabileceğiniz anlamına gelir; kullanıcıdan durumu düzeltip yeniden denemesini istemeniz gerekir. O zaman yanlış gidebilecek diğer her şey, ayrıca bir şekilde yerel olarak düzeltmeniz gerekir. İstisnalar dışında, sadece std::exceptionmantıksal işlemin daha yüksek seviyesine ulaşırsınız , kullanıcıya "X'in ex.what () nedeniyle başarısız olduğunu" söylersiniz ve tüm işlemi hazır olduğunda ve ne zaman yeniden yapmayı denemeyi teklif edersiniz .
İşe yaramaz

13
@AdrianMaire: "inceliksizce çalışıp tekrar çalıştırılmasına izin verilmesi" olarak da uygulanabilir showing the Save dialog again along with an error message and allowing the user to specify an alternative location to try. Bu, genellikle ilk depolama konumunun dolu olduğunu tespit eden koddan yapılamayan bir sorunun zarif bir şekilde ele alınmasıdır.
Bart van Ingen Schenau,

3
@ Yararsız Tembel değerlendirmenin, Rust, OCaml ve F # gibi katı değerlendirme dilleri tarafından açıkça kanıtlandığı gibi, Error monad'ın kullanımıyla hiçbir ilgisi yoktur.
8bittree

1
@Useless IMO kaliteli yazılım için, bu does “Bir dosya oluşturmak olabilir her yerde, durumu ve yeniden deneyin düzeltmek için kullanıcı istemi gerekir” diye mantıklı. İlk programcılar genellikle hataların giderilmesine yönelik kayda değer uzunluklara giderdi, en azından Knuth'un TeX programı onlarla doluydu. “Okuryazar programlama” çerçevesiyle, hata işlemeyi başka bir bölümde tutmanın bir yolunu buldu, böylece kod okunabilir ve hata kurtarma daha dikkatli bir şekilde yazılır (çünkü hata kurtarma bölümünü yazarken, Mesele şu ki programcı daha iyi bir iş çıkarmaya meyilli.
ShreevatsaR

15

Bir projede böyle bir paradigmanın kullanılmasının getirdiği sonuçları daha iyi anlamak istiyorum:

  • Sorunun öncülü doğru mu? veya alakalı bir şeyi mi kaçırdım?
  • Çözüm iyi bir mimari fikir midir? ya da fiyat çok mu yüksek?

Yaklaşımınız bazı büyük sorunları kaynak kodunuza getirir:

  • her zaman değerini kontrol etmeyi hatırlayarak müşteri koduna dayanır s. Bu, hata işleme yaklaşımı için dönüş kodlarının kullanımıyla yaygındır ve istisnaların dile getirilmesinin sebeplerinden biri: istisnalar dışında, başarısız olursanız, sessizce başarısız olmazsınız.

  • Bu yaklaşımla ne kadar çok kod yazarsanız, hata yönetimi için de ekleyeceğiniz daha fazla hata kazan kodu (hata kodunuz artık minimalist değildir) ve bakım çabanız artar.

Ancak geliştirici olarak geçirdiğim birkaç yıl, sorunu farklı bir yaklaşımdan görmemi sağlıyor:

Bu sorunların çözümlerine teknik lider düzeyinde veya takım düzeyinde yaklaşılmalıdır:

Programcılar, belirli bir durumun dikkatle uygulanması için çok nadir göründüğü durumlarda istisnalar atarak görevlerini basitleştirme eğilimindedir. Bunun tipik durumları şunlardır: yetersiz bellek sorunları, disk dolu sorunları, bozuk dosya sorunları, vb. Bu yeterli olabilir, ancak her zaman mimari düzeyde kararlaştırılmaz.

Her zaman, atılabilecek her türlü istisnayı ele alıyorsanız, tasarım iyi değildir; Hangi hataların ele alındığı, geliştiricilerin uygulamak gibi hissetmelerine göre değil, proje spesifikasyonlarına göre kararlaştırılmalıdır.

Otomatik sınama kurarak, birim sınamaların özelliklerini ve uygulamalarını ayırarak ele alın (iki farklı kişinin bunu yapmasını sağlayın).

Programcılar dokümantasyonu dikkatlice okumama eğilimindedir [...] Ayrıca, bilseler bile, onları gerçekten yönetemezler.

Daha fazla kod yazarak bu sorunu ele almayacaksınız. Bence en iyi bahis titizlikle uygulanan kod incelemeleri.

Programcılar, istisnaları yeterince erken yakalama eğilimi göstermezler ve yaptıkları zaman, çoğunlukla daha fazla giriş yapmak ve daha fazla atmaktır. (birinci noktaya bakınız).

Doğru hata işleme zordur, ancak istisnalar dışında, dönüş değerlerinden daha sıkıcıdır (gerçekte geri döndürülmeleri veya g / ç argümanları olarak geçirilmeleri gibi).

Hata işlemenin en zor yanı, hatayı nasıl aldığınız değil, uygulamanızın hataların varlığında tutarlı bir durumu koruduğundan emin olmanızdır.

Bunu ele almak için, hata koşullarını tanımlamaya ve çalıştırmaya daha fazla önem verilmelidir (daha fazla test, daha fazla birim / entegrasyon testi, vb.).


12
Bir hatadan sonraki tüm kodlar atlanır; her bir örneği bağımsız değişken olarak aldığınızı kontrol etmeyi hatırlarsanız . "Bu yaklaşımla ne kadar çok kod yazarsanız, o kadar fazla hata içeren kod da eklemelisiniz" derken bunu kastediyorum. Kodunuzu, başarı örneğinde ifs ile çözmeniz gerekecek ve her unutduğunuzda, bu bir hata. Kontrol etmeyi unutmanın neden olduğu ikinci sorun: tekrar kontrol edene kadar çalıştırılan kod hiç yürütülmemeliydi (kontrol etmeyi unutursanız devam eder, verilerinizi bozar).
utnapistim

11
Hayır, bir istisna işleme (veya bir hata kodunu döndürme) bir çökme değildir - hata / istisna mantıksal olarak ölümcül değilse veya bunu yapmamayı seçtiyseniz. Her hatayı önceden bir hatanın olup olmadığını kontrol etmek zorunda kalmadan açık bir şekilde kontrol etmek zorunda kalmadan , hala hata vakasını ele alma şansına sahipsiniz
Yararsız

11
@AdrianMaire Üzerinde çalıştığım hemen her uygulamada sessizce devam eden bir kazayı büyük ölçüde tercih ederim. Bazı kötü çıktılar almanın ve üzerinde çalışmaya devam etmenin çok para kaybına neden olabileceği kritik iş yazılımları üzerinde çalışıyorum. Doğruluk çok önemli ve kabul edilebilir bir değer ise, istisnaların burada çok büyük bir avantajı vardır.
Chris Hayes,

1
@AdrianMaire - Bir if ifadesini unutma yönteminizin bir istisna ele almayı unutmanın çok daha zor olduğunu düşünüyorum ... Ayrıca - istisnaların ana yararı, hangi katmanın bunları yönettiğidir. Bir uygulama istisnasının uygulama düzeyinde bir hata mesajı göstermek için daha fazla kabarmasına izin vermek, ancak daha düşük düzeyde bildiğiniz durumları ele almak isteyebilirsiniz. Üçüncü taraf kütüphanelerini veya diğer geliştiricilerin kodunu kullanıyorsanız, bu gerçekten tek seçenek ...
Milney

5
@Adrian Hata yok, yazdıklarını yanlış yazmış görünüyorsun ya da ikinci yarısını özledim. Demek istediğim, test / geliştirme sırasında istisnanın ortaya çıkacağı ve geliştiricilerin kendileriyle başa çıkmaları gerektiğinin farkına varacağı anlamına gelmiyor. Mesele şu ki, üretimde tamamen işlenmemiş bir istisna sonucunun, kontrol edilmemiş bir hata kodunun sonucuna tercih edilebilir olmasıdır. Eğer hata kodunu kaçırırsanız, yanlış sonuçlar almaya devam edersiniz. Eğer istisna, uygulama çöker kaçırmak ve çalışmaya devam etmezse, Alacağınız sonuç değil yanlış sonuçlar . (devam)
Mr.Mindor
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.