अगर (अगर और) को संभालने के लिए सुरुचिपूर्ण तरीके


161

यह एक मामूली झगड़ा है, लेकिन हर बार मुझे कुछ इस तरह से कोड करना पड़ता है, दोहराव मुझे परेशान करता है, लेकिन मुझे यकीन नहीं है कि कोई भी समाधान बदतर नहीं हैं।

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
    }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}
  • क्या इस तरह के तर्क के लिए कोई नाम है?
  • क्या मैं एक ओडीएस भी हूं?

मैं बुराई कोड सुझावों के लिए खुला हूँ, अगर केवल जिज्ञासा के लिए ...


8
@ ईमद करीम: दो DefaultActionकॉल DRY सिद्धांत का उल्लंघन करते हैं
Abyx

आपके उत्तर के लिए धन्यवाद, लेकिन मुझे लगता है कि यह ठीक है, केवल कोशिश / कैच का उपयोग न करने के अलावा, क्योंकि ऐसी त्रुटियां हो सकती हैं जो परिणाम नहीं लौटाती हैं और यह आपके प्रोग्रामिंग भाषा पर निर्भर करता है।
NoChance 12

20
मुझे लगता है कि यहां मुख्य मुद्दा यह है कि आप अमूर्त के असंगत स्तरों पर काम कर रहे हैं । उच्च अमूर्त स्तर है: make sure I have valid data for DoSomething(), and then DoSomething() with it. Otherwise, take DefaultAction()। यह सुनिश्चित करने के लिए कि आप के पास DoSomething () का डेटा कम अमूर्त स्तर पर है, और इसलिए एक अलग फ़ंक्शन में होना चाहिए। इस फ़ंक्शन का नाम उच्च अमूर्त स्तर में होगा, और इसका कार्यान्वयन निम्न-स्तर होगा। नीचे दिए गए अच्छे जवाब इस मुद्दे को संबोधित करते हैं।
गिलाद नोर

6
कृपया एक भाषा निर्दिष्ट करें। संभावित समाधान, मानक मुहावरे, और लंबे समय तक सांस्कृतिक मानदंड अलग-अलग भाषाओं के लिए अलग-अलग हैं और इससे आपके Q.
Caleb

1
आप इस पुस्तक को "रिफ़ैक्टरिंग: इम्प्रूवमेंट ऑफ़ द डिज़ाइन ऑफ़ एक्सिस्टिंग कोड" का संदर्भ दे सकते हैं। इफ-स्ट्रक्चर के बारे में कई खंड हैं, वास्तव में उपयोगी अभ्यास।
Vacker

जवाबों:


96

इसे अलग फ़ंक्शन (विधि) और उपयोग returnकथन के लिए निकालें :

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
        return;
    }
}

DefaultAction();

या, शायद बेहतर, अलग हो रही सामग्री और उसका प्रसंस्करण:

contents_t get_contents(name_t file)
{
    if(!FileExists(file))
        return null;

    contents = OpenFile(file);
    if(!SomeTest(contents)) // like IsContentsValid
        return null;

    return contents;
}

...

contents = get_contents(file)
contents ? DoSomething(contents) : DefaultAction();

upd:

अपवाद क्यों नहीं, OpenFileIO अपवाद क्यों नहीं फेंकता:
मुझे लगता है कि यह वास्तव में सामान्य प्रश्न है, बजाय फाइल IO के बारे में सवाल करने के। जैसे नाम FileExists, OpenFileभ्रामक हो सकते हैं, लेकिन अगर उन लोगों के साथ बदलने के लिए Foo, Bar, आदि, - यह स्पष्ट है कि हो सकता है DefaultActionजितनी बार बुलाया जा सकता है DoSomething, तो यह गैर असाधारण मामला हो सकता है। Péter Török ने अपने जवाब के अंत में इसके बारे में लिखा

द्वितीय संस्करण में टर्नरी सशर्त संचालक क्यों है:
यदि वहाँ [C ++] टैग होगा, तो मैंने इसके स्थिति भाग में ifघोषणा के साथ बयान लिखा था contents:

if(contents_t contents = get_contents(file))
    DoSomething(contents);
else
    DefaultAction();

लेकिन अन्य (सी-लाइक) भाषाओं के लिए, if(contents) ...; else ...;टार्नेरी सशर्त ऑपरेटर के साथ अभिव्यक्ति के बयान के समान ही है, लेकिन लंबे समय तक। क्योंकि कोड का मुख्य भाग get_contentsफ़ंक्शन था , मैंने केवल छोटे संस्करण का उपयोग किया (और contentsटाइप किए गए भी )। वैसे भी, यह इस सवाल से परे है।


93
कई रिटर्न के लिए +1 - जब विधियाँ पर्याप्त रूप से छोटी हो जाती हैं , तो इस दृष्टिकोण ने मेरे लिए सबसे अच्छा काम किया
gnat

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

3
एकाधिक वापसी पथों में C ++ कार्यक्रमों में नकारात्मक प्रदर्शन प्रभाव पड़ सकता है, आरवीओ (NRVO को भी नियोजित करने के लिए ऑप्टिमाइज़र के प्रयासों को पराजित करते हुए, जब तक कि प्रत्येक पथ समान वस्तु नहीं देता)।
क्रियाशील

