अपवाद: जल्दी क्यों फेंकें? क्यों देर से पकड़ा?


156

अलगाव में अपवाद हैंडलिंग के बारे में कई प्रसिद्ध सर्वोत्तम प्रथाएं हैं। मुझे पता है कि "क्या करें और क्या न करें" अच्छी तरह से समझ में आता है, लेकिन जब बड़े वातावरण में सर्वोत्तम प्रथाओं या पैटर्न की बात आती है तो चीजें जटिल हो जाती हैं। "जल्दी फेंको, देर से पकड़ना" - मैंने कई बार सुना है और यह अभी भी मुझे भ्रमित करता है।

मुझे जल्दी क्यों फेंकना चाहिए और देर से पकड़ना चाहिए, अगर कम-स्तर-परत पर एक शून्य सूचक अपवाद फेंक दिया जाता है? मुझे इसे एक उच्च परत पर क्यों पकड़ना चाहिए? उच्च स्तर पर निम्न स्तर के अपवाद को पकड़ना मेरे लिए कोई मतलब नहीं है, जैसे कि व्यावसायिक परत। यह प्रत्येक परत की चिंताओं का उल्लंघन लगता है।

निम्नलिखित स्थिति की कल्पना करें:

मेरे पास एक सेवा है जो एक आंकड़े की गणना करती है। आंकड़े की गणना करने के लिए सेवा कच्चे डेटा और कुछ अन्य सेवाओं की गणना तैयार करने के लिए एक रिपॉजिटरी का उपयोग करती है। यदि डेटा पुनर्प्राप्ति परत में कुछ गलत हो गया है, तो मुझे DataRetrievalException को उच्च स्तर पर क्यों फेंकना चाहिए? इसके विपरीत, मैं अपवाद को एक सार्थक अपवाद में लपेटना पसंद करूंगा, उदाहरण के लिए एक CalculServiceException।

जल्दी क्यों फेंकते हैं, देर से क्यों पकड़ते हैं?


104
"देर से पकड़ना" के पीछे का विचार यह है कि जितना संभव हो उतना देर से पकड़ना, जितना जल्दी हो सके उतना जल्दी पकड़ लेना उपयोगी है। यदि आपके पास एक फ़ाइल पार्सर है, उदाहरण के लिए, कोई फ़ाइल नहीं मिली तो कोई बिंदु नहीं है। आप उस के साथ क्या करना चाहते हैं, आपका पुनर्प्राप्ति पथ क्या है? एक नहीं है, इसलिए पकड़ नहीं है। अपने लेक्सर पर जाएं, आप वहां क्या करते हैं, आप इस तरह से कैसे ठीक हो जाते हैं कि आपका कार्यक्रम जारी रह सकता है? यह, अपवाद को पारित नहीं कर सकता। आपका स्कैनर इसे कैसे संभाल सकता है? यह नहीं हो सकता, इसे पास होने दो। कॉलिंग कोड इसे कैसे संभाल सकता है? यह या तो किसी अन्य फ़ाइलपथ की कोशिश कर सकता है या उपयोगकर्ता को सचेत कर सकता है, इसलिए पकड़ें।
फॉशी

16
ऐसे बहुत कम मामले हैं जहां NullPointerException (मुझे लगता है कि NPE का मतलब है) को कभी पकड़ा जाना चाहिए; यदि संभव हो तो, इसे पहली जगह से बचा जाना चाहिए। यदि आपके पास NullPointerException है, तो आपके पास कुछ टूटे हुए कोड हैं जिन्हें ठीक करने की आवश्यकता है। यह सबसे अधिक संभावना है एक आसान तय भी है।
फिल

6
कृपया, दोस्तों, इस प्रश्न को एक डुप्लिकेट के रूप में बंद करने का सुझाव देने से पहले, यह देख लें कि अन्य प्रश्न का उत्तर इस प्रश्न का उत्तर बहुत अच्छी तरह से नहीं देता है।
Doc Brown

