विचारों को संभालने में त्रुटि


31

समस्या:

लंबे समय से, मैं exceptionsतंत्र के बारे में चिंतित हूं , क्योंकि मुझे लगता है कि यह वास्तव में हल नहीं करता है कि इसे क्या करना चाहिए।

CLAIM: इस विषय के बारे में लंबी बहसें होती हैं, और उनमें से अधिकांश exceptionsएक त्रुटि कोड को वापस करने की तुलना में संघर्ष करते हैं । यह निश्चित रूप से यहाँ विषय नहीं है।

एक त्रुटि को परिभाषित करने की कोशिश कर रहा हूं, मैं CppCoreGuidelines के साथ सहमत हूं, Bjarne Stroustrup &bb गटर से

त्रुटि का अर्थ है कि फ़ंक्शन अपने विज्ञापित उद्देश्य को प्राप्त नहीं कर सकता है

CLAIM: exceptionतंत्र त्रुटियों को संभालने के लिए एक भाषा शब्दार्थ है।

CLAIM: मेरे लिए, कार्य प्राप्त न करने के लिए एक फ़ंक्शन का "कोई बहाना" नहीं है: या तो हमने गलत तरीके से पूर्व / पोस्ट की शर्तों को परिभाषित किया है ताकि फ़ंक्शन परिणाम सुनिश्चित नहीं कर सके, या कुछ विशिष्ट असाधारण मामले को विकसित करने में समय बिताने के लिए पर्याप्त महत्वपूर्ण नहीं माना जाता है एक तरकीब। उस पर विचार करते हुए, IMO, सामान्य कोड और त्रुटि कोड हैंडलिंग के बीच का अंतर (कार्यान्वयन से पहले) एक बहुत ही व्यक्तिपरक रेखा है।

सीएलएआईएम: पूर्व या पोस्ट की स्थिति नहीं रखने पर इंगित करने के लिए अपवादों का उपयोग करना exceptionतंत्र का एक अन्य उद्देश्य है , मुख्य रूप से डीबगिंग उद्देश्य के लिए। मैं exceptionsयहां इस उपयोग को लक्षित नहीं करता हूं ।

कई पुस्तकों, ट्यूटोरियल और अन्य स्रोतों में, वे एक काफी उद्देश्य विज्ञान के रूप में त्रुटि से निपटने के लिए दिखाते हैं, जो कि हल किया जाता है exceptionsऔर आपको बस catchएक मजबूत सॉफ्टवेयर होने के लिए, किसी भी स्थिति से उबरने में सक्षम होना चाहिए। लेकिन एक डेवलपर के रूप में मेरे कई साल मुझे एक अलग दृष्टिकोण से समस्या को देखने के लिए बनाते हैं:

  • प्रोग्रामर अपवादों को फेंककर अपने कार्य को आसान बनाने के लिए जाते हैं जब विशिष्ट मामले को ध्यान से लागू करने के लिए बहुत कम लगता है। इसके विशिष्ट मामले इस प्रकार हैं: मेमोरी इश्यूज, डिस्क फुल इश्यूज, दूषित फाइल इश्यूज आदि। यह पर्याप्त हो सकता है, लेकिन यह हमेशा एक आर्किटेक्चरल लेवल से तय नहीं किया जाता है।
  • प्रोग्रामर पुस्तकालयों में अपवादों के बारे में सावधानीपूर्वक प्रलेखन नहीं पढ़ते हैं, और आमतौर पर यह नहीं जानते हैं कि कौन सा और कब फ़ंक्शन फेंकता है। इसके अलावा, जब भी वे जानते हैं, वे वास्तव में उन्हें प्रबंधित नहीं करते हैं।
  • प्रोग्रामर अपवादों को जल्दी पकड़ नहीं पाते हैं, और जब वे ऐसा करते हैं, तो यह ज्यादातर लॉग इन करना और आगे फेंकना होता है। (प्रथम बिंदु को देखें)।

इसके दो परिणाम हैं:

  1. अक्सर होने वाली त्रुटियों को विकास में जल्दी पता लगाया जाता है और डीबग किया जाता है (जो अच्छा है)।
  2. दुर्लभ अपवाद प्रबंधित नहीं किए जाते हैं और उपयोगकर्ता के घर पर क्रैश (एक अच्छे लॉग संदेश के साथ) करने के लिए सिस्टम बनाते हैं। कुछ बार त्रुटि की सूचना दी जाती है, या नहीं भी।

यह देखते हुए कि, IMO एक त्रुटि तंत्र का मुख्य उद्देश्य होना चाहिए:

  1. कोड में दिखाई दें जहां कुछ विशिष्ट मामले का प्रबंधन नहीं किया गया है।
  2. इस स्थिति के होने पर संबंधित कोड (कम से कम कॉलर) के लिए समस्या रनटाइम का संचार करें।
  3. वसूली तंत्र प्रदान करता है

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.

