Ulaşılamaz kodda yeni RuntimeExceptions yazmak kötü bir stil midir?


31

Bir süre önce daha yetenekli geliştiriciler tarafından yazılmış bir uygulamayı sürdürmek için görevlendirildim. Bu kod parçasına rastladım:

public Configuration retrieveUserMailConfiguration(Long id) throws MailException {
        try {
            return translate(mailManagementService.retrieveUserMailConfiguration(id));
        } catch (Exception e) {
            rethrow(e);
        }
        throw new RuntimeException("cannot reach here");
    }

Fırlatmanın RuntimeException("cannot reach here")haklı olup olmadığını merak ediyorum . Muhtemelen bu kod parçasının daha deneyimli meslektaşlardan geldiğini bilen bir şeyi özlüyorum.
EDIT: İşte bazı cevapların değinildiği tekrar vücut. Bu soruda önemli olmadığını düşündüm.

private void rethrow(Exception e) throws MailException {
        if (e instanceof InvalidDataException) {
            InvalidDataException ex = (InvalidDataException) e;
            rethrow(ex);
        }
        if (e instanceof EntityAlreadyExistsException) {
            EntityAlreadyExistsException ex = (EntityAlreadyExistsException) e;
            rethrow(ex);
        }
        if (e instanceof EntityNotFoundException) {
            EntityNotFoundException ex = (EntityNotFoundException) e;
            rethrow(ex);
        }
        if (e instanceof NoPermissionException) {
            NoPermissionException ex = (NoPermissionException) e;
            rethrow(ex);
        }
        if (e instanceof ServiceUnavailableException) {
            ServiceUnavailableException ex = (ServiceUnavailableException) e;
            rethrow(ex);
        }
        LOG.error("internal error, original exception", e);
        throw new MailUnexpectedException();
    }


private void rethrow(ServiceUnavailableException e) throws
            MailServiceUnavailableException {
        throw new MailServiceUnavailableException();
    }

private void rethrow(NoPermissionException e) throws PersonNotAuthorizedException {
    throw new PersonNotAuthorizedException();
}

private void rethrow(InvalidDataException e) throws
        MailInvalidIdException, MailLoginNotAvailableException,
        MailInvalidLoginException, MailInvalidPasswordException,
        MailInvalidEmailException {
    switch (e.getDetail()) {
        case ID_INVALID:
            throw new MailInvalidIdException();
        case LOGIN_INVALID:
            throw new MailInvalidLoginException();
        case LOGIN_NOT_ALLOWED:
            throw new MailLoginNotAvailableException();
        case PASSWORD_INVALID:
            throw new MailInvalidPasswordException();
        case EMAIL_INVALID:
            throw new MailInvalidEmailException();
    }
}

private void rethrow(EntityAlreadyExistsException e)
        throws MailLoginNotAvailableException, MailEmailAddressAlreadyForwardedToException {
    switch (e.getDetail()) {
        case LOGIN_ALREADY_TAKEN:
            throw new MailLoginNotAvailableException();
        case EMAIL_ADDRESS_ALREADY_FORWARDED_TO:
            throw new MailEmailAddressAlreadyForwardedToException();
    }
}

private void rethrow(EntityNotFoundException e) throws
        MailAccountNotCreatedException,
        MailAliasNotCreatedException {
    switch (e.getDetail()) {
        case ACCOUNT_NOT_FOUND:
            throw new MailAccountNotCreatedException();
        case ALIAS_NOT_FOUND:
            throw new MailAliasNotCreatedException();
    }
}

12
Bir istisna dışında rethrowbaşarısız olursa , teknik olarak erişilebilir throw. (uygulama değişirse bir gün olabilir)
njzk2

34
Bir kenara, bir AssertionError, RuntimeException'dan anlamsal olarak daha iyi bir seçim olabilir.
JvR,

1
Son atış gereksiz. Bulucuları veya diğer statik analiz araçlarını etkinleştirirseniz, bu tür çizgileri kaldırılmak üzere işaretler.
Bon Ami,

7
Şimdi kodun geri kalanını gördüğümde, belki de "daha yetenekli geliştiricileri" "daha üst düzey geliştiricilere" dönüştürmeniz gerektiğini düşünüyorum . Kod İnceleme nedenini açıklamaktan mutluluk duyacaktır.
JvR,

3
İstisnaların neden korkunç olduğunu gösteren varsayımsal örnekler oluşturmaya çalıştım. Ama yaptığım hiçbir şey buna bir mum tutmuyor.
Paul Draper

Yanıtlar:


36

