Bir istisna atmak burada bir anti-patern midir?


33

Kod incelemesinden sonra bir tasarım seçimi hakkında bir tartışma yaptım. Düşüncelerinizin ne olduğunu merak ediyorum.

PreferencesAnahtar-değer çiftleri için bir kova olan bu sınıf var . Boş değerler yasaldır (bu önemlidir). Bazı değerlerin henüz kaydedilmeyebileceğini düşünüyoruz ve talep edildiğinde bu vakaları önceden tanımlanmış varsayılan değerle başlatarak otomatik olarak ele almak istiyoruz.

Aşağıdaki modeli kullanılan tartışılan çözüm (NOT: Bu gerçek kod değil , açıkçası - açıklama amaçlı basitleştirilmiştir):

public class Preferences {
    // null values are legal
    private Map<String, String> valuesFromDatabase;

    private static Map<String, String> defaultValues;

    class KeyNotFoundException extends Exception {
    }

    public String getByKey(String key) {
        try {
            return getValueByKey(key);
        } catch (KeyNotFoundException e) {
            String defaultValue = defaultValues.get(key);
            valuesFromDatabase.put(key, defaultvalue);
            return defaultValue;
        }
    }

    private String getValueByKey(String key) throws KeyNotFoundException {
        if (valuesFromDatabase.containsKey(key)) {
            return valuesFromDatabase.get(key);
        } else {
            throw new KeyNotFoundException();
        }
    }
}

Akışı kontrol altına almak için istismar edici istisnalar hariç tutuldu . KeyNotFoundException- sadece bir kullanım durumu için hayata geçirildi - asla bu sınıfın kapsamı dışında görülmeyecek.

Sadece beraberinde bir şeyler iletmek için getirme oynayan iki yöntemdir.

Veritabanında bulunmayan anahtar, endişe verici bir durum değildir veya istisnai bir durum değildir - bunun, yeni bir tercih ayarı eklendiğinde gerçekleşmesini bekliyoruz;

Buna karşılık, şu getValueByKeyanda tanımlandığı gibi - özel yöntem - kamu yöntemini hem değer hakkında hem de anahtarın orada olup olmadığı konusunda bilgilendirmenin doğal bir yoluydu . (Değilse, değerin güncellenebilmesi için eklenmelidir).

Geri dönüş nullbelirsiz olacaktır, çünkü nulltamamen yasal bir değerdir, dolayısıyla anahtarın orada olup olmadığı veya a olup olmadığına dair hiçbir şey söylenemez null.