मैं दूसरे समाधान पर तर्क को उलटने की सलाह देता हूं: {if (फ़ाइल मौजूद है) {सेट की गई सामग्री; अगर (कुछ) {वापसी सामग्री; }} वापसी अशक्त; } यह प्रवाह को सरल करता है और लाइनों की संख्या को कम करता है।
वेज

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

56

यदि आप जिस प्रोग्रामिंग लैंग्वेज का उपयोग कर रहे हैं (0) शॉर्ट सर्किट बाइनरी तुलना (यदि वह कॉल नहीं करता है SomeTestतो FileExistsवह गलत है) और (1) असाइनमेंट एक मान लौटाता है (परिणाम OpenFileको सौंपा गया है contentsऔर फिर उस मान को एक तर्क के रूप में पारित किया जाता है। करने के लिए SomeTest), तो आप निम्नलिखित की तरह कुछ का उपयोग कर सकते हैं, लेकिन यह अभी भी कोड ध्यान देने योग्य बात है कि एक टिप्पणी करने के लिए आप के लिए सलाह दी जाती है =जानबूझकर है।

if( FileExists(file) && SomeTest(contents = OpenFile(file)) )
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

अगर यह कितना जटिल है, इस पर निर्भर करते हुए, ध्वज चर होना बेहतर हो सकता है (जो कि DefaultActionइस मामले में त्रुटि को संभालने वाले कोड के साथ सफलता / विफलता की स्थितियों के परीक्षण को अलग करता है )


यह है कि मैं यह कैसे करेंगे।
एंथनी

13
ifमेरी राय में, एक बयान में इतना कोड डालने के लिए सुंदर सकल ।
22

15
मैं, इसके विपरीत, इस तरह के "अगर कुछ मौजूद है और इस शर्त को पूरा करता है" कथन। +1
गोरोपिक

मैं भी करता हूँ! मैं व्यक्तिगत रूप से उस तरह से नापसंद करता हूं जिस तरह से लोग कई रिटर्न का उपयोग करते हैं कुछ परिसर को पूरा नहीं किया जाता है। अगर आप मिलते हैं तो आप उन इफ़ेर्टों को उल्टा क्यों नहीं करते और आपके कोड को निष्पादित नहीं करते हैं ?
क्लेर

"अगर कुछ मौजूद है और इस शर्त को पूरा करता है" तो ठीक है। "अगर कुछ मौजूद है और यहाँ कुछ संबंधित है और इस स्थिति को पूरा करता है", OTOH, भ्रामक है। दूसरे शब्दों में, मैं एक स्थिति में दुष्प्रभाव को नापसंद करता हूं।
पिस्कवर

26

DefaultAction पर कॉल की पुनरावृत्ति की तुलना में अधिक गंभीरता से शैली ही है क्योंकि कोड गैर-ऑर्थोगोनल लिखा गया है ( ऑर्थोगोनली लिखने के अच्छे कारणों के लिए यह उत्तर देखें )।

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

if(FileExists(file))
{
    if(! OnNetworkDisk(file))
    {
        contents = OpenFile(file); // <-- prevents inclusion in if
        if(SomeTest(contents))
        {
            DoSomething(contents);
        }
        else
        {
            DefaultAction();
        }
    }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}

लेकिन फिर एक आवश्यकता यह भी है कि हमें 2Gb से अधिक बड़ी फाइलें नहीं खोलनी चाहिए। खैर, हम अभी फिर से अपडेट करते हैं:

if(FileExists(file))
{
    if(LessThan2Gb(file))
    {
        if(! OnNetworkDisk(file))
        {
            contents = OpenFile(file); // <-- prevents inclusion in if
            if(SomeTest(contents))
            {
                DoSomething(contents);
            }
            else
            {
                DefaultAction();
            }
        }
        else
        {
            DefaultAction();
        }
    else
    {
        DefaultAction();
    }
}
else
{
    DefaultAction();
}

यह बहुत स्पष्ट होना चाहिए कि ऐसी कोड शैली एक विशाल रखरखाव दर्द होगी।

यहाँ उन उत्तरों के बीच जो ठीक से मौखिक रूप से लिखे गए हैं, वे एबिक्स का दूसरा उदाहरण हैं और जान हुडेक का उत्तर , इसलिए मैं यह नहीं दोहराऊंगा कि, बस उन बिंदुओं में दो आवश्यकताओं को जोड़ने का मतलब होगा

if(! LessThan2Gb(file))
    return null;

if(OnNetworkDisk(file))
    return null;

(या goto notexists;इसके बजाय return null;), उन लाइनों की तुलना में किसी अन्य कोड को प्रभावित नहीं करना । जैसे ओर्थोगोनल।

परीक्षण करते समय, सामान्य नियम अपवादों का परीक्षण करना चाहिए , न कि सामान्य स्थिति का


8
मेरे लिए +1। शुरुआती रिटर्न एरोहेड एंटी पैटर्न से बचने में मदद करते हैं। देखें कोडिंगहोरर.कॉम / blog/2006/01/flattening - arrow- code.html और lostechies.com/chrismissal/2009/05/27/… इस पैटर्न के बारे में पढ़ने से पहले, मैंने हमेशा प्रति फ़ंक्शन 1 प्रविष्टि / निकास के लिए सदस्यता ली सिद्धांत जो मुझे 15 साल या उससे पहले सिखाया गया था। मुझे लगता है कि यह सिर्फ कोड को पढ़ने में इतना आसान बनाता है और जैसा कि आप अधिक रख-रखाव का उल्लेख करते हैं।
मि। मॉस

3
@MrMoose: तीरंदाजी विरोधी पैटर्न का आपका उल्लेख बेंजोल के स्पष्ट प्रश्न का उत्तर देता है: "क्या इस तरह के तर्क के लिए कोई नाम है?" इसे उत्तर के रूप में पोस्ट करें और आपको मेरा वोट मिल गया है।
5

यह एक शानदार जवाब है, धन्यवाद। और @ मरमोस: "एरोहेड एंटी पैटर्न" संभवतः मेरी पहली गोली का जवाब देता है, इसलिए हां, इसे पोस्ट करें। मैं वादा नहीं कर सकता मैं इसे स्वीकार करूंगा, लेकिन यह वोटों का हकदार है!
बेंजोल

@outis। धन्यवाद। मैंने जवाब जोड़ दिया है। एरोहेड विरोधी पैटर्न निश्चित रूप से हवलदार की पोस्ट में प्रासंगिक है और उनके गार्ड क्लॉज उनके चारों ओर होने पर अच्छी तरह से काम करते हैं। मुझे नहीं पता कि आप दूसरी बुलेट प्वाइंट का जवाब कैसे दे सकते हैं। मैं उस निदान के लिए योग्य नहीं हूं :)
श्री मूस

4
+1 "अपवादों को परखें, न कि सामान्य स्थिति को।"
रॉय टिंकर

25

जाहिर है:

Whatever(Arguments)
{
    if(!FileExists(file))
        goto notexists;
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(!SomeTest(contents))
        goto notexists;
    DoSomething(contents);
    return;
notexists:
    DefaultAction();
}

आपने कहा कि आप बुरे समाधानों के लिए भी खुले हैं, इसलिए बुराई गोटो मायने रखता है, नहीं?

वास्तव में संदर्भ के आधार पर यह समाधान दो बार कार्रवाई करने या बुराई अतिरिक्त चर की तुलना में अच्छी तरह से कम बुराई हो सकता है। मैंने इसे एक फ़ंक्शन में लपेटा, क्योंकि यह निश्चित रूप से लंबे फ़ंक्शन के बीच में ठीक नहीं होगा (बीच में वापसी के कारण कम से कम)। लेकिन लंबे कार्य की तुलना में ठीक नहीं है, अवधि।

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


"बुराई" पर ध्यान दें: C ++ FAQ 6.15 "बुराई" को परिभाषित करता है:

इसका मतलब यह है कि ऐसा कुछ ऐसा है जिससे आपको ज्यादातर समय बचना चाहिए , लेकिन ऐसा कुछ नहीं जो आपको हर समय बचना चाहिए । उदाहरण के लिए, आप इन "बुराई" चीजों का उपयोग करते हुए समाप्त हो जाएंगे जब भी वे "बुराई विकल्पों में से सबसे कम बुराई" होंगे।

और जो gotoइस संदर्भ में लागू होता है । संरचित प्रवाह नियंत्रण निर्माण ज्यादातर समय बेहतर होता है, लेकिन जब आप उस स्थिति में पहुंच जाते हैं, जहां वे अपनी बहुत सी बुराइयों को जमा कर लेते हैं, जैसे कि स्थिति में असाइनमेंट, लगभग 3 स्तरों से अधिक का घोंसला बनाना, कोड या लंबे समय तक डुप्लिकेट कोड डालना, gotoबस समाप्त हो सकता है बुराई कम होना।


11
मेरा कर्सर स्वीकार किए जाने वाले बटन पर मंडरा रहा है ... बस सभी शुद्धतावादियों के बावजूद। ऊओह का प्रलोभन: डी
बेंजोल

2
हाँ हाँ! कोड लिखने का यह बिल्कुल "सही" तरीका है। कोड की संरचना अब है "यदि त्रुटि, त्रुटि को संभालें। सामान्य क्रिया। यदि त्रुटि, त्रुटि को संभालें। सामान्य क्रिया" जो बिल्कुल वैसी ही होनी चाहिए जैसी होनी चाहिए। सभी "सामान्य" कोड सिर्फ एक स्तर के इंडेंटेशन के साथ लिखे गए हैं जबकि सभी त्रुटि संबंधित कोड में इंडेंटेशन के दो स्तर हैं। तो सामान्य और सबसे महत्वपूर्ण कोड को सबसे प्रमुख दृश्य स्थान मिलता है और यह बहुत ही सरलता से और आसानी से प्रवाह को क्रमिक रूप से नीचे की ओर पढ़ने के लिए संभव है। हर तरह से इस जवाब को स्वीकार करते हैं।
२०:२५ बजे