25
"यह प्रश्न शोध के प्रयास को दर्शाता है; यह उपयोगी और स्पष्ट है", इसलिए नहीं कि मैं सहमत हूं: मुझे लगता है कि कुछ विचार गलत हैं। (विवरण एक उत्तर में अनुसरण कर सकते हैं।)
मार्टिन बा

2
बिल्कुल, मैं समझता हूं और उस पर सहमत हूं! इस प्रश्न का उद्देश्य आलोचना करना है। और प्रश्न का स्कोर अच्छे / बुरे प्रश्नों को इंगित करता है, यह नहीं कि ओपी सही है।
एड्रियन मैयर

2
अगर मैं सही तरीके से समझूं, तो अपवादों के बारे में आपकी मुख्य पकड़ यह है कि लोग उन्हें संभालने के बजाय इसे (c ++ में) अनदेखा कर सकते हैं। हालाँकि, आपके सफलता निर्माण में डिज़ाइन के अनुसार दोष है। अपवादों की तरह, वे इसे अनदेखा करेंगे। इससे भी बदतर: यह अधिक वर्बोज़ है, कैस्केडिंग रिटर्न की ओर जाता है, और आप इसे ऊपर की ओर "पकड़" भी नहीं सकते हैं।
डेगनीज

3
सिर्फ भिक्षुओं की तरह कुछ का उपयोग क्यों नहीं करते? वे आपकी त्रुटियों को अंतर्निहित करते हैं, लेकिन वे चलाने के दौरान चुप नहीं रहेंगे। दरअसल, आपके कोड को देखते समय सबसे पहले मैंने सोचा था "भिक्षु, अच्छा"। उन पर एक नजर है।
18

2
मुख्य कारण जो मुझे पसंद है, वह यह है कि वे आपको कोड के दिए गए ब्लॉक से सभी अप्रत्याशित त्रुटियों को पकड़ने और उन्हें लगातार संभालने की अनुमति देते हैं। हां, कोई अच्छा कारण नहीं है कि कोड को अपना कार्य नहीं करना चाहिए - "एक बग था" एक खराब कारण है, लेकिन यह अभी भी होता है , और जब ऐसा होता है तो आप कारण को लॉग करना चाहते हैं और एक संदेश प्रदर्शित करते हैं, या फिर से प्रयास करते हैं। (मेरे पास कुछ कोड हैं जो एक रिमोट सिस्टम के साथ एक जटिल, पुनरारंभ करने योग्य बातचीत करता है; दूरस्थ सिस्टम को नीचे जाना चाहिए, मैं इसे लॉग इन करना चाहता हूं और शुरुआत से ही पुन: प्रयास करना चाहता हूं)
user253751

जवाबों:


32

त्रुटि-हैंडलिंग शायद एक कार्यक्रम का सबसे कठिन हिस्सा है।

सामान्य तौर पर, यह महसूस करना कि त्रुटि की स्थिति आसान है; हालाँकि इसे इस तरह से संकेत देना कि इसे दरकिनार न किया जा सके और इसे उचित तरीके से हैंडल किया जा सके (देखें आबोहर्म्स का अपवाद सुरक्षा स्तर ) वास्तव में कठिन है।

सी में, सिग्नलिंग त्रुटियां एक रिटर्न कोड द्वारा की जाती हैं, जो आपके समाधान के लिए आइसोमोर्फिक है।

इस तरह के दृष्टिकोण के कम आने के कारण सी ++ ने अपवाद पेश किए ; अर्थात्, यह केवल तभी काम करता है जब कॉलर्स यह जांचना याद रखें कि कोई त्रुटि हुई या नहीं और अन्यथा बुरी तरह से विफल हो जाती है। जब भी आप अपने आप को यह कहते हुए पाते हैं "जब तक यह ठीक है तब तक हर बार ..." आपको एक समस्या है; मनुष्य उस सावधानी से नहीं हैं, जब वे देखभाल करते हैं।

हालाँकि, समस्या यह है कि अपवादों के अपने मुद्दे हैं। अर्थात्, अदृश्य / छुपा नियंत्रण प्रवाह। इसका अभिप्राय था: त्रुटि मामले को छिपाना ताकि बायलरप्लेट को संभालने में त्रुटि से कोड का तर्क बाधित न हो। यह "खुश पथ" बहुत स्पष्ट (और तेज़!) बनाता है, त्रुटि पथ बनाने की कीमत पर nigh inscrutable।


