कमियों के लिए नेस्टेड प्रयास-कैच के विकल्प


14

मेरे पास एक ऐसी स्थिति है जहां मैं एक वस्तु को पुनः प्राप्त करने की कोशिश कर रहा हूं। यदि लुकअप विफल रहता है तो मेरे पास कई कमियां हैं, जिनमें से प्रत्येक विफल हो सकती है। तो कोड जैसा दिखता है:

try {
    return repository.getElement(x);
} catch (NotFoundException e) {
    try {
        return repository.getSimilarElement(x);
    } catch (NotFoundException e1) {
        try {
            return repository.getParentElement(x);
        } catch (NotFoundException e2) {
            //can't recover
            throw new IllegalArgumentException(e);
        }
    }
}

यह भयानक बदसूरत लग रहा है। मुझे अशक्त लौटने में नफरत है, लेकिन क्या इस स्थिति में बेहतर है?

Element e = return repository.getElement(x);
if (e == null) {
    e = repository.getSimilarElement(x);
}
if (e == null) {
    e = repository.getParentElement(x);
}
if (e == null) {
    throw new IllegalArgumentException();
}
return e;

क्या अन्य विकल्प हैं?

क्या नेस्टेड कोशिश-कैच ब्लॉक का उपयोग एक विरोधी पैटर्न है? संबंधित है, लेकिन इसके उत्तर "कभी-कभी, लेकिन यह आमतौर पर परिहार्य है" की तर्ज पर हैं, बिना यह कहे कि इसे कब या कैसे टालना है।


1
क्या NotFoundExceptionकुछ ऐसा है जो वास्तव में असाधारण है?

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

@ मेरी राय में, निश्चित रूप से नहीं - यह उम्मीद की जानी है। देखें stackoverflow.com/questions/729379/...
कोनराड Morawski

जवाबों:


17

घोंसले को खत्म करने का सामान्य तरीका कार्यों का उपयोग करना है:

Element getElement(x) {
    try {
        return repository.getElement(x);
    } catch (NotFoundException e) {
        return fallbackToSimilar(x);
    }  
}

Element fallbackToSimilar(x) {
    try {
        return repository.getSimilarElement(x);
     } catch (NotFoundException e1) {
        return fallbackToParent(x);
     }
}

Element fallbackToParent(x) {
    try {
        return repository.getParentElement(x);
    } catch (NotFoundException e2) {
        throw new IllegalArgumentException(e);
    }
}

यदि ये फ़ॉलबैक नियम सार्वभौमिक हैं, तो आप इसे सीधे repositoryऑब्जेक्ट में लागू करने पर विचार कर सकते हैं , जहाँ आप ifअपवाद के बजाय केवल सादे स्टेटमेंट का उपयोग करने में सक्षम हो सकते हैं।


1
इस संदर्भ में, methodएक बेहतर शब्द होगा function
सुल्तान

12

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

अपनी कार्यात्मक-प्रोग्रामिंग मानसिकता में, मैं विभिन्न संभावित स्रोतों का प्रतिनिधित्व करने वाले कॉलबैक की सूची स्थापित करूँगा, और जब तक हम पहला सफल नहीं पाते, तब तक उनके माध्यम से लूप करेंगे:

interface ElementSource {
    public Element get();
}

...

final repository = ...;

// this could be simplified a lot using Java 8's lambdas
List<ElementSource> sources = Arrays.asList(
    new ElementSource() {
        @Override
        public Element get() { return repository.getElement(); }
    },
    new ElementSource() {
        @Override
        public Element get() { return repository.getSimilarElement(); }
    },
    new ElementSource() {
        @Override
        public Element get() { return repository.getParentElement(); }
    }
);

Throwable exception = new NoSuchElementException("no sources set up");
for (ElementSource source : sources) {
    try {
        return source.get();
    } catch (NotFoundException e) {
        exception = e;
    }
}
// we end up here if we didn't already return
// so throw the last exception
throw exception;

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


+1 Tryस्काला में प्रकार का उल्लेख करने के लिए, साधुओं का उल्लेख करने के लिए, और एक लूप का उपयोग करके समाधान के लिए।
जियोर्जियो

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

1
दरअसल, जब तक यह जवाब पोस्ट किया गया, तब तक Optionalमोनाड ( प्रमाण ) के समर्थन के साथ जावा 8 पहले ही जारी कर दिया गया था।
mkalkov

3

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

public class TolerantRepository implements SomeKindOfRepositoryInterfaceHopefully {

    private Repository repo;

    public TolerantRepository( Repository r ) {
        this.repo = r;
    }

    public SomeType getElement( SomeType x ) {
        try {
            return this.repo.getElement(x);
        }
        catch (NotFoundException e) {
            /* For example */
            return null;
        }
    }

    // and the same for other methods...

}

3

