अपवादों को पकड़ने और फिर से फेंकने के लिए सर्वोत्तम अभ्यास क्या हैं?


156

क्या अपवादों को सीधे पकड़ा जाना चाहिए, या उन्हें एक नए अपवाद के चारों ओर लपेटा जाना चाहिए?

यही है, मुझे यह करना चाहिए:

try {
  $connect = new CONNECT($db, $user, $password, $driver, $host);
} catch (Exception $e) {
  throw $e;
}

या यह:

try {
  $connect = new CONNECT($db, $user, $password, $driver, $host);
} catch (Exception $e) {
  throw new Exception("Exception Message", 1, $e);
}

यदि आपका जवाब सीधे फेंकना है तो कृपया अपवाद जंजीर के उपयोग का सुझाव दें , मैं एक वास्तविक विश्व परिदृश्य को समझने में सक्षम नहीं हूं जहां हम अपवाद जंजीरों का उपयोग करते हैं।

जवाबों:


287

जब तक आप कुछ सार्थक करने का इरादा नहीं रखते तब तक आपको अपवाद को नहीं पकड़ना चाहिए ।

"कुछ सार्थक" इनमें से एक हो सकता है:

अपवाद को संभालना

सबसे स्पष्ट सार्थक कार्रवाई अपवाद को संभालने के लिए है, उदाहरण के लिए एक त्रुटि संदेश प्रदर्शित करने और ऑपरेशन को निरस्त करके:

try {
    $connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
    echo "Error while connecting to database!";
    die;
}

लॉगिंग या आंशिक सफाई

कभी-कभी आप नहीं जानते कि किसी विशिष्ट संदर्भ के अंदर अपवाद को कैसे ठीक से संभालना है; शायद आपको "बड़ी तस्वीर" के बारे में जानकारी की कमी है, लेकिन आप विफलता को उस बिंदु के करीब से लॉग इन करना चाहते हैं जहां यह संभव हुआ। इस स्थिति में, आप पकड़ना, लॉग इन करना और फिर से फेंकना चाहते हैं:

try {
    $connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
    logException($e); // does something
    throw $e;
}

एक संबंधित परिदृश्य वह है जहां आप असफल ऑपरेशन के लिए कुछ सफाई करने के लिए सही जगह पर हैं, लेकिन यह तय करने के लिए नहीं कि विफलता को शीर्ष स्तर पर कैसे नियंत्रित किया जाए। पहले के PHP संस्करणों में इसे लागू किया जाता था

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
    $connect->insertSomeRecord();
}
catch (Exception $e) {
    $connect->disconnect(); // we don't want to keep the connection open anymore
    throw $e; // but we also don't know how to respond to the failure
}

PHP 5.5 ने finallyकीवर्ड की शुरुआत की है , इसलिए क्लीनअप परिदृश्यों के लिए अब इसे अप्रोच करने का एक और तरीका है। यदि क्लीन कोड को कोई फर्क नहीं पड़ता है कि क्या हुआ है (यानी त्रुटि पर और सफलता दोनों पर) तो अब ऐसा करना संभव है, जबकि पारदर्शी रूप से किसी भी अपवाद को फेंकने की अनुमति नहीं है:

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
    $connect->insertSomeRecord();
}
finally {
    $connect->disconnect(); // no matter what
}

अमूर्त त्रुटि (अपवाद जंजीर के साथ)

एक तीसरा मामला यह है कि आप तार्किक रूप से एक बड़ी छतरी के नीचे कई संभावित विफलताओं को समूह बनाना चाहते हैं। तार्किक समूहन के लिए एक उदाहरण:

class ComponentInitException extends Exception {
    // public constructors etc as in Exception
}

class Component {
    public function __construct() {
        try {
            $connect = new CONNECT($db, $user, $password, $driver, $host);
        }
        catch (Exception $e) {
            throw new ComponentInitException($e->getMessage(), $e->getCode(), $e);
        }
    }
}

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

समृद्ध संदर्भ प्रदान करना (अपवाद की रूपरेखा के साथ)

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

class FileOperation {
    public static function copyFiles() {
        try {
            $copier = new FileCopier(); // the constructor may throw

            // this may throw if the files do no not exist
            $copier->ensureSourceFilesExist();

            // this may throw if the directory cannot be created
            $copier->createTargetDirectory();

            // this may throw if copying a file fails
            $copier->performCopy();
        }
        catch (Exception $e) {
            throw new Exception("Could not perform copy operation.", 0, $e);
        }
    }
}

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

ऐसा करने के मूल्य का सचित्र वर्णन किया गया है, यदि आप ऐसे परिदृश्य के बारे में सोचते हैं, जैसे कि UserProfileऑब्जेक्ट बनाते समय फ़ाइलों की प्रतिलिपि बनाई जाती है क्योंकि उपयोगकर्ता प्रोफ़ाइल फ़ाइलों में संग्रहीत होती है और यह लेनदेन शब्दार्थ का समर्थन करती है: आप परिवर्तनों को "पूर्ववत" कर सकते हैं क्योंकि वे केवल एक पर किए जाते हैं प्रोफ़ाइल की प्रतिलिपि जब तक आप नहीं करते।

