Czy powinienem rzucić wyjątek w przypadku znaczącej wartości spoza zakresu, czy sam go obsłużyć?

42

Napisałem strukturę, która reprezentuje współrzędne szerokości i długości geograficznej. Ich wartości wynoszą od -180 do 180 dla długości i 90 do -90 dla szerokości.

Jeśli użytkownik tej struktury podaje mi wartość spoza tego zakresu, mam 2 opcje:

  1. Rzuć wyjątek (argument poza zakresem)
  2. Konwertuj wartość na ograniczenie

Ponieważ współrzędne -185 ma znaczenie (można je bardzo łatwo przekonwertować na +175, ponieważ są to współrzędne biegunowe), mógłbym je zaakceptować i przekonwertować.

Czy lepiej jest rzucić wyjątek, aby poinformować użytkownika, że ​​jego kod dał mi wartość, której nie powinien mieć?

Edycja: Znam też różnicę między lat / lng a współrzędnymi, ale chciałem to uprościć dla łatwiejszej dyskusji - nie był to najjaśniejszy pomysł

K. Gkinis
źródło
13
Czy użytkownik powinien mieć możliwość wstawienia wartości spoza zakresu? Jeśli odpowiedź brzmi „nie”, wyślij wyjątek. Jeśli reguły nie są tak rygorystyczne, wykonaj konwersję, ale wyraźnie zaznacz w dokumentacji, że konwersja może nastąpić. Należy również pamiętać, że w niektórych językach obsługa wyjątków jest dość kosztowna.
Andy
C #, czy to ma znaczenie? Natywnie nie obsługuje ograniczeń, jeśli o to ci chodzi.
K. Gkinis,
2
Wydaje mi się więc dość jasne, że prawidłowe podejście pozwala użytkownikowi wprowadzić cokolwiek poza zakresem i zgłasza wyjątek, gdy to robi (jeśli standard mówi, że wartość abs nie może przekraczać 180, co oznacza, że ​​jest większa niż wyraźne naruszenie). Nawiasem mówiąc, C # jest w rzeczywistości jednym z języków, w których wyjątki są dość kosztowne, więc używaj ich naprawdę tylko w wyjątkowych sytuacjach, co oznacza, że ​​nie złapanie go spowoduje uszkodzenie aplikacji, takie jak ten.
Andy
2
Staram się unikać przyjmowania założeń dotyczących tego, co użytkownik „rozumiał”, przekazując określone wartości parametrów, zwłaszcza te, których mój kod nie obsługuje. Brzmi jak podobna sprawa.
JᴀʏMᴇᴇ
2
Współrzędne Web Mercator nie mają wartości od -180 do 180 i od -90 do 90. To jest szerokość / długość geograficzna (i jest nawet kilka układów współrzędnych). Rzuty Mercatora są zwykle w setkach tysięcy lub milionów i mają jednostki „metrów” (nawet nie ściśle, ponieważ długość każdej jednostki obejmuje zwiększenie rzeczywistej odległości od ziemi podczas zbliżania się do biegunów), a nie stopni. Nawet pod względem stopni jest ograniczony do ± 85.051129 stopni, ponieważ rzut staje się nieskończenie szeroki na biegunach. (
Przesłałem

Odpowiedzi:

51

Jeśli sednem twojego pytania jest to ...

Jeśli jakiś kod klienta przekaże argument, którego wartość jest niepoprawna dla rzeczy, którą modeluje moja struktura danych, czy powinienem odrzucić tę wartość lub przekonwertować ją na coś sensownego?

... wtedy moja ogólna odpowiedź brzmiałaby „odrzuć”, ponieważ pomoże to zwrócić uwagę na potencjalne błędy w kodzie klienta, które w rzeczywistości powodują wyświetlanie nieprawidłowej wartości w programie i dotarcie do twojego konstruktora. Zwracanie uwagi na błędy jest na ogół pożądaną właściwością w większości systemów, przynajmniej podczas programowania (chyba że pożądana właściwość twojego systemu do pomieszania w przypadku błędów).

Pytanie brzmi, czy rzeczywiście masz do czynienia z tą sprawą .

  • Jeśli struktura danych ma ogólnie modelować współrzędne biegunowe, zaakceptuj wartość, ponieważ kąty poza zakresem -180 i +180 nie są tak naprawdę nieprawidłowe. Są całkowicie poprawne i po prostu zawsze mają odpowiednik w zakresie od -180 do +180 (a jeśli chcesz przekonwertować je na ten zakres, nie krępuj się - kod klienta zwykle nie musi się tym przejmować) .

  • Jeśli twoja struktura danych wyraźnie modeluje współrzędne Web Mercator (zgodnie z pytaniem w swojej pierwotnej formie), najlepiej postępować zgodnie z wszelkimi postanowieniami wymienionymi w specyfikacji (których nie wiem, więc nie powiem o tym nic) . Jeśli specyfikacja modelowanego elementu mówi, że niektóre wartości są nieprawidłowe, odrzuć je. Jeśli mówi, że można je interpretować jako coś sensownego (a tym samym faktycznie obowiązującego), zaakceptuj je.

Mechanizm użyć do sygnału, czy wartości zostały przyjęte, czy też nie, zależy od cech swojego języka, jego ogólnej filozofii i Twoich wymagań eksploatacyjnych. Tak więc możesz zgłaszać wyjątek (w konstruktorze) lub zwracać zerową wersję swojej struktury (za pomocą metody statycznej, która wywołuje prywatnego konstruktora) lub zwracać wartość logiczną i przekazywać swoją strukturę do obiektu wywołującego jako outparametr (ponownie przez metoda statyczna, która wywołuje konstruktora prywatnego) i tak dalej.

Theodoros Chatzigiannakis
źródło
12
Długość geograficzną poza zakresem od -180 do +180 należy prawdopodobnie uznać za akceptowalną, ale szerokość geograficzna poza zakresem od -90 do +90 wydaje się nonsensowna. Po osiągnięciu bieguna północnego lub południowego dalsza podróż na północ lub południe jest niezdefiniowana.
supercat
4
@ supercat Zwykle się z tobą zgadzam, ale ponieważ nie wiem, które wartości są faktycznie nieprawidłowe w specyfikacji Web Mercator, staram się nie wyciągać żadnych twardych wniosków. OP zna domenę problemów lepiej niż ja.
Theodoros Chatzigiannakis
1
@ supercat: zacznij, gdzie południk osiągnie równik. podróż wzdłuż południka zgodnie z szerokością geograficzną. Jeśli szerokość wynosi> 90, po prostu idź wzdłuż tego samego wielkiego koła. Nie ma problemu. Dokumentuj to, a resztę pozostaw klientowi.
kevin cline
4
@ superupat Jest gorzej. W projekcji Web Mercator ± 90 faktycznie zostaje rzutowane na nieskończoną szerokość. Tak więc norma faktycznie odcina go na ± 85,051129. Także współrzędne lat / long! = Web mercator. Współrzędne Mercator używają zmian w metrach, a nie stopniach. (Gdy zbliżasz się do biegunów, każdy „metr” faktycznie odpowiada coraz większemu kawałkowi ziemi.) Współrzędne OP są czysto długie / długie. Web Mercator nie ma z nimi nic wspólnego, poza tym, że mogą być wyświetlane na górze mapy bazowej Web Mercator, a niektóre biblioteki przestrzenne wyświetlają się dla nich pod maską.
jpmc26
1
Powinienem powiedzieć „równowartość ± 85,051129”, ponieważ nie jest to rzeczywista współrzędna.
jpmc26
10

To bardzo zależy. Ale powinieneś zdecydować się coś zrobić i udokumentować .

Jedynym zdecydowanie błędem w kodzie jest zapomnienie o tym, że dane wejściowe użytkownika mogą znajdować się poza oczekiwanym zakresem i napisanie kodu, który przypadkowo zachowuje się trochę. Ponieważ wtedy niektórzy ludzie przyjmą błędne założenia co do zachowania twojego kodu i spowoduje błędy, podczas gdy inni skończą w zależności od zachowania, które przypadkowo ma Twój kod (nawet jeśli to zachowanie jest całkowicie nieprzyjemne), a więc będziesz powodować więcej błędów kiedy później naprawisz problem.

W tym przypadku widzę argumenty tak czy inaczej. Jeśli ktoś podróżuje +10 stopni od 175 stopni, powinien skończyć na -175. Jeśli zawsze normalizujesz dane wprowadzane przez użytkownika i traktujesz 185 jako ekwiwalent -175, kod klienta nie może zrobić źle, gdy dodaje 10 stopni; zawsze ma odpowiedni efekt. Jeśli traktujesz 185 jako błąd, zmuszasz każdy przypadek, w którym kod klienta dodaje względne stopnie, aby wprowadzić logikę normalizacji (lub przynajmniej pamiętasz, aby wywołać procedurę normalizacji), w rzeczywistości spowodujeszbłędy (choć, miejmy nadzieję, łatwe do złapania, które zostaną szybko zgniecione). Ale jeśli liczba długości geograficznej zostanie wprowadzona przez użytkownika, zapisana dosłownie w programie lub obliczona za pomocą jakiejś procedury mającej zawsze być w [-180, 180), wówczas wartość spoza tego zakresu najprawdopodobniej wskaże błąd, więc „pomocne „konwersja może ukryć problemy.

Moim ideałem w tym przypadku byłoby prawdopodobnie zdefiniowanie typu reprezentującego prawidłową domenę. Użyj typu abstrakcyjnego (nie pozwól, aby kod klienta po prostu uzyskiwał dostęp do surowych liczb w nim zawartych) i zapewnij zarówno normalizującą, jak i sprawdzającą fabrykę (aby klient mógł dokonać kompromisu). Ale niezależnie od tego, jaką wartość tego typu stworzysz, 185 powinien być nie do odróżnienia od -175, gdy widzisz go za pomocą publicznego interfejsu API (nie ma znaczenia, czy są one konwertowane podczas budowy, czy zapewniasz równość, akcesoria i inne operacje, które w jakiś sposób ignorują różnicę) .

Ben
źródło
3

Jeśli wybranie jednego rozwiązania nie ma dla ciebie większego znaczenia, możesz po prostu pozwolić użytkownikowi zdecydować.

Biorąc pod uwagę, że Twoja struktura jest obiektem o wartości tylko do odczytu i utworzonym przez metodę / konstruktor, możesz zapewnić dwa przeciążenia w zależności od opcji, które użytkownik ma:

  • Rzuć wyjątek (argument poza zakresem)
  • Konwertuj wartość na ograniczenie

Nigdy też nie pozwól, aby użytkownik miał niepoprawną strukturę do przekazania do innych metod, popraw to przy tworzeniu.

Edycja: w oparciu o komentarze, zakładam, że używasz c #.

Filipe Borges
źródło
Dzięki za wkład! Zastanowię się nad tym, choć obawiam się, że podważyłoby to cel konstruktora wyrzucającego wyjątek, gdyby tylko można było tego uniknąć. To interesujące!
K. Gkinis,
Jeśli zamierzasz skorzystać z tego pomysłu, lepiej byłoby zdefiniować interfejs i mieć dwie implementacje, które rzucają lub konwertują. Trudno byłoby przeciążać konstruktora w rozsądny sposób, aby działał zgodnie z opisem.
2
@Snowman: Tak, trudno byłoby przeciążać konstruktora tymi samymi typami argumentów, ale nie byłoby trudno mieć dwie metody statyczne i prywatnego konstruktora.
wchargin
1
@ K.Gkinis: Konstruktorem zgłaszającym wyjątek nie jest „upewnienie się, że aplikacja umrze” - w końcu klient zawsze może zaakceptować catchwyjątki. Jak powiedzieli inni, pozwala to klientowi ograniczyć się, jeśli sobie tego życzy. Tak naprawdę niczego nie obchodzisz.
wchargin
Ta sugestia komplikuje kod bez korzyści. Albo metoda powinna przekonwertować nieprawidłowe dane wejściowe, albo nie powinna. Dwa przeciążenia sprawiają, że kod staje się bardziej złożony bez rozwiązywania dylematu.
JacquesB
0

To zależy, czy dane wejściowe pochodzą bezpośrednio od użytkownika przez jakiś interfejs użytkownika, czy pochodzą z systemu.

Wejście przez interfejs użytkownika

Pytanie użytkownika dotyczy sposobu postępowania z nieprawidłowymi danymi wejściowymi. Nie wiem o twoim konkretnym przypadku, ale ogólnie istnieje kilka opcji:

  • Powiadom użytkownika o błędzie i poproś go o naprawienie przed kontynuowaniem (najczęściej)
  • Automatycznie przekonwertuj na prawidłowy zakres (jeśli to możliwe), ale powiadom użytkownika o zmianie i pozwól mu zweryfikować przed kontynuowaniem.
  • Po cichu przekonwertuj na prawidłowy zakres i kontynuuj.

Wybór zależy od oczekiwań użytkowników i tego, jak ważne są dane. Na przykład Google automatycznie poprawia pisownię w zapytaniach, ale jest to niskie ryzyko, ponieważ nieprzydatna zmiana nie stanowi problemu i jest łatwa do naprawy (a nawet wtedy na stronie wyników jest wyraźnie zaznaczone, że zapytanie zostało zmienione). Z drugiej strony, jeśli wprowadzasz współrzędne dla pocisku nuklearnego, możesz chcieć bardziej sztywnego sprawdzania poprawności danych wejściowych i żadnych cichych poprawek nieprawidłowych danych. Więc nie ma uniwersalnej odpowiedzi.

Co najważniejsze, należy rozważyć, czy poprawienie danych wejściowych ma nawet korzyść dla użytkownika. Dlaczego użytkownik wprowadziłby nieprawidłowe dane? Łatwo jest zobaczyć, jak ktoś może popełnić błąd ortograficzny, ale dlaczego ktoś miałby wprowadzić długość -185? Jeśli użytkownik naprawdę miał na myśli +175, prawdopodobnie napisałby +175. Myślę, że najprawdopodobniej nieprawidłowa długość geograficzna jest po prostu błędem podczas pisania, a użytkownik miał na myśli -85 lub coś innego. W tym przypadku konwersja po cichu jest zła i nieprzydatna . Najbardziej przyjaznym dla użytkownika podejściem dla Twojej aplikacji byłoby prawdopodobnie powiadomienie użytkownika o nieprawidłowej wartości i samodzielne jej poprawienie.

Dane wejściowe za pośrednictwem interfejsu API

Jeśli dane wejściowe pochodzą z innego systemu lub podsystemu, nie ma pytania. Powinieneś rzucić wyjątek. Nigdy nie należy cicho konwertować niepoprawnych danych wejściowych z innego systemu, ponieważ może to maskować błędy w innym miejscu w systemie. Jeśli dane wejściowe są „poprawione”, powinno to nastąpić w warstwie interfejsu użytkownika, a nie głębiej w systemie.

JacquesB
źródło
0

Powinieneś rzucić wyjątek.

W podanym przykładzie, wysyłając 185 i konwertując to na -175, w niektórych przypadkach może być przydatne zapewnienie takiej funkcjonalności. Ale co, jeśli dzwoniący wyśle ​​1 milion? Czy oni naprawdę chcą to przekonwertować? Bardziej prawdopodobne jest, że to błąd. Jeśli więc musisz zgłosić wyjątek dla 1 000 000, ale nie dla 185, musisz podjąć decyzję o arbitralnym progu wyrzucenia wyjątku. Ten próg może cię kiedyś podnieść, ponieważ jakaś aplikacja dzwoniąca wysyła wartości wokół tego progu.

Lepiej wyrzucić wyjątek dla wartości spoza zakresu.

brendan
źródło
-1

Najwygodniejszą opcją dla programisty byłaby obsługa błędu czasu kompilacji na platformie dla wartości spoza zakresu. W takim przypadku zakres powinien być również częścią sygnatury metody, podobnie jak typ parametrów. W ten sam sposób, w jaki użytkownik interfejsu API nie może przekazać ciągu znaków, jeśli sygnatura metody jest zdefiniowana jako przyjmująca liczbę całkowitą , użytkownik nie powinien mógł przekazać wartości bez sprawdzenia, czy wartość mieści się w zakresie podanym w sygnaturze metody. Jeśli nie jest zaznaczone, powinien otrzymać błąd czasu kompilacji, dzięki czemu można uniknąć błędu czasu wykonywania.

Ale obecnie bardzo niewiele kompilatorów / platform obsługuje ten rodzaj sprawdzania czasu kompilacji. Więc to jest ból głowy programistów. Ale idealnie, twoja metoda powinna po prostu rzucić znaczący wyjątek dla nieobsługiwanych wartości i jasno to udokumentować.

BTW, naprawdę podoba mi się model błędu zaproponowany przez Joe Duffy tutaj .

Gulszan
źródło
Jak dostarczyłbyś błędy czasu kompilacji dla danych wprowadzanych przez użytkownika?
JacquesB
@JacquesB Kompilator wygeneruje błąd, jeśli dane wejściowe użytkownika nie zostaną sprawdzone, aby znajdowały się w zakresie po wstawieniu, niezależnie od faktycznej wartości z tego zakresu.
Gulshan
Ale nadal musisz zdecydować, czy chcesz odrzucić dane wejściowe lub przekonwertować na prawidłowy zakres, więc nie odpowiada to pierwotne pytanie.
JacquesB
@JacquesB Na początku powinienem to powiedzieć - zawsze myślałem, że OP opracowuje interfejs API, a interfejs użytkownika opracowuje ktoś inny, zużywając jego interfejs API. Myliłem się co do tego punktu. Właśnie przeczytałem pytanie i zdałem sobie z tego sprawę. Próbuję tylko powiedzieć, że walidacja powinna zostać przeprowadzona u konsumentów API, a jeśli nie, kompilator powinien zgłosić błąd.
Gulshan
Więc ... musisz stworzyć nową klasę, która zawiera dane wejściowe i sprawdza poprawność. Annnnnd, kiedy konstruujesz obiekt klasy z surowych danych wejściowych, co się stanie? Rzuć wyjątek? Po cichu przejść? Po prostu przenieś problem.
djechlin
-1

Domyślnie powinno być zgłoszone wyjątek. Możesz również zezwolić na opcję typu strict=falseprzymus i zrobić na podstawie flagi, gdzie oczywiście strict=truejest domyślna. Jest to dość powszechne:

  • Java DateFormatobsługuje łagodny .
  • Parser JSON firmy Gson obsługuje również tryb łagodny .
  • Itp.
djechlin
źródło
To komplikuje kod bez żadnych korzyści.
JacquesB
@JacquesB domyślnie zgłasza wyjątek, czy pozwala strict=false?
djechlin
@JacquesB Dodałem kilka przykładów interfejsów API obsługujących tryby ścisłe i łagodne, proszę spojrzeć.
djechlin
-2

Dla mnie najlepszą praktyką jest nigdy nie zmieniać danych wejściowych użytkownika. Podejście, które zazwyczaj wybieram, polega na oddzieleniu sprawdzania poprawności od wykonania.

  • Mają prostą klasę, która korzysta tylko z podanych parametrów.
  • Użyj dekoratora, aby zapewnić warstwę sprawdzania poprawności, którą można dowolnie zmieniać bez wpływu na klasę wykonawczą (lub też wstrzyknij weryfikator, jeśli takie podejście jest zbyt trudne).
David A.
źródło
„Użytkownik”, którego dotyczy pytanie, to programista korzystający z interfejsu API, a nie użytkownik końcowy ...
Jay Elston,