1
(Citebot) Today.java.net/article/2003/11/20/… यदि यह उद्धरण की उत्पत्ति नहीं है, तो कृपया उस स्रोत का संदर्भ प्रदान करें जो आपको लगता है कि सबसे अधिक संभावना वाला मूल उद्धरण है।
रवांग

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

जवाबों:


118

मेरे अनुभव में, उस बिंदु पर अपवादों को फेंकने के लिए इसका सबसे अच्छा है जहां त्रुटियां होती हैं। आप ऐसा करते हैं क्योंकि यह वह बिंदु है जहां आप सबसे अधिक जानते हैं कि अपवाद को क्यों ट्रिगर किया गया था।

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

आखिरकार अपवाद एक परत तक पहुंच जाएगा जहां आप कोड प्रवाह पर निर्णय लेने में सक्षम हैं (उदाहरण के लिए कार्रवाई के लिए एक संकेत उपयोगकर्ता)। यह वह बिंदु है जहां आपको अंत में अपवाद को संभालना चाहिए और सामान्य निष्पादन जारी रखना चाहिए।

अपने कोड आधार के साथ अभ्यास और अनुभव के साथ, त्रुटियों के लिए अतिरिक्त संदर्भ जोड़ने के लिए न्याय करना काफी आसान हो जाता है, और जहां यह वास्तव में सबसे समझदार है, अंत में त्रुटियों को संभालना।

पकड़ → रीथ्रो

ऐसा करें जहां आप उपयोगी रूप से अधिक जानकारी जोड़ सकते हैं जो एक डेवलपर को समस्या को समझने के लिए सभी परतों के माध्यम से काम करने से बचाएगा।

कैच → हैंडल

ऐसा करें जहां आप अंतिम निर्णय ले सकते हैं कि क्या उपयुक्त है, लेकिन सॉफ़्टवेयर के माध्यम से अलग-अलग निष्पादन प्रवाह।

कैच → त्रुटि वापसी

जब भी ऐसी परिस्थितियाँ हों जहाँ यह उपयुक्त हो, अपवादों को पकड़ना और कॉल करने वाले को एक त्रुटि मान देकर कैच → रेथ्रो कार्यान्वयन में विचार करना चाहिए।


हां, मुझे पहले से ही पता है और इसके प्रश्न से बाहर है, कि मुझे उस बिंदु पर अपवाद फेंकना चाहिए जहां त्रुटि उत्पन्न होती है। लेकिन मुझे एनपीई क्यों नहीं पकड़ना चाहिए और इसके बजाय इसे स्टैकट्रेस पर चढ़ने देना चाहिए? मैं हमेशा एनपीई को पकड़ता हूं और इसे एक सार्थक अपवाद में लपेटता हूं। मुझे इस बात का भी कोई फायदा नहीं दिख रहा है कि मुझे सेवा या यूआई लेयर तक डीएओ-अपवाद क्यों फेंकना चाहिए। मैं हमेशा इसे सेवा स्तर पर पकड़ता हूं और अतिरिक्त विस्तृत जानकारी के साथ सेवा अपवाद में इसे लपेटता हूं, क्यों सेवा विफल रही।
shylynx

8
@shylynx एक अपवाद को पकड़ना और फिर एक अधिक सार्थक अपवाद को पुनर्जीवित करना एक अच्छी बात है। आप जो नहीं कर रहे हैं वह बहुत जल्दी एक अपवाद को पकड़ रहा है और फिर इसे वापस नहीं लेना चाहिए। गलती जो कह रही है कि इसके बारे में चेतावनी बहुत जल्दी पकड़ रही है, और फिर कोड में गलत परत पर इसे संभालने का प्रयास किया जा रहा है।
साइमन बी

अपवाद प्राप्त करने के बिंदु पर संदर्भ स्पष्ट करना आपकी टीम के डेवलपर्स के लिए जीवन को आसान बनाता है। समस्या को समझने के लिए एक एनपीई को आगे की जांच की आवश्यकता है
माइकल शॉ

