Rozważ następujący kod:
public Object getClone(Cloneable a) throws TotallyFooException {
if (a == null) {
throw new TotallyFooException();
}
else {
try {
return a.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
//cant be reached, in for syntax
return null;
}
Jest return null;
to konieczne, ponieważ wyjątek może zostać przechwycony, jednak w takim przypadku, ponieważ sprawdziliśmy już, czy jest pusty (i załóżmy, że wiemy, że klasa, którą wywołujemy, obsługuje klonowanie), więc wiemy, że instrukcja try nigdy nie zawiedzie.
Czy jest złą praktyką umieszczanie dodatkowej instrukcji return na końcu tylko po to, aby spełnić składnię i uniknąć błędów kompilacji (z komentarzem wyjaśniającym, że nie zostanie osiągnięty), czy też jest lepszy sposób na zakodowanie czegoś takiego, aby dodatkowe instrukcja zwrotu jest niepotrzebna?
Object
parametr. Jeślia
nie jest klasą, która obsługujeclone
metodę (lub jest to zdefiniowane wObject
?) Lub jeśli wystąpi błąd podczasclone
metody (lub jakikolwiek inny błąd, o którym nie mogę teraz pomyśleć), może zostać wyrzucony wyjątek i osiągniesz instrukcja zwrotu.Cloneable
rzucanieCloneNotSupportedException
. Źródło: javadocsAssertionError
zamiastInternalError
. Ta ostatnia wydaje się być do specjalnego użytku, jeśli coś w JVM pójdzie nie tak. I zasadniczo zapewniasz, że kod nie został osiągnięty.Odpowiedzi:
Jaśniejszy sposób bez dodatkowej instrukcji powrotu jest następujący. Ja też bym nie złapał
CloneNotSupportedException
, ale zostawiłam dzwoniącemu.if (a != null) { try { return a.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } throw new TotallyFooException();
Prawie zawsze można bawić się kolejnością, aby uzyskać prostszą składnię niż ta, którą masz na początku.
źródło
AssertionError
jest klasą wbudowaną o podobnym zamiarzeTotallyFooException
(która w rzeczywistości nie istnieje).Zdecydowanie można to osiągnąć. Zwróć uwagę, że drukujesz tylko ślad stosu w
catch
klauzuli.W scenariuszu, w którym
a != null
i będzie wyjątek,return null
zostanie osiągnięty. Możesz usunąć to oświadczenie i zastąpić jethrow new TotallyFooException();
.Ogólnie * , jeśli
null
jest poprawnym wynikiem metody (tj. Użytkownik tego oczekuje i to coś znaczy), to zwrócenie go jako sygnału „nie znaleziono danych” lub wystąpił wyjątek nie jest dobrym pomysłem. W przeciwnym razie nie widzę problemu, dlaczego nie powinieneś wracaćnull
.Weźmy na przykład
Scanner#ioException
metodę:W tym przypadku zwracana wartość
null
ma wyraźne znaczenie, gdy używam metody mam pewność, że otrzymałemnull
tylko dlatego, że nie było takiego wyjątku a nie dlatego, że metoda próbowała coś zrobić i się nie udało.* Zwróć uwagę, że czasami chcesz wrócić,
null
nawet jeśli znaczenie jest niejednoznaczne. Na przykładHashMap#get
:W tym przypadku
null
może wskazywać, że wartośćnull
została znaleziona i zwrócona lub że hashmap nie zawiera żądanego klucza.źródło
Optional<Throwable>
lubOptional<TValue>
. Uwaga: to nie jest krytyka twojej odpowiedzi, a raczej projektu API tych dwóch metod. (Jest to jednak dość powszechny błąd projektowy, który jest wszechobecny nie tylko w całym API Java, ale także np. W .NET.)null
jest nie ważna wartość powrotna, wracającnull
jest jeszcze gorzej pomysł. Innymi słowy, powrótnull
do nieoczekiwanej sytuacji nigdy nie jest dobrym pomysłem.null
, tonull
nigdy nie może być "prawidłową" wartością zwracaną w tym sensie, że musi wskazywać coś innego niż normalną wartość zwracana. W tym przypadku jego zwrot ma szczególne znaczenie i nie widzę nic złego w tym projekcie (oczywiście użytkownik powinien wiedzieć, jak sobie z tym poradzić).null
jest to możliwa wartość zwracana, z którą ma do czynienia dzwoniący. To sprawia, że jest to ważna wartość i mamy tutaj takie samo rozumienie terminu „prawidłowy”, jak opisujesz „prawidłowy” jako „użytkownik tego oczekuje , a to coś znaczy”. Ale tutaj i tak sytuacja jest inna. OP stwierdza, zgodnie z komentarzem do kodu, że stwierdza on, żereturn null;
stwierdzenie „nie można osiągnąć”, co oznacza, że zwrotnull
jest nieprawidłowy.throw new AssertionError();
Zamiast tego powinno być …Myślę, że
return null
to zła praktyka na końcu niedostępnej gałęzi. Lepiej jest rzucićRuntimeException
(AssertionError
byłoby to również dopuszczalne), ponieważ aby dostać się do tej linii, coś poszło bardzo źle i aplikacja jest w nieznanym stanie. Większość z nich jest (jak powyżej), ponieważ programista coś przeoczył (obiekty mogą być niezerowe i niemożliwe do sklonowania).Prawdopodobnie nie użyłbym,
InternalError
chyba że jestem bardzo pewien, że kod jest nieosiągalny (na przykład po aSystem.exit()
), ponieważ jest bardziej prawdopodobne, że popełnię błąd niż maszyna wirtualna.Użyłbym niestandardowego wyjątku (takiego jak
TotallyFooException
) tylko wtedy, gdy przejście do tej „nieosiągalnej linii” oznacza to samo, co gdziekolwiek indziej, w którym rzucisz ten wyjątek.źródło
AssertionError
rozsądnym wyborem może być również dodanie zdarzenia „nie może się zdarzyć” .SomeJerkImplementedClonableAndStillThrewACloneNotSupportedExceptionException
. Oczywiście to by się przedłużyłoRuntimeException
.Cloneable
nie implementujeclone()
metody publicznej ,clone()
aw obiekcieprotected
powyższy kod nie jest faktycznie kompilowany.JerkException
, nie sądzę, by byłby doceniony).Złapałeś
CloneNotSupportedException
co oznacza, że twój kod może sobie z tym poradzić. Ale kiedy go złapiesz, dosłownie nie masz pojęcia, co zrobić, gdy dojdziesz do końca funkcji, co oznacza, że nie możesz sobie z tym poradzić. Więc masz rację, że w tym przypadku jest to zapach kodu i moim zdaniem oznacza, że nie powinieneś był złapaćCloneNotSupportedException
.źródło
Wolałbym użyć,
Objects.requireNonNull()
aby sprawdzić, czy parametr a nie jest pusty. Więc kiedy czytasz kod, jest jasne, że parametr nie powinien mieć wartości null.Aby uniknąć zaznaczonych wyjątków, odrzuciłbym
CloneNotSupportedException
jakoRuntimeException
.W obu przypadkach możesz dodać ładny tekst z intencją, dlaczego tak się nie stanie lub tak się stanie.
public Object getClone(Object a) { Objects.requireNonNull(a); try { return a.clone(); } catch (CloneNotSupportedException e) { throw new IllegalArgumentException(e); } }
źródło
W takiej sytuacji napisałbym
public Object getClone(SomeInterface a) throws TotallyFooException { // Precondition: "a" should be null or should have a someMethod method that // does not throw a SomeException. if (a == null) { throw new TotallyFooException() ; } else { try { return a.someMethod(); } catch (SomeException e) { throw new IllegalArgumentException(e) ; } } }
Co ciekawe, mówisz, że „instrukcja try nigdy nie zawiedzie”, ale nadal zadałeś sobie trud napisania oświadczenia,
e.printStackTrace();
które według Ciebie nigdy nie zostanie wykonane. Czemu?Być może twoja wiara nie jest tak mocno zakorzeniona. To jest dobre (moim zdaniem), ponieważ twoja wiara nie jest oparta na kodzie, który napisałeś, ale raczej na oczekiwaniu, że twój klient nie naruszy warunku wstępnego. Lepiej programować metody publiczne defensywnie.
Nawiasem mówiąc, twój kod się nie skompiluje. Nie możesz zadzwonić,
a.clone()
nawet jeśli typa
jestCloneable
. Przynajmniej tak mówi kompilator Eclipse. Wyrażeniea.clone()
powoduje błądTo, co zrobiłbym w twoim konkretnym przypadku, to
public Object getClone(PubliclyCloneable a) throws TotallyFooException { if (a == null) { throw new TotallyFooException(); } else { return a.clone(); } }
Gdzie
PubliclyCloneable
jest zdefiniowane przezinterface PubliclyCloneable { public Object clone() ; }
Lub, jeśli absolutnie potrzebujesz tego typu parametru
Cloneable
, przynajmniej poniższe elementy zostaną skompilowane.public static Object getClone(Cloneable a) throws TotallyFooException { // Precondition: "a" should be null or point to an object that can be cloned without // throwing any checked exception. if (a == null) { throw new TotallyFooException(); } else { try { return a.getClass().getMethod("clone").invoke(a) ; } catch( IllegalAccessException e ) { throw new AssertionError(null, e) ; } catch( InvocationTargetException e ) { Throwable t = e.getTargetException() ; if( t instanceof Error ) { // Unchecked exceptions are bubbled throw (Error) t ; } else if( t instanceof RuntimeException ) { // Unchecked exceptions are bubbled throw (RuntimeException) t ; } else { // Checked exceptions indicate a precondition violation. throw new IllegalArgumentException(t) ; } } catch( NoSuchMethodException e ) { throw new AssertionError(null, e) ; } } }
źródło
Cloneable
nie deklaruje sięclone
jako metody publicznej, która nie zgłasza żadnych sprawdzonych wyjątków. Istnieje interesująca dyskusja na bugs.java.com/view_bug.do?bug_id=4098033Powyższe przykłady są poprawne i bardzo Java. Jednak oto, jak odniósłbym się do pytania OP, jak obsłużyć ten zwrot:
public Object getClone(Cloneable a) throws CloneNotSupportedException { return a.clone(); }
Nie ma korzyści ze sprawdzenia,
a
czy jest zerowa. Zmierza do NPE. Drukowanie śladu stosu również nie jest pomocne. Ślad stosu jest taki sam, niezależnie od tego, gdzie jest obsługiwany.Nie ma korzyści z odrzucania kodu za pomocą niepomocnych testów zerowych i niepomocnej obsługi wyjątków. Usunięcie śmieci sprawia, że kwestia zwrotu jest dyskusyjna.
(Zwróć uwagę, że OP zawiera błąd w obsłudze wyjątków; z tego powodu zwrot był potrzebny. OP nie pomyliłby metody, którą proponuję).
źródło
Jak wspominali inni, w twoim przypadku to nie ma zastosowania.
Aby odpowiedzieć na pytanie, programy typu Lint z pewnością tego nie rozgryzły! Widziałem dwóch różnych walczących o to w oświadczeniu przełącznika.
switch (var) { case A: break; default: return; break; // Unreachable code. Coding standard violation? }
Jeden narzekał, że brak przerwy stanowił naruszenie standardu kodowania. Drugi narzekał, że posiadanie go było jednym, ponieważ był to nieosiągalny kod.
Zauważyłem to, ponieważ dwóch różnych programistów ponownie sprawdzało kod, dodając przerwę, a następnie usuwając ją, a następnie dodając i usuwając, w zależności od tego, który analizator kodu uruchomili tego dnia.
Jeśli znajdziesz się w takiej sytuacji, wybierz jedną i skomentuj anomalię, która jest dobrą formą, którą sobie pokazałeś. To najlepsze i najważniejsze danie na wynos.
źródło
Nie chodzi o „tylko zaspokojenie składni”. Warunkiem semantycznym języka jest to, że każda ścieżka kodu prowadzi do powrotu lub rzutu. Ten kod nie jest zgodny. Jeśli wyjątek zostanie przechwycony, wymagany jest następujący zwrot.
Żadnych „złych praktyk” w tym względzie, ani ogólnie dotyczących satysfakcji kompilatora.
W każdym razie, czy to składniowo, czy semantycznie, nie masz wyboru.
źródło
Przepisałbym to tak, aby na końcu miał powrót. Pseudo kod:
if a == null throw ... // else not needed, if this is reached, a is not null Object b try { b = a.clone } catch ... return b
źródło
Nikt jeszcze o tym nie wspomniał, więc oto:
public static final Object ERROR_OBJECT = ... //... public Object getClone(Cloneable a) throws TotallyFooException { Object ret; if (a == null) throw new TotallyFooException(); //no need for else here try { ret = a.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); //something went wrong! ERROR_OBJECT could also be null ret = ERROR_OBJECT; } return ret; }
Z tego właśnie powodu nie lubię
return
wewnętrznychtry
bloków.źródło
Jeśli znasz szczegóły dotyczące danych wejściowych w taki sposób, że wiesz, że
try
stwierdzenie nigdy nie zawiedzie, jaki jest sens jego posiadania? Unikaj tego,try
jeśli wiesz na pewno, że wszystko zawsze się powiedzie (choć rzadko można mieć całkowitą pewność przez cały okres istnienia kodu).W każdym razie kompilator niestety nie jest czytnikiem w myślach. Widzi funkcję i jej dane wejściowe, a biorąc pod uwagę posiadane informacje, potrzebuje tego
return
oświadczenia na dole, tak jak masz.Wręcz przeciwnie, sugerowałbym dobrą praktykę unikanie jakichkolwiek ostrzeżeń kompilatora, np. Nawet jeśli kosztuje to kolejną linię kodu. Nie martw się tutaj zbytnio o liczbę linii. Ustal niezawodność funkcji poprzez testowanie, a następnie przejdź dalej. Po prostu udając, że możesz pominąć
return
stwierdzenie, wyobraź sobie, że wracasz do tego kodu rok później, a następnie spróbuj zdecydować, czy toreturn
stwierdzenie na dole spowoduje więcej zamieszania niż komentarz szczegółowo wyjaśniający, dlaczego zostało pominięte z powodu przypuszczeń, które możesz zrobić o parametrach wejściowych. Najprawdopodobniejreturn
łatwiej będzie sobie z tym poradzić.To powiedziawszy, szczególnie w tej części:
try { return a.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } ... //cant be reached, in for syntax return null;
Myślę, że jest coś nieco dziwnego w podejściu do obsługi wyjątków. Zwykle chcesz połknąć wyjątki w witrynie, w której masz coś znaczącego, co możesz zrobić w odpowiedzi.
Możesz myśleć o
try/catch
mechanizmie transakcji.try
dokonanie tych zmian, jeśli zawiodą i rozgałęzimy się docatch
bloku, zrób to (cokolwiek jest wcatch
bloku) w odpowiedzi jako część procesu wycofywania i odzyskiwania.W tym przypadku samo wydrukowanie śladu stosu, a następnie zmuszenie do zwrócenia wartości null nie jest dokładnie nastawieniem do transakcji / odzyskiwania. Kod przenosi odpowiedzialność za obsługę błędów na wszystkie wywołania kodu
getClone
celu ręcznego sprawdzenia błędów. Możesz chcieć przechwycićCloneNotSupportedException
i przetłumaczyć go na inną, bardziej znaczącą formę wyjątku i wyrzucić go, ale nie chcesz po prostu połykać wyjątku i zwracać null w tym przypadku, ponieważ nie jest to witryna odzyskiwania transakcji.Skończysz z wyciekiem obowiązków na dzwoniących, aby ręcznie sprawdzić i poradzić sobie z awarią w ten sposób, gdy rzucenie wyjątku pozwoliłoby tego uniknąć.
To tak, jakbyś załadował plik, to transakcja wysokiego poziomu. Możesz
try/catch
tam mieć . Podczastrying
ładowania pliku można klonować obiekty. Jeśli wystąpi awaria w dowolnym miejscu tej operacji wysokiego poziomu (ładowania pliku), zazwyczaj chcesz zgłosić wyjątki z powrotem do tegotry/catch
bloku transakcji najwyższego poziomu , aby można było bezpiecznie odzyskać sprawność po niepowodzeniu ładowania pliku (niezależnie od tego, czy jest to z powodu błędu w klonowaniu lub czegokolwiek innego). Dlatego generalnie nie chcemy po prostu wchłaniać wyjątku w takim szczegółowym miejscu, jak to, a następnie zwracać wartość null, np. Ponieważ podważyłoby to wiele wartości i celu wyjątków. Zamiast tego chcemy propagować wyjątki z powrotem do witryny, w której możemy w znaczący sposób sobie z tym poradzić.źródło
Twój przykład nie jest idealny do zilustrowania pytania, o którym mowa w ostatnim akapicie:
Lepszym przykładem może być implementacja samego klona:
public class A implements Cloneable { public Object clone() { try { return super.clone() ; } catch (CloneNotSupportedException e) { throw new InternalError(e) ; // vm bug. } } }
Tutaj nigdy nie należy wpisywać klauzuli catch . Wciąż składnia wymaga albo rzucenia czegoś, albo zwrócenia wartości. Ponieważ zwracanie czegoś nie ma sensu,
InternalError
jest używany do wskazania poważnego stanu maszyny wirtualnej.źródło
CloneNotSupportedException
to nie jest „VM bug” - to całkowicie legalne dlaCloneable
wyrzucić go. Może to stanowić błąd w twoim kodzie i / lub w nadklasie, w takim przypadku zawinięcie go w aRuntimeException
lub prawdopodobnieAssertionError
(ale nieInternalError
) może być odpowiednie. Lub możesz po prostu uchylić się od wyjątku - jeśli twoja nadklasa nie obsługuje klonowania, to ty też nie, więcCloneNotSupportedException
jest semantycznie właściwe.