İlk olarak, sorunuzu çözdüğünüz ve bize ne yaptığını gösterdiğiniz için teşekkür ederiz rethrow. Yani aslında, yaptığı şey, istisnaları istisnalarla birlikte daha fazla özel durum sınıflarına dönüştürmektir. Bunun üzerine daha sonra.

Aslında asıl soruya aslında cevap vermediğim için, işte burada: evet, çalışma zamanı istisnalarını erişilemez kodda atmak genellikle kötü bir stildir; iddiaları kullansanız daha iyi, hatta problemden kaçınmalısınız. Daha önce de belirtildiği gibi, buradaki derleyici kodun hiçbir zaman try/catchbloktan çıkmadığından emin olamaz . Bundan yararlanarak kodunuzu yeniden değerlendirebilirsiniz ...

Hatalar değerlerdir

(Şaşırtıcı olmayan bir şekilde, halindeyken iyi bilinir )

Düzenlemenizden önce kullandığım basit bir örneği kullanalım: bu yüzden bir şeyi kaydettiğinizi ve Konrad'ın cevabında olduğu gibi bir sarıcı istisnası oluşturduğunuzu hayal edin . Hadi diyelim logAndWrap.

İstisnaları bir yan etki olarak atmak yerine logAndWrap, işini bir yan etki olarak yapmasına izin verebilir ve bir istisna döndürmesini sağlayabilirsiniz (en azından girişte verilen). Jenerik kullanmanız gerekmez, sadece temel fonksiyonlar:

private Exception logAndWrap(Exception exception) {
    // or whatever it actually does
    Log.e("Ouch! " + exception.getMessage());
    return new CustomWrapperException(exception);
}

Öyleyse, throwaçıkça ve derleyiciniz mutludur:

try {
     return translate(mailManagementService.retrieveUserMailConfiguration(id));
} catch (Exception e) {
     throw logAndWrap(e);
}

Ya unutursan throw?

Joe23'ün yorumunda açıklandığı gibi , istisnanın her zaman atıldığından emin olmak için bir savunma programlama yolu açıkça birthrow new CustomWrapperException(exception) Guava.ThrowableslogAndWrap tarafından yapıldığı gibi sonun yapılmasından ibarettir . Bu şekilde, istisnanın atılacağını bilirsiniz ve tip analizörünüz mutludur. Bununla birlikte, özel istisnalarınızın her zaman mümkün olmayan kontrolsüz istisnalar olması gerekir. Ayrıca, bir geliştiricinin yazma riskini throwçok düşük olması için derecelendiririm : geliştirici bunu unutmalı ve çevreleyen yöntem hiçbir şey döndürmemelidir, aksi takdirde derleyici eksik bir dönüş tespit eder. Bu, tip sistemiyle savaşmanın ilginç bir yoludur ve yine de işe yarıyor.

rethrow

Gerçek rethrowde bir işlev olarak yazılabilir, ancak şu anki uygulamasında sorun yaşıyorum:

  • Gibi birçok yararsız yayın var Aslında yayınlar gereklidir (yorumlara bakınız):

    if (e instanceof ServiceUnavailableException) {
        ServiceUnavailableException ex = (ServiceUnavailableException) e;
        rethrow(ex);
    }
  • Yeni istisnalar atarken / geri gönderirken, eskisi atılır; Aşağıdaki kodda, MailLoginNotAvailableExceptionhangi girişin uygun olmadığını, hangisinin uygunsuz olduğunu bilmeme izin vermez; dahası, yığınlar eksik olacak:

    private void rethrow(EntityAlreadyExistsException e)
        throws MailLoginNotAvailableException, MailEmailAddressAlreadyForwardedToException {
        switch (e.getDetail()) {
            case LOGIN_ALREADY_TAKEN:
                throw new MailLoginNotAvailableException();
            case EMAIL_ADDRESS_ALREADY_FORWARDED_TO:
                throw new MailEmailAddressAlreadyForwardedToException();
        }
    }
  • Kaynak kod, neden bu özel istisnaları ilk başta atmıyor? Bunun rethrowbir (postalama) alt sistemi ve iş mantığı mantığı arasında bir uyum katmanı olarak kullanıldığından şüpheleniyorum (belki de amaç, atılan istisnalar gibi uygulama detaylarını özel istisnalar ile değiştirerek gizlemektir). Pete Becker'in cevabında önerildiği gibi, serbest kodun daha iyi olacağı konusunda hemfikir olsam bile , buradaki catchve rethrowbüyük çaplı yeniden yapılandırmalar olmadan kodu kaldırma fırsatınız olacağını sanmıyorum .


