[C#] क्या शून्य या खाली संग्रह वापस करना बेहतर है?


Answers

फ्रेमवर्क डिजाइन दिशानिर्देश द्वितीय संस्करण से (पृष्ठ 256):

संग्रह गुणों से या शून्य लौटने वाले विधियों से शून्य मान वापस न करें। इसके बजाय एक खाली संग्रह या एक खाली सरणी लौटें।

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

संपादित करें- क्योंकि एरिक लिपर्ट ने अब मूल प्रश्न पर टिप्पणी की है, मैं भी blogs.msdn.com/ericlippert/archive/2009/05/14/… से blogs.msdn.com/ericlippert/archive/2009/05/14/… करना चाहता हूं।

Question

यह एक सामान्य सवाल है (लेकिन मैं सी # का उपयोग कर रहा हूं), सबसे अच्छा तरीका क्या है (सर्वोत्तम अभ्यास), क्या आप एक ऐसे तरीके के लिए शून्य या खाली संग्रह वापस करते हैं जिसमें रिटर्न प्रकार के रूप में संग्रह होता है?




ज्यादातर मामलों में एक खाली संग्रह वापस करना बेहतर है।

इसका कारण कॉलर, निरंतर अनुबंध, और आसान कार्यान्वयन के कार्यान्वयन की सुविधा है।

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

IENumerable के लिए शून्य का वैध उपयोग अनुपस्थित परिणाम, या एक ऑपरेशन विफलता का संकेत हो सकता है, लेकिन इस मामले में अन्य तकनीकों पर विचार किया जाना चाहिए, जैसे अपवाद फेंकना।

using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

namespace .EmptyCollectionUsageTests.Tests
{
    /// <summary>
    /// Demonstrates different approaches for empty collection results.
    /// </summary>
    class Container
    {
        /// <summary>
        /// Elements list.
        /// Not initialized to an empty collection here for the purpose of demonstration of usage along with <see cref="Populate"/> method.
        /// </summary>
        private List<Element> elements;

        /// <summary>
        /// Gets elements if any
        /// </summary>
        /// <returns>Returns elements or empty collection.</returns>
        public IEnumerable<Element> GetElements()
        {
            return elements ?? Enumerable.Empty<Element>();
        }

        /// <summary>
        /// Initializes the container with some results, if any.
        /// </summary>
        public void Populate()
        {
            elements = new List<Element>();
        }

        /// <summary>
        /// Gets elements. Throws <see cref="InvalidOperationException"/> if not populated.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>.</returns>
        public IEnumerable<Element> GetElementsStrict()
        {
            if (elements == null)
            {
                throw new InvalidOperationException("You must call Populate before calling this method.");
            }

            return elements;
        }

        /// <summary>
        /// Gets elements, empty collection or nothing.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with zero or more elements, or null in some cases.</returns>
        public IEnumerable<Element> GetElementsInconvenientCareless()
        {
            return elements;
        }

        /// <summary>
        /// Gets elements or nothing.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with elements, or null in case of empty collection.</returns>
        /// <remarks>We are lucky that elements is a List, otherwise enumeration would be needed.</remarks>
        public IEnumerable<Element> GetElementsInconvenientCarefull()
        {
            if (elements == null || elements.Count == 0)
            {
                return null;
            }
            return elements;
        }
    }

    class Element
    {
    }

    /// <summary>
    /// http://.com/questions/1969993/is-it-better-to-return-null-or-empty-collection/
    /// </summary>
    class EmptyCollectionTests
    {
        private Container container;

        [SetUp]
        public void SetUp()
        {
            container = new Container();
        }

        /// <summary>
        /// Forgiving contract - caller does not have to implement null check in addition to enumeration.
        /// </summary>
        [Test]
        public void UseGetElements()
        {
            Assert.AreEqual(0, container.GetElements().Count());
        }

        /// <summary>
        /// Forget to <see cref="Container.Populate"/> and use strict method.
        /// </summary>
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void WrongUseOfStrictContract()
        {
            container.GetElementsStrict().Count();
        }

        /// <summary>
        /// Call <see cref="Container.Populate"/> and use strict method.
        /// </summary>
        [Test]
        public void CorrectUsaOfStrictContract()
        {
            container.Populate();
            Assert.AreEqual(0, container.GetElementsStrict().Count());
        }

        /// <summary>
        /// Inconvenient contract - needs a local variable.
        /// </summary>
        [Test]
        public void CarefulUseOfCarelessMethod()
        {
            var elements = container.GetElementsInconvenientCareless();
            Assert.AreEqual(0, elements == null ? 0 : elements.Count());
        }

        /// <summary>
        /// Inconvenient contract - duplicate call in order to use in context of an single expression.
        /// </summary>
        [Test]
        public void LameCarefulUseOfCarelessMethod()
        {
            Assert.AreEqual(0, container.GetElementsInconvenientCareless() == null ? 0 : container.GetElementsInconvenientCareless().Count());
        }

        [Test]
        public void LuckyCarelessUseOfCarelessMethod()
        {
            // INIT
            var praySomeoneCalledPopulateBefore = (Action)(()=>container.Populate());
            praySomeoneCalledPopulateBefore();

            // ACT //ASSERT
            Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
        }

        /// <summary>
        /// Excercise <see cref="ArgumentNullException"/> because of null passed to <see cref="Enumerable.Count{TSource}(System.Collections.Generic.IEnumerable{TSource})"/>
        /// </summary>
        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void UnfortunateCarelessUseOfCarelessMethod()
        {
            Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
        }

        /// <summary>
        /// Demonstrates the client code flow relying on returning null for empty collection.
        /// Exception is due to <see cref="Enumerable.First{TSource}(System.Collections.Generic.IEnumerable{TSource})"/> on an empty collection.
        /// </summary>
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void UnfortunateEducatedUseOfCarelessMethod()
        {
            container.Populate();
            var elements = container.GetElementsInconvenientCareless();
            if (elements == null)
            {
                Assert.Inconclusive();
            }
            Assert.IsNotNull(elements.First());
        }

        /// <summary>
        /// Demonstrates the client code is bloated a bit, to compensate for implementation 'cleverness'.
        /// We can throw away the nullness result, because we don't know if the operation succeeded or not anyway.
        /// We are unfortunate to create a new instance of an empty collection.
        /// We might have already had one inside the implementation,
        /// but it have been discarded then in an effort to return null for empty collection.
        /// </summary>
        [Test]
        public void EducatedUseOfCarefullMethod()
        {
            Assert.AreEqual(0, (container.GetElementsInconvenientCarefull() ?? Enumerable.Empty<Element>()).Count());
        }
    }
}



हालात के उपर निर्भर। यदि यह एक विशेष मामला है, तो शून्य वापस लौटें। यदि फ़ंक्शन बस एक खाली संग्रह वापस करने के लिए होता है, तो जाहिर है कि यह लौट रहा है ठीक है। हालांकि, अमान्य पैरामीटर या अन्य कारणों के कारण एक विशेष मामले के रूप में एक खाली संग्रह को वापस करना एक अच्छा विचार नहीं है, क्योंकि यह एक विशेष मामला स्थिति मास्किंग कर रहा है।

असल में, इस मामले में मैं आमतौर पर यह सुनिश्चित करने के लिए अपवाद फेंकना पसंद करता हूं कि इसे वास्तव में अनदेखा नहीं किया गया है :)

यह कहकर कि यह कोड को और अधिक मजबूत बनाता है (खाली संग्रह लौटकर) क्योंकि उन्हें शून्य स्थिति को संभालने की ज़रूरत नहीं है, क्योंकि यह केवल एक समस्या को मास्क कर रहा है जिसे कॉलिंग कोड द्वारा संभाला जाना चाहिए।




रिटर्निंग नल अधिक कुशल हो सकता है, क्योंकि कोई नई ऑब्जेक्ट नहीं बनाई गई है। हालांकि, इसे अक्सर एक null चेक (या अपवाद हैंडलिंग) की आवश्यकता होती है।

अर्थात्, null और खाली सूची का मतलब एक ही चीज़ नहीं है। मतभेद सूक्ष्म हैं और एक विकल्प विशिष्ट उदाहरणों में दूसरे की तुलना में बेहतर हो सकता है।

आपकी पसंद के बावजूद, भ्रम से बचने के लिए इसे दस्तावेज करें।




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

    public static IEnumerable<string> GetFavoriteEmoSongs()
    {
        yield break;
    }

इस विधि को कॉल करते समय सी # भाषा एक खाली गणनाकर्ता लौटाएगी। इसलिए, भाषा डिजाइन (और, इस प्रकार, प्रोग्रामर अपेक्षाओं) के साथ संगत होने के लिए एक खाली संग्रह वापस किया जाना चाहिए।




अपने ग्राहकों के पक्ष में हमेशा सोचें (जो आपके एपीआई का उपयोग कर रहे हैं):

रिटर्निंग 'नल' अक्सर ग्राहकों के साथ समस्याएं बनाता है जो नल चेक को सही तरीके से संभाल नहीं पाते हैं, जो रनटाइम के दौरान एक नलपोइंटर एक्सेप्शन का कारण बनता है। मैंने ऐसे मामलों को देखा है जहां इस तरह के लापता नल-चेक ने प्राथमिकता उत्पादन समस्या को मजबूर कर दिया है (एक ग्राहक ने शून्य मूल्य पर foreach (...) का उपयोग किया था)। परीक्षण के दौरान समस्या नहीं हुई, क्योंकि संचालित डेटा थोड़ा अलग था।




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




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

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

मैं अक्सर उन प्रणालियों से निराश हूं जो शून्य और कोई जवाब के बीच अंतर नहीं कर सकते हैं। मेरे पास कई बार एक सिस्टम ने मुझे कुछ नंबर दर्ज करने के लिए कहा है, मैं शून्य दर्ज करता हूं, और मुझे एक त्रुटि संदेश मिलता है जो मुझे बता रहा है कि मुझे इस क्षेत्र में एक मान दर्ज करना होगा। मैंने अभी किया: मैं शून्य दर्ज किया! लेकिन यह शून्य स्वीकार नहीं करेगा क्योंकि यह इसे किसी भी उत्तर से अलग नहीं कर सकता है।

सौंडर्स को जवाब दें:

हां, मुझे लगता है कि "व्यक्ति ने सवाल का जवाब नहीं दिया" और "जवाब शून्य था" के बीच एक अंतर है। यह मेरे उत्तर के अंतिम अनुच्छेद का मुद्दा था। कई कार्यक्रम खाली या शून्य से "नहीं जानते" में अंतर करने में असमर्थ हैं, जो मुझे संभावित रूप से गंभीर दोष लगता है। उदाहरण के लिए, मैं एक साल या उससे पहले एक घर के लिए खरीदारी कर रहा था। मैं एक रियल एस्टेट वेबसाइट पर गया और $ 0 की मांग कीमत के साथ सूचीबद्ध कई घर थे। मेरे लिए बहुत अच्छा लगा: वे इन घरों को मुफ्त में दे रहे हैं! लेकिन मुझे यकीन है कि दुखी वास्तविकता यह थी कि उन्होंने कीमत में प्रवेश नहीं किया था। उस स्थिति में आप कह सकते हैं, "ठीक है, वास्तव में शून्य का मतलब है कि उन्होंने कीमत में प्रवेश नहीं किया - कोई भी घर को मुफ्त में नहीं दे रहा है।" लेकिन साइट ने विभिन्न शहरों में घरों की औसत मांग और बिक्री की कीमत भी सूचीबद्ध की। मैं मदद नहीं कर सकता लेकिन आश्चर्यचकित हूं कि औसत में शून्य शामिल नहीं है, इस प्रकार कुछ स्थानों के लिए गलत तरीके से कम औसत दिया जाता है। यानी $ 100,000 का औसत क्या है; $ 120,000; और "पता नहीं"? तकनीकी रूप से जवाब "पता नहीं" है। हम शायद वास्तव में देखना चाहते हैं $ 110,000 है। लेकिन हमें शायद $ 73,333 मिल जाएगा, जो पूरी तरह से गलत होगा। साथ ही, अगर हमें ऐसी साइट पर यह समस्या हो, जहां उपयोगकर्ता ऑनलाइन ऑर्डर कर सकते हैं? (अचल संपत्ति के लिए असंभव है, लेकिन मुझे यकीन है कि आपने इसे कई अन्य उत्पादों के लिए देखा है।) क्या हम वास्तव में "मुक्त" कीमत को "मुक्त" के रूप में व्याख्या करने के लिए चाहते हैं?

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

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

जाममीकेक्स को उत्तर दें:

विचार करें कि वास्तविक कोड कैसा दिखता है। मुझे पता है कि सवाल सी # है लेकिन अगर मैं जावा लिखता हूं तो मुझे क्षमा करें। मेरा सी # बहुत तेज नहीं है और सिद्धांत समान है।

शून्य वापसी के साथ:

HospList list=patient.getHospitalizationList(patientId);
if (list==null)
{
   // ... handle missing list ...
}
else
{
  for (HospEntry entry : list)
   //  ... do whatever ...
}

एक अलग समारोह के साथ:

if (patient.hasHospitalizationList(patientId))
{
   // ... handle missing list ...
}
else
{
  HospList=patient.getHospitalizationList(patientId))
  for (HospEntry entry : list)
   // ... do whatever ...
}

यह वास्तव में शून्य वापसी के साथ एक पंक्ति या दो कम कोड है, इसलिए यह कॉलर पर अधिक बोझ नहीं है, यह कम है।

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

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

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

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




Links