@ अमोन के सुझाव पर, यहां एक जवाब दिया गया है कि यह अधिक राक्षसी है। यह एक बहुत ही उबला हुआ संस्करण है, जहाँ आपको कुछ मान्यताओं को स्वीकार करना होगा:

  • "यूनिट" या "रिटर्न" फ़ंक्शन क्लास कंस्ट्रक्टर है

  • "बाइंड" ऑपरेशन संकलन समय पर होता है, इसलिए यह आह्वान से छिपा हुआ है

  • "कार्रवाई" फ़ंक्शन भी संकलन समय पर कक्षा के लिए बाध्य हैं

  • हालांकि कक्षा सामान्य है और किसी भी मनमानी वर्ग E को लपेटता है, मुझे लगता है कि वास्तव में इस मामले में ओवरकिल है। लेकिन मैंने इसे इस तरह छोड़ दिया कि आप क्या कर सकते हैं।

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

public class RepositoryLookup<E> {
    private String source;
    private E answer;
    private Exception exception;

    public RepositoryLookup<E>(String source) {
        this.source = source;
    }

    public RepositoryLookup<E> fetchElement() {
        if (answer != null) return this;
        if (! exception instanceOf NotFoundException) return this;

        try {
            answer = lookup(source);
        }
        catch (Exception e) {
            exception = e;
        }

        return this;
    }

    public RepositoryLookup<E> orFetchSimilarElement() {
        if (answer != null) return this; 
        if (! exception instanceOf NotFoundException) return this;

        try {
            answer = lookupVariation(source);
        }
        catch (Exception e) {
            exception = e;
        }

        return this;
    }

    public RepositoryLookup<E> orFetchParentElement() {
        if (answer != null) return this; 
        if (! exception instanceOf NotFoundException) return this;

        try {
            answer = lookupParent(source);
        }
        catch (Exception e) {
            exception = e;
        }

        return this;
    }

    public boolean failed() {
        return exception != null;
    }

    public Exception getException() {
        return exception;
    }

    public E getAnswer() {
        // better to check failed() explicitly ;)
        if (this.exception != null) {
            throw new IllegalArgumentException(exception);
        }
        // TODO: add a null check here?
        return answer;
    }
}

(यह संकलन नहीं होगा ... कुछ विवरणों को नमूना छोटा रखने के लिए अधूरा छोड़ दिया गया है)

और आह्वान इस तरह दिखेगा:

Repository<String> repository = new Repository<String>(x);
repository.fetchElement().orFetchParentElement().orFetchSimilarElement();

if (repository.failed()) {
    throw new IllegalArgumentException(repository.getException());
}

System.err.println("Got " + repository.getAnswer());

ध्यान दें कि आपके पास "लाने" के संचालन के लिए लचीलापन है जैसा आप चाहते हैं। जब इसका उत्तर या अपवाद नहीं मिलेगा तो यह बंद हो जाएगा।

मैंने वास्तव में यह उपवास किया था; यह काफी सही नहीं है, लेकिन उम्मीद है कि इस विचार को व्यक्त करता है


1
repository.fetchElement().fetchParentElement().fetchSimilarElement();- मेरी राय में: बुराई कोड (जॉन स्कीट द्वारा दिए गए अर्थ में)
कोनराड मोरावस्की

कुछ लोग उस शैली को पसंद नहीं करते हैं, लेकिन return thisचाइनिंग ऑब्जेक्ट कॉल बनाने के लिए उपयोग करना लंबे समय से है। चूँकि OO में उत्परिवर्तित वस्तुएँ शामिल होती हैं, अतः return thisकम या ज्यादा return nullबिना जंजीर के समतुल्य है । हालाँकि, return new Thing<E>एक और क्षमता के लिए द्वार खोलता है जो इस उदाहरण में नहीं जाता है, इसलिए इस पैटर्न के लिए यह महत्वपूर्ण है यदि आप इस रास्ते से नीचे जाना चाहते हैं।
रोब

1
लेकिन मुझे वह शैली पसंद है और मैं कॉलिंग या धाराप्रवाह इंटरफेस के खिलाफ नहीं हूं। हालाँकि CustomerBuilder.withName("Steve").withID(403)और इस कोड के बीच एक अंतर है , क्योंकि बस देखने से .fetchElement().fetchParentElement().fetchSimilarElement()यह स्पष्ट नहीं होता है कि क्या होता है, और यह यहाँ महत्वपूर्ण बात है। क्या वे सब पाने के लिए? यह इस मामले में संचित नहीं है, और इसलिए यह सहज नहीं है। मुझे यह देखने की आवश्यकता है कि if (answer != null) return thisइससे पहले कि मैं वास्तव में इसे प्राप्त करूं। शायद यह सिर्फ उचित नामकरण ( orFetchParent) की बात है , लेकिन यह वैसे भी "जादू" है।
कोनराड मोरावस्की

