yedekler için iç içe geçmiş yakalama seçeneklerine alternatifler


14

Bir nesneyi almaya çalıştığım bir durum var. Arama başarısız olursa, her biri başarısız olabilecek birkaç yedekim var. Kod şöyle görünür:

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

Bu çok çirkin gözüküyor. Boş döndürmekten nefret ediyorum, ama bu durumda daha iyi mi?

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;

Başka alternatifler var mı?

Yuvalanmış try-catch blokları kullanmak bir anti-desen midir? ilgili, ancak cevaplar "bazen, ama genellikle önlenebilir" çizgisinde, ne zaman veya nasıl önleneceğini söylemeden.


1
NotFoundExceptionaslında olağanüstü bir şey?

Bilmiyorum ve muhtemelen bu yüzden sorun yaşıyorum. Bu, ürünlerin günlük olarak kesildiği bir e-ticaret bağlamındadır. Birisi daha sonra durdurulan bir ürüne yer işareti koyar ve ardından yer işaretini açmaya çalışırsa ... bu istisnai midir?
Alex Wittig

Bence @FiveNine, kesinlikle hayır - beklenebilir. Bkz. Stackoverflow.com/questions/729379/…
Konrad Morawski

Yanıtlar:


17

Yerleştirmeyi ortadan kaldırmanın genel yolu işlevleri kullanmaktır:

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

Bu geri dönüş kuralları evrenselse, bunu doğrudan bir istisna yerine repositorydüz ififadeleri kullanabileceğiniz nesneye uygulamayı düşünebilirsiniz .


1
Bu bağlamda, methoddaha iyi bir kelime olurdu function.
Sulthan

12

Seçenek monad gibi bir şeyle bu gerçekten kolay olurdu. Ne yazık ki, Java bunlara sahip değil. Scala'da, ilk başarılı çözümü bulmak için Trytürü kullanırdım.

İşlevsel programlama zihniyetimde, çeşitli olası kaynakları temsil eden geri aramaların bir listesini ayarladım ve ilk başarılı olanı bulana kadar aralarında dolaştım:

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;

Bu, yalnızca çok fazla kaynağınız varsa veya kaynakları çalışma zamanında yapılandırmanız gerektiğinde önerilebilir. Aksi takdirde, bu gereksiz bir soyutlamadır ve kodunuzu basit ve aptal tutmaktan daha fazla kazanç elde edersiniz ve sadece bu çirkin yuvalanmış try-catch'ları kullanın.


TryScala'daki türü belirtmek, monadlardan bahsetmek ve bir döngü kullanarak çözüm için +1 .
Giorgio

Java 8'de olsaydım, bunun için giderdim, ama dediğin gibi, sadece birkaç yedek için biraz fazla.
Alex Wittig

1
Aslında, bu cevap gönderildiğinde, Optionalmonad ( kanıt ) desteğine sahip Java 8 zaten yayınlandı.
mkalkov

3

Bu havuz çağrılarının birçoğunun atılacağını tahmin ediyorsanız NotFoundException, kodu düzene koymak için havuzun etrafında bir sarıcı kullanabilirsiniz. Bunu normal operasyonlar için tavsiye etmem, unutmayın:

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

@ Amon'un önerisine göre, daha monadik bir cevap. Birkaç varsayımı kabul etmeniz gereken çok haşlanmış bir versiyon:

  • "birim" veya "dönüş" işlevi sınıf yapıcısıdır

  • "bağlama" işlemi derleme zamanında gerçekleşir, dolayısıyla çağrıdan gizlenir

  • "action" işlevleri de derleme zamanında sınıfa bağlıdır

  • Her ne kadar sınıf genel ve herhangi bir keyfi E sınıfı sararsa da, bu durumda aslında aşırıya kaçan şey olduğunu düşünüyorum. Ama bunu yapabileceğin bir örnek olarak bıraktım.

Bu düşüncelerle, monad akıcı bir sarmalayıcı sınıfına dönüşür (tamamen işlevsel bir dilde alacağınız esnekliğin çoğundan vazgeçmenize rağmen):

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

(bu derlenmeyecektir ... numuneyi küçük tutmak için bazı ayrıntılar tamamlanmamıştır)

Ve çağırma şöyle görünecektir:

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());

"Getirme" işlemlerini istediğiniz gibi oluşturma esnekliğine sahip olduğunuzu unutmayın. Bulunamayan bir cevap veya istisna aldığında duracaktır.

Bunu çok hızlı yaptım; bu doğru değil, ama umarım fikri iletir


1
repository.fetchElement().fetchParentElement().fetchSimilarElement();- bence: kötü kod (Jon Skeet tarafından verilen anlamda)
Konrad Morawski