getValueByKeyBir çeşit dönmek zorunda kalacak Tuple<Boolean, String>biz ayırt edebilir böylece, anahtar zaten varsa, true olarak ayarlanır bool (true, null)ve (false, null). (Bir outparametre C # ile kullanılabilir, ancak bu Java'dır).

Bu daha iyi bir alternatif mi? Evet, bazı tek kullanımlık sınıfları bunun etkisine göre tanımlamak Tuple<Boolean, String>zorunda kalacaksınız, fakat o zaman bu durumdan kurtulmak için bundan kurtulacağız KeyNotFoundException. Ayrıca, bir istisna sorununu da önlüyoruz, ancak pratik açıdan anlamlı olmasa da - konuşacak performans kaygıları yok, bir istemci uygulaması ve kullanıcı tercihlerinin saniyede milyonlarca kez alınacağı gibi değil.

Bu yaklaşımın bir çeşitlemesi Optional<String>bazı özel yerine Guava'yı (Guava zaten proje boyunca kullanılıyor) Tuple<Boolean, String>kullanabilir ve ardından Optional.<String>absent()"uygun" ile farklılaşabiliriz null. Yine de, kolay anlaşılır nedenlerden ötürü çılgınca geliyor - iki seviyeli "sıfırlık" vermek Optional, ilk başta s yaratmanın ardında yatan kavramı kötüye kullanıyor gibi görünüyor .

Başka bir seçenek, anahtarın var olup olmadığını açıkça kontrol etmek olacaktır (bir boolean containsKey(String key)yöntem ekleyin ve getValueByKeysadece zaten var olduğunu iddia ettiğimizde arayın ).

Sonunda, kişi özel yöntemi de satırlandırabilir, ancak gerçek getByKeykod örneğimden biraz daha karmaşıktır, bu nedenle satır içi oluşturma işlemi oldukça kötü görünmesini sağlar.

Burada kılları ayırıyor olabilirim, ama bu durumda en iyi uygulamaya en yakın olmak için neye bahse gireceğinizi merak ediyorum. Oracle’ın veya Google’ın stil kılavuzlarında bir cevap bulamadım.

Kod örneğindeki gibi istisnalar anti-patern mi kullanıyor yoksa alternatiflerin de çok temiz olmadığı kabul edilebilir mi? Öyleyse, hangi koşullar altında iyi olurdu? Ve tam tersi?


1
@ GlenH7 kabul edilen (ve en yüksek oyu alan) cevap, "daha az kod karmaşası ile hata işlemeyi kolaylaştırırsa bunları [istisnalar] kullanmanız gerektiği" anlamına gelir. Sanırım burada listelediğim alternatiflerin "daha az kod karışıklığı" olarak kabul edilip edilmemesi gerektiğini soruyorum.
Konrad Morawski

1
VTC'mi geri çektim - ilgili bir şey istiyorsun, ancak önerdiğim kopyadan farklı.

3
Ben getValueByKeyde kamuya açık olduğunda sorunun daha ilginç hale geldiğini düşünüyorum .
Doktor Brown,

2
Gelecekte başvurmak için doğru olanı yaptınız. Bu, örnek kod olduğu için Kod İncelemede konu dışı kalacaktı.
RubberDuck

1
Sadece çınlamak için --- bu tamamen aptalca bir Python ve bu sorunu bu dilde ele almanın tercih edilen yolu olurdu (sanırım). Açıkçası, yine de, farklı dillerin farklı kuralları vardır.
Patrick Collins

Yanıtlar:


75

Evet, meslektaşın haklı: bu kötü kod. Yerel olarak bir hata işlenebiliyorsa, derhal ele alınmalıdır. Bir istisna atılmamalı ve hemen ele alınmamalıdır.

Bu sürümünüzden çok daha temiz ( getValueByKey()yöntem kaldırıldı):

public String getByKey(String key) {
    if (valuesFromDatabase.containsKey(key)) {
        return valuesFromDatabase.get(key);
    } else {
        String defaultValue = defaultValues.get(key);
        valuesFromDatabase.put(key, defaultvalue);
        return defaultValue;
    }
}

Bir istisna, yalnızca hatayı yerel olarak nasıl çözeceğinizi bilmiyorsanız atılmalıdır.


14
Cevap için teşekkürler. Kayıt için, ben bu iş arkadaşım (orijinal kodu beğenmedim), soruyu nötr bir şekilde yazdım, böylece yüklenmedi :)
Konrad Morawski

59
Deliliğin ilk işareti: kendinle konuşmaya başla.
Robert Harvey,

1
@ BЈовић: peki, bu durumda tamam olduğunu düşünüyorum, fakat ya iki değişkene ihtiyacınız varsa - biri eksik anahtarların otomatik yaratılması olmadan bir değer elde ederken, diğeriyle? Sizce en temiz (ve kuru) alternatif nedir?
Doc Brown,

3
@RobertHarvey :) başkası bu kod parçasını yazdı, gerçek bir ayrı kişi, yorumcuydum
Konrad Morawski

1
@Alvaro, istisnaları sık sık kullanmak sorun değil. İstisnaları kötü kullanmak, dile bakmaksızın, bir sorundur.
kdbanman

11

Bu İstisnalar'ı bir anti-kalıp olarak kullanmam, karmaşık bir sonuç iletme sorununa en iyi çözüm değil.