4
@shylynx एक सवाल पूछ सकता है, "आपके पास अपने कोड में एक बिंदु क्यों है जो एक फेंक सकता है NullPointerException? nullएक अपवाद (शायद एक IllegalArgumentException) के लिए जल्द ही जांच न करें और फेंक दें ताकि फोन करने वाले को ठीक से पता चल जाए कि खराब कहाँ से nullपास हुआ है?" मेरा मानना ​​है कि यह कहावत का "शुरुआती जल्दी" हिस्सा होगा।
jpmc26

2
@jpmc I ने NPE को केवल एक उदाहरण के रूप में लेयर्स और अपवादों के आसपास की चिंताओं पर जोर देने के लिए लिया। मैं इसे एक IllegalArgumentException के साथ बदल सकता हूं।
shylynx

56

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

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

अपवादों को सही प्रकार से लपेटना विशुद्ध रूप से रूढ़िवादी चिंता है।


1
+1 स्पष्ट रूप से समझाने के लिए कि विभिन्न स्तर क्यों मायने रखते हैं। फ़ाइल सिस्टम त्रुटि पर उत्कृष्ट उदाहरण।
जुआन कार्लोस कोटो

24

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

क्यों अपवाद हैं?

इस बात को लेकर काफी भ्रम है कि अपवाद पहले स्थान पर क्यों हैं। मुझे यहां बड़ा रहस्य साझा करने दें: अपवादों और अपवादों से निपटने का कारण ... ABSTRACTION

क्या आपने इस तरह कोड देखा है:

static int divide(int dividend, int divisor) throws DivideByZeroException {
    if (divisor == 0)
        throw new DivideByZeroException(); // that's a checked exception indeed

    return dividend / divisor;
}