1
Yayınlar işe yaramaz değil. Gerekir, çünkü argüman türüne göre dinamik bir gönderme yoktur. Argüman türü, doğru yöntemi çağırmak için derleme zamanında bilinmelidir.
Joe23

Senin içinde logAndWrapyöntemle yerine geri getirilmesinden istisna, ama dönüş türü tutabilir (Runtime)Exception. Bu şekilde catch bloğu, yazdığınız şekilde kalabilir (ulaşılamaz kodda atmadan). Ama unutamayacağın ek bir avantaja sahip throw. Eğer istisnayı geri gönderir ve fırlatmayı unutursanız, bu istisnanın sessizce yutulmasına yol açacaktır. RuntimeException bir dönüş türüne sahip olurken atmak, derleyiciyi mutlu ederken aynı zamanda bu hataları da önler. Guava Throwables.propagateböyle uygulanır.
Joe23

@ Joe23 Statik gönderim ile ilgili açıklama için teşekkür ederiz. İşlevin içine istisnalar atma hakkında: açıkladığınız olası hatanın çağıran logAndWrapancak döndürülen istisna ile hiçbir şey yapmayan bir alıcı bloğu olduğu anlamına mı geliyorsunuz ? İlginç. Bir değer döndürmesi gereken bir işlev içinde, derleyici, değeri döndürülmeyen bir yol olduğunu fark eder, ancak bu, hiçbir şey döndürmeyen yöntemler için kullanışlıdır. Tekrar teşekkürler.
coredump,

1
Ben de aynen böyle söyledim. Ve sadece konuyu tekrar vurgulamak için: Bu benim gelip kredi aldığım bir şey değil, Guava kaynağından toplandı.
Joe23

@ Joe23 Kütüphaneye açık bir referans ekledim
coredump

52

Bu rethrow(e);fonksiyon, normal şartlar altında bir fonksiyonun geri döneceğini, istisnai şartlar altında bir fonksiyonun istisna atacağını söyleyen prensibi ihlal eder. Bu işlev normal koşullarda bir istisna atarak bu prensibi ihlal eder. Tüm karışıklığın kaynağı bu.

Derleyici bu işlevin normal koşullar altında döneceğini varsayar, derleyicinin söyleyebildiği kadarıyla, yürütme retrieveUserMailConfigurationişlevin sonuna ulaşabilir, bu noktada bir returnifadeye sahip olmamak bir hatadır . RuntimeExceptionOrada derleyici bu endişeyi hafifletmek için gerekiyordu, ama bunu yapmanın oldukça aksak bir yoldur atılmış. function must return a valueHatayı önlemenin bir başka yolu da bir return null; //to keep the compiler happyifade eklemek , ancak bence eşit derecede zor.

Yani, şahsen, bunun yerine:

rethrow(e);

Bununla:

report(e); 
throw e;

