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




hashmap (6)

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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


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

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() बेहतर है।


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

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

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

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

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

    ...
}

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


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

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

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

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

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





hashmap