En iyi çözüm (hala Java 7'de olduğunuzu varsayarak) Guava'nın İsteğe Bağlı'sını kullanmak olacaktır; Bu durumda kullanımı hackish olacağını kabul etmiyorum. Bana öyle geliyor ki, Guava'nın İsteğe Bağlı seçeneğine ilişkin genişletilmiş açıklamasına dayanarak , bunun ne zaman kullanılacağının mükemmel bir örneği olduğu anlaşılıyor. "Hiçbir değer bulunamadı" ve "boş bulunan" değeri arasında ayrım yapıyorsunuz.


1
OptionalBoş tutabileceğini sanmıyorum . Optional.of()yalnızca boş olmayan bir başvuru alır ve Optional.fromNullable()boş değeri "değer yok" olarak kabul eder.
Navin

1
@Navin boş tutamaz, ancak Optional.absent()emrinizde. Ve böylece, Optional.fromNullable(string)eşit olacaktır Optional.<String>absent()eğer stringya null oldu Optional.of(string)o olmasaydı.
Konrad Morawski

@KonradMorawski OP'deki problemin, bir istisna atmadan boş bir dize ile olmayan bir dize arasında ayrım yapamayacağınız olduğunu düşündüm. Optional.absent()bu senaryolardan birini kapsar. Diğerini nasıl temsil ediyorsun?
Navin

@Navin ile gerçek null.
Konrad Morawski

4
@KonradMorawski: Bu gerçekten kötü bir fikir gibi geliyor. "Bu yöntem asla geri dönmez null!" Demenin daha net bir yolunu düşünemiyorum. iade olarak ilan etmekten daha Optional<...>. . .
ruakh

8

Performans değerlendirmesi olmadığından ve bir uygulama detayı olduğundan, hangi çözümü seçtiğinizin bir önemi yoktur. Ama bunun kötü bir tarz olduğuna katılıyorum; Anahtar varlık yoktur bildiğine göre bir şeydir edecektir olur ve hatta bunu istisnalar en kullanışlı nerede yığını, yukarı birden fazla çağrı ele yoktur.

Halat yaklaşımı biraz bozucudur, çünkü ikinci alan, boole yanlış olduğunda anlamsızdır. Anahtarın önceden var olup olmadığını kontrol etmek aptalca çünkü harita iki kez aranıyor. (Pekala, zaten yapıyorsunuz, bu yüzden bu durumda üç kez.) OptionalÇözüm, sorun için mükemmel bir seçimdir. İronik bir bit saklamak için görünebilir nullbir in Optional, ama bu kullanıcı yapmak istediği buysa, hiç diyemeyiz.

Yorumlarda Mike tarafından belirtildiği gibi, bununla ilgili bir sorun var; Ne Guava ne de Java Optional8'ler nulls depolamaya izin vermez . Bu nedenle, dürüst olmak gerekirse - makul miktarda bir kazan plakası içeren kendinize ait bir rulo kullanmanız gerekir; bu nedenle, yalnızca bir kez dahili olarak kullanılacak bir şey için fazla mazur görülebilir. Ayrıca, haritanın türünü de değiştirebilirsiniz Map<String, Optional<String>>, ancak kullanımı Optional<Optional<String>>zorlaşır.

Makul bir uzlaşma istisnayı sürdürmek olabilir, ancak rolünü "alternatif bir geri dönüş" olarak kabul etmek olabilir. Yığın izleme devre dışı bırakıldığında yeni bir işaretli istisna oluşturun ve statik değişkende tutabileceğiniz önceden oluşturulmuş bir örneğini atın. Bu ucuz - muhtemelen yerel bir şube kadar ucuz - ve bununla başa çıkmayı unutamazsınız, bu nedenle yığın izi eksikliği bir sorun değildir.


1
Aslında, gerçekte sadece Map<String, String>veritabanı tablosu düzeyindedir, çünkü valuessütun tek tip olmalıdır. Yine de, her bir anahtar / değer çiftini güçlü şekilde yazan bir sarıcı DAO katmanı var. Ama bunu netlik için atladım. (Tabii ki n sütunlu tek satırlık bir tablonuz olabilir, ancak daha sonra her yeni değerin eklenmesi için tablo şemasının güncellenmesi gerekir, bu nedenle bunları ayrıştırmakla ilişkili karmaşıklığı kaldırmak başka bir karmaşıklığı başka bir yere koyma pahasına gelir).
Konrad Morawski

Bağlantı hakkında iyi bir nokta. Gerçekten, ne (false, "foo")anlama gelmesi gerekiyordu?
Konrad Morawski

En azından Guava'nın İsteğe Bağlı'sı ile, bir istisna dışında sonuçlarda boş bırakmaya çalışmak. Optional.absent () kullanmanız gerekir.
Mike Partridge

@MikePartridge İyi nokta. Çirkin bir geçici çözüm, haritayı olarak değiştirmektir Map<String, Optional<String>>ve sonra sona erersiniz Optional<Optional<String>>. Daha az kafa karıştırıcı bir çözüm null, izin vermeyen, nullancak 3 duruma sahip olan (bulunmayan, boş veya mevcut) benzer bir cebirsel veri türüne izin veren kendi İsteğe Bağlı öğenizi almak olacaktır . Her ikisinin de uygulanması kolaydır, ancak içerdiği kazan plakası miktarı yalnızca dahili olarak kullanılacak bir şey için yüksektir.
Doval

2
"Performansla ilgili bir değerlendirme olmadığından ve bir uygulama detayı olduğundan, sonuçta hangi çözümü seçeceğiniz önemli değildir" - Bir istisna kullanarak C # kullanıyor olsaydınız, hata ayıklamaya çalışan herkesi rahatsız eder "Bir istisna atıldığında "CLR istisnaları için açık.
Stephen,

5

Anahtar çiftleri için bir kova olan bu Tercihler sınıfı var. Boş değerler yasaldır (bu önemlidir). Bazı değerlerin henüz kaydedilmeyebileceğini düşünüyoruz ve talep edildiğinde bu vakaları önceden tanımlanmış varsayılan değerle başlatarak otomatik olarak ele almak istiyoruz.

Sorun tam olarak bu. Ama çözümü zaten kendin gönderdin:

Bu yaklaşımın bir çeşitlemesi bazı özel Tuple yerine Guava'nın İsteğe Bağlı'sını (Guava zaten proje boyunca kullanılıyor) kullanabilir ve daha sonra Optional.absent () ile "proper" null değerlerini ayırt edebiliriz . Yine de, kolay anlaşılır nedenlerden ötürü çılgınca geliyor - iki seviyeli "sıfırlık" vermek, ilk başta Seçenek oluşturmanın ardında yatan kavramı kötüye kullanıyor gibi görünüyor.

Ancak kullanmayın null veya Optional . Sadece kullanabilirsiniz ve kullanmalısınız Optional. "Kesmek" hissiniz için, sadece yuvalanmış kullanın, böylece Optional<Optional<String>>veritabanında bir anahtar olabileceğini (birinci seçenek katmanı) ve önceden tanımlanmış bir değer (ikinci seçenek katmanı) içerebileceğini açıkça belirtirsiniz.

Bu yaklaşım istisnaları kullanmaktan daha iyidir ve Optionalsizin için yeni olmadığı sürece anlaşılması oldukça kolaydır .

Ayrıca Optionalbazı konfor fonksiyonlarına sahip olduğunu ve tüm işi kendiniz yapmanız gerekmediğini lütfen unutmayın . Bunlar şunları içerir:

  • static static <T> Optional<T> fromNullable(T nullableReference)veritabanı girişinizi Optionaltürlere dönüştürmek için
  • abstract T or(T defaultValue) iç seçenek katmanının anahtar değerini almak veya (yoksa) varsayılan anahtar değerini almak için

1
Optional<Optional<String>>kötü görünüyor, itiraf etmeliyim ki bazı çekicilikleri olmasına rağmen
:)

