Zastanawiam się nad tym zagadnieniem od dłuższego czasu i byłbym ciekawy opinii innych programistów.
Mam tendencję do bardzo defensywnego stylu programowania. Mój typowy blok lub metoda wygląda następująco:
T foo(par1, par2, par3, ...)
{
// Check that all parameters are correct, return undefined (null)
// or throw exception if this is not the case.
// Compute and (possibly) return result.
}
Ponadto podczas obliczania sprawdzam wszystkie wskaźniki przed ich odwołaniem. Mój pomysł jest taki, że jeśli jest jakiś błąd i jakiś wskaźnik NULL powinien się gdzieś pojawić, mój program powinien sobie z tym poradzić i po prostu odmówić kontynuowania obliczeń. Oczywiście może powiadomić o problemie z komunikatem o błędzie w dzienniku lub innym mechanizmie.
Mówiąc bardziej abstrakcyjnie, moje podejście jest takie
if all input is OK --> compute result
else --> do not compute result, notify problem
Inni programiści, w tym moi koledzy, używają innej strategii. Np. Nie sprawdzają wskaźników. Zakładają, że fragment kodu powinien mieć poprawne dane wejściowe i nie powinien być odpowiedzialny za to, co się stanie, jeśli dane wejściowe są nieprawidłowe. Ponadto, jeśli wyjątek wskaźnika NULL spowoduje awarię programu, błąd zostanie łatwiej znaleziony podczas testowania i będzie miał większe szanse na naprawienie.
Moja odpowiedź na to pytanie jest normalna: ale co jeśli błąd nie zostanie wykryty podczas testowania i pojawi się, gdy produkt będzie już używany przez klienta? Jaki jest preferowany sposób ujawnienia się błędu? Czy powinien to być program, który nie wykonuje określonej czynności, ale może nadal działać, czy program, który ulega awarii i wymaga ponownego uruchomienia?
Zreasumowanie
Które z dwóch podejść do obsługi niewłaściwych danych wejściowych doradziłbyś?
Inconsistent input --> no action + notification
lub
Inconsistent input --> undefined behaviour or crash
Edytować
Dziękuję za odpowiedzi i sugestie. Jestem również fanem projektowania na podstawie umowy. Ale nawet jeśli ufam osobie, która napisała kod wywołujący moje metody (być może to jestem ja), nadal mogą występować błędy, które prowadzą do błędnych danych wejściowych. Więc moim podejściem jest, aby nigdy nie zakładać, że metoda została poprawnie przesłana.
Ponadto użyłbym mechanizmu, aby złapać problem i powiadomić o nim. W systemie programistycznym np. Otwiera okno dialogowe, aby powiadomić użytkownika. W systemie produkcyjnym po prostu zapisuje pewne informacje w dzienniku. Nie sądzę, że dodatkowe kontrole mogą prowadzić do problemów z wydajnością. Nie jestem pewien, czy stwierdzenia są wystarczające, czy są wyłączone w systemie produkcyjnym: być może wystąpi jakaś sytuacja w produkcji, która nie wystąpiła podczas testowania.
W każdym razie byłem naprawdę zaskoczony, że wiele osób postępuje w odwrotny sposób: pozwalają aplikacji na awarię „celowo”, ponieważ utrzymują, że ułatwi to znalezienie błędów podczas testowania.
źródło
Odpowiedzi:
Masz rację. Bądź paranoikiem. Nie ufaj innym kodom, nawet jeśli to Twój własny kod. Zapominacie rzeczy, wprowadzacie zmiany, kod ewoluuje. Nie ufaj kodowi zewnętrznemu.
Podkreślono dobrze: co, jeśli dane wejściowe są nieprawidłowe, ale program nie ulega awarii? Następnie dostajesz śmieci do bazy danych i błędy w dół linii.
Na pytanie o liczbę (np. Cenę w dolarach lub liczbę jednostek) chciałbym wpisać „1e9” i zobaczyć, co robi kod. To może się zdarzyć.
Cztery dekady temu, otrzymując licencjat z informatyki z UCBerkeley, powiedziano nam, że dobry program obsługuje 50% błędów. Bądź paranoikiem.
źródło
Masz już dobry pomysł
Które z dwóch podejść do obsługi niewłaściwych danych wejściowych doradziłbyś?
albo lepiej
Nie możesz tak naprawdę podejść do programowania w stylu „ciasteczek” (możesz), ale skończyłbyś się formalnym projektem, który robi rzeczy z przyzwyczajenia, a nie ze świadomego wyboru.
Temperament dogmatyzmu z pragmatyzmem.
Steve McConnell powiedział to najlepiej
Steve McConnell prawie napisał książkę ( Code Complete ) o programowaniu obronnym i była to jedna z metod, które doradził, abyś zawsze sprawdzał poprawność swoich danych wejściowych.
Nie pamiętam, czy Steve o tym wspominał, jednak powinieneś rozważyć zrobienie tego dla nieprywatnych metod i funkcji, i tylko dla innych, gdy zostanie to uznane za konieczne.
źródło
Nie ma tutaj „poprawnej” odpowiedzi, szczególnie bez określenia języka, rodzaju kodu i rodzaju produktu, do którego kod może się dostać. Rozważać:
Język ma znaczenie. W Objective-C często wysyłanie wiadomości do zera jest w porządku; nic się nie dzieje, ale program również nie ulega awarii. Java nie ma wyraźnych wskaźników, więc zerowe wskaźniki nie są tutaj dużym problemem. W C musisz być bardziej ostrożny.
Być paranoikiem oznacza nierozsądne, nieuzasadnione podejrzenie lub nieufność. Prawdopodobnie nie jest to lepsze dla oprogramowania niż dla ludzi.
Twój poziom troski powinien być współmierny do poziomu ryzyka w kodzie i prawdopodobnej trudności w identyfikowaniu pojawiających się problemów. Co dzieje się w najgorszym przypadku? Użytkownik uruchamia ponownie program i kontynuuje od momentu przerwania? Firma traci miliony dolarów?
Nie zawsze można zidentyfikować złe dane wejściowe. Możesz religijnie porównać swoje wskaźniki do zera, ale to łapie tylko jedną z 2 ^ 32 możliwych wartości, z których prawie wszystkie są złe.
Istnieje wiele różnych mechanizmów radzenia sobie z błędami. Znowu zależy to w pewnym stopniu od języka. Możesz użyć makr asercji, instrukcji warunkowych, testów jednostkowych, obsługi wyjątków, starannego projektowania i innych technik. Żaden z nich nie jest niezawodny i żaden nie jest odpowiedni dla każdej sytuacji.
Więc sprowadza się głównie do tego, gdzie chcesz nałożyć odpowiedzialność. Jeśli piszesz bibliotekę do użytku przez innych, prawdopodobnie chcesz zachować jak największą ostrożność w zakresie otrzymywanych danych wejściowych i starać się emitować pomocne błędy, jeśli to możliwe. W twoich prywatnych funkcjach i metodach możesz użyć aserts, aby wyłapać głupie błędy, ale w inny sposób nałożyć odpowiedzialność na osobę dzwoniącą (to ty), aby nie przekazywać śmieci.
źródło
Zdecydowanie powinno być powiadomienie, takie jak zgłoszony wyjątek. Służy jako informacja od innych programistów, którzy mogą niewłaściwie wykorzystywać kod, który napisałeś (próbując użyć go do czegoś, co nie było zamierzone), że ich dane wejściowe są nieprawidłowe lub powodują błędy. Jest to bardzo przydatne w śledzeniu błędów, podczas gdy jeśli po prostu zwrócisz null, ich kod będzie kontynuowany, dopóki nie spróbują użyć wyniku i uzyskać wyjątek od innego kodu.
Jeśli Twój kod napotka błąd podczas połączenia z innym kodem (być może nieudaną aktualizacją bazy danych), który jest poza zakresem tego fragmentu kodu, naprawdę nie masz nad nim kontroli, a jedynym wyjściem jest zgłoszenie wyjątku wyjaśniającego wiesz (tylko to, co mówi kod, do którego zadzwoniłeś). Jeśli wiesz, że niektóre dane wejściowe nieuchronnie doprowadzą do takiego wyniku, po prostu nie możesz zawracać sobie głowy wykonaniem kodu i zgłosić wyjątek stwierdzający, które dane wejściowe są nieprawidłowe i dlaczego.
W przypadku uwagi bardziej związanej z użytkownikiem końcowym najlepiej jest zwrócić coś opisowego, ale prostego, aby każdy mógł to zrozumieć. Jeśli klient zadzwoni i powie „program się zawiesił, napraw go”, masz dużo pracy, aby wyśledzić, co poszło nie tak i dlaczego, i masz nadzieję, że uda ci się odtworzyć problem. Właściwe postępowanie z wyjątkami może nie tylko zapobiec awarii, ale także dostarczyć cennych informacji. Wywołanie klienta z informacją: „Program wyświetla mi błąd. Mówi, że„ XYZ nie jest poprawnym wejściem dla metody M, ponieważ Z jest zbyt duży ”, lub coś takiego, nawet jeśli nie mają pojęcia, co to znaczy, ty dokładnie wiedzieć, gdzie szukać. Dodatkowo, w zależności od praktyk biznesowych Twojej / Twojej firmy, może to nawet nie być naprawienie tych problemów, więc najlepiej zostawić im dobrą mapę.
Krótka wersja mojej odpowiedzi jest taka, że Twoja pierwsza opcja jest najlepsza.
źródło
Walczyłem z tym samym problemem podczas przechodzenia przez uniwersytecką klasę programowania. Pochyliłem się w stronę strony paranoicznej i zwykle sprawdzam wszystko, ale powiedziano mi, że to niewłaściwe zachowanie.
Uczono nas „Projektowanie na podstawie umowy”. Nacisk kładziony jest na to, aby warunki wstępne, niezmienniki i warunki końcowe były określone w komentarzach i dokumentach projektowych. Jako osoba wdrażająca moją część kodu, powinienem zaufać architektowi oprogramowania i wzmocnić go, postępując zgodnie ze specyfikacjami zawierającymi warunki wstępne (jakie dane wejściowe muszą obsługiwać moje metody i jakie dane wejściowe nie będę wysyłał) . Nadmierne sprawdzanie w każdym wywołaniu metody powoduje wzdęcia.
Podczas iteracji kompilacji należy stosować twierdzenia w celu weryfikacji poprawności programu (sprawdzanie warunków wstępnych, niezmienników, warunków końcowych). Twierdzenia zostałyby wówczas wyłączone w kompilacji produkcyjnej.
źródło
Używanie „asercji” jest sposobem na powiadomienie innych programistów, że robią to źle, oczywiście tylko metodami „prywatnymi” . Włączanie / wyłączanie ich to tylko jedna flaga do dodania / usunięcia w czasie kompilacji i dlatego łatwo jest usunąć twierdzenia z kodu produkcyjnego. Są też doskonałym narzędziem wiedzieć, czy jesteś w jakiś sposób robi to źle własnymi metodami.
Jeśli chodzi o weryfikację parametrów wejściowych w ramach metod publicznych / chronionych, wolę pracować defensywnie i sprawdzać parametry i zgłaszać wyjątek InvalidArgumentException lub podobny. Dlatego tu są. Zależy to również od tego, czy piszesz interfejs API, czy nie. Jeśli jest to interfejs API, a nawet więcej, jeśli jest to zamknięte źródło, lepiej zweryfikuj wszystko, aby programiści wiedzieli dokładnie, co poszło nie tak. W przeciwnym razie, jeśli źródło jest dostępne dla innych programistów, nie jest ono czarno-białe. Po prostu bądź zgodny ze swoimi wyborami.
Edycja: aby dodać, że jeśli spojrzysz na przykład na Oracle JDK, zobaczysz, że nigdy nie sprawdzają „zerowej” i nie powodują awarii kodu. Ponieważ i tak będzie generować wyjątek NullPointerException, po co zawracać sobie głowę sprawdzaniem wartości NULL i zgłaszaniem jawnego wyjątku. To chyba ma sens.
źródło