स्वाभाविक रूप से ऐसा करने के बजाय स्पष्ट रूप से NullPointerException को क्यों फेंकें?


182

JDK स्रोत कोड को पढ़ते समय, मुझे यह सामान्य लगता है कि लेखक मापदंडों की जाँच करेगा यदि वे अशक्त हैं और फिर नए NullPointerException () को मैन्युअल रूप से फेंकते हैं। वे ऐसा क्यों करते हैं? मुझे लगता है कि ऐसा करने की कोई जरूरत नहीं है क्योंकि यह किसी भी विधि को कॉल करने पर नए NullPointerException () को फेंक देगा। (यहाँ कुछ उदाहरण के लिए HashMap का स्रोत कोड है :)

public V computeIfPresent(K key,
                          BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    if (remappingFunction == null)
        throw new NullPointerException();
    Node<K,V> e; V oldValue;
    int hash = hash(key);
    if ((e = getNode(hash, key)) != null &&
        (oldValue = e.value) != null) {
        V v = remappingFunction.apply(key, oldValue);
        if (v != null) {
            e.value = v;
            afterNodeAccess(e);
            return v;
        }
        else
            removeNode(hash, key, null, false, true);
    }
    return null;
}

32
कोडिंग का एक प्रमुख बिंदु इरादा है
डरावना वोमबेट

19
यह आपके पहले प्रश्न के लिए एक बहुत अच्छा सवाल है! मैंने कुछ मामूली संपादन किए हैं; मुझे आशा है कि आपको कोई आपत्ति नहीं है। मैंने आपका पहला सवाल होने के नाते धन्यवाद और नोट को भी हटा दिया क्योंकि आम तौर पर उस तरह का कोई चीज एसओ सवालों का हिस्सा नहीं होता है।
डेविड कॉनराड

11
मैं C # कन्वेंशन इस ArgumentNullExceptionतरह के (बल्कि NullReferenceException) के मामलों को उठाने के लिए है - यह वास्तव में एक बहुत अच्छा सवाल है कि आप NullPointerExceptionयहाँ (एक अलग के बजाय) स्पष्ट रूप से क्यों उठाएँगे।
EJoshuaS -

21
@EJoshuaS यह एक पुरानी बहस है कि फेंकना है IllegalArgumentExceptionया NullPointerExceptionअशक्त तर्क के लिए। JDK सम्मेलन उत्तरार्द्ध है।
shmosel

33
वास्तविक समस्या यह है कि वे एक त्रुटि फेंकते हैं और उन सभी सूचनाओं को फेंक देते हैं जिनके कारण यह त्रुटि हुई । लगता है कि यह वास्तविक स्रोत कोड है। एक साधारण खूनी स्ट्रिंग संदेश भी नहीं। दुखी।
मार्टिन बा

जवाबों:


254

कई कारण हैं जो मन में आते हैं, कई निकट से संबंधित हैं:

असफलता-उपवास: यदि यह विफल होने जा रहा है, तो बाद में जल्द से जल्द असफल होना सबसे अच्छा है। यह समस्याओं को उनके स्रोत के करीब ले जाने की अनुमति देता है, जिससे उन्हें पहचानना और उनसे उबरना आसान हो जाता है। यह कोड पर सीपीयू चक्रों को बर्बाद करने से भी बचता है जो विफल होने के लिए बाध्य है।

आशय: अपवाद को स्पष्ट रूप से फेंकना यह सुनिश्चित करने के लिए स्पष्ट करता है कि त्रुटि जानबूझकर है और लेखक को परिणामों के बारे में पता था।

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

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


14
इसके अलावा: इस तरह से वह स्थान जहाँ से अपवाद फेंका गया है, ठीक उसी तरह से बँधा है जिसे जाँचा गया है। इसके बिना, अपवाद कई चर में से एक शून्य होने के कारण हो सकता है।
जेन्स स्काउडर

45
एक और एक: यदि आप स्वाभाविक रूप से एनपीई की प्रतीक्षा करते हैं, तो कुछ-इन-कोड आपके साइड-इफेक्ट्स के माध्यम से आपके प्रोग्राम की स्थिति को पहले से ही बदल सकते हैं।
थॉमस

6
हालांकि यह स्निपेट ऐसा नहीं करता है, आप new NullPointerException(message)कंस्ट्रक्टर का उपयोग यह स्पष्ट करने के लिए कर सकते हैं कि क्या अशक्त है। उन लोगों के लिए अच्छा है, जिनके पास आपके स्रोत कोड तक पहुंच नहीं है। उन्होंने इसे JDK 8 में Objects.requireNonNull(object, message)उपयोगिता विधि के साथ वन-लाइनर भी बनाया ।
रॉबिन