मुझे यह देखना दिलचस्प है कि अन्य भाषाएं इस मुद्दे पर कैसे पहुँचती हैं:

  • जावा ने अपवादों की जाँच की है (और अनियंत्रित),
  • गो त्रुटि कोड / पैनिक का उपयोग करता है,
  • जंग योग के प्रकार / पैनिक का उपयोग करता है )।
  • सामान्य रूप से एफपी भाषाएँ।

C ++ में कुछ अपवादों की जाँच की जाती थी, आपने देखा होगा कि इसे मूल के noexcept(<bool>)बजाय पदावनत और सरलीकृत किया गया है : या तो एक फ़ंक्शन को संभवतः फेंकने के लिए घोषित किया गया है, या इसे कभी भी घोषित नहीं किया गया है। चेक किए गए अपवाद कुछ हद तक समस्याग्रस्त हैं, जिनमें उनकी विलुप्तता की कमी है, जो अजीब मैपिंग / नेस्टिंग का कारण बन सकता है। और आक्षेपित अपवाद पदानुक्रम (आभासी उत्तराधिकार के प्रमुख उपयोग मामलों में से एक अपवाद है ...)।

इसके विपरीत, गो और रस्ट दृष्टिकोण लेते हैं:

  • त्रुटियों को बैंड में संकेत दिया जाना चाहिए,
  • अपवाद का उपयोग वास्तव में असाधारण स्थितियों के लिए किया जाना चाहिए।

उत्तरार्द्ध में स्पष्ट है कि (1) वे अपने अपवाद का नाम पैनिक रखते हैं और (2) यहां किसी प्रकार का पदानुक्रम / जटिल क्लॉज नहीं है। भाषा "आतंक" की सामग्री का निरीक्षण करने के लिए सुविधाएं प्रदान नहीं करती है: कोई प्रकार की पदानुक्रम, कोई उपयोगकर्ता-परिभाषित सामग्री नहीं, बस एक "उफ़, चीजें इतनी गलत हो गईं कि कोई संभावित पुनर्प्राप्ति नहीं है"।

यह प्रभावी रूप से उपयोगकर्ताओं को उचित त्रुटि हैंडलिंग का उपयोग करने के लिए प्रोत्साहित करता है, जबकि असाधारण परिस्थितियों में जमानत का एक आसान तरीका अभी भी छोड़ रहा है (जैसे: "रुको, मैं अभी तक उस पर अमल नहीं करता!")।

बेशक, गो दृष्टिकोण दुर्भाग्य से आप की तरह है कि आप आसानी से त्रुटि की जांच करने के लिए भूल सकते हैं ...

... लेकिन जंग का रुख ज्यादातर दो प्रकारों पर केंद्रित होता है:

  • Optionहै, जो करने के लिए इसी तरह की है std::optional,
  • Result, जो दो संभावनाओं वाला संस्करण है: ओके और एर।

यह बहुत ज्यादा बकवास है क्योंकि सफलता के लिए जाँच किए बिना गलती से परिणाम का उपयोग करने का कोई अवसर नहीं है: यदि आप करते हैं, तो कार्यक्रम आतंकित करता है।


एफपी भाषाएं अपनी त्रुटियों को उन निर्माणों में संभालती हैं जिन्हें तीन परतों में विभाजित किया जा सकता है: - फंक्टर - आवेदक / वैकल्पिक - मोनाड या वैकल्पिक

आइए नजर डालते हैं हास्केल के Functorटाइपसेकल्स पर:

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

सबसे पहले, टाइपकास्ट कुछ समान हैं लेकिन इंटरफेस के बराबर नहीं हैं। हास्केल के फंक्शन सिग्नेचर पहले लुक में थोड़े डरावने लगते हैं। लेकिन उन्हें समझाना चाहिए। फ़ंक्शन fmapपहले पैरामीटर के रूप में एक फ़ंक्शन लेता है जो कुछ हद तक समान है std::function<a,b>। अगली बात ए m a। आप कल्पना कर सकते हैं mजैसे कुछ std::vectorऔर m aजैसा कुछ std::vector<a>। लेकिन अंतर m aयह है कि यह स्पष्ट रूप से कहना नहीं है std:vector। तो यह भी हो सकता std::optionहै। भाषा को यह बताकर कि हमारे पास Functorएक विशिष्ट प्रकार के लिए टाइपकास्ट के लिए एक उदाहरण है std::vectorया std::option, हम fmapउस प्रकार के लिए फ़ंक्शन का उपयोग कर सकते हैं । एक ही टाइपकास्ट के लिए किया जाना चाहिए Applicative, AlternativeऔरMonadजो आपको स्टेटफुल, संभव विफलताओं की गणना करने की अनुमति देता है। AlternativeTypeclass औजार त्रुटि वसूली कपोल-कल्पना। इसके द्वारा आप कुछ कह सकते हैं जैसे a <|> bकि यह शब्द aया शब्द b। यदि दोनों में से कोई भी गणना सफल नहीं होती है, तो यह अभी भी एक त्रुटि है।