2
और दूसरा पहलू यह है कि इस तरह लिखा गया कोड ऑर्थोगोनल है। उदाहरण के लिए, दो पंक्तियाँ "यदि (! FileExists (फ़ाइल)) \ n \ tgoto notexists;" अब केवल इस एक त्रुटि पहलू (चुंबन) से निपटने और सबसे महत्वपूर्ण बात यह करने के लिए संबंधित हैं अन्य लाइनों में से किसी को प्रभावित नहीं करता । यह जवाब stackoverflow.com/a/3272062/23118 कोड ऑर्थोगोनल रखने के कई अच्छे कारणों को सूचीबद्ध करता है।
हवलदार

5
बुरे समाधानों की बात करते हुए: मैं आपके समाधान को बिना गोटो के कर सकता हूं:for(;;) { if(!FileExists(file)) break; contents = OpenFile(file); if(!SomeTest(contents)) break; DoSomething(contents); return; } /* broken out */ DefaultAction();
21

4
@herby: आपके समाधान की तुलना में अधिक बुराई है goto, क्योंकि आप breakएक तरह से गाली दे रहे हैं, किसी को भी यह उम्मीद नहीं है कि इसका दुरुपयोग किया जाएगा, इसलिए कोड पढ़ने वाले लोगों को यह देखने में अधिक समस्याएं होंगी कि ब्रेक उन्हें गोटो की तुलना में कहां ले जाता है जो इसे स्पष्ट रूप से कहते हैं। इसके अलावा आप एक अनंत लूप का उपयोग कर रहे हैं जो केवल एक बार चलेगा, जो कि भ्रामक होगा। दुर्भाग्य do { ... } while(0)से बिल्कुल भी पढ़ने योग्य नहीं है, क्योंकि आप केवल यह देखते हैं कि यह केवल एक अजीब ब्लॉक है जब आप अंत तक पहुंचते हैं और सी अन्य ब्लॉकों (पर्ल के विपरीत) से टूटने का समर्थन नहीं करता है।
Jan Hudec

12
function FileContentsExists(file) {
    return FileExists(file) ? OpenFile(file) : null;
}

...

contents = FileContentExists(file);
if(contents && SomeTest(contents))
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

या अतिरिक्त पुरुष जाएं और एक अतिरिक्त FileExistsAndConditionMet (फ़ाइल) विधि
बनाएं

@herby SomeTestफ़ाइल अस्तित्व की SomeTestजाँच करता है, तो फ़ाइल अस्तित्व के रूप में एक ही शब्दार्थ हो सकता है , उदाहरण के लिए .gif वास्तव में GIF फ़ाइल है।
Abyx

1
हाँ। निर्भर करता है। @ बंजोल बेहतर जानते हैं।
18

3
... निश्चित रूप से मेरा मतलब था "अतिरिक्त mIle जाओ" ... :)
चाचा 25

2
यहां तक ​​कि मैं नहीं जाता है (और मैं इस में चरम हूँ ) चरम सीमाओं पर रैवियोली ले जा रहा हूँ ... मुझे लगता है कि अब यह अच्छी तरह से पढ़ने योग्य है contents && f(contents)। एक दूसरे को बचाने के लिए दो कार्य ?!
हर्बी

12

एक संभावना:

boolean handled = false;

if(FileExists(file))
{
    contents = OpenFile(file); // <-- prevents inclusion in if
    if(SomeTest(contents))
    {
        DoSomething(contents);
        handled = true;
    }
}
if (!handled)
{
    DefaultAction();
}

बेशक, यह एक अलग तरीके से कोड को और अधिक जटिल बनाता है। इसलिए यह काफी हद तक एक शैली का सवाल है।

एक अलग दृष्टिकोण अपवादों का उपयोग करेगा, उदाहरण के लिए:

try
{
    contents = OpenFile(file); // throws IO exception if file not found
    DoSomething(contents); // calls SomeTest() and throws exception on failure
}
catch(Exception e)
{
    DefaultAction();
    // and the exception should be at least logged...
}

यह सरल दिखता है, हालाँकि यह केवल तभी लागू होता है यदि

  • हम ठीक-ठीक जानते हैं कि किस प्रकार के अपवादों की अपेक्षा की जाती है, और DefaultAction()प्रत्येक पर फिट बैठता है
  • हम उम्मीद करते हैं कि फ़ाइल प्रोसेसिंग सफल होगी, और एक लापता फ़ाइल या असफलता SomeTest()स्पष्ट रूप से एक गलत स्थिति है, इसलिए इस पर एक अपवाद फेंकना उपयुक्त है।

19
, नहीं ~! फ्लैग वैरिएबल नहीं, यह निश्चित रूप से गलत तरीका है, क्योंकि इससे जटिल, समझने में मुश्किल (जहां-जहां-झंडा-सच हो जाता है) और रिफैक्टर कोड के लिए मुश्किल होता है।
एबिक्स नोव

यदि आप इसे संभव दायरे के रूप में स्थानीय रूप में प्रतिबंधित नहीं करते हैं। (function () { ... })()जावास्क्रिप्ट में, { flag = false; ... }सी-
लाइक

अपवाद तर्क के लिए +1, जो परिदृश्य के आधार पर बहुत उपयुक्त समाधान हो सकता है।
स्टीवन ज्यूरिस