3
असफलता FAULT के पास होनी चाहिए। "फेल फास्ट" अंगूठे के एक नियम से अधिक है। आप इस व्यवहार को कब नहीं चाहेंगे? किसी भी अन्य व्यवहार का मतलब है कि आप त्रुटियों को छिपा रहे हैं। "FAULTS" और "FAILURES" हैं। विफलता तब होती है जब यह प्रोग्राम NULL पॉइंटर को खोद कर क्रैश कर देता है। लेकिन कोड की वह रेखा नहीं है जहाँ FAULT झूठ है। NULL कहीं से आया - एक विधि तर्क। उस तर्क को किसने पारित किया? स्थानीय चर का संदर्भ देने वाली कोड की कुछ लाइन से। वो कहाँ… देखिये? वह चूसता है। एक खराब मूल्य को संग्रहीत करने की जिम्मेदारी किसकी होनी चाहिए थी? आपका प्रोग्राम तब क्रैश हो जाना चाहिए था।
नोआ स्परियर

4
@ थोमस गुड पॉइंट। शमोसल: थॉमस की बात असफल-तेज़ बिंदु में निहित हो सकती है, लेकिन यह दफन की तरह है। यह एक महत्वपूर्ण पर्याप्त अवधारणा है कि इसका अपना नाम है: असफलता परमाणु । बलोच, प्रभावी जावा , आइटम 46 देखें। इसमें विफल-तेज की तुलना में मजबूत शब्दार्थ है। मेरा सुझाव है कि इसे एक अलग बिंदु में कहें। उत्कृष्ट उत्तर समग्र, BTW। +1
स्टुअर्ट मार्क्स

40

यह स्पष्टता, निरंतरता और अतिरिक्त, अनावश्यक कार्य को प्रदर्शन से रोकने के लिए है।

विचार करें कि यदि विधि के शीर्ष पर एक गार्ड क्लॉज नहीं होता तो क्या होता। यह हमेशा कॉल करेगा hash(key)और getNode(hash, key)यहां तक ​​कि जब एनपीई फेंके nullजाने remappingFunctionसे पहले के लिए पारित किया गया था।

इससे भी बदतर, अगर ifहालत है falseतो हम elseशाखा लेते हैं , जो बिल्कुल भी उपयोग नहीं करता है remappingFunction, जिसका अर्थ है कि विधि हमेशा एनपीई को फेंकती नहीं है जब nullयह पारित हो जाता है; क्या यह नक्शे की स्थिति पर निर्भर करता है।

दोनों परिदृश्य खराब हैं। यदि विधि के nullलिए एक वैध मूल्य नहीं है remappingFunction, तो कॉल के समय ऑब्जेक्ट की आंतरिक स्थिति की परवाह किए बिना लगातार एक अपवाद फेंकना चाहिए, और यह अनावश्यक काम किए बिना ऐसा करना चाहिए जो व्यर्थ दिया गया है कि यह सिर्फ फेंकने वाला है। अंत में, यह साफ, स्पष्ट कोड का एक अच्छा सिद्धांत है कि गार्ड के ठीक सामने हो ताकि स्रोत कोड की समीक्षा करने वाला कोई भी व्यक्ति आसानी से देख सके कि वह ऐसा करेगा।

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


25

@ Shmosel के शानदार जवाब द्वारा सूचीबद्ध कारणों के अलावा ...

प्रदर्शन: JVM को करने देने के बजाय स्पष्ट रूप से NPE को फेंकने के लिए (कुछ JVM पर) प्रदर्शन लाभ / हो सकते हैं।

यह इस रणनीति पर निर्भर करता है कि जावा दुभाषिया और जेआईटी संकलक अशक्त बिंदुओं के डेरेफेरिंग का पता लगाने के लिए क्या करते हैं। एक रणनीति अशक्त के लिए परीक्षण नहीं करना है, लेकिन इसके बजाय SIGSEGV को फंसाना होता है जब एक निर्देश पते पर पहुंचने की कोशिश करता है 0. यह उस मामले में सबसे तेज़ दृष्टिकोण है जहां संदर्भ हमेशा मान्य होता है, लेकिन एनपीई मामले में यह महंगा है।

nullकोड के लिए एक स्पष्ट परीक्षण एक ऐसे परिदृश्य में SIGSEGV प्रदर्शन को हिट करने से बचता है जहां NPEs अक्सर होते थे।

(मुझे संदेह है कि यह आधुनिक जेवीएम में एक सार्थक सूक्ष्म अनुकूलन होगा, लेकिन यह अतीत में हो सकता है।)


