Java Language
जावा नुकसान - अपवाद का उपयोग
खोज…
परिचय
कई जावा प्रोग्रामिंग भाषा मिसयूज सही ढंग से संकलित होने के बावजूद गलत परिणाम उत्पन्न करने के लिए एक कार्यक्रम का आयोजन कर सकती है। इस विषय का मुख्य उद्देश्य अपवाद से निपटने के लिए आम नुकसान की सूची है, और इस तरह के नुकसान से बचने के लिए सही तरीके का प्रस्ताव करना है।
नुकसान - अपवादों को नजरअंदाज करना या तोड़ना
यह उदाहरण जानबूझकर अनदेखी या "स्क्वाशिंग" अपवादों के बारे में है। या अधिक सटीक होने के लिए, यह इस तरह से अपवाद को पकड़ने और संभालने के तरीके के बारे में है जो इसे अनदेखा करता है। हालांकि, इससे पहले कि हम यह वर्णन करें कि यह कैसे करना है, हमें पहले यह बताना चाहिए कि आम तौर पर स्क्वाशिंग अपवाद उनके साथ निपटने का सही तरीका नहीं है।
अपवाद आमतौर पर (कुछ के द्वारा) कार्यक्रम के अन्य हिस्सों को सूचित करने के लिए फेंक दिए जाते हैं कि कुछ महत्वपूर्ण (यानी "असाधारण") घटना हुई है। आम तौर पर (हालांकि हमेशा नहीं) अपवाद का मतलब है कि कुछ गलत हो गया है। यदि आप अपवाद को स्क्वैश करने के लिए अपने कार्यक्रम को कोड करते हैं, तो एक उचित मौका है कि समस्या दूसरे रूप में फिर से दिखाई देगी। चीजों को बदतर बनाने के लिए, जब आप अपवाद को स्क्वाश करते हैं, तो आप अपवाद वस्तु और उसके संबद्ध स्टैक ट्रेस में जानकारी फेंक रहे हैं। इससे यह पता लगाना कठिन हो जाता है कि समस्या का मूल स्रोत क्या था।
व्यवहार में, अपवाद स्क्वाशिंग अक्सर तब होता है जब आप आईडीई के ऑटो-करेक्शन फीचर का उपयोग एक अनधिकृत अपवाद के कारण संकलन त्रुटि को "ठीक" करने के लिए करते हैं। उदाहरण के लिए, आप इस तरह कोड देख सकते हैं:
try {
inputStream = new FileInputStream("someFile");
} catch (IOException e) {
/* add exception handling code here */
}
स्पष्ट रूप से, प्रोग्रामर ने संकलन त्रुटि को दूर करने के लिए आईडीई के सुझाव को स्वीकार कर लिया है, लेकिन सुझाव अनुचित था। (यदि फ़ाइल खुली विफल हो गई है, तो प्रोग्राम को सबसे अधिक संभावना है कि इसके बारे में कुछ करना चाहिए। उपरोक्त "सुधार" के साथ, कार्यक्रम बाद में विफल होने के लिए उत्तरदायी है। उदाहरण के लिए NullPointerException
साथ। क्योंकि inputStream
अब null
।)
कहा गया है कि, यहाँ एक अपवाद को जानबूझकर तोड़ना है। (तर्क के उद्देश्यों के लिए, मान लें कि हमने निर्धारित किया है कि सेल्फी दिखाते समय एक बाधा हानिरहित है।) टिप्पणी पाठक को बताती है कि हमने अपवाद को जानबूझकर छोड़ दिया, और हमने ऐसा क्यों किया।
try {
selfie.show();
} catch (InterruptedException e) {
// It doesn't matter if showing the selfie is interrupted.
}
इस पर प्रकाश डालने का एक और पारंपरिक तरीका यह है कि हम जानबूझकर एक अपवाद नहीं कह रहे हैं कि यह अपवाद चर के नाम के साथ क्यों इंगित करना है, जैसे:
try {
selfie.show();
} catch (InterruptedException ignored) { }
कुछ IDEs (IntelliJ IDEA की तरह) यदि वैरिएबल नाम को ignored
करने के लिए सेट किया गया है, तो खाली कैच ब्लॉक के बारे में चेतावनी प्रदर्शित नहीं करेगा।
नुकसान - पकड़ने योग्य, अपवाद, त्रुटि या रनटाइम अपवाद
अनुभवहीन जावा प्रोग्रामर के लिए एक आम सोचा पैटर्न है कि अपवाद "एक समस्या" या "एक बोझ" और सबसे अच्छा तरीका है निपटने के लिए के साथ इस पकड़ उन्हें जल्द से जल्द के रूप में सभी 1 है कर रहे हैं। यह इस तरह कोड की ओर जाता है:
....
try {
InputStream is = new FileInputStream(fileName);
// process the input
} catch (Exception ex) {
System.out.println("Could not open file " + fileName);
}
उपरोक्त कोड में एक महत्वपूर्ण दोष है। catch
वाला वास्तव में अधिक अपवादों को पकड़ने वाला है, जो प्रोग्रामर अपेक्षा कर रहा है। मान लें कि fileName
का मान null
, अनुप्रयोग में कहीं और बग के कारण। यह NullPointerException
को फेंकने के लिए FileInputStream
कंस्ट्रक्टर का कारण होगा। हैंडलर इसे पकड़ लेगा, और उपयोगकर्ता को रिपोर्ट करेगा:
Could not open file null
जो अनहोनी और भ्रामक है। इससे भी बदतर, मान लीजिए कि यह "इनपुट प्रक्रिया" कोड था जिसने अप्रत्याशित अपवाद (चेक या अनचेक!) को फेंक दिया। अब उपयोगकर्ता को एक समस्या के लिए भ्रामक संदेश मिलेगा जो फ़ाइल खोलते समय नहीं हुआ था, और I / O से संबंधित नहीं हो सकता है।
समस्या की जड़ यह है कि प्रोग्रामर ने Exception
लिए एक हैंडलर को कोडित किया है। यह लगभग हमेशा एक गलती है:
- कैचिंग
Exception
सभी चेक किए गए अपवादों को पकड़ लेगा, और सबसे अनियंत्रित अपवाद भी। -
RuntimeException
को पकड़ने से सबसे अनियंत्रित अपवाद पकड़े जाएंगे। - कैचिंग
Error
जेवीएम की आंतरिक त्रुटियों का संकेत देने वाले अनियंत्रित अपवादों को पकड़ लेगा। ये त्रुटियां आमतौर पर पुनर्प्राप्त करने योग्य नहीं होती हैं, और इन्हें पकड़ा नहीं जाना चाहिए। -
Throwable
को पकड़ने से सभी संभावित अपवादों को पकड़ा जा सकेगा।
अपवादों के एक सेट को पकड़ने के साथ समस्या यह है कि हैंडलर आमतौर पर उन सभी को उचित रूप से नहीं संभाल सकता है। Exception
और इसी तरह के मामले में, प्रोग्रामर के लिए यह अनुमान लगाना मुश्किल है कि क्या पकड़ा जा सकता है; यानी क्या उम्मीद की जाए।
सामान्य तौर पर, सही समाधान उन अपवादों से निपटने के लिए होता है जिन्हें फेंक दिया जाता है। उदाहरण के लिए, आप उन्हें पकड़ सकते हैं और उन्हें सीटू में संभाल सकते हैं:
try {
InputStream is = new FileInputStream(fileName);
// process the input
} catch (FileNotFoundException ex) {
System.out.println("Could not open file " + fileName);
}
या आप उन्हें एनक्लोजिंग विधि द्वारा thrown
घोषणा कर सकते हैं।
ऐसी बहुत कम स्थितियाँ हैं जहाँ Exception
को पकड़ना उचित है। केवल एक ही आम तौर पर कुछ इस तरह से उत्पन्न होता है:
public static void main(String[] args) {
try {
// do stuff
} catch (Exception ex) {
System.err.println("Unfortunately an error has occurred. " +
"Please report this to X Y Z");
// Write stacktrace to a log file.
System.exit(1);
}
}
यहां हम वास्तव में सभी अपवादों से निपटना चाहते हैं, इसलिए Exception
(या यहां तक कि Throwable
) को Throwable
सही है।
1 - इसे पोकेमॉन एक्सेप्शन हैंडलिंग के रूप में भी जाना जाता है।
नुकसान - फेंकने योग्य, अपवाद, त्रुटि या रनटाइम अपवाद
पकड़ने जबकि Throwable
, Exception
, Error
और RuntimeException
अपवाद फेंक उन्हें और भी बदतर है बुरा है,।
मूल समस्या यह है कि जब आपके आवेदन को अपवादों को संभालने की आवश्यकता होती है, तो शीर्ष स्तर के अपवादों की उपस्थिति विभिन्न त्रुटि स्थितियों के बीच भेदभाव करना कठिन बनाती है। उदाहरण के लिए
try {
InputStream is = new FileInputStream(someFile); // could throw IOException
...
if (somethingBad) {
throw new Exception(); // WRONG
}
} catch (IOException ex) {
System.err.println("cannot open ...");
} catch (Exception ex) {
System.err.println("something bad happened"); // WRONG
}
समस्या यह है कि क्योंकि हमने एक Exception
उदाहरण फेंक दिया है, हम इसे पकड़ने के लिए मजबूर हैं। हालांकि जैसा कि एक अन्य उदाहरण में बताया गया है, Exception
को पकड़ना बुरा है। इस स्थिति में, एक Exception
के "अपेक्षित" मामले के बीच भेदभाव करना मुश्किल हो जाता है जो कि फेंक दिया जाता है अगर somethingBad
true
, और अप्रत्याशित मामला जहां हम वास्तव में NullPointerException
जैसे अनियंत्रित अपवाद को पकड़ते हैं।
यदि शीर्ष-स्तरीय अपवाद को प्रचारित करने की अनुमति है, तो हम अन्य समस्याओं में भाग लेते हैं:
- अब हमें उन सभी अलग-अलग कारणों को याद रखना होगा, जिन्हें हमने शीर्ष-स्तर पर फेंक दिया था, और भेदभाव / उन्हें संभालना।
-
Exception
औरThrowable
की स्थिति में, हमें इन अपवादों को जोड़ने की आवश्यकता होती है,Throwable
हमException
कोThrowable
चाहते हैं, तो इन तरीकों कोthrows
। यह समस्याग्रस्त है, जैसा कि नीचे वर्णित है।
संक्षेप में, इन अपवादों को मत फेंकिए। अधिक विशिष्ट अपवाद को फेंको जो अधिक "असाधारण घटना" का वर्णन करता है। यदि आपको कस्टम अपवाद वर्ग को परिभाषित करने और उसका उपयोग करने की आवश्यकता है।
एक विधि के "फेंकता" में फेंकने योग्य या अपवाद की घोषणा करना समस्याग्रस्त है।
यह एक विधि में फेंक दिया अपवाद की एक लंबी सूची को बदलने के लिए की आकर्षक है throws
के साथ खंड Exception
या यहाँ तक कि `फेंकने योग्य। यह विचार अच्छा नहीं है:
- यह कॉलर को
Exception
को संभालने (या प्रचार) के लिए मजबूर करता है। - हम अब संकलक पर भरोसा नहीं कर सकते हैं कि हमें विशिष्ट जाँच किए गए अपवादों के बारे में बताएं जिन्हें संभालने की आवश्यकता है।
-
Exception
ठीक से संभालना मुश्किल है। यह जानना कठिन है कि वास्तविक अपवादों को क्या पकड़ा जा सकता है, और यदि आप नहीं जानते कि क्या पकड़ा जा सकता है, तो यह जानना कठिन है कि वसूली की रणनीति क्या उपयुक्त है। -
Throwable
कोThrowable
और भी कठिन है, क्योंकि अब आपको संभावित विफलताओं से भीThrowable
,Throwable
कभी भीThrowable
नहीं चाहिए।
इस सलाह का मतलब है कि कुछ अन्य पैटर्न से बचा जाना चाहिए। उदाहरण के लिए:
try {
doSomething();
} catch (Exception ex) {
report(ex);
throw ex;
}
उपरोक्त सभी अपवादों को लॉग करने का प्रयास करते हैं, क्योंकि वे निश्चित रूप से उन्हें संभालने के बिना गुजरते हैं। दुर्भाग्य से, जावा 7 से पहले, throw ex;
बयान ने कंपाइलर को यह सोचने का कारण बनाया कि किसी भी Exception
को फेंक दिया जा सकता है। यह आपको throws Exception
रूप में संलग्न करने की विधि घोषित करने के लिए मजबूर कर सकता है। जावा 7 के बाद से, कंपाइलर जानता है कि अपवादों का सेट जो छोटा हो सकता है (फिर से फेंका जा सकता है)।
नुकसान - रुकावट को बाधित करना
जैसा कि पहले से ही अन्य नुकसानों में बताया गया है, सभी अपवादों को पकड़कर
try {
// Some code
} catch (Exception) {
// Some error handling
}
बहुत सारी विभिन्न समस्याओं के साथ आता है। लेकिन एक समस्या यह है कि यह गतिरोध पैदा कर सकता है क्योंकि यह बहु-थ्रेडेड अनुप्रयोगों को लिखते समय व्यवधान प्रणाली को तोड़ देता है।
यदि आप एक धागा शुरू करते हैं तो आपको आमतौर पर विभिन्न कारणों से इसे रोकने में सक्षम होना चाहिए।
Thread t = new Thread(new Runnable() {
public void run() {
while (true) {
//Do something indefinetely
}
}
}
t.start();
//Do something else
// The thread should be canceld if it is still active.
// A Better way to solve this is with a shared variable that is tested
// regularily by the thread for a clean exit, but for this example we try to
// forcibly interrupt this thread.
if (t.isAlive()) {
t.interrupt();
t.join();
}
//Continue with program
t.interrupt()
उस थ्रेड को बंद करने के इरादे से उस थ्रेड में एक इंटरप्टेड t.interrupt()
। लेकिन क्या होगा अगर थ्रेड को पूरी तरह से बंद होने से पहले कुछ संसाधनों को साफ करने की आवश्यकता है? इसके लिए यह InterruptedException को पकड़ सकता है और कुछ सफाई कर सकता है।
Thread t = new Thread(new Runnable() {
public void run() {
try {
while (true) {
//Do something indefinetely
}
} catch (InterruptedException ex) {
//Do some quick cleanup
// In this case a simple return would do.
// But if you are not 100% sure that the thread ends after
// catching the InterruptedException you will need to raise another
// one for the layers surrounding this code.
Thread.currentThread().interrupt();
}
}
}
लेकिन अगर आपके पास अपने कोड में एक कैच-ऑल एक्सप्रेशन है, तो इंटरप्टेड अपवाद इसे भी पकड़ा जाएगा और रुकावट जारी नहीं रहेगी। जो इस मामले में गतिरोध पैदा कर सकता है क्योंकि माता-पिता धागा इस अनिश्चित काल के लिए प्रतीक्षा करता है t.join()
साथ t.join()
।
Thread t = new Thread(new Runnable() {
public void run() {
try {
while (true) {
try {
//Do something indefinetely
}
catch (Exception ex) {
ex.printStackTrace();
}
}
} catch (InterruptedException ex) {
// Dead code as the interrupt exception was already caught in
// the inner try-catch
Thread.currentThread().interrupt();
}
}
}
इसलिए अपवादों को व्यक्तिगत रूप से पकड़ना बेहतर है, लेकिन यदि आप एक कैच-ऑल का उपयोग करने पर जोर देते हैं, तो कम से कम व्यक्तिगत रूप से पहले से बाधित Interception को पकड़ें।
Thread t = new Thread(new Runnable() {
public void run() {
try {
while (true) {
try {
//Do something indefinetely
} catch (InterruptedException ex) {
throw ex; //Send it up in the chain
} catch (Exception ex) {
ex.printStackTrace();
}
}
} catch (InterruptedException ex) {
// Some quick cleanup code
Thread.currentThread().interrupt();
}
}
}
नुकसान - सामान्य फ्लोकोन्ट्रोल के अपवादों का उपयोग करना
एक मंत्र है कि कुछ जावा विशेषज्ञ पाठ करने के लिए अभ्यस्त हैं:
"अपवादों का उपयोग केवल असाधारण मामलों के लिए किया जाना चाहिए।"
(उदाहरण के लिए: http://programmers.stackexchange.com/questions/184654 )
इसका सार यह है कि सामान्य प्रवाह नियंत्रण को लागू करने के लिए अपवादों और अपवाद से निपटने के लिए यह (जावा में) एक बुरा विचार है। उदाहरण के लिए, एक पैरामीटर से निपटने के इन दो तरीकों की तुलना करें जो शून्य हो सकते हैं।
public String truncateWordOrNull(String word, int maxLength) {
if (word == null) {
return "";
} else {
return word.substring(0, Math.min(word.length(), maxLength));
}
}
public String truncateWordOrNull(String word, int maxLength) {
try {
return word.substring(0, Math.min(word.length(), maxLength));
} catch (NullPointerException ex) {
return "";
}
}
इस उदाहरण में, हम (डिजाइन द्वारा) उस मामले का इलाज कर रहे हैं जहां word
null
जैसे कि यह एक खाली शब्द है। दो संस्करण या तो पारंपरिक का उपयोग करते हुए null
के साथ सौदा करते हैं ... और या फिर कोशिश करें ... पकड़ें । हमें यह कैसे तय करना चाहिए कि कौन सा संस्करण बेहतर है?
पहली कसौटी पठनीयता है। जबकि पठनीयता वस्तुनिष्ठ रूप से निर्धारित करना कठिन है, अधिकांश प्रोग्रामर इस बात से सहमत होंगे कि पहले संस्करण का आवश्यक अर्थ विचार करना आसान है। वास्तव में, दूसरे रूप को समझने के लिए, आपको यह समझने की जरूरत है कि NullPointerException
को Math.min
या String.substring
विधियों द्वारा फेंका नहीं जा सकता।
दूसरी कसौटी दक्षता है। जावा 8 के पूर्व जावा के रिलीज में, दूसरा संस्करण पहले संस्करण की तुलना में काफी कम है (परिमाण के क्रम)। विशेष रूप से, एक अपवाद ऑब्जेक्ट का निर्माण स्टैकफ्रेम को कैप्चर और रिकॉर्ड करने पर जोर देता है, बस जब स्टैकट्रेस की आवश्यकता होती है।
दूसरी ओर, कई स्थितियां हैं जहां अपवादों का उपयोग करना अधिक पठनीय है, "असाधारण" घटनाओं से निपटने के लिए सशर्त कोड का उपयोग करने की तुलना में अधिक कुशल और (कभी-कभी) अधिक सही है। वास्तव में, ऐसी दुर्लभ स्थितियां हैं जहां "गैर-असाधारण" घटनाओं के लिए उनका उपयोग करना आवश्यक है; यानी ऐसी घटनाएं जो अपेक्षाकृत बार-बार होती हैं। उत्तरार्द्ध के लिए, यह अपवाद वस्तुओं को बनाने के ओवरहेड्स को कम करने के तरीकों को देखने के लायक है।
नुकसान - अत्यधिक या अनुचित स्टैकट्रैक्स
प्रोग्रामर कर सकते हैं कि अधिक कष्टप्रद चीजों में से एक अपने कोड में printStackTrace()
को कॉल को तितर बितर करना है।
समस्या यह है कि printStackTrace()
स्टैकट्रेस को मानक आउटपुट में लिखने जा रहा है।
एक एप्लिकेशन के लिए जो एंड-यूजर्स के लिए है जो जावा प्रोग्रामर नहीं हैं, एक स्टैकट्रेस सबसे बेहतर है, और सबसे खराब है।
सर्वर-साइड एप्लिकेशन के लिए, संभावना है कि कोई भी मानक आउटपुट को नहीं देखेगा।
एक बेहतर विचार यह है कि printStackTrace
सीधे कॉल न करें, या यदि आप इसे कॉल करते हैं, तो इसे इस तरह से करें कि स्टैक ट्रेस अंत उपयोगकर्ता के कंसोल के बजाय लॉग फ़ाइल या त्रुटि फ़ाइल में लिखा हो।
ऐसा करने का एक तरीका लॉगिंग फ्रेमवर्क का उपयोग करना है, और लॉग इवेंट के पैरामीटर के रूप में अपवाद ऑब्जेक्ट को पास करना है। हालांकि, यदि आकस्मिक रूप से किया जाता है, तो भी अपवाद लॉग करना हानिकारक हो सकता है। निम्नलिखित को धयान मे रखते हुए:
public void method1() throws SomeException {
try {
method2();
// Do something
} catch (SomeException ex) {
Logger.getLogger().warn("Something bad in method1", ex);
throw ex;
}
}
public void method2() throws SomeException {
try {
// Do something else
} catch (SomeException ex) {
Logger.getLogger().warn("Something bad in method2", ex);
throw ex;
}
}
यदि अपवाद method2
में फेंका गया है, तो आपको लॉगफ़ाइल में एक ही स्टैकट्रेस की दो प्रतियां देखने की संभावना है, जो समान विफलता के अनुरूप है।
संक्षेप में, या तो अपवाद लॉग इन करें या इसे फिर से फेंक दें (संभवतः दूसरे अपवाद के साथ लिपटे)। दोनों मत करो।
ख़तरा - प्रत्यक्ष रूप से `थ्रैलेबल` को उपवर्गित करना
Throwable
में दो सीधे उपवर्ग, Exception
और Error
। हालांकि, एक नया वर्ग बनाना संभव है जो सीधे Throwable
विस्तार Throwable
है, यह Throwable
है क्योंकि कई एप्लिकेशन केवल Exception
और Error
मौजूद हैं।
इस बिंदु पर अधिक, Throwable
को सीधे उप-वर्ग में Throwable
का कोई व्यावहारिक लाभ नहीं है, क्योंकि परिणामी वर्ग, वास्तव में, केवल एक अपवाद अपवाद है। Exception
बजाय उपश्रेणी Exception
को एक ही व्यवहार में परिणाम देगा, लेकिन अधिक स्पष्ट रूप से आपके इरादे को व्यक्त करेगा।