हस्केल के Maybeप्रकार पर एक नजर डालते हैं ।

data Maybe a
  = Nothing
  | Just a

इसका मतलब है, कि जहां आप उम्मीद करते हैं Maybe a, आप Nothingया तो मिलेंगे या Just a। जब fmapऊपर से देखते हैं, तो एक कार्यान्वयन जैसा दिख सकता है

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

case ... ofअभिव्यक्ति पैटर्न मिलान और जैसा दिखता है क्या के रूप में OOP दुनिया में जाना जाता है कहा जाता है visitor pattern। के case m ofरूप में लाइन की कल्पना करें m.apply(...)और डॉट्स प्रेषण कार्यों को लागू करने वाले एक वर्ग की तात्कालिकता है। case ... ofअभिव्यक्ति के नीचे की पंक्तियाँ संबंधित प्रेषण कार्य हैं जो कक्षा के क्षेत्रों को सीधे नाम के दायरे में लाते हैं। जिस Nothingशाखा में हम बनाते हैं Nothingऔर जिस Just aशाखा में हम अपने एकमात्र मूल्य को नाम देते aहैं और Just ...जिस परिवर्तन के fलिए आवेदन किया जाता है उसके साथ एक और बनाते हैं a। के रूप में यह पढ़ें: new Just(f(a))

यह अब वास्तविक त्रुटि जांच को दूर करते हुए गलत संगणना को संभाल सकता है। अन्य इंटरफेस के लिए कार्यान्वयन मौजूद हैं जो इस तरह की गणनाओं को बहुत शक्तिशाली बनाता है। दरअसल, Maybeरस्ट- Optionटाइप के लिए प्रेरणा है ।


मैं आपको अपनी Successकक्षा को एक Resultबदले की ओर फिर से प्रोत्साहित करने के लिए प्रोत्साहित करूँगा । अलेक्जेंड्रेस्कु ने वास्तव में कुछ वास्तव में पास का प्रस्ताव रखा, जिसे बुलाया गया था expected<T>, जिसके लिए मानक प्रस्ताव बनाए गए थे

मैं रूस्ट नामकरण और एपीआई से सिर्फ इसलिए चिपका रहूंगा क्योंकि ... यह प्रलेखित है और काम करता है। बेशक, रस्ट में एक निफ्टी ?प्रत्यय ऑपरेटर है जो कोड को बहुत अधिक मीठा बना देगा; C ++ में, हम इसका अनुकरण करने TRYके लिए मैक्रो और GCC के स्टेटमेंट एक्सप्रेशन का उपयोग करेंगे।

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); })

नोट: यह Resultएक प्लेसहोल्डर है। एक उचित कार्यान्वयन एनकैप्सुलेशन और ए का उपयोग करेगा union। हालांकि यह बिंदु भर पाने के लिए पर्याप्त है।

जो मुझे लिखने की अनुमति देता है ( इसे कार्रवाई में देखें ):

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);
}

जो मुझे बहुत साफ लगता है:

  • त्रुटि कोड (या आपकी Successकक्षा) के उपयोग के विपरीत , त्रुटियों की जांच करने के लिए भूल जाने से कुछ यादृच्छिक व्यवहार के बजाय एक रनटाइम त्रुटि 1 हो जाएगी ,
  • अपवादों के उपयोग के विपरीत, यह कॉल साइट पर स्पष्ट है कि कौन से कार्य विफल हो सकते हैं इसलिए कोई आश्चर्य नहीं है।
  • C ++ - 2X मानक के साथ, हम conceptsमानक में प्राप्त कर सकते हैं । यह इस तरह की प्रोग्रामिंग को और अधिक सुखद बना देगा क्योंकि हम त्रुटि प्रकार पर चुनाव छोड़ सकते हैं। उदाहरण के लिए std::vector, परिणाम के कार्यान्वयन के साथ, हम एक ही बार में सभी संभव समाधानों की गणना कर सकते हैं। या जैसा कि आपने प्रस्तावित किया था, हम त्रुटि से निपटने में सुधार कर सकते हैं।

1 एक अच्छी तरह से समझाया Resultकार्यान्वयन के साथ;)