इस मामले में, यदि आपने किया

try {
    $profile = UserProfile::getInstance();
}

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


मैं केवल अंतिम 2 कारणों से सहमत हूं: 1 / अपवाद को संभालना: आप इसे इस स्तर पर नहीं करना चाहिए, 2 / लॉगिंग या क्लीनअप: अंत में उपयोग करें और अपने डैटलेयर के ऊपर अपवाद को लॉग इन करें
रेमी बोगरेलर

1
@remi: सिवाय इसके कि PHP finallyनिर्माण का समर्थन नहीं करता है (अभी तक कम से कम नहीं) ... तो वह यह है कि इसका मतलब है कि हमें इस तरह की गंदी चीजों का सहारा लेना चाहिए ...
ircmaxell

@remibourgarel: 1: यह सिर्फ एक उदाहरण था। बेशक आप इसे इस स्तर पर नहीं करना चाहिए, लेकिन इसका जवाब बहुत लंबा है। 2: जैसा कि @ircmaxell कहते हैं, finallyPHP में कोई नहीं है।
जॉन

3
अंत में, PHP 5.5 अब अंत में लागू होता है।
OCDev

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

37

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

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

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

जब तक आपको कुछ पोस्ट-प्रोसेसिंग करने की आवश्यकता न हो, तब तक एक ही अपवाद को न पकड़ें और न ही निरस्त करें। लेकिन जैसे एक ब्लॉक } catch (Exception $e) { throw $e; }व्यर्थ है। लेकिन आप कुछ महत्वपूर्ण अमूर्त लाभ के अपवादों को फिर से लपेट सकते हैं।


2
बहुत बढ़िया जवाब। स्टैक ओवरफ्लो (उत्तरों आदि के आधार पर) के आसपास कुछ लोगों को लगता है कि वे गलत उपयोग कर रहे हैं।
जेम्स

8

IMHO, एक अपवाद को पकड़ने के लिए सिर्फ पुनर्विचार करना बेकार है । इस स्थिति में, बस इसे न पकड़ें, और पहले बताए गए तरीकों को इसे हैंडल करें (उर्फ तरीके जो कॉल स्टैक में 'ऊपरी' हैं)

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

एक तरह से कुछ जानकारी जोड़ने के लिए विस्तार करने के लिए है Exception, वर्ग की तरह अपवाद के लिए NullParameterException, DatabaseExceptionआदि अधिक से अधिक, इस developper केवल कुछ अपवाद है कि वह संभाल कर सकते हैं पकड़ने के लिए अनुमति देते हैं। उदाहरण के लिए, कोई केवल पकड़ सकता है DatabaseExceptionऔर हल करने का प्रयास कर सकता है Exception, जैसे कि डेटाबेस को फिर से कनेक्ट करना।


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

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

2
ठीक है, यदि आप संसाधनों को बंद करने की आवश्यकता है, तो आप इसे फिर से बदल सकते हैं, लेकिन जोड़ने के लिए कोई अतिरिक्त जानकारी नहीं है। मैं मानता हूं कि यह दुनिया की सबसे साफ चीज नहीं है, लेकिन यह भीषण
ircmaxell

2
@ircmaxell सहमत हैं, यह प्रतिबिंबित करने के लिए कि यह बेकार है केवल तभी अगर आप कुछ भी नहीं करते हैं सिवाय इसके पुनर्खरीद करने के लिए
क्लेमेंट हर्रेमैन

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

2

आपको PHP 5.3 में एक्सेप्शन बेस्ट प्रैक्टिस पर एक नज़र डालनी होगी

PHP में अपवाद हैंडलिंग किसी भी खिंचाव से नई सुविधा नहीं है। निम्नलिखित लिंक में, आप अपवादों के आधार पर PHP 5.3 में दो नई सुविधाएँ देखेंगे। पहला नेस्टेड अपवाद है और दूसरा एसपीएल एक्सटेंशन (जो अब PHP रनटाइम का एक मुख्य एक्सटेंशन है) द्वारा पेश किए गए अपवाद प्रकारों का एक नया सेट है। इन दोनों नई विशेषताओं ने सर्वोत्तम सर्वोत्तम प्रथाओं की पुस्तक में अपना रास्ता खोज लिया है और विस्तार से जांच के योग्य हैं।

http://ralphschindler.com/2010/09/15/exception-best-practices-in-php-5-3


1

आप आमतौर पर इस तरह से सोचते हैं।

एक वर्ग कई प्रकार के अपवादों को फेंक सकता है जो मेल नहीं खाएंगे। तो आप उस वर्ग या प्रकार के वर्ग के लिए एक अपवाद वर्ग बनाएं और उसे फेंक दें।

अतः कोड का उपयोग करने वाले कोड को केवल एक प्रकार के अपवाद को पकड़ना है।


1
क्या आप कुछ और विवरण या एक लिंक प्रदान कर सकते हैं जहां मैं इस दृष्टिकोण के बारे में अधिक पढ़ सकता हूं।
राहुल प्रसाद
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.