4
+1 यह परस्पर 'नू!' मज़ेदार है। मुझे लगता है कि कुछ मामलों में स्टेटस वैरिएबल और शुरुआती रिटर्न दोनों ही उचित हैं। अधिक जटिल दिनचर्या में, मैं स्थिति चर के लिए जाऊंगा, क्योंकि जटिलता जोड़ने के बजाय, यह वास्तव में क्या करता है तर्क स्पष्ट करता है। कुछ गलत नहीं है उसके साथ।
18

1
यह मेरा पसंदीदा प्रारूप है जहां मैं काम करता हूं। 2 मुख्य प्रयोग करने योग्य विकल्प "एकाधिक रिटर्न" और "ध्वज चर" प्रतीत होते हैं। न तो औसत पर किसी भी तरह का सच्चा लाभ होता है, लेकिन दोनों ही कुछ परिस्थितियों को दूसरों की तुलना में बेहतर मानते हैं। अपने विशिष्ट मामले के साथ जाना है। बस एक और "Emacs" बनाम "Vi" धार्मिक युद्ध। :-)
ब्रायन नोब्लुच

11

यह अमूर्तता के उच्च स्तर पर है:

if (WeCanDoSomething(file))
{
   DoSomething(contents);
}
else
{
   DefaultAction();
} 

और यह विवरण में भर जाता है।

boolean WeCanDoSomething(file)
{
    if FileExists(file)
    {
        contents = OpenFile(file);
        return (SomeTest(contents));
    }
    else
    {
        return FALSE;
    }
}

11

कार्यों को एक काम करना चाहिए। उन्हें इसे अच्छी तरह से करना चाहिए। उन्हें ही करना चाहिए।
- रॉबर्ट मार्टिन क्लीन कोड में

कुछ लोग पाते हैं कि दृष्टिकोण थोड़ा चरम है, लेकिन यह बहुत साफ है। मुझे पाइथन में वर्णन करने की अनुमति दें:

def processFile(self):
    if self.fileMeetsTest():
        self.doSomething()
    else:
        self.defaultAction()

def fileMeetsTest(self):
    return os.path.exists(self.path) and self.contentsTest()

def contentsTest(self):
    with open(self.path) as file:
        line = file.readline()
        return self.firstLineTest(line)

जब वह कहता है कि कार्यों को एक काम करना चाहिए, तो उसका मतलब एक चीज है processFile()एक परीक्षण के परिणाम के आधार पर एक क्रिया चुनता है, और यह सब वह करता है। fileMeetsTest()परीक्षण की सभी शर्तों को जोड़ती है, और यही सब कुछ करता है। contentsTest()पहली पंक्ति को स्थानांतरित करता है firstLineTest(), और यह सब करता है।

यह बहुत सारे कार्यों की तरह लगता है, लेकिन यह सीधे अंग्रेजी की तरह व्यावहारिक रूप से पढ़ता है:

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

दी, यह थोड़ा चिंताजनक है, लेकिन ध्यान दें कि यदि कोई अनुरक्षक विवरण के बारे में परवाह नहीं करता है, तो वह कोड की सिर्फ 4 पंक्तियों के बाद पढ़ना बंद कर सकता है processFile(), और उसे अभी भी उच्च स्तर का ज्ञान होगा कि फ़ंक्शन क्या करता है।


5
+1 यह अच्छी सलाह है, लेकिन "एक बात" का गठन अमूर्तता की वर्तमान परत पर निर्भर करता है। processFile () "एक बात," है, लेकिन दो चीजें हैं: fileMeetsTest () और या तो doSomething () या defaultAction ()। मुझे डर है कि "एक बात" पहलू उन शुरुआती लोगों को भ्रमित कर सकता है जो एक प्राथमिकता नहीं समझते हैं।
कालेब

1
यह एक अच्छा लक्ष्य है ... बस इतना ही मेरे बारे में कहना है ... ;-)
ब्रायन नोब्लुच

1
उदाहरण के लिए चर जैसे कि मैं तर्कपूर्ण रूप से पासिंग तर्कों को पसंद नहीं करता। आप "बेकार" उदाहरण चर से भरे हुए हैं और आपके राज्य को तोड़ने और आक्रमणकारियों को तोड़ने के कई तरीके हैं।
हुगोमग

@ कालेब, प्रोसेफाइल () वास्तव में एक काम कर रहा है। जैसा कि कार्ल अपने पोस्ट में कहते हैं, यह परीक्षण करने के लिए कि कौन सी कार्रवाई करनी है, और अन्य तरीकों से कार्रवाई की संभावनाओं के वास्तविक कार्यान्वयन को संदर्भित करने के लिए उपयोग कर रहा है। यदि आप कई और वैकल्पिक क्रियाओं को जोड़ने के लिए थे, तो विधि का एकल उद्देश्य मानदंड तब भी पूरा किया जाएगा जब तक कि तत्काल विधि में तर्क का कोई घोंसला न हो।
S.Robins

6