नोट: अपवाद के विपरीत, इस हल्के Resultमें बैकट्रैक्स नहीं हैं, जो लॉगिंग को कम कुशल बनाता है; आपको उस फ़ाइल / लाइन नंबर पर लॉग इन करना उपयोगी हो सकता है जिस पर त्रुटि संदेश उत्पन्न होता है, और आम तौर पर एक समृद्ध त्रुटि संदेश लिखने के लिए। यह फ़ाइल / लाइन पर कब्जा करके TRYमैक्रो का उपयोग करने, अनिवार्य रूप से बैकट्रेस को मैन्युअल रूप से बनाने या प्लेटफ़ॉर्म-विशिष्ट कोड और पुस्तकालयों जैसे libbacktraceकॉलस्टैक में प्रतीकों को सूचीबद्ध करने के लिए उपयोग करके कंपाउंड किया जा सकता है।


हालांकि, एक बड़ा कैवेट है: मौजूदा सी ++ लाइब्रेरी, और यहां तक ​​कि std, अपवादों पर आधारित हैं। यह इस शैली का उपयोग करने के लिए एक कठिन लड़ाई होगी, क्योंकि किसी भी 3 पार्टी लाइब्रेरी की एपीआई को एक एडाप्टर में लपेटा जाना चाहिए ...


3
वह स्थूल दिखता है ... बहुत गलत। मुझे लगता है ({...})कि कुछ gcc एक्सटेंशन होगा, लेकिन फिर भी, यह नहीं होना चाहिए if (!result.ok) return result;? आपकी स्थिति पीछे की ओर दिखाई देती है और आप त्रुटि की एक अनावश्यक प्रतिलिपि बनाते हैं।
मूइंग डक

@MooingDuck उत्तर बताता है कि ({...})gcc के कथन अभिव्यक्ति है
jamesdlin


1
यदि आप C ++ 17 का उपयोग कर रहे हैं तो std::variantइसे लागू करने के लिए उपयोग करने की सलाह Resultदूंगा। इसके अलावा, यदि आप एक त्रुटि को नजरअंदाज करते हैं, तो चेतावनी प्राप्त करने के लिए, उपयोग करें[[nodiscard]]
जस्टिन

2
@ जस्टिन: std::variantअपवाद-हैंडलिंग के आसपास ट्रेड-ऑफ को देखते हुए उपयोग करना या नहीं करना कुछ हद तक स्वाद की बात है। [[nodiscard]]वास्तव में एक शुद्ध जीत है।
मैथ्यू एम।

46

CLAIM: अपवाद तंत्र त्रुटियों से निपटने के लिए एक भाषा शब्दार्थ है

अपवाद एक नियंत्रण-प्रवाह तंत्र हैं। इस नियंत्रण-प्रवाह तंत्र के लिए प्रेरणा, विशेष रूप से गैर-हैंडलिंग हैंडलिंग कोड से त्रुटि हैंडलिंग को अलग कर रही थी , इस मामले में कि त्रुटि हैंडलिंग बहुत दोहरावदार है और तर्क के मुख्य भाग के लिए बहुत कम प्रासंगिकता रखती है।

CLAIM: मेरे लिए, कार्य प्राप्त न करने के लिए एक फ़ंक्शन का "कोई बहाना" नहीं है: या तो हमने गलत तरीके से पूर्व / पोस्ट की शर्तों को परिभाषित किया है ताकि फ़ंक्शन परिणाम सुनिश्चित नहीं कर सके, या कुछ विशिष्ट असाधारण मामले को विकसित करने में समय बिताने के लिए पर्याप्त महत्वपूर्ण नहीं माना जाता है एक तरकीब

विचार करें: मैं एक फ़ाइल बनाने की कोशिश करता हूं। स्टोरेज डिवाइस फुल है।

अब, यह मेरी पूर्व शर्त को परिभाषित करने में विफलता नहीं है: आप सामान्य रूप से पूर्व शर्त के रूप में "पर्याप्त भंडारण होना चाहिए" का उपयोग नहीं कर सकते हैं, क्योंकि साझा भंडारण दौड़ की स्थिति के अधीन है जो इसे संतुष्ट करना असंभव बनाता है।

तो, क्या मेरा कार्यक्रम किसी तरह कुछ जगह खाली कर सकता है और फिर सफलतापूर्वक आगे बढ़ना चाहिए, अन्यथा मैं "समाधान विकसित करने" के लिए बहुत आलसी हूं? यह स्पष्ट रूप से निरर्थक लगता है। साझा भंडारण के प्रबंधन के लिए "समाधान" मेरे कार्यक्रम के दायरे से बाहर है , और मेरे कार्यक्रम को इनायत से विफल होने की अनुमति देता है, और उपयोगकर्ता को कुछ स्थान जारी करने, या कुछ और भंडारण को जोड़ने के बाद पुनः चलाने के लिए ठीक है


