Sorun:
Uzun zamandan beri, exceptions
mekanizma 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, exceptions
bir 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: exception
Mekanizma 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. exceptions
Burada 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 exceptions
ihtiyaç 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:
- Sıkça meydana gelen hatalar gelişimde erken tespit edilir ve hata ayıklanır (bu iyidir).
- 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:
- Bazı özel durumların yönetilmediği kodda görünür olun.
- Bu durum gerçekleştiğinde, sorun çalışma zamanını ilgili koda (en azından arayan) iletin.
- Kurtarma mekanizmaları sağlar
exception
Anlamsallığın hata işleme mekanizması olarak ana kusuru IMO'dur: a'nın throw
kaynak 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.
Success
Sı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.