इसे क्या कहा जाता है के संबंध में, यह आसानी से एरोहेड एंटी पैटर्न में विकसित हो सकता है क्योंकि आपका कोड अधिक आवश्यकताओं को संभालने के लिए बढ़ता है (जैसा कि https://softwareengineering.stackexchange.com/a/122625/33922 पर दिए गए उत्तर द्वारा दिखाया गया है ) फिर नेस्टेड सशर्त बयानों के साथ कोड के विशाल वर्गों के जाल में गिर जाता है जो एक तीर जैसा दिखता है।

लिंक जैसे देखें;

http://codinghorror.com/blog/2006/01/flattening-arrow-code.html

http://lostechies.com/chrismissal/2009/05/27/anti-patterns-and-worst-practices-the-arrowhead-anti-pattern/

Google पर पाए जाने वाले इस और अन्य विरोधी पैटर्न पर बहुत अधिक है।

जेफ इस बारे में अपने ब्लॉग पर कुछ बेहतरीन सुझाव देता है;

1) गार्ड क्लॉस के साथ शर्तों को बदलें।

2) अलग कार्यों में सशर्त ब्लॉकों का विघटन।

3) नकारात्मक जाँचों को सकारात्मक जाँच में बदलें

4) हमेशा अवसर से जल्द से जल्द वापसी करें।

जेफ के ब्लॉग पर स्टीव मैककोनेल्स के बारे में कुछ टिप्पणियों को देखें, जो शुरुआती रिटर्न पर भी सुझाव देते हैं;

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

...

"प्रत्येक दिनचर्या में रिटर्न की संख्या कम से कम करें: किसी दिनचर्या को समझना कठिन होता है, जब इसे नीचे पढ़ रहे हों, तो आप इस संभावना से अनजान होते हैं कि यह कहीं खो गई है। इस कारण से, विवेकपूर्ण तरीके से रिटर्न का उपयोग करें - केवल जब वे सुधरें। पठनीयता। "

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


6

यह DRY, नो-गेटो और नो-मल्टीपल-रिटर्न नियमों के अनुरूप है, मेरी राय में स्केलेबल और पठनीय है:

success = FileExists(file);
if (success)
{
    contents = OpenFile(file);
    success = SomeTest(contents);
}
if (success)
{
    DoSomething(contents);
}
else
{
    DefaultAction();
}

1
मानकों के अनुरूप होना जरूरी नहीं कि अच्छे कोड के बराबर हो। मैं वर्तमान में कोड के इस स्निपेट पर अनिर्दिष्ट हूं।
ब्रायन नोब्लुच

यह सिर्फ 2 डिफ़ॉल्ट जगह (); 2 समान के साथ अगर स्थितियाँ और एक ध्वज चर जोड़ता है जो imo ज्यादा खराब है।
रायथल

3
इस तरह से एक निर्माण का उपयोग करने का लाभ यह है कि जैसे-जैसे परीक्षण की संख्या बढ़ती है, कोड ifअन्य ifएस के अंदर अधिक से अधिक घोंसला बनाना शुरू नहीं करता है । इसके अलावा, असफल केस ( DefaultAction()) को संभालने के लिए कोड केवल एक ही स्थान पर है और डिबगिंग उद्देश्यों के लिए कोड सहायक कार्यों के आसपास नहीं कूदता है और ब्रेकपॉइंट को उन लाइनों में जोड़ देता है जहां successपरिवर्तन किया जाता है जो जल्दी से दिखा सकता है कि कौन से परीक्षण पास हुए (ट्रिगर के ऊपर) ब्रेकपाइंट) और जिनका परीक्षण नहीं किया गया है (नीचे)।
फ्रेंजेंको

1
Yeeaah, मैं एक तरह से यह पसंद कर रहा हूँ, लेकिन मुझे लगता है कि मैं नाम बदलने चाहते हैं successकरने के लिए ok_so_far:)
Benjol

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

3

मैं इसे एक अलग विधि से निकालूंगा और फिर:

if(!FileExists(file))
{
    DefaultAction();
    return;
}

contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return;
}

DoSomething(contents);

जो भी अनुमति देता है

if(!FileExists(file))
{
    DefaultAction();
    return Result.FileNotFound;
}

contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return Result.TestFailed;
}

DoSomething(contents);
return Result.Success;            

तब संभवतः आप DefaultActionकॉल हटा सकते हैं और कॉल करने वाले के DefaultActionलिए निष्पादन छोड़ सकते हैं :

Result OurMethod(file)
{
    if(!FileExists(file))
    {
        return Result.FileNotFound;
    }

    contents = OpenFile(file);
    if(!SomeTest(contents))
    {
        return Result.TestFailed;
    }

    DoSomething(contents);
    return Result.Success;            
}

void Caller()
{
    // something, something...

    var result = OurMethod(file);
    // if (result == Result.FileNotFound || result == Result.TestFailed), or just
    if (result != Result.Success)        
    {
        DefaultAction();
    }
}

मुझे जीन पिंडार का दृष्टिकोण पसंद है ।


3

इस विशेष मामले के लिए, उत्तर आसान है ...

के बीच एक दौड़ की स्थिति है FileExistsऔर OpenFile: यदि फ़ाइल हटा दी जाती है तो क्या होता है?

इस विशेष मामले से निपटने का एकमात्र एकमात्र तरीका है FileExists:

contents = OpenFile(file);
if (!contents) // open failed
    DefaultAction();