आपकी सफलता वर्ग क्या करता है, यह आपके प्रोग्राम लॉजिक के साथ बहुत ही स्पष्टता से त्रुटि-निवारण है। हर एक फंक्शन को जांचने की ज़रूरत है, दौड़ने से पहले, कि क्या कुछ त्रुटि पहले से ही थी जिसका मतलब है कि उसे कुछ भी नहीं करना चाहिए। प्रत्येक लाइब्रेरी फ़ंक्शन को एक और फ़ंक्शन में लिपटे जाने की आवश्यकता होती है, जिसमें एक और तर्क होता है (और उम्मीद है कि पूर्ण अग्रेषण), जो बिल्कुल यही काम करता है।

यह भी ध्यान दें कि आपके mySqrtफ़ंक्शन को एक मान वापस करने की आवश्यकता है, भले ही वह विफल हो (या एक पूर्व फ़ंक्शन विफल हो गया था)। इसलिए, आप या तो एक जादू मूल्य (जैसे NaN) वापस कर रहे हैं , या अपने कार्यक्रम में एक अनिश्चित मूल्य इंजेक्ट कर रहे हैं और कुछ भी उम्मीद नहीं करता है कि आपके द्वारा निष्पादित सफलता राज्य की जांच किए बिना।

शुद्धता के लिए - और प्रदर्शन - किसी भी प्रगति नहीं कर पाने के बाद नियंत्रण को वापस दायरे से बाहर करना बेहतर होगा। अपवाद और सी-स्टाइल स्पष्ट त्रुटि की जाँच के साथ प्रारंभिक वापसी दोनों इसे पूरा करते हैं।


तुलना के लिए, आपके विचार का एक उदाहरण जो वास्तव में काम करता है, हास्केल में त्रुटि मोनाड है। आपके सिस्टम पर लाभ यह है कि आप अपने तर्क के थोक को सामान्य रूप से लिखते हैं, और फिर इसे मोनाड में लपेटते हैं जो एक चरण में विफल होने पर मूल्यांकन को रोक देता है। इस तरह से त्रुटि-हैंडलिंग प्रणाली को सीधे छूने वाला एकमात्र कोड वह कोड होता है जो विफल हो सकता है (एक त्रुटि फेंकें) और वह कोड जिसे विफलता का सामना करना पड़ता है (अपवाद को पकड़ें)।

मुझे यकीन नहीं है कि मोनड शैली और आलसी मूल्यांकन सी ++ के लिए अच्छी तरह से अनुवाद करते हैं।


1
आपके उत्तर के लिए धन्यवाद, यह विषय में प्रकाश जोड़ता है। मुझे लगता है कि उपयोगकर्ता तब असहमत होगा and allowing my program to fail gracefully, and be re-runजब वह सिर्फ 2h काम खो देगा:
एड्रियन मैयर

14
आपके समाधान का मतलब है कि हर जगह आप एक फ़ाइल बना सकते हैं, आपको स्थिति को ठीक करने और फिर से प्रयास करने के लिए उपयोगकर्ता को संकेत देना होगा। फिर हर दूसरी चीज जो गलत हो सकती है, आपको किसी तरह स्थानीय स्तर पर ठीक करने की भी जरूरत है। अपवादों के साथ, आप केवल std::exceptionतार्किक ऑपरेशन के उच्च स्तर पर पकड़ते हैं, उपयोगकर्ता को बताएं "ex.what ()" के कारण एक्स विफल हो गया है , और जब वे तैयार हों तो पूरे ऑपरेशन को फिर से करने की पेशकश करें।
बेकार

13
@ एड्रियनमेयर: "इनायत को नाकाम रहने और फिर से चलाने की अनुमति" को भी लागू किया जा सकता है showing the Save dialog again along with an error message and allowing the user to specify an alternative location to try। यह एक समस्या का एक सुंदर संभाल है जो आमतौर पर उस कोड से नहीं किया जा सकता है जो यह पता लगाता है कि पहला भंडारण स्थान भरा हुआ है।
बार्ट वैन इनगेन शेनौ जूल

3
@ यूज़लेस आलसी मूल्यांकन का एरर मोनाड के उपयोग से कोई लेना-देना नहीं है, जैसा कि सख्त मूल्यांकन भाषाओं जैसे कि रस्ट, ओकेमेल, और एफ # से पता चलता है कि सभी इसका भारी उपयोग करते हैं।
8 बिट्ट्री