static void doDivide() {
    int a = readInt();
    int b = readInt(); 
    try {
        int res = divide(a, b);
        System.out.println(res);
    } catch (DivideByZeroException e) {
        // checked exception... I'm forced to handle it!
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

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

static int divide(int dividend, int divisor) {
    // throws unchecked ArithmeticException for 0 divisor
    return dividend / divisor;
}

static void doDivide() {
    int a = readInt();
    int b = readInt();
    if (b != 0) {
        int res = divide(a, b);
        System.out.println(res);
    } else {
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

वैकल्पिक रूप से, आप इस तरह OOP शैली पर पूर्ण कमांडो जा सकते हैं:

static class Division {
    final int dividend;
    final int divisor;

    private Division(int dividend, int divisor) {
        this.dividend = dividend;
        this.divisor = divisor;
    }

    public boolean check() {
        return divisor != 0;
    }

    public int eval() {
        return dividend / divisor;
    }

    public static Division with(int dividend, int divisor) {
        return new Division(dividend, divisor);
    }
}

static void doDivide() {
    int a = readInt();
    int b = readInt(); 
    Division d = Division.with(a, b);
    if (d.check()) {
        int res = d.eval();
        System.out.println(res);
    } else {
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

जैसा कि आप देखते हैं, कॉलर कोड में प्री-चेक का बोझ होता है, लेकिन इसके बाद कोई अपवाद हैंडलिंग नहीं करता है। यदि ArithmeticExceptionकभी कोई कॉलिंग से आता है divideया करता है eval, तो यह आप ही हैं, जिसे अपवाद हैंडलिंग करना है और अपना कोड ठीक करना है, क्योंकि आप भूल गए हैं check()। इसी तरह के कारणों के लिए एक पकड़ना NullPointerExceptionलगभग हमेशा गलत काम है।

अब कुछ लोग हैं जो कहते हैं कि वे विधि / फ़ंक्शन हस्ताक्षर में असाधारण मामलों को देखना चाहते हैं, अर्थात आउटपुट डोमेन को स्पष्ट रूप से विस्तारित करना चाहते हैं । वे ऐसे हैं जो अपवादों की जाँच करते हैं । बेशक, आउटपुट डोमेन को बदलने से किसी भी प्रत्यक्ष कॉलर कोड को अनुकूलित करने के लिए मजबूर होना चाहिए, और यह वास्तव में चेक किए गए अपवादों के साथ हासिल किया जाएगा। लेकिन आपको इसके लिए अपवादों की आवश्यकता नहीं है! यही कारण है कि आपके पास Nullable<T> सामान्य कक्षाएं , केस क्लासेस , बीजीय डेटा प्रकार और यूनियन प्रकार हैंकुछ ओओ लोग इस तरह की साधारण त्रुटि के मामलों में भी वापसी करना पसंद कर सकते हैंnull :

static Integer divide(int dividend, int divisor) {
    if (divisor == 0) return null;
    return dividend / divisor;
}

static void doDivide() {
    int a = readInt();
    int b = readInt(); 
    Integer res = divide(a, b);
    if (res != null) {
        System.out.println(res);
    } else {
        System.out.println("Nah, can't divide by zero. Try again.");
    }
}

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

कैच लेट कैसे करें?

तो हम यहाँ हैं। मैंने अपने तरीके से यह दिखाने के लिए तर्क दिया है कि उपरोक्त परिदृश्यों में अपवादों का उपयोग करना अपवाद नहीं है। हालांकि एक वास्तविक उपयोग मामला मौजूद है, जहां अपवाद से निपटने की पेशकश की गई अमूर्तता और अप्रत्यक्षता अपरिहार्य है। इस तरह के उपयोग को समझने से कैच लेट सिफारिश को भी समझने में मदद मिलेगी ।

इस मामले का उपयोग है: संसाधन के खिलाफ प्रोग्रामिंग ...

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

तो उन कार्यान्वयन विशिष्ट अपवादों को कौन संभाल सकता है? क्या व्यापार तर्क में किसी भी संसाधन विशिष्ट अपवाद को संभालना संभव है? नहीं, यह नहीं है। व्यापार तर्क को अमूर्तता के खिलाफ क्रमादेशित किया जाता है, जो उन कार्यान्वयन विशिष्ट अपवाद विवरणों के ज्ञान को बाहर करता है।

"अहा!", आप कह सकते हैं: "लेकिन इसीलिए हम अपवादों को तोड़ सकते हैं और अपवाद पदानुक्रम बना सकते हैं" ( मिस्टर स्प्रिंग की जाँच करें !)। मैं आपको बता दूं, यह एक पतन है। सबसे पहले, ओओपी पर प्रत्येक उचित पुस्तक कहती है कि ठोस विरासत खराब है, फिर भी किसी तरह जेवीएम का यह मुख्य घटक, अपवाद हैंडलिंग, कंक्रीट विरासत के साथ निकटता से जुड़ा हुआ है। विडंबना यह है कि जोशुआ बलोच अपनी प्रभावी जावा पुस्तक लिख नहीं सकते थे इससे पहले कि वह एक काम कर रहे जेवीएम के साथ अनुभव प्राप्त कर सकें, क्या वह कर सकते थे? यह अगली पीढ़ी के लिए "सबक सीखा" पुस्तक से अधिक है। दूसरी बात, और इससे भी महत्वपूर्ण बात यह है कि यदि आप एक उच्च-स्तरीय अपवाद को पकड़ते हैं तो आप इसे कैसे संभालेंगे?PatientNeedsImmediateAttentionException: क्या हमें उसे एक गोली देनी है या उसके पैर काटना है !? सभी संभावित उपवर्गों पर एक स्विच स्टेटमेंट के बारे में कैसे? वहां तुम्हारा बहुरूपता जाता है, वहां अमूर्तता जाती है। सही पकड़े हैं।

तो संसाधन विशिष्ट अपवादों को कौन संभाल सकता है? यह वह होना चाहिए जो समास जानता हो! जिस ने संसाधन को तुरंत बदल दिया! "वायरिंग" कोड बेशक! इसकी जांच करें:

व्यापार तर्क अमूर्तता के खिलाफ कोडित ... कोई परिणाम नहीं त्रुटि!

static interface InputResource {
    String fetchData();
}

static interface OutputResource {
    void writeData(String data);
}

static void doMyBusiness(InputResource in, OutputResource out, int times) {
    for (int i = 0; i < times; i++) {
        System.out.println("fetching data");
        String data = in.fetchData();
        System.out.println("outputting data");
        out.writeData(data);
    }
}

इस बीच कहीं और ठोस कार्यान्वयन ...

static class ConstantInputResource implements InputResource {
    @Override
    public String fetchData() {
        return "Hello World!";
    }
}

static class FailingInputResourceException extends RuntimeException {
    public FailingInputResourceException(String message) {
        super(message);
    }
}

static class FailingInputResource implements InputResource {
    @Override
    public String fetchData() {
        throw new FailingInputResourceException("I am a complete failure!");
    }
}

static class StandardOutputResource implements OutputResource {
    @Override
    public void writeData(String data) {
        System.out.println("DATA: " + data);
    }
}

और अंत में वायरिंग कोड ... कंक्रीट संसाधन अपवादों को कौन संभालता है? जो उनके बारे में जानता है!

static void start() {
    InputResource in1 = new FailingInputResource();
    InputResource in2 = new ConstantInputResource();
    OutputResource out = new StandardOutputResource();

    try {
        ReusableBusinessLogicClass.doMyBusiness(in1, out, 3);
    }
    catch (FailingInputResourceException e)
    {
        System.out.println(e.getMessage());
        System.out.println("retrying...");
        ReusableBusinessLogicClass.doMyBusiness(in2, out, 3);
    }
}

अब मेरे साथ सहन करो। इसके बाद के संस्करण कोड है सरलीकृत। आप कह सकते हैं कि आपके पास आईओसी कंटेनर प्रबंधित संसाधनों के कई स्कोप के साथ एक एंटरप्राइज़ एप्लिकेशन / वेब कंटेनर है, और आपको सत्र के लिए स्वचालित पुनर्प्रयास और पुनर्संरचना या अनुरोध गुंजाइश संसाधनों आदि की आवश्यकता है। निचले स्तर के स्कोप पर तारों के तर्क को अमूर्त कारखानों को दिया जा सकता है। संसाधन बनाएं, इसलिए सटीक कार्यान्वयन के बारे में पता नहीं है। केवल उच्च स्तर के स्कोप्स को वास्तव में पता होगा कि उन निचले स्तर के संसाधनों को क्या अपवाद फेंक सकते हैं। अब पकड़ो!

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

सुम्मा सारांश

इसलिए मेरी व्याख्या के अनुसार सबसे सुविधाजनक जगह पर अपवादों को पकड़ने के लिए देर से पकड़ने का मतलब है, जहां आप किसी भी तरह से ब्रेकिंग नहीं कर रहे हैं । बहुत जल्दी पकड़ना नहीं है! उस परत पर अपवादों को पकड़ो जहां आप संसाधन सार के फेंकने वाले ठोस अपवाद बनाते हैं, वह परत जो अमूर्त तत्वों को जानती है। "वायरिंग" परत।

HTH। हैप्पी कोडिंग!


आप सही हैं कि इंटरफ़ेस की आपूर्ति करने वाले कोड को इस बारे में अधिक पता होगा कि इंटरफ़ेस का उपयोग करने वाले कोड की तुलना में क्या गलत हो सकता है, लेकिन मान लें कि एक ही इंटरफ़ेस प्रकार के दो संसाधनों का उपयोग करता है और विफलताओं को अलग तरीके से संभालने की आवश्यकता है? या यदि उन संसाधनों में से एक आंतरिक रूप से, कार्यान्वयन विस्तार के रूप में जो इसके निर्माता को ज्ञात नहीं है, उसी प्रकार के अन्य नेस्टेड संसाधनों का उपयोग करता है? इस समस्या के मूल कारण को देखने के लिए व्यवसायिक परत को फेंकने WrappedFirstResourceExceptionया WrappedSecondResourceException"वायरिंग" परत की आवश्यकता होने पर उस अपवाद को देखने के लिए ...
Supercat

... icky हो सकता है, लेकिन यह मानने से बेहतर होगा कि किसी भी FailingInputResourceअपवाद के साथ एक ऑपरेशन का एक परिणाम होगा in1। वास्तव में, मुझे लगता है कि कई मामलों में वायरिंग लेयर के पास एक अपवाद-हैंडलिंग ऑब्जेक्ट पास करने के लिए सही दृष्टिकोण होगा, और व्यापार परत में एक catchवस्तु शामिल है, जो तब उस ऑब्जेक्ट की handleExceptionविधि को आमंत्रित करती है । यह विधि डिफ़ॉल्ट डेटा को रीथ्रो या आपूर्ति कर सकती है, या "एबोर्ट / रिट्री / फेल" प्रॉम्प्ट डाल सकती है और ऑपरेटर को यह तय करने देना चाहिए कि क्या करना है, आदि इस बात पर निर्भर करता है कि आवेदन की क्या आवश्यकता है।
सुपरकैट

@supercat मैं समझता हूँ कि आप क्या कह रहे हैं। मैं कहूंगा कि एक ठोस संसाधन कार्यान्वयन जिम्मेदार है कि वह अपवादों को जान सकता है। इसमें सब कुछ निर्दिष्ट नहीं करना है (जिसे अपरिभाषित व्यवहार कहा जाता है ), लेकिन यह सुनिश्चित करना चाहिए कि कोई अस्पष्टता न हो। इसके अलावा, अनियंत्रित रन-टाइम अपवादों को प्रलेखित किया जाना है। यदि यह प्रलेखन का खंडन करता है, तो यह एक बग है। यदि कॉलर कोड को अपवाद के बारे में कुछ भी समझदार होने की उम्मीद है, तो नंगे न्यूनतम यह है कि संसाधन उन्हें UnrecoverableInternalExceptionHTTP 500 त्रुटि कोड के समान कुछ में लपेटता है ।
डैनियल डिनिएस

@ कॉन्फ़िगर करने योग्य त्रुटि संचालकों पर आपके सुझाव के बारे में: बिल्कुल! मेरे अंतिम उदाहरण में लॉजिक को हैंडल करने वाले एरर को हार्ड-कोड किया गया है, जिसे स्टैटिक doMyBusinessविधि कहा जाता है। यह संक्षिप्तता के लिए था, और इसे और अधिक गतिशील बनाने के लिए पूरी तरह से संभव है। इस तरह के Handlerवर्ग को कुछ इनपुट / आउटपुट संसाधनों के साथ त्वरित किया जाएगा, और एक handleविधि है जो एक वर्ग को लागू करने वाले प्राप्त करता है ReusableBusinessLogicInterface। फिर आप उनके ऊपर कहीं वायरिंग लेयर में अलग-अलग हैंडलर, रिसोर्स और बिजनेस लॉजिक इंप्लीमेंटेशन का उपयोग करने के लिए कंबाइन / कॉन्फिगर कर सकते हैं।
डैनियल डिनिएस

10

इस प्रश्न का ठीक से उत्तर देने के लिए, आइए एक कदम पीछे हटें और एक और अधिक मौलिक प्रश्न पूछें।

ऐसा क्यों है कि हमारे पास पहली जगह में अपवाद हैं?

हम अपनी पद्धति के कॉलर को यह बताने के लिए अपवाद फेंकते हैं कि हम वह नहीं कर सके जो हमें करने के लिए कहा गया था। अपवाद का प्रकार बताता है कि हम वह क्यों नहीं कर पाए जो हम करना चाहते थे।

आइए कुछ कोड देखें:

double MethodA()
{
    return PropertyA - PropertyB.NestedProperty;
}

यह कोड स्पष्ट रूप से एक अशक्त संदर्भ अपवाद फेंक सकता है यदि PropertyBअशक्त है। इस स्थिति के लिए दो चीजें हैं जो हम इस मामले में "सही" कर सकते हैं। हम:

  • यदि हमारे पास नहीं है तो संपत्ति को स्वचालित रूप से बनाएं; या
  • कॉल करने की विधि तक अपवाद को छोड़ दें।

यहां प्रॉपर्टी बनाना बहुत खतरनाक हो सकता है। प्रॉपर्टीबी बनाने के लिए इस विधि का क्या कारण है? निश्चित रूप से यह एकल जिम्मेदारी सिद्धांत का उल्लंघन होगा। सभी संभावना में, यदि प्रॉपर्टी यहां मौजूद नहीं है, तो यह इंगित करता है कि कुछ गलत हो गया है। विधि को आंशिक रूप से निर्मित ऑब्जेक्ट पर बुलाया जा रहा है या प्रॉपर्टीबी को गलत तरीके से शून्य करने के लिए सेट किया गया था। यहाँ PropertyB बनाकर, हम एक बहुत बड़ा बग छिपा सकते हैं जो बाद में हमें काट सकता है, जैसे कि बग जो डेटा भ्रष्टाचार का कारण बनता है।

यदि, इसके बजाय, हम अशक्त संदर्भ बबल को छोड़ देते हैं, तो हम डेवलपर को इस पद्धति को बताने दे रहे हैं, जो कि जैसे ही हम जान सकते हैं, कि कुछ गलत हो गया है। इस पद्धति को कॉल करने की एक महत्वपूर्ण पूर्व शर्त छूट गई है।

इसलिए वास्तव में, हम जल्दी फेंक रहे हैं क्योंकि यह हमारी चिंताओं को बेहतर ढंग से अलग करता है। जैसे ही कोई गलती हुई, हम अपस्ट्रीम डेवलपर्स को इसके बारे में बता रहे हैं।

क्यों हम "देर से पकड़ना" एक अलग कहानी है। हम वास्तव में देर से पकड़ना नहीं चाहते हैं, हम वास्तव में जल्द से जल्द पकड़ना चाहते हैं क्योंकि हम जानते हैं कि समस्या को ठीक से कैसे संभालना है। कुछ समय बाद यह अमूर्तन की पंद्रह परतें होंगी और कुछ समय यह सृजन के बिंदु पर होगा।

मुद्दा यह है कि हम एब्सट्रैक्शन की परत पर अपवाद को पकड़ना चाहते हैं जो हमें उस बिंदु पर अपवाद को संभालने की अनुमति देता है जहां हमारे पास अपवाद को ठीक से संभालने के लिए आवश्यक सभी जानकारी है।


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

आपके दिए गए उदाहरण में, घटाव ऑपरेशन से पहले अशक्त के लिए जाँच के बारे में, इस तरह सेif(PropertyB == null) return 0;
user1451111

1
क्या आप अपने अंतिम पैराग्राफ पर भी विस्तार से बता सकते हैं, विशेष रूप से ab अमूर्त की परत ’से आपका क्या मतलब है ।
user1451111

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

"आपके दिए गए उदाहरण में, घटाव ऑपरेशन से पहले अशक्त के लिए जाँच के बारे में क्या है, इस तरह यदि (PropertyB == नल) 0;" छी। यह कॉलिंग विधि को बताएगा कि मेरे पास और उससे घटने के लिए वैध चीजें हैं। बेशक यह प्रासंगिक है, लेकिन ज्यादातर मामलों में मेरी त्रुटि जांच करना बुरा होगा।
स्टीफन

6

जैसे ही आप किसी वस्तु को अमान्य स्थिति में डालने से बचने के लिए फेंकने लायक कुछ देखते हैं, फेंक दें। इसका मतलब है कि यदि एक अशक्त सूचक पारित किया गया था, तो आप इसके लिए शीघ्रता से जांच करें और एक एनपीई को फेंक दें, इससे पहले कि यह निम्न स्तर पर छल करने का मौका हो।

जैसे ही आप जानते हैं कि त्रुटि को ठीक करने के लिए क्या करना है (यह आम तौर पर वह जगह नहीं है जहां आप फेंकते हैं अन्यथा आप सिर्फ एक-का उपयोग कर सकते हैं), यदि कोई अमान्य पैरामीटर पारित किया गया था, तो पैरामीटर प्रदान करने वाली परत को परिणामों से निपटना चाहिए ।


1
आपने लिखा: जल्द ही फेंक दो, ... जल्द ही पकड़ लो ...! क्यों? यह "जल्दी फेंक, देर से पकड़ना" के विपरीत पूरी तरह से विपरीत दृष्टिकोण है।
shylynx

1
@shylynx मुझे नहीं पता कि "कहाँ जल्दी फेंकें, देर से पकड़ें" से आता है, लेकिन इसका मूल्य संदिग्ध है। क्या, वास्तव में, "देर" का अर्थ है? जहां एक अपवाद को पकड़ने के लिए समझ में आता है (यदि बिल्कुल) समस्या पर निर्भर करता है। केवल एक चीज जो स्पष्ट है वह यह है कि आप जितनी जल्दी हो सके समस्याओं (और फेंक) का पता लगाना चाहते हैं।
डोभाल

2
मेरा मानना ​​है कि "कैच लेट" का अर्थ है कैच करने की प्रथा के विपरीत, इससे पहले कि आप जान सकें कि त्रुटि को ठीक करने के लिए क्या करना है - जैसे कि कभी-कभी आप ऐसे फ़ंक्शंस देखते हैं जो हर चीज़ को पकड़ते हैं, ताकि वे एक एरर मैसेज को प्रिंट कर सकें और फिर अपवाद को हटा सकें।
अपवर्जित और बंद

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

4

एक मान्य व्यावसायिक नियम है 'यदि निचले स्तर का सॉफ़्टवेयर किसी मान की गणना करने में विफल रहता है, तो ...'

यह केवल उच्च स्तर पर व्यक्त किया जा सकता है, अन्यथा निचले स्तर का सॉफ़्टवेयर अपने स्वयं के शुद्धता के आधार पर अपने व्यवहार को बदलने की कोशिश कर रहा है, जो केवल एक गाँठ में समाप्त होने जा रहा है।


2

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

मेरे अनुभव से, स्टैक के ऊपर चलते हुए अमूर्त अपवादों का अच्छा अभ्यास है। आमतौर पर जिन बिंदुओं पर आप यह करना चाहते हैं, जब भी कोई अपवाद दो परतों के बीच की सीमा को पार करता है।

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

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

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

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

वायदा करते हुए आप (एक आदर्श दुनिया में) यूआई से पूरी तरह से बाहर try/ catchकोड छोड़ सकते हैं । इसके बजाय एक वैश्विक अपवाद हैंडलर का उपयोग करें जो उन अपवादों को समझने में सक्षम है जो संभवतः निचली परतों द्वारा फेंके गए हैं, उन्हें कुछ लॉग में लिखते हैं और त्रुटि वस्तुओं में लपेटते हैं जिसमें त्रुटि की सार्थक (और संभवतः स्थानीयकृत) जानकारी होती है। उन वस्तुओं को आसानी से उपयोगकर्ता को किसी भी रूप में प्रस्तुत किया जा सकता है जिसे आप चाहते हैं (संदेश बॉक्स, सूचनाएं, संदेश टोस्ट, और इसी तरह)।


1

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

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.