else (SomeTest(contents))
    DoSomething(contents);

यह बड़े करीने से इस समस्या का हल और कोड स्वच्छ बनाता है।

सामान्य तौर पर: समस्या पर पुनर्विचार करने और एक अन्य समाधान तैयार करने की कोशिश करें जो पूरी तरह से समस्या से बचा जाता है।


2

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

इस प्रकार आपका उदाहरण बन सकता है:

void DoABunchOfStuff()
{
    if(FileExists(file))
    {
        DoSomethingWithFileContent(file);
        return;
    }

    DefaultAction();
}

void DoSomethingWithFileContent(file)
{        
    var contents = GetFileContents(file)

    if(SomeTest(contents))
    {
        DoSomething(contents);
        return;
    }

    DefaultAction();
}

AReturnType GetFileContents(file)
{
    return OpenFile(file);
}

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


2

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

file = OpenFile(path);
if(isValidFileHandle(file) && SomeTest(file)) {
    DoSomething(file);
} else {
    DefaultAction();
}

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

OpenFileIfSomething(path:String) : FileHandle {
    file = OpenFile(path);
    if (file && SomeTest(file)) {
        return file;
    }
    return null;
}

...

if ((file = OpenFileIfSomething(path))) {
    DoSomething(file);
} else {
    DefaultAction();
}

2

हालांकि, मैं सी # वैसे भी, फ्रेंजेंकोई के साथ समझौता कर रहा हूं, मैंने सोचा कि यह ट्रायपर्स तरीकों के सिंटैक्स का पालन करने में मदद करेगा।

if(FileExists(file) && TryOpenFile(file, out contents))
    DoSomething(contents);
else
    DefaultAction();
bool TryOpenFile(object file, out object contents)
{
    try{
        contents = OpenFile(file);
    }
    catch{
        //something bad happened, computer probably exploded
        return false;
    }
    return true;
}

1

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

if (!ProcessFile(file)) { 
  DefaultAction(); 
}

पर्ल और रूबी प्रोग्रामर लिखते हैं processFile(file) || defaultAction()

अब लिखो ProcessFile:

if (FileExists(file)) { 
  contents = OpenFile(file);
  if (SomeTest(contents)) {
    processContents(contents);
    return true;
  }
}
return false;

1

बेशक आप केवल इन जैसे परिदृश्यों में इतनी दूर जा सकते हैं, लेकिन यहां जाने का एक तरीका है:

interface File<T> {
    function isOK():Bool;
    function getData():T;
}

var appleFile:File<Apple> = appleStorage.get(fileURI);
if (appleFile.isOK())
    eat(file.getData());
else
    cry();

आप अतिरिक्त फ़िल्टर चाह सकते हैं। फिर ऐसा करें:

var appleFile = appleStorage.get(fileURI, isEdible);
//isEdible is of type Apple->Bool and will be used internally to answer to the isOK call
if (appleFile.isOK())
    eat(file.getData());
else
    cry();

हालांकि यह सिर्फ समझ में आ सकता है:

function eat(apple:Apple) {
     if (isEdible(apple)) 
         digest(apple);
     else
         die();
}
var appleFile = appleStorage.get(fileURI);
if (appleFile.isOK())
    eat(appleFile.getData());
else
    cry();

जो सबसे अच्छा है? यह वास्तविक दुनिया की समस्या पर निर्भर करता है जिसका आप सामना कर रहे हैं।
लेकिन दूर करने वाली बात यह है: आप रचना और बहुरूपता के साथ बहुत कुछ कर सकते हैं।


1

क्या गलत है

if(!FileExists(file)) {
    DefaultAction();
    return;
}
contents = OpenFile(file);
if(!SomeTest(contents))
{
    DefaultAction();
    return;
}        
DoSomething(contents);

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


0

मुझे पता है कि यह एक पुराना सवाल है, लेकिन मैंने एक पैटर्न पर ध्यान दिया है जिसका उल्लेख नहीं किया गया है; मुख्य रूप से, बाद में विधि को निर्धारित करने के लिए एक चर सेट करना / आप कॉल करना चाहेंगे (यदि ... और ... के बाहर)।

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

विधि के सभी उल्लेखों को बदलने के लिए होने के बजाय (और शायद कुछ परिदृश्यों को याद कर रहे हैं), वे सभी यदि ... ... ब्लॉक के अंत में सूचीबद्ध हैं और पढ़ने और बदलने के लिए सरल हैं। उदाहरण के लिए जब मैं इसका इस्तेमाल करता हूं, तो कई तरीके कहे जा सकते हैं, लेकिन नेस्टेड के भीतर अगर ... और ... एक विधि को कई मैचों में बुलाया जा सकता है।

यदि आप एक चर सेट करते हैं जो राज्य को परिभाषित करता है, तो आपके पास बहुत से नेस्टेड विकल्प हो सकते हैं, और जब कुछ किया जाना है (या नहीं होना है) राज्य को अपडेट करें।