bazı insanlar bu stili sevmez, ancak return thiszincirleme nesne çağrıları oluşturmak için kullanmak uzun zamandır var. OO değişken nesneleri içerdiğinden, zincirleme olmaksızın return thisaşağı yukarı eşdeğerdir return null. Bununla birlikte, return new Thing<E>bu örneğin girmediği başka bir yeteneğin kapısını açar, bu nedenle bu yolda ilerlemeyi seçerseniz bu desen için önemlidir.
Rob

1
Ama bu tarzı seviyorum ve zincirleme çağrılara veya akıcı arayüzlere karşı değilim. Ancak CustomerBuilder.withName("Steve").withID(403)bu kod ile bu kod arasında bir fark var , çünkü sadece .fetchElement().fetchParentElement().fetchSimilarElement()ne olduğunu net olarak görmekten ve buradaki anahtar şey bu. Hepsi getiriliyor mu? Bu durumda birikmez ve bu nedenle sezgisel değildir. Bunu if (answer != null) return thisgerçekten anlamadan önce görmeliyim . Belki de sadece uygun bir adlandırma meselesi ( orFetchParent), ama yine de "sihir".
Konrad Morawski

1
, Belki bir klon dönmek iyi olurdu arada (Ben senin kod basitleştirilmiş olduğunu ve kavram sadece bir kanıtı) answeriçinde getAnswerve sıfırlama (net) answerdeğerini dönmeden önce alan kendisi. Aksi takdirde, komut / sorgu ayırma ilkesini ihlal eder, çünkü bir öğeyi getirmeyi (sorgulama) istemek depo nesnenizin durumunu değiştirir ( answerasla sıfırlanmaz) ve bir fetchElementdahaki sefere çağırdığınızda davranışını etkiler . Evet, biraz niteliyorum, bence cevap geçerli, bunu aşağılayan kişi ben değildim.
Konrad Morawski

1
İyi bir noktaya değindin. Başka bir yol da "attemptToFetch ..." olacaktır. Önemli olan, bu durumda, tüm 3 yöntemin çağrılmasıdır, ancak başka bir durumda bir istemci sadece "attemptFetch (). AttemptFetchParent ()" kullanabilir. Ayrıca, "Havuz" olarak adlandırmak yanlıştır, çünkü gerçekten tek bir getirmeyi modellemektedir. Belki de "RepositoryLookup" adını vermek için isimlerle uğraşacağım ve bunun bir arama etrafında belirli anlambilim sağlayan tek adımlı, geçici bir yapı olduğunu açıkça belirtmeye çalışacağım.
Rob

2

Bunun gibi bir dizi koşulu yapılandırmanın başka bir yolu, koşulları zincirlemek için bir bayrak taşımak veya başka bir şekilde null testi yapmak (daha iyisi, iyi bir cevabın ne zaman mevcut olduğunu belirlemek için Guava'nın İsteğe Bağlı'sını kullanmaktır) kullanmaktır.

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;

Bu şekilde, öğenin durumunu izliyor ve durumuna göre doğru çağrıları yapıyorsunuz, yani henüz bir cevabınız olmadığı sürece.

(Ben, gerçi @amon katılıyorum. Ben böyle bir sarıcı nesne ile bir Monad şekline bakarak öneriyoruz class Repository<E>o üyeye sahip E answer;ve Exception error;bir istisna olmadığını görmek için her aşama Check., Ve eğer öyleyse, kalan her adımı atlayın. At Sonunda, bir cevap, bir cevabın olmaması ya da bir istisna ile kalırsınız ve bununla ne yapacağınıza karar verebilirsiniz.)


-2

Birincisi, belli bir repository.getMostSimilar(x)eleman için en yakın veya en benzer öğeyi bulmak için kullanılan bir mantık var gibi göründüğü gibi (daha uygun bir isim seçmelisiniz) gibi bir fonksiyon olmalı.

Depo daha sonra amons postunda gösterildiği gibi mantığı uygulayabilir. Bu, bir istisnanın atılması gereken tek durumun bulunabilecek tek bir unsur olmadığıdır.

Ancak bu elbette ancak en yakın öğeyi bulmak için mantıkların depoya kapsüllenebilmesi durumunda mümkündür. Bu mümkün değilse, en yakın elemanın nasıl (hangi kriterlere göre) seçilebileceği hakkında daha fazla bilgi verin.


cevaplar değil istek açıklama soruyu cevaplamak için vardır
tatarcık

Cevabım, belirli koşullar altında iç içe deneme / yakalamalardan kaçınmanın bir yolunu gösterdiğinden, sorununu çözmektir. Sadece bu koşullar yerine getirilmezse daha fazla bilgiye ihtiyacımız var. Bu neden geçerli bir cevap değil?
valenterry
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.