1
@Useless IMO गुणवत्ता सॉफ्टवेयर के लिए, यह करता है मेकअप भावना है कि "हर जगह आप एक फ़ाइल बना सकता है, तो आप स्थिति और पुन: प्रयास को ठीक करने के लिए संकेत की जरूरत है"। प्रारंभिक प्रोग्रामर अक्सर त्रुटि-पुनर्प्राप्ति की दिशा में उल्लेखनीय लंबाई तक चले गए, कम से कम नूथ के टीएक्स कार्यक्रम उनमें से भरे हुए हैं। और अपने "साक्षर प्रोग्रामिंग" ढांचे के साथ, उन्होंने त्रुटि-हैंडलिंग को किसी अन्य अनुभाग में रखने का एक तरीका ढूंढ लिया, ताकि कोड पठनीय रहे और त्रुटि पुनर्प्राप्ति अधिक सावधानी के साथ लिखी जाए (क्योंकि जब आप त्रुटि-पुनर्प्राप्ति अनुभाग लिख रहे हैं, तो) यह बात है और प्रोग्रामर एक बेहतर काम करने के लिए जाता है)।
श्रीवत्सआर

15

मैं एक परियोजना में इस तरह के प्रतिमान का उपयोग करने के निहितार्थों को बेहतर ढंग से समझना चाहूंगा:

  • क्या समस्या का आधार सही है? या मैं कुछ प्रासंगिक याद किया?
  • क्या समाधान एक अच्छा वास्तुशिल्प विचार है? या कीमत बहुत अधिक है?

आपका दृष्टिकोण आपके स्रोत कोड में कुछ बड़ी समस्याएं लाता है:

  • यह उस ग्राहक कोड पर निर्भर करता है जिसे हमेशा याद रखना चाहिए कि उसका मूल्य क्या है s। यह त्रुटि से निपटने के दृष्टिकोण के लिए उपयोग रिटर्न कोड के साथ आम है , और एक कारण यह है कि अपवाद भाषा में पेश किए गए थे: अपवादों के साथ, यदि आप असफल होते हैं, तो आप चुपचाप विफल नहीं होते हैं।

  • इस दृष्टिकोण के साथ आप जितना अधिक कोड लिखेंगे, त्रुटि हैंडलिंग के लिए आपको उतना ही अधिक त्रुटि बॉयलरप्लेट कोड भी जोड़ना होगा (आपका कोड अब न्यूनतर नहीं है) और आपके रखरखाव का प्रयास बढ़ता चला जाता है।

लेकिन एक डेवलपर के रूप में मेरे कई साल मुझे एक अलग दृष्टिकोण से समस्या को देखने के लिए बनाते हैं:

इन समस्याओं के समाधान के लिए तकनीकी लीड स्तर या टीम स्तर पर संपर्क किया जाना चाहिए:

प्रोग्रामर अपवाद को फेंककर अपने कार्य को सरल बनाते हैं जब विशिष्ट मामले को ध्यान से लागू करने के लिए बहुत कम लगता है। इसके विशिष्ट मामले इस प्रकार हैं: मेमोरी इश्यूज, डिस्क फुल इश्यूज, दूषित फाइल इश्यूज आदि। यह पर्याप्त हो सकता है, लेकिन यह हमेशा एक आर्किटेक्चरल लेवल से तय नहीं किया जाता है।

यदि आप अपने आप को हर प्रकार के अपवाद को संभालते हुए पाते हैं, जिसे हर समय फेंक दिया जा सकता है, तो डिजाइन अच्छा नहीं है; क्या त्रुटियों को नियंत्रित किया जाता है, परियोजना के लिए विनिर्देशों के अनुसार तय किया जाना चाहिए, न कि क्या देवों को लागू करने जैसा लगता है।

स्वचालित परीक्षण स्थापित करके पता, इकाई परीक्षणों और कार्यान्वयन के विनिर्देश को अलग करना (दो अलग-अलग व्यक्ति ऐसा करते हैं)।

प्रोग्रामर ध्यान से प्रलेखन नहीं पढ़ता है [...] इसके अलावा, जब भी वे जानते हैं, वे वास्तव में उन्हें प्रबंधित नहीं करते हैं।

आप अधिक कोड लिखकर इसे संबोधित नहीं करेंगे। मुझे लगता है कि आपकी सबसे अच्छी शर्त सावधानीपूर्वक लागू की गई कोड समीक्षाएं हैं।

प्रोग्रामर अपवादों को जल्दी पकड़ नहीं पाते हैं, और जब वे ऐसा करते हैं, तो यह ज्यादातर लॉग इन करना और आगे फेंकना होता है। (प्रथम बिंदु को देखें)।

उचित त्रुटि हैंडलिंग कठिन है, लेकिन वापसी मूल्यों की तुलना में अपवादों के साथ कम थकाऊ (चाहे वे वास्तव में लौटे हों या i / o तर्क के रूप में पारित किए गए हों)।

