समस्या:
लंबे समय से, मैं exceptions
तंत्र के बारे में चिंतित हूं , क्योंकि मुझे लगता है कि यह वास्तव में हल नहीं करता है कि इसे क्या करना चाहिए।
CLAIM: इस विषय के बारे में लंबी बहसें होती हैं, और उनमें से अधिकांश exceptions
एक त्रुटि कोड को वापस करने की तुलना में संघर्ष करते हैं । यह निश्चित रूप से यहाँ विषय नहीं है।
एक त्रुटि को परिभाषित करने की कोशिश कर रहा हूं, मैं CppCoreGuidelines के साथ सहमत हूं, Bjarne Stroustrup &bb गटर से
त्रुटि का अर्थ है कि फ़ंक्शन अपने विज्ञापित उद्देश्य को प्राप्त नहीं कर सकता है
CLAIM: exception
तंत्र त्रुटियों को संभालने के लिए एक भाषा शब्दार्थ है।
CLAIM: मेरे लिए, कार्य प्राप्त न करने के लिए एक फ़ंक्शन का "कोई बहाना" नहीं है: या तो हमने गलत तरीके से पूर्व / पोस्ट की शर्तों को परिभाषित किया है ताकि फ़ंक्शन परिणाम सुनिश्चित नहीं कर सके, या कुछ विशिष्ट असाधारण मामले को विकसित करने में समय बिताने के लिए पर्याप्त महत्वपूर्ण नहीं माना जाता है एक तरकीब। उस पर विचार करते हुए, IMO, सामान्य कोड और त्रुटि कोड हैंडलिंग के बीच का अंतर (कार्यान्वयन से पहले) एक बहुत ही व्यक्तिपरक रेखा है।
सीएलएआईएम: पूर्व या पोस्ट की स्थिति नहीं रखने पर इंगित करने के लिए अपवादों का उपयोग करना exception
तंत्र का एक अन्य उद्देश्य है , मुख्य रूप से डीबगिंग उद्देश्य के लिए। मैं exceptions
यहां इस उपयोग को लक्षित नहीं करता हूं ।
कई पुस्तकों, ट्यूटोरियल और अन्य स्रोतों में, वे एक काफी उद्देश्य विज्ञान के रूप में त्रुटि से निपटने के लिए दिखाते हैं, जो कि हल किया जाता है exceptions
और आपको बस catch
एक मजबूत सॉफ्टवेयर होने के लिए, किसी भी स्थिति से उबरने में सक्षम होना चाहिए। लेकिन एक डेवलपर के रूप में मेरे कई साल मुझे एक अलग दृष्टिकोण से समस्या को देखने के लिए बनाते हैं:
- प्रोग्रामर अपवादों को फेंककर अपने कार्य को आसान बनाने के लिए जाते हैं जब विशिष्ट मामले को ध्यान से लागू करने के लिए बहुत कम लगता है। इसके विशिष्ट मामले इस प्रकार हैं: मेमोरी इश्यूज, डिस्क फुल इश्यूज, दूषित फाइल इश्यूज आदि। यह पर्याप्त हो सकता है, लेकिन यह हमेशा एक आर्किटेक्चरल लेवल से तय नहीं किया जाता है।
- प्रोग्रामर पुस्तकालयों में अपवादों के बारे में सावधानीपूर्वक प्रलेखन नहीं पढ़ते हैं, और आमतौर पर यह नहीं जानते हैं कि कौन सा और कब फ़ंक्शन फेंकता है। इसके अलावा, जब भी वे जानते हैं, वे वास्तव में उन्हें प्रबंधित नहीं करते हैं।
- प्रोग्रामर अपवादों को जल्दी पकड़ नहीं पाते हैं, और जब वे ऐसा करते हैं, तो यह ज्यादातर लॉग इन करना और आगे फेंकना होता है। (प्रथम बिंदु को देखें)।
इसके दो परिणाम हैं:
- अक्सर होने वाली त्रुटियों को विकास में जल्दी पता लगाया जाता है और डीबग किया जाता है (जो अच्छा है)।
- दुर्लभ अपवाद प्रबंधित नहीं किए जाते हैं और उपयोगकर्ता के घर पर क्रैश (एक अच्छे लॉग संदेश के साथ) करने के लिए सिस्टम बनाते हैं। कुछ बार त्रुटि की सूचना दी जाती है, या नहीं भी।
यह देखते हुए कि, IMO एक त्रुटि तंत्र का मुख्य उद्देश्य होना चाहिए:
- कोड में दिखाई दें जहां कुछ विशिष्ट मामले का प्रबंधन नहीं किया गया है।
- इस स्थिति के होने पर संबंधित कोड (कम से कम कॉलर) के लिए समस्या रनटाइम का संचार करें।
- वसूली तंत्र प्रदान करता है
exception
सिमेंटिक का मुख्य दोष एक त्रुटि हैंडलिंग तंत्र के रूप में IMO है: यह देखना आसान throw
है कि स्रोत कोड में कहां है, लेकिन यह जानने के लिए बिल्कुल स्पष्ट नहीं है कि क्या कोई विशिष्ट फ़ंक्शन घोषणा को देखकर फेंक सकता है। यह सभी समस्या है जो मैंने ऊपर पेश की।
भाषा त्रुटि कोड को लागू नहीं करती है और उसे कड़ाई से जांचती है क्योंकि यह भाषा के अन्य पहलुओं (जैसे मजबूत प्रकार के चर) के लिए बनाती है
समाधान के लिए प्रयास करें
इसे सुधारने के इरादे से, मैंने एक बहुत ही साधारण एरर हैंडलिंग सिस्टम विकसित किया, जो सामान्य कोड की तुलना में एरर हैंडलिंग को समान स्तर पर रखने की कोशिश करता है।
विचार यह है:
- प्रत्येक (प्रासंगिक) फ़ंक्शन को एक
success
बहुत ही हल्की वस्तु का संदर्भ प्राप्त होता है , और यह मामले में त्रुटि स्थिति में सेट कर सकता है। ऑब्जेक्ट तब तक बहुत हल्का होता है जब तक कि टेक्स्ट के साथ कोई एरर सेव न हो जाए। - यदि किसी ऑब्जेक्ट में पहले से कोई त्रुटि है, तो फ़ंक्शन को उसके कार्य को छोड़ने के लिए प्रोत्साहित किया जाता है।
- एक त्रुटि कभी भी ओवरराइड नहीं होनी चाहिए।
पूर्ण डिजाइन स्पष्ट रूप से प्रत्येक पहलू (लगभग 10 पृष्ठों) पर अच्छी तरह से विचार करता है, यह भी कि इसे ओओपी पर कैसे लागू किया जाए।
Success
कक्षा का उदाहरण :
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;
};
उपयोग:
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;
मैंने अपने कई (स्वयं के) कोड का उपयोग किया और यह प्रोग्रामर (मुझे) को संभावित असाधारण मामलों के बारे में और सोचने के लिए मजबूर करता है कि उन्हें (अच्छा) कैसे हल किया जाए। हालाँकि, इसमें सीखने की अवस्था है और कोड के साथ अच्छी तरह से एकीकृत नहीं है जो अब इसका उपयोग करते हैं।
प्रश्न
मैं एक परियोजना में इस तरह के प्रतिमान का उपयोग करने के निहितार्थों को बेहतर ढंग से समझना चाहूंगा:
- क्या समस्या का आधार सही है? या मैं कुछ प्रासंगिक याद किया?
- क्या समाधान एक अच्छा वास्तुशिल्प विचार है? या कीमत बहुत अधिक है?
संपादित करें:
तरीकों के बीच तुलना:
//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.