Całkiem głupie pytanie. Biorąc pod uwagę kod:
public static int sum(String a, String b) /* throws? WHAT? */ {
int x = Integer.parseInt(a); // throws NumberFormatException
int y = Integer.parseInt(b); // throws NumberFormatException
return x + y;
}
Czy możesz powiedzieć, czy to dobra Java, czy nie? To, o czym mówię, NumberFormatException
jest niezaznaczonym wyjątkiem. Nie musisz określać tego jako części sum()
podpisu. Co więcej, o ile rozumiem, idea niezaznaczonych wyjątków ma na celu tylko zasygnalizowanie, że implementacja programu jest nieprawidłowa, a co więcej, przechwytywanie niezaznaczonych wyjątków jest złym pomysłem, ponieważ jest to jak naprawianie złego programu w czasie wykonywania .
Czy ktoś mógłby wyjaśnić, czy:
- Powinienem określić
NumberFormatException
jako część podpisu metody. - Powinienem zdefiniować swój własny sprawdzony wyjątek (
BadDataException
), obsłużyćNumberFormatException
wewnątrz metody i ponownie wyrzucić go jakoBadDataException
. - Powinienem zdefiniować mój własny sprawdzony wyjątek (
BadDataException
), zweryfikować oba ciągi w sposób podobny do wyrażeń regularnych i wyrzucić mój,BadDataException
jeśli nie pasuje. - Twój pomysł?
Aktualizacja :
Wyobraź sobie, że to nie jest framework open source, którego powinieneś używać z jakiegoś powodu. Patrzysz na sygnaturę metody i myślisz - „OK, ona nigdy nie rzuca”. Pewnego dnia miałeś wyjątek. Jest to normalne?
Aktualizacja 2 :
Są komentarze, które mówią, że mój sum(String, String)
projekt jest zły. Całkowicie się zgadzam, ale dla tych, którzy uważają, że pierwotny problem nigdy by się nie pojawił, gdybyśmy mieli dobry projekt, oto dodatkowe pytanie:
Definicja problemu jest następująca: masz źródło danych, w którym liczby są przechowywane jako String
s. Źródłem tym może być plik XML, strona internetowa, okno pulpitu z 2 polami edycji, cokolwiek.
Twoim celem jest zaimplementowanie logiki, która zajmuje te 2 String
sekundy, konwertuje je na int
si wyświetla okno komunikatu „Suma to xxx”.
Bez względu na to, jakie podejście zastosujesz do zaprojektowania / wdrożenia tego, będziesz mieć następujące 2 punkty wewnętrznej funkcjonalności :
- To miejsce, gdzie można przekonwertować
String
doint
- Miejsce, w którym dodajesz 2
int
s
Podstawowym pytaniem dotyczącym mojego oryginalnego posta jest:
Integer.parseInt()
oczekuje przekazania poprawnego ciągu. Ilekroć przekażesz zły ciąg , oznacza to, że twój program jest nieprawidłowy (a nie „ twój użytkownik jest idiotą”). Musisz zaimplementować fragment kodu, w którym z jednej strony masz Integer.parseInt () z semantyką MUST, az drugiej strony musisz być OK z przypadkami, gdy dane wejściowe są niepoprawne - POWINIENEŚ semantykę .
A więc pokrótce: jak zaimplementować semantykę POWINIENEM, jeśli mam tylko MUSI biblioteki .
Odpowiedzi:
To jest dobre pytanie. Chciałbym, żeby więcej ludzi myślało o takich rzeczach.
IMHO, rzucanie niezaznaczonych wyjątków jest dopuszczalne, jeśli przekazano parametry śmieci.
Ogólnie rzecz biorąc, nie powinieneś rzucać,
BadDataException
ponieważ nie powinieneś używać wyjątków do sterowania przepływem programu. Wyjątki dotyczą wyjątków. Wywołujące metodę mogą wiedzieć, zanim ją wywołają, czy ich ciągi są liczbami, czy nie, więc można uniknąć przekazywania śmieci i dlatego można je uznać za błąd programowania , co oznacza, że można rzucać niezaznaczone wyjątki.Odnośnie deklarowania
throws NumberFormatException
- nie jest to zbyt przydatne, ponieważ niewielu to zauważy z powodu odznaczenia NumberFormatException. Jednak IDE mogą z tego skorzystać i zaoferowaćtry/catch
prawidłowe zawijanie . Dobrym rozwiązaniem jest również użycie javadoc, np:/** * Adds two string numbers * @param a * @param b * @return * @throws NumberFormatException if either of a or b is not an integer */ public static int sum(String a, String b) throws NumberFormatException { int x = Integer.parseInt(a); int y = Integer.parseInt(b); return x + y; }
ZMIENIONO : Komentatorzy podali
ważne punkty. Musisz zastanowić się, jak to będzie używane i jaki jest ogólny projekt Twojej aplikacji.
Jeśli metoda będzie używana w każdym miejscu i ważne jest, aby wszyscy wywołujący radzili sobie z problemami, deklarują metodę jako rzucającą sprawdzony wyjątek (zmuszając wywołujące do radzenia sobie z problemami), ale zaśmiecając kod
try/catch
blokami.Jeśli z drugiej strony używamy tej metody z danymi, którym ufamy, zadeklaruj ją jak powyżej, ponieważ nie oczekuje się, że kiedykolwiek wybuchnie i unikniesz bałaganu w kodzie w postaci niepotrzebnych
try/catch
bloków.źródło
RuntimeException
zająć? Czy powinien to być rozmówca, czy też należy pozostawić wyjątek, aby się unosił? Jeśli tak, jak należy sobie z tym radzić?Exceptions
z niższych warstw i zawinięciu ich w wyższy poziom Wyjątki, które są bardziej znaczące dla użytkownika końcowego, a drugie polega na użyciu nieprzechwyconego modułu obsługi wyjątków, który można zainicjować w program uruchamiający aplikacje.throws
klauzuli jest miłym dodatkiem, ale nie jest wymagane.Moim zdaniem byłoby lepiej obsługiwać logikę wyjątków tak daleko, jak to możliwe. Dlatego wolałbym podpis
public static int sum(int a, int b);
Dzięki twojej metodzie niczego bym nie zmienił. Albo jesteś
Dlatego obsługa wyjątków w tym przypadku staje się kwestią dokumentacji .
źródło
sum
funkcja, która spodziewa się dwa numery, powinien dostać dwa numery. Sposób, w jaki je otrzymałeś, nie jest związany zsum
funkcją. Oddziel poprawnie logikę . Więc odniósłbym to do problemu projektowego.Numer 4. Jak podano, ta metoda nie powinna przyjmować łańcuchów jako parametrów, a powinna przyjmować liczby całkowite. W takim przypadku (ponieważ java zawija się zamiast przepełniać) nie ma możliwości wystąpienia wyjątku.
jest o wiele jaśniejsze, co to znaczy
x = sum(a, b)
Chcesz, aby wyjątek występował jak najbliżej źródła (wejścia).
Jeśli chodzi o opcje 1-3, nie definiujesz wyjątku, ponieważ oczekujesz, że wywołujący zakładają, że w przeciwnym razie twój kod nie może zawieść, definiujesz wyjątek, aby zdefiniować, co dzieje się w znanych warunkach niepowodzenia, które są WYJĄTKOWE DLA TWOJEJ METODY. To znaczy, jeśli masz metodę, która jest opakowaniem wokół innego obiektu i zgłasza wyjątek, a następnie przekazuje go dalej. Tylko jeśli wyjątek jest unikalny dla twojej metody, powinieneś rzucić niestandardowy wyjątek (frex, w twoim przykładzie, jeśli suma miała zwracać tylko pozytywne wyniki, to sprawdzenie tego i zgłoszenie wyjątku byłoby właściwe, jeśli z drugiej strony java rzucił wyjątek przepełnienia zamiast zawijania, wtedy można go przekazać dalej, nie definiować go w swoim podpisie, zmieniać jego nazwy ani zjadać).
Aktualizacja w odpowiedzi na aktualizację pytania:
Rozwiązaniem tego problemu jest zawinięcie biblioteki MUST i zwrócenie wartości SHOULD. W tym przypadku funkcja, która zwraca liczbę całkowitą. Napisz funkcję, która pobiera ciąg i zwraca obiekt Integer - albo działa, albo zwraca null (jak Ints.tryParse guawy). Wykonaj walidację oddzielnie od operacji, twoja operacja powinna mieć ints. To, czy twoja operacja zostanie wywołana z wartościami domyślnymi, gdy masz nieprawidłowe dane wejściowe, czy zrobisz coś innego, będzie zależeć od twoich specyfikacji - większość, co mogę o tym powiedzieć, to to, że jest naprawdę mało prawdopodobne, aby miejsce do podjęcia takiej decyzji było w operacji metoda.
źródło
sum(int, int)
iparseIntFromString(String)
. Aby uzyskać tę samą funkcjonalność, w obu przypadkach będę musiał napisać fragment kodu, w którym są 2 wywołaniaparseIntFromString()
i 1 wywołaniesum()
. Rozważ ten przypadek, jeśli ma to dla ciebie sens - nie widzę różnicy z punktu widzenia pierwotnego problemu.int sum(String, String)
w rzeczywistości nigdy nie jest możliwe?Chyba tak. To niezła dokumentacja.
Czasem tak. Zaznaczone wyjątki są uważane za lepsze w niektórych przypadkach, ale praca z nimi to dość PITA. Dlatego wiele frameworków (np. Hibernate) używa tylko wyjątków czasu wykonywania.
Nigdy. Więcej pracy, mniejsza prędkość (chyba że spodziewasz się, że rzucanie wyjątku będzie regułą) i żadnego zysku.
Wcale.
źródło
Nr 4.
Myślę, że w ogóle nie zmieniłbym metody. Położyłbym próbę złapania wokół metody wywoływania lub wyżej w śledzeniu stosu, gdzie jestem w kontekście, w którym mogę wdzięcznie odzyskać logikę biznesową z wyjątku.
Nie zrobiłbym tego na pewno, ponieważ uważam, że to przesada.
źródło
Zakładając, że to, co piszesz, będzie używane (np. Jako API) przez kogoś innego, powinieneś wybrać 1, NumberFormatException jest specjalnie przeznaczony do komunikowania takich wyjątków i powinien być używany.
źródło
Najpierw musisz zapytać siebie, czy użytkownik mojej metody musi się martwić o wprowadzenie błędnych danych, czy też oczekuje się od niego wprowadzenia odpowiednich danych (w tym przypadku String). To oczekiwanie jest również znane jako projekt na podstawie umowy .
i 3. Tak, prawdopodobnie powinieneś zdefiniować BadDataException lub nawet lepiej użyć niektórych z wykluczających, takich jak NumberFormatException, ale raczej pozostawić standardowy komunikat do pokazania. Złap wyjątek NumberFormatException w metodzie i wyślij go ponownie wraz z komunikatem, nie zapominając o dołączeniu oryginalnego śladu stosu.
Zależy to od sytuacji, ale prawdopodobnie wybrałbym ponownie wyjątek NumberFormatException z dodatkowymi informacjami. Musi również istnieć wyjaśnienie javadoc dotyczące oczekiwanych wartości
String a, String b
źródło
Wiele zależy od scenariusza, w którym się znajdujesz.
Przypadek 1. Zawsze debugujesz kod i nikt inny i wyjątek nie spowodują złego doświadczenia użytkownika
Wyrzuć domyślny wyjątek NumberFormatException
Przypadek 2: Kod powinien być łatwy w utrzymaniu i zrozumiały
Zdefiniuj własny wyjątek i dodaj o wiele więcej danych do debugowania podczas jego przesyłania.
Nie potrzebujesz sprawdzania wyrażeń regularnych, ponieważ i tak przejdzie do wyjątku przy złym wejściu.
Gdyby był to kod na poziomie produkcyjnym, moim pomysłem byłoby zdefiniowanie więcej niż jednego niestandardowego wyjątku, na przykład
i zajmij się tym wszystkimi osobno
źródło
Ogólnie rzecz biorąc, NumberFormaException nie jest zaznaczony, ponieważ oczekuje się, że zapewniono poprawnie analizowalne dane wejściowe. Walidacja danych wejściowych jest czymś, z czym powinieneś się zająć. Jednak w rzeczywistości analizowanie danych wejściowych jest najłatwiejszym sposobem na zrobienie tego. Możesz po prostu pozostawić swoją metodę bez zmian i ostrzec w dokumentacji, że oczekiwane są prawidłowe dane wejściowe i każdy wywołujący twoją funkcję powinien sprawdzić poprawność obu danych wejściowych przed jej użyciem.
źródło
Wszelkie wyjątkowe zachowania należy wyjaśnić w dokumentacji. Powinien albo wskazywać, że ta metoda zwraca specjalną wartość w przypadku niepowodzenia (np.
null
Poprzez zmianę typu zwracanego naInteger
), albo należy użyć przypadku 1. Umieszczenie go w sygnaturze metody pozwala użytkownikowi zignorować go, jeśli zapewni poprawne ciągi w inny sposób, ale nadal jest oczywiste, że metoda sama nie radzi sobie z tego rodzaju niepowodzeniem.źródło
Odpowiedz na zaktualizowane pytanie.
Tak, to zupełnie normalne, że pojawiają się „niespodzianki”. Pomyśl o wszystkich błędach w czasie wykonywania, które pojawiły się, gdy jesteś nowy w programowaniu.
Również częsty wyjątek zaskoczenia z pętli for.
źródło
Chociaż zgadzam się z odpowiedzią, że wyjątek środowiska uruchomieniowego powinien być poddawany perkolacji, z punktu widzenia projektu i użyteczności, dobrym pomysłem byłoby umieszczenie go w wyjątku IllegalArgumentException zamiast wrzucania go jako NumberFormatException. To sprawia, że kontrakt twojej metody jest bardziej przejrzysty, przez co deklaruje, że został do niej przekazany niedozwolony argument, z powodu którego rzuciła wyjątek.
Jeśli chodzi o aktualizację pytania „Wyobraź sobie, to nie jest framework open source, którego powinieneś używać z jakiegoś powodu. Patrzysz na sygnaturę metody i myślisz -„ OK, to nigdy nie rzuca ”. Potem pewnego dnia pojawił się wyjątek . Jest to normalne?" javadoc twojej metody powinien zawsze ujawniać zachowanie twojej metody (ograniczenia przed i po). Pomyśl o wierszach interfejsów say collection, gdzie jeśli wartość null nie jest dozwolona, javadoc mówi, że zostanie zgłoszony wyjątek wskaźnika zerowego, chociaż nigdy nie jest on częścią sygnatury metody.
źródło
Skoro mówisz o dobrej praktyce w Javie, moim zdaniem zawsze jest lepiej
Aby obsłużyć niezaznaczony wyjątek, przeanalizuj go i wykorzystaj niestandardowy niezaznaczony wyjątek.
Również podczas rzucania niestandardowego niezaznaczonego wyjątku można dodać komunikat o wyjątku, który klient może zrozumieć, a także wydrukować ślad stosu oryginalnego wyjątku
Nie ma potrzeby deklarowania wyjątku niestandardowego jako „rzutów”, ponieważ jest on niezaznaczony.
W ten sposób nie naruszasz użycia tego, do czego służą niezaznaczone wyjątki, a jednocześnie klient kodu z łatwością zrozumie przyczynę i rozwiązanie wyjątku.
Również prawidłowe dokumentowanie w java-doc jest dobrą praktyką i bardzo pomaga.
źródło
Myślę, że zależy to od twojego celu, ale udokumentowałbym to jako minimum:
/** * @return the sum (as an int) of the two strings * @throws NumberFormatException if either string can't be converted to an Integer */ public static int sum(String a, String b) int x = Integer.parseInt(a); int y = Integer.parseInt(b); return x + y; }
Możesz też pobrać stronę z kodu źródłowego Java dla klasy java.lang.Integer:
public static int parseInt(java.lang.String string) throws java.lang.NumberFormatException;
źródło
A co z wzorcem sprawdzania poprawności danych wejściowych zaimplementowanym przez bibliotekę Google „Guava” lub bibliotekę „Validator” Apache ( porównanie )?
Z mojego doświadczenia wynika, że dobrą praktyką jest sprawdzanie poprawności parametrów funkcji na początku funkcji i zgłaszanie wyjątków tam, gdzie to konieczne.
Uważam również, że to pytanie jest w dużej mierze niezależne od języka. „Dobra praktyka” miałaby tutaj zastosowanie do wszystkich języków, które mają funkcje, które mogą przyjmować parametry, które mogą być prawidłowe lub nie.
źródło
Myślę, że twoje pierwsze zdanie w „Dość głupie pytanie” jest bardzo istotne. Dlaczego w ogóle miałbyś napisać metodę z tym podpisem? Czy w ogóle ma sens sumowanie dwóch ciągów? Jeśli metoda wywołująca chce zsumować dwa ciągi, obowiązkiem metody wywołującej jest upewnienie się, że są one prawidłowe i przekonwertowanie ich przed wywołaniem metody.
W tym przykładzie, jeśli metoda wywołująca nie może przekonwertować dwóch ciągów znaków na int, może zrobić kilka rzeczy. To naprawdę zależy, w jakiej warstwie zachodzi to sumowanie. Zakładam, że konwersja String byłaby bardzo zbliżona do kodu front-end (jeśli została wykonana poprawnie), w takim przypadku 1. byłby najbardziej prawdopodobny:
źródło
Jest wiele interesujących odpowiedzi na to pytanie. Ale nadal chcę to dodać:
Do analizowania ciągów zawsze wolę używać „wyrażeń regularnych”. Pakiet java.util.regex służy nam pomocą. Więc skończę z czymś takim, co nigdy nie stanowi żadnego wyjątku. Ode mnie zależy zwrócenie specjalnej wartości, jeśli chcę złapać jakiś błąd:
import java.util.regex.Pattern; import java.util.regex.Matcher; public static int sum(String a, String b) { final String REGEX = "\\d"; // a single digit Pattern pattern = Pattern.compile(REGEX); Matcher matcher = pattern.matcher(a); if (matcher.find()) { x = Integer.matcher.group(); } Matcher matcher = pattern.matcher(b); if (matcher.find()) { y = Integer.matcher.group(); } return x + y; }
Jak widać, kod jest tylko trochę dłuższy, ale możemy sobie poradzić z tym, co chcemy (i ustawić wartości domyślne dla x i y, kontrolować, co się dzieje z klauzulami else itp.). Moglibyśmy nawet napisać bardziej ogólną transformację procedura, do której możemy przekazywać ciągi znaków, domyślne zwracane wartości, kod REGEX do kompilacji, komunikaty o błędach do wysyłania, ...
Mam nadzieję, że to było przydatne.
Ostrzeżenie: nie udało mi się przetestować tego kodu, więc przepraszam za ewentualne problemy ze składnią.
źródło
Integer.matcher
?private
zmienna wewnątrz metody? Brakuje()
IFS, brakuje wiele;
,x
,y
nie zadeklarował,matcher
ogłosił dwa razy ...Integer.matcher.group()
tak ma byćInteger.parseInt(matcher.group())
)Napotykasz ten problem, ponieważ pozwalasz błędom użytkownika rozprzestrzeniać się zbyt głęboko do rdzenia aplikacji, a częściowo także dlatego, że nadużywasz typów danych Java.
Powinieneś mieć wyraźniejsze oddzielenie między walidacją danych wejściowych użytkownika a logiką biznesową, używać odpowiedniego wpisywania danych, a ten problem zniknie sam.
Faktem jest, że semantyka
Integer.parseInt()
jest znana - jej głównym celem jest analizowanie prawidłowych liczb całkowitych. Brakuje jawnego kroku weryfikacji / analizy danych wejściowych użytkownika.źródło