Jestem w trakcie opracowywania nowego języka programowania, aby rozwiązać niektóre wymagania biznesowe, a ten język jest skierowany do początkujących użytkowników. Dlatego nie ma obsługi obsługi wyjątków w tym języku i nie spodziewałbym się, że będą go używać, nawet jeśli go dodam.
Doszedłem do punktu, w którym muszę wdrożyć operator dzielenia i zastanawiam się, jak najlepiej obsłużyć błąd dzielenia przez zero?
Wydaje mi się, że mam tylko trzy możliwe sposoby rozwiązania tej sprawy.
- Zignoruj błąd i wygeneruj
0
jako wynik. Rejestrowanie ostrzeżenia, jeśli to możliwe. - Dodaj
NaN
jako możliwą wartość dla liczb, ale to rodzi pytania dotyczące sposobu obsługiNaN
wartości w innych obszarach języka. - Zakończ wykonywanie programu i zgłoś użytkownikowi poważny błąd.
Opcja nr 1 wydaje się jedynym rozsądnym rozwiązaniem. Opcja nr 3 nie jest praktyczna, ponieważ ten język będzie używany do uruchamiania logiki jako nocny cron.
Jakie są moje alternatywy dla obsługi błędu dzielenia przez zero i jakie jest ryzyko związane z opcją nr 1.
źródło
reject "Foo"
został zaimplementowany, ale po prostu odrzuca dokument, jeśli zawiera słowo kluczoweFoo
. Staram się uczynić ten język tak łatwym do odczytania za pomocą terminów, które użytkownik zna. Nadanie użytkownikowi własnego języka programowania pozwala mu dodawać reguły biznesowe bez uzależnienia od personelu technicznego.Odpowiedzi:
Zdecydowanie odradzałbym # 1, ponieważ ignorowanie błędów jest niebezpiecznym anty-wzorem. Może to prowadzić do trudnych do analizy błędów. Ustawienie wyniku dzielenia przez zero na zero nie ma żadnego sensu, a kontynuowanie wykonywania programu z bezsensowną wartością spowoduje kłopoty. Zwłaszcza, gdy program działa bez nadzoru. Gdy interpreter programu zauważy, że w programie wystąpił błąd (a dzielenie przez zero prawie zawsze jest błędem projektowym), zwykle przerywa się go i utrzymuje wszystko w niezmienionym stanie, niż wypełnianie bazy danych śmieciami.
Ponadto, prawdopodobnie nie odniesiesz sukcesu przy dokładnym przestrzeganiu tego wzoru. Wcześniej czy później napotkasz sytuacje błędów, których po prostu nie można zignorować (na przykład brak pamięci lub przepełnienie stosu) i będziesz musiał zaimplementować sposób zakończenia programu.
Opcja nr 2 (użycie NaN) byłaby trochę pracochłonna, ale nie tak bardzo, jak mogłoby się wydawać. Sposób obsługi NaN w różnych obliczeniach jest dobrze udokumentowany w standardzie IEEE 754, więc prawdopodobnie możesz po prostu robić to, w jakim języku jest napisany Twój tłumacz.
Nawiasem mówiąc: Tworzenie języka programowania używanego przez nieprogramowych programistów jest czymś, co próbowaliśmy robić od 1964 roku (Dartmouth BASIC). Jak dotąd nie udało nam się. Ale i tak powodzenia.
źródło
PHP
miał na mnie zły wpływ.NaN
w języku początkującym, ale ogólnie świetna odpowiedź.To nie jest dobry pomysł. W ogóle. Ludzie zaczną w zależności od tego i jeśli kiedykolwiek to naprawisz, złamiesz dużo kodu.
Powinieneś obsługiwać NaN tak, jak robią to środowiska wykonawcze innych języków: Każde dalsze obliczenia również dają NaN, a każde porównanie (nawet NaN == NaN) daje fałsz.
Myślę, że jest to do przyjęcia, ale niekoniecznie nowe przyjazne dla początkujących.
Myślę, że to najlepsze rozwiązanie. Mając te informacje w ręku, użytkownicy powinni być w stanie obsłużyć 0. Powinieneś zapewnić środowisko testowe, zwłaszcza jeśli ma ono działać raz na noc.
Jest też czwarta opcja. Niech podział będzie operacją potrójną. Każdy z tych dwóch będzie działać:
źródło
NaN == NaN
będziefalse
, to trzeba będzie dodaćisNaN()
funkcję, dzięki czemu użytkownicy są w stanie wykryćNaN
s.isNan(x) => x != x
. Mimo to, kiedy pojawiaNaN
się kod programowania, nie powinieneś zaczynać dodawaćisNaN
kontroli, a raczej wyśledzić przyczynę i dokonać niezbędnych kontroli. Dlatego ważne jest,NaN
aby propagować w pełni.NaN
są głównie sprzeczne z intuicją. W języku początkującym są martwi po przyjeździe.1/0
- musisz coś z tym zrobić. Nie ma potencjalnie użyteczny wynik inny niżInf
lubNaN
- coś, co będzie dalej propagować błąd w programie. W przeciwnym razie jedynym rozwiązaniem jest zatrzymanie się z błędem w tym momencie.Zakończ działającą aplikację z wyjątkowymi uprzedzeniami. (Podając odpowiednie informacje debugowania)
Następnie naucz użytkowników, jak identyfikować i obsługiwać warunki, w których dzielnik może wynosić zero (wartości wprowadzone przez użytkownika itp.)
źródło
W Haskell (i podobnym w Scali) zamiast zgłaszania wyjątków (lub zwracania referencji zerowych) można zastosować typy opakowań
Maybe
iEither
można je stosować. DziękiMaybe
temu użytkownik ma szansę sprawdzić, czy wartość, którą otrzymał, jest „pusta” lub może podać wartość domyślną podczas „rozpakowywania”.Either
jest podobny, ale można go użyć zwraca obiekt (np. ciąg błędu) opisujący problem, jeśli taki istnieje.źródło
error "some message"
oceniana funkcja.Haskell
nie pozwala czystym wyrażeniom na zgłaszanie wyjątków.Inne odpowiedzi już rozważały względne zalety twoich pomysłów. Proponuję inny: użyj podstawowej analizy przepływu, aby ustalić, czy zmienna może wynosić zero. Następnie możesz po prostu zabronić dzielenia według zmiennych, które są potencjalnie zerowe.
Alternatywnie, mieć inteligentną funkcję potwierdzenia, która ustanawia niezmienniki:
Jest to tak dobre, jak zgłoszenie błędu czasu wykonywania - całkowicie pomija się niezdefiniowane operacje - ale ma tę zaletę, że ścieżka kodu nie musi nawet zostać trafiona, aby możliwe było ujawnienie potencjalnej awarii. Można to zrobić podobnie jak zwykłe sprawdzanie typów, oceniając wszystkie gałęzie programu z zagnieżdżonymi środowiskami do pisania w celu śledzenia i weryfikacji niezmienników:
Ponadto naturalnie rozszerza się na zasięg i
null
sprawdzanie, czy Twój język ma takie funkcje.źródło
def foo(a,b): return a / ord(sha1(b)[0])
. Analizator statyczny nie może odwrócić SHA-1. Clang ma tego rodzaju analizę statyczną i doskonale nadaje się do wyszukiwania płytkich błędów, ale jest wiele przypadków, z którymi nie może sobie poradzić.Liczba 1 (wstaw niezbadane zero) jest zawsze zła. Wybór między numerem 2 (propagacja NaN) a numerem 3 (zabicie procesu) zależy od kontekstu i najlepiej powinien być ustawieniem globalnym, tak jak ma to miejsce w Numpy.
Jeśli wykonujesz jedno duże, zintegrowane obliczenie, propagowanie NaN jest złym pomysłem, ponieważ ostatecznie rozprzestrzeni się i zainfekuje całe twoje obliczenia --- kiedy spojrzysz na wyniki rano i zobaczysz, że wszystkie są NaN, „ d i tak muszę wyrzucić wyniki i zacząć od nowa. Byłoby lepiej, gdyby program się zakończył, dostałeś telefon w środku nocy i naprawiłeś go - przynajmniej pod względem liczby zmarnowanych godzin.
Jeśli wykonujesz wiele niewielkich, w większości niezależnych obliczeń (takich jak pomniejszenie mapy lub żenująco równoległe obliczenia) i możesz tolerować pewien procent z nich, które nie nadają się do użycia z powodu NaN, jest to prawdopodobnie najlepsza opcja. Zakończenie programu i niewykonanie 99%, co byłoby dobre i przydatne ze względu na zniekształcenie 1% i podzielenie przez zero, może być błędem.
Inna opcja związana z NaN: ta sama specyfikacja zmiennoprzecinkowa IEEE definiuje Inf i -Inf i są one propagowane inaczej niż NaN. Na przykład jestem całkiem pewien, że Inf> dowolna liczba i -Inf <dowolna liczba, co byłoby tym, czego chciałbyś, gdyby twój podział przez zero nastąpił, ponieważ zero miało być tylko małą liczbą. Jeśli dane wejściowe są zaokrąglone i występują błędy pomiaru (takie jak pomiary fizyczne wykonywane ręcznie), różnica dwóch dużych ilości może spowodować zero. Bez dzielenia przez zero uzyskałbyś dużą liczbę i być może nie obchodzi Cię, jak duża jest. W takim przypadku In i -Inf są perfekcyjnie poprawnymi wynikami.
To może być formalnie poprawne - po prostu powiedz, że pracujesz w rozszerzonych realiach.
źródło
Oczywiście jest to praktyczne: programiści mają obowiązek napisania programu, który ma sens. Dzielenie przez 0 nie ma sensu. Dlatego, jeśli programista wykonuje podział, jego obowiązkiem jest również wcześniejsze sprawdzenie , czy dzielnik nie jest równy 0. Jeśli programista nie wykona tej kontroli sprawdzania poprawności, powinien on / ona zdać sobie sprawę z tego błędu, jak tylko możliwe i zdenormalizowane (NaN) lub niepoprawne (0) wyniki obliczeń po prostu nie pomogą w tym zakresie.
Zdarza się, że opcja 3 jest tą, którą poleciłbym wam, ponieważ jest najbardziej prosta, uczciwa i poprawna matematycznie.
źródło
Wydaje mi się, że to zły pomysł na uruchamianie ważnych zadań (np. „Night cron”) w środowisku, w którym błędy są ignorowane. To straszny pomysł, aby włączyć tę funkcję. To wyklucza opcje 1 i 2.
Opcja 3 jest jedynym dopuszczalnym rozwiązaniem. Wyjątki nie muszą być częścią języka, ale są częścią rzeczywistości. Twoja wiadomość o zakończeniu powinna być jak najbardziej szczegółowa i zawierać informacje o błędzie.
źródło
IEEE 754 faktycznie ma dobrze zdefiniowane rozwiązanie twojego problemu. Obsługa wyjątków bez użycia
exceptions
http://en.wikipedia.org/wiki/IEEE_floating_point#Exception_handlingw ten sposób wszystkie twoje operacje mają matematyczny sens.
\ lim_ {x \ do 0} 1 / x = Inf
Moim zdaniem przestrzeganie IEEE 754 jest najbardziej sensowne, ponieważ zapewnia, że twoje obliczenia są tak poprawne, jak na komputerze, a także jesteś zgodny z zachowaniem innych języków programowania.
Jedynym problemem, który się pojawia, jest to, że Inf i NaN będą zanieczyszczać twoje wyniki, a twoi użytkownicy nie będą dokładnie wiedzieć, skąd pochodzi problem. Spójrz na język taki jak Julia, który robi to całkiem dobrze.
Błąd podziału jest poprawnie propagowany przez operacje matematyczne, ale na końcu użytkownik niekoniecznie wie, z której operacji wynika błąd.
edit:
Nie widziałem drugiej części odpowiedzi Jima Pivarskiego, która jest zasadniczo tym, co mówię powyżej. Mój błąd.źródło
SQL, z łatwością język najczęściej używany przez nieprogramowych programistów, zajmuje trzecie miejsce, niezależnie od tego, co jest tego warte. Z mojego doświadczenia obserwując i pomagając nie-programistom piszącym SQL, takie zachowanie jest ogólnie dobrze zrozumiałe i łatwo kompensowane (za pomocą instrukcji case lub podobnego). Pomaga to, że otrzymany komunikat o błędzie wydaje się być dość bezpośredni, np. W Postgres 9 pojawia się komunikat „BŁĄD: dzielenie przez zero”.
źródło
Myślę, że problem jest „skierowany do początkujących użytkowników. -> Więc nie ma wsparcia dla ...”
Dlaczego uważasz, że obsługa wyjątków jest problematyczna dla początkujących użytkowników?
Co jest gorsze? Masz „trudną” funkcję lub nie masz pojęcia, dlaczego coś się stało? Co może dezorientować więcej? Awaria z zrzutem rdzenia lub „Błąd krytyczny: Dziel przez zero”?
Zamiast tego myślę, że DALEJ lepiej dążyć do WIELKICH błędów komunikatów. Zamiast tego: „Złe obliczenia, podziel 0/0” (tzn .: Zawsze pokazuj DANE, które powodują problem, a nie tylko rodzaj problemu). Zobacz, jak PostgreSql robi błędy komunikatów, które są świetne IMHO.
Możesz jednak spojrzeć na inne sposoby pracy z wyjątkami, takie jak:
http://dlang.org/exception-safe.html
Marzę również o zbudowaniu języka, i w tym przypadku myślę, że połączenie Może / Opcjonalne z normalnymi Wyjątkami może być najlepsze:
źródło
Moim zdaniem twój język powinien zapewniać ogólny mechanizm wykrywania i obsługi błędów. Błędy programowania powinny być wykryte w czasie kompilacji (lub tak wcześnie, jak to możliwe) i zwykle powinny prowadzić do zakończenia programu. Błędy, które wynikają z nieoczekiwanych lub błędnych danych lub z nieoczekiwanych warunków zewnętrznych, powinny zostać wykryte i udostępnione dla odpowiednich działań, ale pozwalają programowi na kontynuowanie w miarę możliwości.
Możliwe działania obejmują (a) zakończ (b) poproś użytkownika o działanie (c) zaloguj błąd (d) zastąp poprawioną wartość (e) ustaw wskaźnik do przetestowania w kodzie (f) wywołaj procedurę obsługi błędu. Które z nich udostępniasz i za pomocą jakich środków musisz dokonać wyboru.
Z mojego doświadczenia wynika, że typowe błędy danych, takie jak wadliwe konwersje, dzielenie przez zero, przepełnienie i wartość poza zakresem są łagodne i powinny być domyślnie obsługiwane przez podstawienie innej wartości i ustawienie flagi błędu. (Nieprogramiści) używający tego języka zobaczą wadliwe dane i szybko zrozumieją potrzebę sprawdzenia błędów i obsługi ich.
[Na przykład rozważ arkusz kalkulacyjny Excel. Program Excel nie kończy arkusza kalkulacyjnego, ponieważ liczba została przelana lub cokolwiek innego. Komórka ma dziwną wartość, a ty dowiedz się, dlaczego ją naprawisz.]
Aby odpowiedzieć na twoje pytanie: z pewnością nie powinieneś kończyć. Możesz zastąpić NaN, ale nie powinieneś tego robić, tylko upewnij się, że obliczenia się zakończyły i wygenerowały dziwną wysoką wartość. I ustaw flagę błędu, aby użytkownicy, którzy jej potrzebują, mogli ustalić, że wystąpił błąd.
Ujawnienie: Stworzyłem właśnie taką implementację języka (Powerflex) i dokładnie rozwiązałem ten problem (i wiele innych) w latach 80. W ciągu ostatnich 20 lat postęp w dziedzinie języków był niewielki lub żaden. Dla nieprogramiści postępy są niewielkie, a ty będziesz przyciągał mnóstwo krytyki za próby, ale mam nadzieję, że ci się uda.
źródło
Podobał mi się trójskładnikowy operator, w którym podajesz alternatywną wartość w przypadku, gdy licznik wynosi 0.
Jeszcze jeden pomysł, którego nie widziałem, to wygenerowanie ogólnej „nieprawidłowej” wartości. Ogólne „ta zmienna nie ma wartości, ponieważ program zrobił coś złego”, który sam przenosi ślad pełnego stosu. Następnie, jeśli kiedykolwiek użyjesz tej wartości w dowolnym miejscu, wynik jest ponownie niepoprawny, a nowa operacja zostanie podjęta na górze (tj. Jeśli niepoprawna wartość kiedykolwiek pojawi się w wyrażeniu, całe wyrażenie zwróci wartość niepoprawną i nie będą podejmowane żadne wywołania funkcji; wyjątek być operatorami logicznymi - prawda lub niepoprawność to prawda, a fałsz, a niepoprawność to fałsz - mogą istnieć również inne wyjątki). Gdy nigdzie nie ma już takiej wartości, zapisujesz ładny długi opis całego łańcucha, w którym coś jest nie tak, i kontynuujesz biznes jak zwykle. Może wysłać ślad e-mailem do kierownika projektu lub coś takiego.
Zasadniczo coś w rodzaju monady Może. Będzie działał ze wszystkim, co również może zawieść, i możesz pozwolić ludziom na budowę własnych inwalidów. I program będzie działał tak długo, jak długo błąd nie będzie zbyt głęboki, tak myślę, że naprawdę tego tutaj potrzebujemy.
źródło
Istnieją dwa podstawowe powody podziału przez zero.
Po 1. musisz poinformować użytkowników, że popełnili błąd, ponieważ to oni są odpowiedzialni i to oni najlepiej wiedzą, jak naprawić sytuację.
W przypadku 2. To nie jest wina użytkownika, możesz wskazać algorytm, implementację sprzętu itp., Ale to nie jest wina użytkownika, więc nie powinieneś kończyć programu ani nawet rzucać wyjątku (jeśli jest to dozwolone, co nie jest w tym przypadku). Dlatego rozsądnym rozwiązaniem jest kontynuowanie operacji w rozsądny sposób.
Widzę, że osoba zadająca to pytanie zadała pytanie 1. Więc musisz się z powrotem skontaktować z użytkownikiem. Używanie dowolnego standardu zmiennoprzecinkowego, Inf, -Inf, Nan, IEEE nie pasuje do tej sytuacji. Zasadniczo zła strategia.
źródło
Nie zezwalaj na to w języku. Innymi słowy, nie zezwalaj na dzielenie przez liczbę, dopóki nie będzie ona zerowa, zwykle najpierw testując ją. To znaczy.
źródło
int
dopuszcza wartości zerowe, ale GCC wciąż może określić, gdzie w kodzie ints nie może wynosić zero.Pisząc język programowania, powinieneś skorzystać z tego faktu i uczynić obowiązkowym dołączenie akcji dla urządzenia przez stan zerowy. a <= n / c: 0 div-by-zero-action
Wiem, co właśnie zasugerowałem, w zasadzie dodając „goto” do twojego PL.
źródło