संगतता: संभावित कारण यह है कि अपवाद में कोई संदेश नहीं है जो NPEs के साथ संगतता के लिए है जो कि JVM द्वारा ही फेंके गए हैं। एक जावा अनुपालन में, एक NPE JVM द्वारा फेंका गया nullसंदेश है। (Android जावा अलग है।)


20

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

इसलिए, मूल रूप से, C # में NullReferenceExceptionइसका मतलब है कि आपके प्रोग्राम ने वास्तव में इसका उपयोग करने की कोशिश की है, जबकि ArgumentNullExceptionइसका मतलब है कि इसका मतलब है कि यह मान गलत था और इसे इस्तेमाल करने की कोशिश करने की जहमत नहीं उठाई। निहितार्थ वास्तव में अलग हो सकते हैं (परिस्थितियों के आधार पर) क्योंकि ArgumentNullExceptionइसका मतलब है कि प्रश्न में विधि का अभी तक दुष्प्रभाव नहीं हुआ है (क्योंकि यह विधि पूर्व शर्त के रूप में विफल रहा है)।

संयोग से, अगर आप कुछ पसंद कर रहे हैं ArgumentNullExceptionया IllegalArgumentException, यह चेक करने के बिंदु का हिस्सा है: आप "सामान्य रूप से" प्राप्त करने की तुलना में एक अलग अपवाद चाहते हैं।

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


4
मध्य अनुच्छेद के लिए +1। मैं तर्क दूंगा कि प्रश्न में कोड 'नए अवैध अतिक्रमण' को फेंक देना चाहिए ("रीमूलेशनफंक्शन शून्य नहीं हो सकता है"); ' इस तरह यह तुरंत स्पष्ट था कि क्या गलत था। दिखाया गया NPE थोड़ा अस्पष्ट है।
क्रिस पार्कर

1
@ChrisParker मैं एक ही राय का हुआ करता था, लेकिन यह पता चला है कि NullPointerException का उद्देश्य एक अशक्त तर्क को इंगित करने का एक तरीका है जो एक गैर-अशक्त तर्क की उम्मीद करने के लिए एक विधि के लिए पारित किया गया है, इसके अलावा एक अशांति की कोशिश के लिए एक अशांति प्रतिक्रिया है। Javadoc से: "एप्लिकेशन को nullऑब्जेक्ट के अन्य अवैध उपयोगों को इंगित करने के लिए इस वर्ग के उदाहरणों को फेंकना चाहिए ।" मैं इसके बारे में पागल नहीं हूं, लेकिन यह इच्छित डिजाइन प्रतीत होता है।
वीजीआर

1
मैं सहमत हूँ, @ क्रिसपार्क - मुझे लगता है कि वह अपवाद अधिक विशिष्ट है (क्योंकि कोड ने कभी भी मूल्य के साथ कुछ भी करने की कोशिश नहीं की थी, यह तुरंत मान गया कि इसका उपयोग नहीं करना चाहिए)। मुझे इस मामले में C # सम्मेलन पसंद है। C # कन्वेंशन यह है कि NullReferenceException(समतुल्य NullPointerException) का अर्थ है कि आपके कोड ने वास्तव में इसका उपयोग करने की कोशिश की है (जो हमेशा बग है - यह उत्पादन कोड में कभी नहीं होना चाहिए), बनाम "मुझे पता है कि तर्क गलत है, इसलिए मैंने भी नहीं किया इसे इस्तेमाल करने की कोशिश करो। ” वहाँ भी है ArgumentException(जिसका अर्थ है कि तर्क किसी अन्य कारण से गलत था)।
EJoshuaS

2
मैं इतना ही कहूंगा, मैं हमेशा वर्णित के रूप में एक अवैध अतिक्रमण अपवाद को फेंक देता हूं। जब मुझे लगता है कि अधिवेशन गूंगा है तो मैं हमेशा सहज फलावण सम्मेलन महसूस करता हूं।
क्रिस पार्कर

1
@PieterGeerkens - हाँ, क्योंकि NullPointerException लाइन 35 IllegalArgumentException ("फ़ंक्शन को शून्य नहीं किया जा सकता") की तुलना में बहुत बेहतर है। लाइन 35. गंभीरता से?
क्रिस पार्कर

12

ऐसा होता है कि जैसे ही आप नक्शे का उपयोग कर रहे होते हैं, उसके बजाय आपको त्रुटि मिल जाएगी, बल्कि बाद में आपको अपवाद मिलेगा, और समझ में नहीं आएगा कि ऐसा क्यों हुआ।


9

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

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

जाहिर है, जेनेरिक NullPointerExceptionको फेंकना वास्तव में एक बुरा विकल्प है, क्योंकि यह अपने आप में एक अनुबंध उल्लंघन का संकेत नहीं देता है। IllegalArgumentExceptionएक बेहतर विकल्प होगा।


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

