क्या बाद में इसका उपयोग करने के लिए एक झंडे को लूप में सेट करने के लिए एक कोड गंध है?


30

मेरे पास कोड का एक टुकड़ा है जहां मैं एक नक्शे को पुनरावृत्त करता हूं जब तक कि एक निश्चित स्थिति सत्य नहीं होती है और फिर बाद में कुछ और सामान करने के लिए उस स्थिति का उपयोग करते हैं।

उदाहरण:

Map<BigInteger, List<String>> map = handler.getMap();

if(map != null && !map.isEmpty())
{
    for (Map.Entry<BigInteger, List<String>> entry : map.entrySet())
    {
        fillUpList();

        if(list.size() > limit)
        {
            limitFlag = true;
            break;
        }
    }
}
else
{
    logger.info("\n>>>>> \n\t 6.1 NO entries to iterate over (for given FC and target) \n");
}

if(!limitFlag) // Continue only if limitFlag is not set
{
    // Do something
}

मुझे लगता है कि एक ध्वज स्थापित करना और फिर अधिक सामान करने के लिए उपयोग करना एक कोड गंध है।

क्या मैं सही हू? मैं इसे कैसे निकाल सकता हूं?


10
आपको ऐसा क्यों लगता है कि यह एक कोड गंध है? ऐसा करते समय आप किस प्रकार की विशिष्ट समस्याओं को दूर कर सकते हैं जो एक अलग संरचना के तहत नहीं होगी?
बेन कॉटरेल

13
@ gnasher729 जिज्ञासा से बाहर, आप इसके बजाय किस शब्द का उपयोग करेंगे?
बेन कॉटरेल

11
-1, आपके उदाहरण का कोई मतलब नहीं है। entryफ़ंक्शन लूप के अंदर कहीं भी उपयोग नहीं किया जाता है, और हम केवल अनुमान लगा सकते हैं कि क्या listहै। है fillUpListभरण करने वाला list? यह एक पैरामीटर के रूप में क्यों नहीं मिलता है?
डॉक्टर ब्राउन

13
मुझे आपके व्हाट्सएप और खाली लाइनों के उपयोग पर पुनर्विचार करना चाहिए।
डैनियल जर्स

11
कोड बदबू आने जैसी कोई बात नहीं है। "कोड गंध" एक शब्द है जो सॉफ्टवेयर डेवलपर्स द्वारा आविष्कार किया गया है जो अपनी नाक पकड़ना चाहते हैं जब उन्हें कोड दिखाई देता है जो उनके अभिजात्य मानकों को पूरा नहीं करता है।
रॉबर्ट हार्वे

जवाबों:


70

अपने इच्छित उद्देश्य के लिए बूलियन मान का उपयोग करने में कुछ भी गलत नहीं है: एक द्विआधारी भेद को रिकॉर्ड करने के लिए।

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