veya, daha iyisi, ( coredump'un önerdiği gibi) şununla:

throw reportAndTransform(e);

Böylece, kontrolün akışı derleyiciye açıklık kazandıracak, yani finaliniz throw new RuntimeException("cannot reach here"); açıklık kazandıracak, sadece gereksiz olmayacak, hatta derleyici tarafından erişilemez kod olarak işaretleneceğinden derlenmeyecek hale gelecekti.

Bu çirkin durumdan kurtulmanın en zarif ve en basit yolu budur.


1
-1 İşlevin iki ifadeye bölünmesini önermek, kötü kopyalama / yapıştırma nedeniyle programlama hataları verebilir; Ben de biraz sen önermek için sorunuzu düzenlenmiş olmasından endişe ediyorum throw reportAndTransform(e)bir dakika çift ben kendi yanıt gönderdiniz sonra. Belki sizi değiştirmeden sorunuzu değiştirdiniz, ve eğer öyleyse şimdiden özür dilerim, ama aksi halde, biraz kredi vermek, insanların genellikle yaptığı bir şeydir.
coredump,

4
@coredump Aslında önerinizi okudum ve bu öneriyi size bağlayan başka bir cevap daha okudum ve hatırladım ki geçmişte tam olarak böyle bir yapı kullandığımı hatırladım, bu yüzden cevabımı eklemeye karar verdim. Bir nitelik eklemek için çok önemli olmadığını düşündüm, çünkü burada özgün bir fikirden bahsetmiyoruz, ancak bununla bir sorun çıkarırsanız, sizi ağırlaştırmak istemiyorum, bu yüzden nitelik orada, bir bağlantı ile tamamlandı. Şerefe.
Mike Nakis,

Bu yapı için oldukça yaygın olan bir patentim yok; ama bir cevap yazdığınızı ve çok geçmeden, başka bir kişi tarafından "asimile edildiğini" hayal edin. Bu nedenle atfetme çok takdir edilmektedir. Teşekkür ederim.
coredump,

3
Dönmeyen bir fonksiyonda, kendi başına yanlış bir şey yoktur. Her zaman, örneğin, gerçek zamanlı sistemlerde olur. Nihai problem, dilin dillerin doğruluk kavramını çözümlemesi ve zorlaması bakımından java'daki bir kusurdur, ancak dil, bir fonksiyonun geri dönmemesi için bir şekilde açıklayıcı bir mekanizma sağlamamaktadır. Ancak, bunun gibi etrafta dolan tasarım desenleri var throw reportAndTransform(e). Pek çok tasarım deseni yamalar eksik dil özellikleridir. Bu onlardan biri.
David Hammen

2
Öte yandan, birçok modern dil yeterince karmaşık. Her tasarım desenini ana dil özelliğine çevirmek onları daha da şişirir. Bu, ana dile ait olmayan bir tasarım modelinin güzel bir örneğidir. Yazıldığı gibi, sadece derleyici tarafından değil, aynı zamanda kodu ayrıştırması gereken birçok araç tarafından anlaşılmıştır.
MSalters,

7

throwMuhtemelen yoksa ortaya çıkabilecek hata "bir değer döndürmesi gerekir yöntemi" almak için eklendi - Java veri analiz hayır olduğunu anlamak için akıllı yeterlidir akış returnbir sonraki gereklidir throw, ancak özel sonrasında rethrow()yöntemle ve hiçbir orada @NoReturnaçıklama Bunu düzeltmek için kullanabileceğini.

Bununla birlikte, ulaşılamaz kodda yeni bir İstisna oluşturmak gereksiz görünüyor. return nullAslında, asla olmadığını asla bilerek yazdım .


Haklısın. throw new...Çizgiyi yorumlasam ve dönüş beyanı olmadığından şikayet edersem ne olacağını kontrol ettim . Kötü programlama mı? Ulaşılamaz bir iade ifadesinin yerine konulması ile ilgili herhangi bir sözleşme var mı? Daha fazla girişi teşvik etmek için bir süre bu soruyu cevapsız bırakacağım. Yine de cevabınız için teşekkürler.
Sok Pomaranczowy

3
@SokPomaranczowy Evet, kötü programlama. Bu rethrow()yöntem catch, istisnayı yeniden sorgulamasının gerçeğini gizlemektedir . Böyle bir şey yapmaktan kaçınırdım. Ayrıca, elbette, rethrow()aslında istisnayı yeniden yazmakta başarısız olabilir, bu durumda başka bir şey olur.
Ross Patterson,

26
Lütfen istisnayı değiştirmeyin return null. İstisna ile, gelecekteki bir kişi kodu kırarsa, son satırın nasıl erişilebileceği bir sonuca varırsa, istisna atıldığında hemen anlaşılacaktır. Öyleyse return null, şimdi nullbaşvurunuzda beklenmeyen bir değere sahip bir değere sahipsiniz . Uygulamada nerede NullPointerExceptionortaya çıkacağını kim bilebilir ? Şanslı olmadıkça ve bu işleve çağrıldıktan sonra bilerek gerçekleşmezse, asıl sorunu bulmak çok zor olacaktır.
unholysampler

5
@MatthewRead Ulaşılamaz olması amaçlanan kodun çalıştırılması ciddi bir hatadır, hatayı return nullmaskeleme olasılığı yüksektir, çünkü nullbu hatayı döndürdüğü diğer durumları ayırt edemezsiniz .
CodesInChaos

4
Ayrıca, işlev bazı durumlarda zaten null değerini döndürürse ve bu durumda da null değeri kullanırsanız, kullanıcılar şimdi null değeri aldıklarında alabilecekleri iki olası senaryoya sahiptir. Bazen bunun önemi yoktur, çünkü null dönüş zaten “nesne yok ve neden olduğunu belirtmiyorum” anlamına gelir, bu yüzden neden çok az fark yaratan olası bir neden daha ekleyerek. Ancak çoğu zaman belirsizlik eklenmesi kötüdür ve bu kesinlikle derhal "bu bozuldu" gibi görünmek yerine başlangıçta "belirli koşullar" gibi ele alınacağı anlamına gelir. Erken başarısız.
Steve Jessop

6

The

throw new RuntimeException("cannot reach here");

İfade bir KİŞİSELİ açıkça ortaya koyar olanları okuyan , bu nedenle örneğin null döndürmek çok daha iyidir. Ayrıca, kodun beklenmeyen şekilde değişmesi durumunda hata ayıklamayı kolaylaştırır.

Ancak rethrow(e)sadece yanlış görünüyor! Yani sizin durumunuzda kodu yeniden düzenlemenin daha iyi bir seçenek olduğunu düşünüyorum. Kodunuzu çözmenin yollarını öğrenmek için diğer cevaplara (coredump'ın en iyisini severim) bakın.


4

Bir kongre var mı bilmiyorum.

Her neyse, başka bir numara da böyle yapmak olacaktır:

private <T> T rethrow(Exception exception) {
    // or whatever it actually does
    Log.e("Ouch! " + exception.getMessage());
    throw new CustomWrapperException(exception);
}

Bunun için izin vermek:

try {
     return translate(mailManagementService.retrieveUserMailConfiguration(id));
} catch (Exception e) {
     return rethrow(e);
}

Ve artık yapay RuntimeExceptiongerekmiyor. rethrowAsla herhangi bir değer vermese de , şimdi derleyici için yeterince iyi.

Teoride bir değer döndürür (yöntem imzası) ve sonra bunun yerine bir istisna atarken aslında yapmaktan muaftır.

Evet, garip görünebilir, ama sonra tekrar - bir hayalet atmak RuntimeExceptionya da bu dünyada hiç görülmeyecek şekilde boşa dönmek - bu da tam bir güzellik meselesi değil.

Okunabilirlik için çabalıyorsanız, yeniden adlandırabilir rethrowve şöyle bir şey olabilir:

} catch (Exception e) {
     return nothingJustRethrow(e);
}

2

tryBloğu tamamen çıkarırsanız , rethrowveya veya 'ye ihtiyacınız olmaz throw. Bu kod tam olarak orijinal ile aynı şeyi yapar:

public Configuration retrieveUserMailConfiguration(Long id) throws MailException {
    return translate(mailManagementService.retrieveUserMailConfiguration(id));
}

Daha deneyimli geliştiricilerin geldiği gerçeğinin sizi aldatmasına izin vermeyin. Bu kod çürümesi ve her zaman olur. Sadece tamir et.

EDIT: Ben rethrow(e)sadece istisna yeniden atma olarak yanlış okudum e. Bu rethrowyöntem aslında istisnayı yeniden atmaktan başka bir şey yaparsa, ondan kurtulmak bu yöntemin anlamını değiştirir.


İnsanlar hiçbir şeyi ele geçirmeyen joker karakterlerin istisna eylemcileri olduğuna inanmaya devam edecekler. Sürümünüz, kusurlu olduğu varsayımını, yapamadıklarını halletmek gibi davranarak ihmal eder. RetrieveUserMailConfiguration () öğesinin arayanı bir şeylerin geri alınmasını bekler. Bu işlev makul bir şey döndüremezse, hızlı ve yüksek bir şekilde başarısız olmalıdır. Sanki diğer cevaplar catch(Exception), garip bir kod kokusu olan hiçbir sorun görmemiş gibi .
msw,

1
@ msw - Kargo kült kodlaması.
Pete Becker,

@ msw - Öte yandan, bir kesme noktası ayarlamak için bir yer verir.
Pete Becker,

1
Mantığın bir bölümünü atmadan hiçbir sorun görmüyorsunuz. Sürümünüz açıkça orjinaliyle aynı şeyi yapmıyor, bu arada zaten hızlı ve yüksek sesle başarısız oluyor. Cevabınızdaki gibi yakalamayan yöntemler yazmayı tercih ederim, ancak mevcut kodla çalışırken diğer iş parçalarıyla yapılan sözleşmeleri bozmamalıyız. İçinde yapılanların rethrowişe yaramaz olduğu olabilir (ve buradaki mesele bu değil), ancak sevmediğiniz ve sadece açıkça değilken orijinalin karşılığı olduğunu söyleyemezsiniz. Özel rethrowişlevde yan etkiler vardır .
coredump,

1
@ jpmc26 Sorunun çekirdeği, önceki deneme / yakalama bloğundan başka bir nedenden dolayı ortaya çıkabilecek "buraya erişilemez" istisnası hakkındadır. Ancak bloğun balık koktuğunu kabul ediyorum ve eğer bu cevap başlangıçta daha dikkatli bir şekilde açıklanmış olsaydı, çok daha fazla oy kazanabilirdi (oy kullanmadı, btw). Son düzenleme iyi.
coredump
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.