void MyClass_foo(MyClass* me, int (*someFunction)(int)) {
    assert(me);
    assert(someFunction);

    ...
}

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


1
assert something != null;लेकिन -assertionsऐप को चलाते समय ध्वज की आवश्यकता होती है । यदि -assertionsझंडा नहीं है, तो मुखर कीवर्ड एक जोर
Zoe

मैं सहमत हूँ, इसीलिए मैं यहाँ C # सम्मेलन को प्राथमिकता देता हूँ - अशक्त संदर्भ, अमान्य तर्क, और अशक्त तर्क सभी आम तौर पर किसी तरह का एक बग का मतलब है, लेकिन वे विभिन्न प्रकार के कीड़े लगाते हैं। "आप एक अशक्त संदर्भ का उपयोग करने की कोशिश कर रहे हैं" अक्सर "पुस्तकालय का दुरुपयोग कर रहे हैं" की तुलना में बहुत अलग है।
EJoshuaS

7

यह इसलिए है क्योंकि यह स्वाभाविक रूप से नहीं होना संभव है । आइए देखें इस तरह का कोड:

bool isUserAMoron(User user) {
    Connection c = UnstableDatabase.getConnection();
    if (user.name == "Moron") { 
      // In this case we don't need to connect to DB
      return true;
    } else {
      return c.makeMoronishCheck(user.id);
    }
}

(निश्चित रूप से कोड गुणवत्ता के बारे में इस नमूने में कई समस्याएं हैं। सही नमूने की कल्पना करने के लिए आलसी को क्षमा करें)

स्थिति जब cवास्तव में उपयोग NullPointerExceptionनहीं किया जाएगा और c == nullसंभव होने पर भी नहीं फेंका जाएगा ।

अधिक जटिल परिस्थितियों में ऐसे मामलों का शिकार करना बहुत ही गैर-आसान हो जाता है। यही कारण है कि सामान्य जांच if (c == null) throw new NullPointerException()बेहतर है।


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

5

यह जानबूझकर आगे की क्षति, या असंगत स्थिति में आने से बचाने के लिए है।


1

यहाँ अन्य सभी उत्कृष्ट उत्तरों के अलावा, मैं कुछ मामलों को जोड़ना चाहूँगा।

यदि आप अपना स्वयं का अपवाद बनाते हैं तो आप एक संदेश जोड़ सकते हैं

यदि आप अपना खुद का फेंकते हैं तो आप NullPointerExceptionएक संदेश जोड़ सकते हैं (जो आपको निश्चित रूप से करना चाहिए!)

डिफ़ॉल्ट संदेश एक है nullसे new NullPointerException()और सभी तरीकों को प्रयोग, उदाहरण के लिए Objects.requireNonNull। यदि आप उस नल को प्रिंट करते हैं, तो यह एक रिक्त स्ट्रिंग में भी अनुवाद कर सकता है ...

थोडा छोटा और असंयमित ...

स्टैक ट्रेस बहुत सारी जानकारी देगा, लेकिन उपयोगकर्ता को यह जानने के लिए कि क्या अशक्त था उन्हें कोड खोदना होगा और सटीक पंक्ति को देखना होगा।

अब कल्पना करें कि NPE को वेब सेवा त्रुटि में एक संदेश के रूप में नेट पर लपेटा और भेजा जा रहा है, शायद विभिन्न विभागों या संगठनों के बीच। सबसे खराब स्थिति में, कोई भी यह पता नहीं लगा सकता है कि क्या nullहै ...

जंजीर विधि कॉल आपको अनुमान लगाते रहेंगे

एक अपवाद केवल आपको बताएगा कि अपवाद किस पंक्ति में हुआ। निम्नलिखित पंक्ति पर विचार करें:

repository.getService(someObject.someMethod());

आप एक एनपीई हो और यह, जिनमें से एक इस पंक्ति में बताते हैं repositoryऔर someObjectथा अशक्त?

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

बहुत सारे इनपुट को संसाधित करते समय त्रुटियां पहचानने वाली जानकारी देनी चाहिए

कल्पना करें कि आपका प्रोग्राम हजारों पंक्तियों के साथ एक इनपुट फ़ाइल संसाधित कर रहा है और अचानक एक NullPointerException है। आप जगह को देखते हैं और महसूस करते हैं कि कुछ इनपुट गलत था ... क्या इनपुट? आपको पंक्ति संख्या के बारे में अधिक जानकारी की आवश्यकता होगी, शायद कॉलम या यहां तक ​​कि संपूर्ण पंक्ति पाठ को समझने के लिए कि फ़ाइल में किस पंक्ति को ठीक करने की आवश्यकता है।

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