1
वैसे (मैं जानता हूँ कि अपने कोड oversimplified है और सिर्फ अवधारणा का एक सबूत), यह अच्छा होगा शायद का क्लोन वापस जाने के लिए answerमें getAnswerऔर रीसेट (स्पष्ट) answerक्षेत्र में ही अपने मूल्य लौटने से पहले। अन्यथा यह कमांड / क्वेरी पृथक्करण के सिद्धांत को तोड़ता है, क्योंकि किसी तत्व (क्वेरी) को लाने से आपकी रिपॉजिटरी ऑब्जेक्ट की स्थिति बदल जाती है ( answerयह कभी भी रीसेट नहीं होता है) और fetchElementजब आप इसे अगली बार कॉल करते हैं तो व्यवहार को प्रभावित करते हैं । हां, मैं थोड़ा परेशान हूं, मुझे लगता है कि इसका जवाब मान्य है, मैं ऐसा नहीं था जिसने इसे कम किया हो।
कोनराड मोरावस्की

1
ये एक अच्छा बिंदु है। एक और तरीका "tryToFetch ..." होगा। महत्वपूर्ण बिंदु यह है कि इस मामले में, सभी 3 विधियों को बुलाया जाता है, लेकिन किसी अन्य मामले में एक ग्राहक सिर्फ "tryFetch ()। TryFetchParent ()" का उपयोग कर सकता है। इसके अलावा, इसे "रिपॉजिटरी" कहना गलत है, क्योंकि यह वास्तव में एक एकल मॉडलिंग है। हो सकता है कि मैं इसे "रिपोजिटरीलूकअप" और "प्रयास" के नामों से पुकारूँ, यह स्पष्ट करने के लिए कि यह एक-शॉट, क्षणिक विरूपण साक्ष्य है जो एक लुकअप के आसपास कुछ शब्दार्थ प्रदान करता है।
रॉब

2

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

Element e = null;

try {
    e = repository.getElement(x);
} catch (NotFoundException e) {
    // nope -- try again!
}

if (e == null) {  // or ! optionalElement.isPresent()
    try {
        return repository.getSimilarElement(x);
    } catch (NotFoundException e1) {
        // nope -- try again!
    }
}

if (e == null) {  // or ! optionalElement.isPresent()
    try {
        return repository.getParentElement(x);
    } catch (NotFoundException e2) {
        // nope -- try again!
    }
}

if (e == null) {  // or ! optionalElement.isPresent()
    //can't recover
    throw new IllegalArgumentException(e);
}

return e;

इस तरह, आप तत्व की स्थिति देख रहे हैं, और उसके राज्य के आधार पर सही कॉल कर रहे हैं - अर्थात, जब तक आपके पास अभी तक कोई उत्तर नहीं है।

(हालांकि, मैं @amon के साथ सहमत हूं। हालांकि, मैं एक मोनड पैटर्न को देखने की सलाह दूंगा, जिसमें एक रैपर ऑब्जेक्ट है, class Repository<E>जिसमें सदस्य हैं E answer;और Exception error;प्रत्येक चरण में यह देखने के लिए कि क्या कोई अपवाद है, और यदि ऐसा है, तो प्रत्येक शेष चरण को छोड़ दें। अंत में, आप या तो एक उत्तर, एक उत्तर की अनुपस्थिति या एक अपवाद के साथ छोड़ दिए जाते हैं और आप तय कर सकते हैं कि उसके साथ क्या करना है।)


-2

सबसे पहले, यह मुझे लगता है कि एक फ़ंक्शन repository.getMostSimilar(x)होना चाहिए (आपको अधिक उपयुक्त नाम चुनना चाहिए) जैसा कि एक तर्क लगता है जो किसी दिए गए तत्व के लिए निकटतम या सबसे समान तत्व को खोजने के लिए उपयोग किया जाता है।

रिपॉजिटरी तब एम्स पोस्ट में दिखाए गए तर्क को लागू कर सकती है। इसका मतलब है, केवल एक अपवाद को फेंक दिया जाना है जब कोई एकल तत्व नहीं है जो पाया जा सकता है।

हालाँकि यह केवल तभी संभव है जब लॉजिक्स को निकटतम तत्व खोजने के लिए रिपॉजिटरी में एनकैप्सुलेट किया जा सकता है। यदि यह संभव नहीं है, तो कृपया अधिक जानकारी प्रदान करें कि कैसे (किन मानदंडों से) निकटतम तत्व चुना जा सकता है।


जवाब सवाल का जवाब देने के लिए हैं, स्पष्टीकरण का अनुरोध करने के लिए नहीं
gnat

खैर, मेरा जवाब उसकी समस्या को हल कर रहा है क्योंकि यह कुछ शर्तों के तहत नेस्टेड कोशिश / कैच से बचने का रास्ता दिखाता है। केवल अगर ये शर्तें पूरी नहीं होती हैं, तो हमें और अधिक जानकारी चाहिए। यह एक वैध उत्तर क्यों नहीं है?
वलंट्री
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.