if(fill_list_from_map()) {
  ...

6
वास्तव में उसके कोड में गंध लंबे कार्य है जिसे छोटे कार्यों में विभाजित करने की आवश्यकता है। आपका सुझाव रास्ता तय करना है।
बर्नहार्ड हिलर

2
एक बेहतर वाक्यांश जो उस कोड के पहले भाग के उपयोगी कार्य का वर्णन करता है, यह पता लगा रहा है कि क्या उन मैप किए गए आइटमों से कुछ जमा होने के बाद सीमा पार हो जाएगी। हम यह भी सुरक्षित रूप से मान सकते हैं कि fillUpList()कुछ कोड (जो ओपी साझा नहीं करने का फैसला करता है) जो वास्तव entryमें पुनरावृत्ति से मूल्य का उपयोग करता है ; इस धारणा के बिना, ऐसा लगेगा कि लूप शरीर लूप पुनरावृत्ति से कुछ भी उपयोग नहीं करता है।
रवांग

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

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

6
क्लीन कोड के पेज 45 पर अंकल बॉब कहते हैं : "फ़ंक्शंस को या तो कुछ करना चाहिए या कुछ का जवाब देना चाहिए, लेकिन दोनों को नहीं। या तो आपके फ़ंक्शन को किसी ऑब्जेक्ट की स्थिति बदलनी चाहिए, या उसे उस ऑब्जेक्ट के बारे में कुछ जानकारी वापस करनी चाहिए। दोनों को करने से अक्सर होता है। उलझन।"
सीजे डेनिस

25

यह जरूरी बुरा नहीं है, और कभी-कभी यह सबसे अच्छा समाधान है। लेकिन नेस्ट ब्लॉकों में इस तरह झंडे की स्थापना कर सकते हैं कोड का पालन करने के लिए कड़ी मेहनत करते हैं।

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


2
यही कारण था कि मुझे लगा कि यह एक कोड गंध है, क्योंकि ब्लॉक पूरी तरह से अलग नहीं हैं और बाद में ट्रैक करना मुश्किल हो सकता है। तो मुझे लगता है कि कोड @Kilian का जवाब है कि हम निकटतम प्राप्त कर सकते हैं?
सिद्धार्थ त्रिखा

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

2
@SiddharthTrikha: इस वसीयत की तरह प्रारंभिक परीक्षण को गार्ड क्लॉज में बदलकर आसानी से हल किया जा सकता है if(map==null || map.isEmpty()) { logger.info(); return;}, हालांकि, केवल तभी काम करें जब हम जो कोड देखते हैं वह किसी फ़ंक्शन का पूर्ण निकाय है, और // Do somethingमामले में मानचित्र की आवश्यकता नहीं है अशक्त या खाली है।
डॉक्टर ब्राउन

14

मैं 'कोड गंध' के बारे में तर्क के खिलाफ सलाह दूंगा। यह सिर्फ अपने स्वयं के पूर्वाग्रहों को तर्कसंगत बनाने का सबसे आसान तरीका है। समय के साथ आप बहुत सारे पूर्वाग्रह विकसित कर लेंगे, और उनमें से बहुत कुछ उचित होगा, लेकिन उनमें से बहुत कुछ बेवकूफ होगा।

इसके बजाय, आपके पास एक चीज़ को दूसरे पर पसंद करने के लिए व्यावहारिक (यानी, हठधर्मिता नहीं) कारण होना चाहिए और यह सोचने से बचना चाहिए कि आपके पास सभी समान प्रश्नों के लिए समान उत्तर होना चाहिए।

जब आप नहीं होते हैं तो "कोड बदबू आती है" सोच रहे हैं। यदि आप वास्तव में कोड के बारे में सोचने जा रहे हैं, तो इसे सही करें!

इस मामले में, निर्णय वास्तव में आसपास के कोड के आधार पर किसी भी तरह से जा सकता है। यह वास्तव में इस बात पर निर्भर करता है कि आप क्या सोचते हैं यह सोचने का सबसे स्पष्ट तरीका है कि कोड क्या कर रहा है। ("स्वच्छ" कोड वह कोड है जो स्पष्ट रूप से यह बताता है कि यह अन्य डेवलपर्स के लिए क्या कर रहा है और यह सत्यापित करना उनके लिए आसान है कि यह सही है)

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

यदि, दूसरी ओर, "// डू समथिंग" कोड बहुत सरल है, तो यह ifध्वज को स्थापित करने के बजाय इसे ब्लॉक के अंदर रखने के लिए अधिक समझ में आता है । यह प्रभाव को कारण के करीब रखता है, और पाठक को यह सुनिश्चित करने के लिए कोड को स्कैन करने की आवश्यकता नहीं है कि झंडा आपके द्वारा निर्धारित मूल्य को बरकरार रखता है।


5

हां, यह एक कोड गंध है (हर कोई जो इसे करता है, उससे नीचे आता है)।

मेरे लिए महत्वपूर्ण बात का उपयोग है break बयान । यदि आपने इसका उपयोग नहीं किया है, तो आप आवश्यकता से अधिक वस्तुओं पर पुनरावृत्ति करेंगे, लेकिन इसका उपयोग करने से लूप से दो संभव निकास बिंदु मिलते हैं।

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

जब कोड आपके उदाहरण के समान सरल होता है, तो इसे एक whileलूप या समकक्ष मानचित्र, फ़िल्टर निर्माण में कम किया जा सकता है ।

जब कोड को झंडे की आवश्यकता के लिए पर्याप्त जटिल होता है और टूट जाता है तो यह कीड़े के लिए प्रवण होगा।

तो जैसा कि सभी कोड से बदबू आती है: यदि आपको कोई झंडा दिखता है, तो उसे बदलने की कोशिश करें while। यदि आप नहीं कर सकते हैं, तो अतिरिक्त इकाई परीक्षण जोड़ें।


मुझ से +1। यह सुनिश्चित करने के लिए एक कोड गंध है और आप स्पष्ट करते हैं कि क्यों, और इसे कैसे संभालना है, अच्छी तरह से।
डेविड अरनो

@ इवान: SO as with all code smells: If you see a flag, try to replace it with a whileक्या आप इस पर एक उदाहरण के साथ विस्तार कर सकते हैं?
सिद्धार्थ त्रिखा

2
लूप से कई एक्ज़िट पॉइंट्स होने के कारण इसके बारे में तर्क करना कठिन हो सकता है, लेकिन इस मामले में लूप कंडीशन को फ़्लैग पर निर्भर करने के लिए इसे रीफैक्टर करने के साथ - इसका मतलब होगा कि इसके for (Map.Entry<BigInteger, List<String>> entry : map.entrySet())साथ रिप्लेस होना चाहिए for (Iterator<Map.Entry<BigInteger, List<String>>> iterator = map.entrySet().iterator(); iterator.hasNext() && !limitFlag; Map.Entry<BigInteger, List<String>> entry = iterator.next())। यह एक असामान्य पर्याप्त पैटर्न है कि मैं इसे अपेक्षाकृत सरल विराम की तुलना में समझने में अधिक परेशानी होगी।
जेम्स_पिक

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

1
क्या आपका मतलब "कतार" के बजाय "क्यू" नहीं है?
psmears

0

बस लिमिटफ्लेग के अलावा किसी अन्य नाम का उपयोग करें जो बताता है कि आप वास्तव में क्या जाँच रहे हैं। और नक्शा अनुपस्थित या खाली होने पर आप कुछ भी लॉग इन क्यों करते हैं? limtFlag झूठी होगी, जिसकी आप सभी परवाह करते हैं। यदि नक्शा खाली है तो लूप ठीक है, इसलिए इसे जांचने की कोई आवश्यकता नहीं है।


0

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

यदि आप सीमा तक पहुँच जाते हैं, तो आपको लूप लॉजिक के लिए fillUpList पद्धति को स्थानांतरित करना चाहिए। फिर सीधे बाद में सूची का आकार जांचें।

यदि वह आपका कोड तोड़ता है, तो क्यों?


0

सामान्य मामले से पहले: एक संग्रह के कुछ तत्व एक निश्चित स्थिति को पूरा करने के लिए एक झंडे का उपयोग करना असामान्य नहीं है। लेकिन जिस पैटर्न को मैंने इसे हल करने के लिए सबसे अधिक बार देखा है वह है चेक को एक अतिरिक्त विधि में ले जाना और सीधे उससे वापस लौटना (जैसे कि उसके उत्तर में बताए गए सिलियन फोथ ):

private <T> boolean checkCollection(Collection<T> collection)
{
    for (T element : collection)
        if (checkElement(element))
            return true;
    return false;
}

जावा 8 के बाद से अधिक संक्षिप्त तरीका है Stream.anyMatch(…):

collection.stream().anyMatch(this::checkElement);

आपके मामले में यह संभवत: इस तरह लगेगा ( list == entry.getValue()आपके प्रश्न में)

map.values().stream().anyMatch(list -> list.size() > limit);

आपके विशिष्ट उदाहरण में समस्या इसके लिए अतिरिक्त कॉल है fillUpList()। उत्तर बहुत कुछ इस बात पर निर्भर करता है कि यह विधि क्या करने वाली है।

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

तो मुझे लगता है कि वास्तविक कोड entryविधि के लिए वर्तमान गुजरता है ।

लेकिन पूछने के लिए अधिक प्रश्न हैं:

  • क्या इस कोड तक पहुँचने से पहले नक्शे की सूचियाँ खाली हैं? यदि ऐसा है, तो पहले से ही एक नक्शा और BigIntegerचाबियों की सूची या सेट क्यों है ? यदि वे खाली नहीं हैं, तो आपको सूचियों को भरने की आवश्यकता क्यों है ? जब सूची में पहले से ही तत्व हैं, तो क्या यह इस मामले में अपडेट या कुछ अन्य संगणना नहीं है?
  • किसी सूची की सीमा से बड़ा बनने का कारण क्या है? क्या यह एक त्रुटि स्थिति है या अक्सर होने की उम्मीद है? क्या यह अमान्य इनपुट के कारण है?
  • क्या आपको उस सीमा तक गणना की गई सूची की आवश्यकता है जो आप सीमा से बड़ी सूची तक पहुँचते हैं?
  • " कुछ करो " भाग क्या करता है?
  • क्या आप इस भाग के बाद भरने को पुनः आरंभ करते हैं?

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

इसका मतलब या तो यह हो सकता है ("सभी या कुछ भी नहीं" और सीमा तक पहुंचना एक त्रुटि को इंगित करता है):

/**
 * Computes the list of all foo strings for each passed number.
 * 
 * @param numbers the numbers to process. Must not be {@code null}.
 * @return all foo strings for each passed number. Never {@code null}.
 * @throws InvalidArgumentException if any number produces a list that is too long.
 */
public Map<BigInteger, List<String>> computeFoos(Set<BigInteger> numbers)
        throws InvalidArgumentException
{
    if (numbers.isEmpty())
    {
        // Do you actually need to log this here?
        // The caller might know better what to do in this case...
        logger.info("Nothing to compute");
    }
    return numbers.stream().collect(Collectors.toMap(
            number -> number,
            number -> computeListForNumber(number)));
}

private List<String> computeListForNumber(BigInteger number)
        throws InvalidArgumentException
{
    // compute the list and throw an exception if the limit is exceeded.
}

या इसका मतलब यह हो सकता है ("पहली समस्या तक अपडेट"):

/**
 * Refreshes all foo lists after they have become invalid because of bar.
 * 
 * @param map the numbers with all their current values.
 *            The values in this map will be modified.
 *            Must not be {@code null}.
 * @throws InvalidArgumentException if any new foo list would become too long.
 *             Some other lists may have already been updated.
 */
public void updateFoos(Map<BigInteger, List<String>> map)
        throws InvalidArgumentException
{
    map.replaceAll(this::computeUpdatedList);
}

private List<String> computeUpdatedList(
        BigInteger number, List<String> currentValues)
        throws InvalidArgumentException
{
    // compute the new list and throw an exception if the limit is exceeded.
}

या यह ("सभी सूचियों को अद्यतन करें लेकिन मूल सूची को बनाए रखें यदि यह बहुत बड़ी हो जाती है"):

/**
 * Refreshes all foo lists after they have become invalid because of bar.
 * Lists that would become too large will not be updated.
 * 
 * @param map the numbers with all their current values.
 *            The values in this map will be modified.
 *            Must not be {@code null}.
 * @return {@code true} if all updates have been successful,
 *         {@code false} if one or more elements have been skipped
 *         because the foo list size limit has been reached.
 */
public boolean updateFoos(Map<BigInteger, List<String>> map)
{
    boolean allUpdatesSuccessful = true;
    for (Entry<BigInteger, List<String>> entry : map.entrySet())
    {
        List<String> newList = computeListForNumber(entry.getKey());
        if (newList.size() > limit)
            allUpdatesSuccessful = false;
        else
            entry.setValue(newList);
    }
    return allUpdatesSuccessful;
}

private List<String> computeListForNumber(BigInteger number)
{
    // compute the new list
}

या यहां तक ​​कि निम्नलिखित ( computeFoos(…)पहले उदाहरण से लेकिन अपवादों के बिना)

/**
 * Processes the passed numbers. An optimized algorithm will be used if any number
 * produces a foo list of a size that justifies the additional overhead.
 * 
 * @param numbers the numbers to process. Must not be {@code null}.
 */
public void process(Collection<BigInteger> numbers)
{
    Map<BigInteger, List<String>> map = computeFoos(numbers);
    if (isLimitReached(map))
        processLarge(map);
    else
        processSmall(map);
}

private boolean isLimitReached(Map<BigInteger, List<String>> map)
{
    return map.values().stream().anyMatch(list -> list.size() > limit);
}

या इसका मतलब कुछ अलग हो सकता है ... ;;

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