त्रुटि से निपटने का सबसे मुश्किल हिस्सा यह नहीं है कि आप त्रुटि कैसे प्राप्त करते हैं, लेकिन यह सुनिश्चित करने के लिए कि आपका आवेदन त्रुटियों की उपस्थिति में एक सुसंगत स्थिति रखता है।

इसे संबोधित करने के लिए, त्रुटि स्थितियों (अधिक परीक्षण, अधिक इकाई / एकीकरण परीक्षण, आदि) की पहचान करने और चलाने के लिए अधिक ध्यान देने की आवश्यकता है।


12
एक त्रुटि के बाद सभी कोड को छोड़ दिया जाता है, यदि आपको हर बार तर्क के रूप में एक उदाहरण प्राप्त करना याद है । इसका मतलब है कि "जितना अधिक कोड आप इस दृष्टिकोण के साथ लिखते हैं, उतना ही अधिक त्रुटि बॉयलरप्लेट कोड आपको भी जोड़ना होगा"। आपको अपने कोड को सफलता के उदाहरण पर ifs के साथ जोड़ना होगा, और हर बार जब आप भूल जाते हैं, तो यह एक बग है। जाँच करने की भूल करने के कारण दूसरी समस्या: जब तक आप फिर से जाँच नहीं करते हैं, तब तक निष्पादित होने वाले कोड को बिल्कुल निष्पादित नहीं किया जाना चाहिए (यदि आप जाँच करना भूल जाते हैं, तो आपके डेटा को दूषित कर देता है)।
utnapistim

11
नहीं, अपवाद को हैंडल करना (या त्रुटि कोड को वापस करना) कोई दुर्घटना नहीं है - जब तक कि त्रुटि / अपवाद तार्किक रूप से घातक नहीं है या आप इसे संभालना नहीं चुनते हैं। आपके पास अभी भी त्रुटि के मामले को संभालने का अवसर है, बिना हर चरण को स्पष्ट रूप से जाँचने के कि क्या कोई त्रुटि पहले हुई है
बेकार

11
@AdrianMaire लगभग हर उस एप्लिकेशन पर, जिस पर मैं काम करता हूं, मैं बहुत हद तक चुपचाप जारी रहने वाली दुर्घटना को प्राथमिकता देता हूं। मैं व्यापार-महत्वपूर्ण सॉफ़्टवेयर पर काम करता हूं जहां कुछ खराब आउटपुट लेते हैं और उस पर काम करना जारी रखते हैं, जिसके परिणामस्वरूप बहुत सारा पैसा खो सकता है। यदि शुद्धता महत्वपूर्ण और दुर्घटनाग्रस्त स्वीकार्य है, तो अपवादों का यहां बहुत बड़ा लाभ है।
क्रिस हेस

1
@ एड्रियनमैयर - मुझे लगता है कि एक अपवाद को संभालने के लिए भूल जाना बहुत कठिन है, अगर एक बयान को भूलने की आपकी विधि ... इसके अलावा - अपवादों का मुख्य लाभ यह है कि कौन सी परत उन्हें संभालती है। आप एक अनुप्रयोग स्तर त्रुटि संदेश दिखाने के लिए सिस्टम अपवाद बबल को और ऊपर जाने की इच्छा कर सकते हैं, लेकिन उन स्थितियों को संभाल सकते हैं जिन्हें आप निचले स्तर पर जानते हैं। यदि आप थर्ड पार्टी लाइब्रेरी या अन्य डेवलपर्स कोड का उपयोग कर रहे हैं, तो यह वास्तव में एकमात्र विकल्प है ...
मिलनी

5
@ एड्रियन कोई गलती नहीं है, आपको लगता है कि मैंने जो लिखा है, उसमें से दूसरे हिस्से में गलत लिखा गया था। मेरा पूरा मुद्दा यह नहीं है कि परीक्षण / विकास के दौरान अपवाद फेंक दिया जाएगा और डेवलपर्स को एहसास होगा कि उन्हें उन्हें संभालने की आवश्यकता है। मुद्दा यह है कि अनियंत्रित त्रुटि कोड के परिणाम में उत्पादन में पूरी तरह से अखंडित अपवाद का परिणाम बेहतर है। यदि आप त्रुटि कोड को याद करते हैं, तो आप गलत परिणाम प्राप्त करते हैं और जारी रखते हैं। यदि आप अपवाद को याद करते हैं, तो एप्लिकेशन क्रैश हो जाता है और चलना जारी नहीं रहता है, तो आपको कोई परिणाम नहीं मिलता है गलत परिणाम नहीं मिलते हैं । (cont।)
मि
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.