Neden kötü göründüğünü daha fazla açıklayabilir misiniz? Aslında aynı kalıp List<List<String>>. Elbette (eğer dil izin verirse) , onu DBConfigKeysaran Optional<Optional<String>>ve tercih ederseniz daha okunaklı kılan yeni bir tür yapabilirsiniz .
Valenterry,

2
@ valenterry Çok farklı. Bir liste listesi, bazı veri türlerini depolamanın meşru bir yoludur; isteğe bağlı bir isteğe bağlı, verinin sahip olabileceği iki tür sorunu belirtmenin tipik bir yoludur.
Ypnypn

Bir seçeneğin seçeneği, her listenin bir öğesinin veya öğesinin olmadığı bir liste listesi gibidir. Aslında aynı. Sırf Java'da sık kullanılmaması , doğal olarak kötü olduğu anlamına gelmez.
Valenterry,

1
Jon Skeet'in anlamında @ merkezci kötülük; yani düpedüz kötü, ama biraz kötü, "zeki kod". Sanırım biraz barok, isteğe bağlı bir isteğe bağlı. Hangi isteğe bağlılık seviyesinin neyi temsil ettiği açıkça belli değildir. Birinin kodunda karşılaştığımda iki katına çıkardım. Sıradışı, sıradışı bir şey olduğu için garip hissettirdiği konusunda haklı olabilirsin. Belki de fonksiyonel bir dil programcısı için, monadları ve hepsi ile süslü bir şey değil. Kendim için gitmezken, hala cevabını + 1'ledim, çünkü ilginç
Konrad Morawski

5

Partiye geç kaldığımı biliyorum, ancak yine de kullanım durumunuz Java'nınProperties birinin varsayılan bir dizi özellik tanımlamasına nasıl izin verdiğini andırıyor ; bu durumda, örnek tarafından yüklenen karşılık gelen bir anahtar yoksa kontrol edilecek.