इसका उपयोग उस उदाहरण के रूप में किया जा सकता है, जिस प्रश्न में हम यह जाँच रहे हैं कि क्या 'DoSomething' हुआ है, और यदि नहीं, तो डिफ़ॉल्ट क्रिया करें। या आपके पास उस प्रत्येक विधि के लिए स्थिति हो सकती है जिसे आप कॉल करना चाहते हैं, लागू होने पर सेट कर सकते हैं, फिर यदि ... के बाहर लागू विधि को कॉल करें ...

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

bool ActionDone = false;

if (Method_1(object_A)) // Test 1
{
    result_A = Method_2(object_A); // Result 1

    if (Method_3(result_A)) // Test 2
    {
        Method_4(result_A); // Action 1
        ActionDone = true;
    }
}

if (!ActionDone)
{
    Method_5(); // Default Action
}

0

नेस्टेड आईएफ को कम करने के लिए:

1 / प्रारंभिक वापसी;

2 / यौगिक अभिव्यक्ति (शॉर्ट-सर्किट अवगत)

तो, आपका उदाहरण इस तरह से फिर से प्रस्तुत किया जा सकता है:

if( FileExists(file) && SomeTest(contents = OpenFile(file)) )
{
    DoSomething(contents);
    return;
}
DefaultAction();

0

मैंने "वापसी" के साथ बहुत सारे उदाहरण देखे जो मैं भी उपयोग करता हूं लेकिन कभी-कभी मैं नए कार्यों को बनाने से बचना चाहता हूं और इसके बजाय एक लूप का उपयोग करता हूं:

while (1) {
    if (FileExists(file)) {
        contents = OpenFile(file);
        if (SomeTest(contents)) {
           DoSomething(contents);
           break;
        } 
    }
    DefaultAction();
    break;
}

यदि आप कम पंक्तियों को लिखना चाहते हैं या आप मेरे रूप में अनंत छोरों से नफरत करते हैं, तो आप लूप प्रकार को "डू ... जबकि (0)" में बदल सकते हैं और अंतिम "ब्रेक" से बच सकते हैं।


0

इस समाधान के बारे में कैसे:

content = NULL; //I presume OpenFile returns a pointer 
if(FileExists(file))
    contents = OpenFile(file);
if(content != NULL && SomeTest(contents))
    DoSomething(contents);
else
    DefaultAction();

मैंने यह धारणा बनाई कि ओपनफाइल एक पॉइंटर लौटाता है, लेकिन यह कुछ डिफॉल्ट वैल्यू नॉट रिटर्नेबल (त्रुटि कोड या ऐसा कुछ) निर्दिष्ट करके वैल्यू टाइप रिटर्न के साथ भी काम कर सकता है।

बेशक मैं NULL पॉइंटर पर SomeTest विधि के माध्यम से कुछ संभावित कार्रवाई की उम्मीद नहीं करता (लेकिन आप कभी नहीं जानते), इसलिए इसे SomeTest (सामग्री) कॉल के लिए NULL पॉइंटर के अतिरिक्त चेक के रूप में भी देखा जा सकता है।


0

स्पष्ट रूप से, प्रीप्रोसेसर मैक्रो का उपयोग करने के लिए सबसे सुरुचिपूर्ण और संक्षिप्त समाधान है।

#define DOUBLE_ELSE(CODE) else { CODE } } else { CODE }

जो आपको इस तरह से सुंदर कोड लिखने की अनुमति देता है:

if(FileExists(file))
{
    contents = OpenFile(file);
    if(SomeTest(contents))
    {
        DoSomething(contents);
    }
    DOUBLE_ELSE(DefaultAction();)

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


कुछ लोगों के लिए, और कुछ भाषाओं में, पूर्वप्रक्रमक मैक्रो हैं बुराई कोड :)
Benjol

@Benjol आपने कहा कि आप बुरे सुझावों के लिए खुले थे, नहीं? ;)
पीटर ओल्सन

हां, बिल्कुल, यह सिर्फ आपकी "बुराइयों से बचें" :) था
बेंजोल

4
यह बहुत भयानक है, मुझे अभी इसे उभारना था: D
back2dos

शर्ली, आप गंभीर नहीं हैं !!!!!!
जिम में टेक्सास में

-1

चूंकि आपने जिज्ञासा से बाहर पूछा है, और आपके प्रश्न को एक विशिष्ट भाषा के साथ टैग नहीं किया गया है (भले ही यह स्पष्ट हो कि आपके मन में अनिवार्य भाषाएं थीं), यह जोड़ने के लायक हो सकता है कि आलसी मूल्यांकन का समर्थन करने वाली भाषाएँ पूरी तरह से अलग दृष्टिकोण की अनुमति देती हैं। उन भाषाओं में, अभिव्यक्तियों का मूल्यांकन केवल जरूरत पड़ने पर किया जाता है, इसलिए आप "चर" को परिभाषित कर सकते हैं और उनका उपयोग केवल तभी कर सकते हैं जब यह ऐसा करने के लिए समझ में आता है। उदाहरण के लिए, आलसी let/ inसंरचनाओं के साथ एक काल्पनिक भाषा में आप प्रवाह नियंत्रण के बारे में भूल जाते हैं और लिखते हैं:

let
  contents = ReadFile(file)
in
  if FileExists(file) && SomeTest(contents) 
    DoSomething(contents)
  else 
    DefaultAction()
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.