Uygulamanın nasıl yapıldığına bakarak Properties.getProperty(String)(Java 7'den):

Object oval = super.get(key);
String sval = (oval instanceof String) ? (String)oval : null;
return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;

Sizin de belirttiğiniz gibi, "akışı kontrol etmek için istisnaları istismar etme" gerçekten gerekmiyor.

@ BЈовић'nin yanıtına benzer, ancak biraz daha kısa bir yaklaşım da şöyle olabilir:

public String getByKey(String key) {
    if (!valuesFromDatabase.containsKey(key)) {
        valuesFromDatabase.put(key, defaultValues.get(key));
    }
    return valuesFromDatabase.get(key);
}

4

@ BЈовић'nin cevabının getValueByKeybaşka hiçbir yerde ihtiyaç duyulmaması durumunda iyi olduğunu düşünmeme rağmen , programınızın her iki kullanım vakasını da içermesi durumunda çözümünüzün kötü olduğunu sanmıyorum:

  • Anahtarın önceden mevcut olmaması durumunda otomatik yaratma ile anahtarın alınması, ve

  • Bu otomatizm olmadan, veri tabanındaki, havuzdaki veya anahtar haritadaki herhangi bir şeyi değiştirmeden geri alma ( getValueByKeykamuya açık olmayı düşünün, özel değil)

Bu sizin durumunuzsa ve performans vuruşu kabul edilebilir olduğu sürece, önerdiğiniz çözümün tamamen uygun olduğunu düşünüyorum. Alma kodunun tekrarlanmasından kaçınma avantajına sahiptir, üçüncü taraf çerçevelerine dayanmaz ve oldukça basittir (en azından gözlerim).

Aslında, böyle bir durumda, eksik bir anahtarın "istisnai" bir durum olup olmadığı bağlamına bağlıdır. Olduğu bir bağlam için, buna benzer bir şeye getValueByKeyihtiyaç vardır. Otomatik anahtar oluşturmanın beklendiği bir bağlamda, halihazırda mevcut işlevi yeniden kullanan bir yöntem sunmak, istisnayı yutar ve farklı bir başarısızlık davranışı sağlar, mükemmel bir anlam ifade eder. Bu getValueByKey, "istisna kontrol akışı için kötüye kullanıldığı" bir işlev değil, bir uzantısı veya "dekoratörü" olarak yorumlanabilir .

Tabii ki, üçüncü bir alternatif var: dönen üçüncü, özel bir yöntem oluşturmak Tuple<Boolean, String>sen önerildiği gibi, ve hem bu yöntemi yeniden getValueByKeyve getByKey. Gerçekten daha iyi bir alternatif olabilecek daha karmaşık bir durum için, ancak burada gösterilen basit bir durum için, bunun IMHO'ya bir aşırı mühendislik kokusu vardır ve kodun bu şekilde daha da sürdürülebilir olduğundan şüpheliyim. Birlikte buradayım Karl Bielefeldt burada en üstteki cevap :

"Kodunuzu basitleştirdiğinde istisnaları kullanma konusunda kendinizi kötü hissetmemelisiniz".


3

Optionaldoğru çözümdür. Ancak, eğer tercih ederseniz, "iki boş" hissine daha az sahip olmayan bir alternatif var, bir nöbetçi düşünün.

keyNotFoundSentinelDeğerli bir özel statik dize tanımlayınnew String("") . *

Şimdi özel yöntem return keyNotFoundSentinelyerine throw new KeyNotFoundException(). Genel yöntem bu değeri kontrol edebilir

String rval = getValueByKey(key);
if (rval == keyNotFoundSentinel) { // reference equality, not string.equals
    ... // generate default value
} else {
    return rval;
}

Gerçekte, nullbir nöbetçi özel bir durumdur. Dil tarafından tanımlanan, birkaç özel davranışla (örneğin bir yöntem çağırırsanız, iyi tanımlanmış davranış) gönderilen bir sentinel değeridir.null . Neredeyse dilin kullandığı gibi bir nöbetçi olmak o kadar yararlı oluyor ki.

* Java'nın new String("")yalnızca "", dize "interning" etmesini engellemek yerine , onu diğer tüm boş dizelerle aynı referansı verecek şekilde kasten kullanıyoruz . Bu fazladan adım attığımız için, buraya atıfta bulunulan Dize'nin keyNotFoundSentinelharitada asla görünmemesini sağlamak için ihtiyacımız olan eşsiz bir örnek olduğu garanti edilir.


Bu, IMHO, en iyi cevap.
fgp

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.