Mój profesor ciągle powołuje się na ten przykład Java, kiedy mówi o „solidnym” kodzie:
if (var == true) {
...
} else if (var == false) {
...
} else {
...
}
Twierdzi, że „solidny kod” oznacza, że twój program bierze pod uwagę wszystkie możliwości i że nie ma czegoś takiego jak błąd - wszystkie sytuacje są obsługiwane przez kod i skutkują prawidłowym stanem, stąd „inne”.
Wątpię jednak. Jeśli zmienna jest wartością logiczną, jaki jest sens sprawdzania stanu trzeciego, gdy stan trzeci jest logicznie niemożliwy?
„Brak czegoś takiego jak błąd” również wydaje się śmieszny; nawet aplikacje Google wyświetlają błędy bezpośrednio użytkownikowi, zamiast połykać je po cichu lub w jakiś sposób uważać je za prawidłowy stan. I to dobrze - lubię wiedzieć, kiedy coś pójdzie nie tak. Wydaje się, że twierdzenie, iż aplikacja nigdy nie zawierałaby żadnych błędów, wydaje się słuszne.
Jaka jest zatem rzeczywista definicja „solidnego kodu”?
źródło
Odpowiedzi:
Co z
Boolean?
tym, że pozwala naNULL
stan, który nie jest ani prawdziwy, ani fałszywy. Co teraz powinno zrobić oprogramowanie? Niektóre programy muszą być bardzo odporne na awarie, takie jak rozruszniki serca. Czy kiedykolwiek widziałeś, jak ktoś dodaje kolumnę do bazy danych, która byłaBoolean
i początkowo inicjuje bieżące daneNULL
? Wiem, że to widziałem.Oto kilka linków, które omawiają, co to znaczy być solidnym pod względem oprogramowania:
Jeśli uważasz, że istnieje jedna powszechnie uzgodniona definicja „solidnego” tutaj, powodzenia. Mogą istnieć synonimy, takie jak dowód na bomby lub idiotyzm. Programator taśm Duct byłby przykładem kogoś, kto zwykle pisze solidny kod, przynajmniej w moim rozumieniu terminów.
źródło
Ze względu na moją dyskusję Bool może mieć 2 stany, Prawda lub Fałsz. Wszystko inne jest niezgodne ze specyfikacją języka programowania. Jeśli Twój łańcuch narzędzi jest niezgodny ze specyfikacją, nie ma znaczenia, co robisz. Jeśli programista stworzył typ Bool, który miał więcej niż 2 stany, jest to ostatnia rzecz, jaką zrobiłby na mojej bazie kodu.
Opcja A.
Opcja B
Twierdzę, że opcja B jest bardziej niezawodna .....
Każdy twit może powiedzieć ci, żebyś obsługiwał nieoczekiwane błędy. Zazwyczaj są one bardzo łatwe do wykrycia, kiedy o nich pomyślisz. Przykład podany przez twojego profesora nie jest czymś, co mogłoby się zdarzyć, więc jest to bardzo słaby przykład.
Niemożliwe jest przetestowanie bez skomplikowanych wiązek testowych. Jeśli nie możesz go utworzyć, jak zamierzasz go przetestować? Jeśli nie przetestowałeś kodu, skąd wiesz, że on działa? Jeśli nie wiesz, że to działa, to nie piszesz solidnego oprogramowania. Myślę, że wciąż nazywają to Catch22 (Świetny film, obejrzyj go kiedyś).
Opcja B jest trywialna do przetestowania.
Następny problem, zadaj profesorowi to pytanie: „Co chcesz, abym to zrobił, jeśli Boolean nie jest ani Prawda, ani Fałsz?” To powinno doprowadzić do bardzo interesującej dyskusji .....
W większości przypadków zrzut rdzenia jest odpowiedni, w najgorszym przypadku drażni użytkownika lub kosztuje dużo pieniędzy. Co jeśli, powiedzmy, moduł jest systemem obliczania ponownych prób w czasie rzeczywistym? Każda odpowiedź, bez względu na to, jak niedokładna, nie może być gorsza niż przerwanie, które zabije użytkowników. Co więc zrobić, jeśli wiesz, że odpowiedź może być zła, wybierz 50/50 lub przerwij i przejdź do 100% awarii. Gdybym był członkiem załogi, wziąłbym 50/50.
Opcja A zabija mnie Opcja B daje mi równą szansę na przeżycie.
Ale poczekaj - to symulacja ponownego wejścia promu kosmicznego - co wtedy? Przerwij, abyś wiedział o tym. Brzmi jak dobry pomysł? - NIE - ponieważ musisz przetestować za pomocą kodu, który planujesz wysłać.
Opcja A jest lepsza do uproszczenia, ale nie można jej wdrożyć. Jest to bezużyteczna opcja B to wdrożony kod, więc symulacja działa tak samo, jak w systemach na żywo.
Powiedzmy, że to był ważny problem. Lepszym rozwiązaniem byłoby odizolowanie obsługi błędów od logiki aplikacji.
Dalsze czytanie - maszyna Therac-25 Xray, awaria rakiety Ariane 5 i inne (Link ma wiele uszkodzonych linków, ale wystarczająca ilość informacji, które pomoże Google)
źródło
if (var != true || var != false) {
należy użyć kodu&&
.W rzeczywistości twój kod nie jest bardziej niezawodny, ale mniej odporny. Ostatecznym
else
jest po prostu martwy kod, którego nie można przetestować.W krytycznym oprogramowaniu, takim jak statki kosmiczne, martwy kod i, ogólnie rzecz biorąc, nieprzetestowany kod jest zabroniony: jeśli promień kosmiczny powoduje zdenerwowanie pojedynczego zdarzenia, które z kolei powoduje aktywację martwego kodu, wszystko jest możliwe. Jeśli SEU aktywuje część solidnego kodu, (nieoczekiwane) zachowanie pozostaje pod kontrolą.
źródło
Myślę, że profesor może mylić „błąd” i „błąd”. Solidny kod z pewnością powinien zawierać kilka błędów / żadnych błędów. Solidny kod może i we wrogim środowisku musi mieć dobre zarządzanie błędami (może to być obsługa wyjątków lub rygorystyczne testy statusu zwrotu).
Zgadzam się, że przykład kodu profesora jest głupi, ale nie tak głupi jak mój.
źródło
boolean x = something(); if (x) { x = True // make sure it's really true, ... }
Nie ma uzgodnionej definicji Robust Code , ponieważ w wielu kwestiach programistycznych jest on mniej więcej subiektywny ...
Przykład podany przez profesora zależy od języka:
Boolean
może być albo,True
alboFalse
nie ma trzeciej opcjibool
może byćtrue
,false
lub (niestety) pochodzić od wątpliwej obsady, która umieściła go w nieznanym przypadku ... To nie powinno się zdarzyć, ale może, w wyniku poprzedniego błędu.Jednak to, co radzi twój profesor, zaciemnia kod, wprowadzając zewnętrzną logikę zdarzeń, które nie powinny się zdarzyć , w środku programu głównego, więc zamiast tego wskażę ci programowanie defensywne .
W przypadku uniwersytetu możesz go nawet rozszerzyć, przyjmując strategię Design By Contract:
size
Jest liczbą pozycji nadata
liście)a
jest mniejsza niż10
)Przykład:
źródło
Podejście twojego profesora jest całkowicie błędne.
Funkcja lub odrobina kodu powinna mieć specyfikację określającą jej działanie, która powinna obejmować wszystkie możliwe dane wejściowe. Kod należy napisać, aby jego zachowanie gwarantowało dopasowanie do specyfikacji. W tym przykładzie napisałbym specyfikację dość prostą:
Następnie piszesz funkcję:
a kod spełnia specyfikację. Więc twój profesor mówi: co jeśli var == 42? Spójrz na specyfikację: mówi, że funkcja powinna zrobić to „to”. Spójrz na kod: Funkcja wykonuje to „to”. Funkcja spełnia specyfikację.
Tam, gdzie kod twojego profesora sprawia, że wszystko jest całkowicie nieufne, to fakt, że przy jego podejściu, gdy var nie jest ani prawdą, ani fałszem, wykona kod, który nigdy wcześniej nie był wywoływany i który jest całkowicie nieprzetestowany, z całkowicie nieprzewidywalnymi rezultatami.
źródło
Zgadzam się ze stwierdzeniem @ gnasher729: podejście twojego profesora jest całkowicie błędne.
Solidny oznacza, że jest odporny na uszkodzenia / awarie, ponieważ nie ma wielu założeń i jest oddzielony: jest samodzielny, samokreślący się i przenośny. Obejmuje to również możliwość dostosowania do zmieniających się wymagań. Jednym słowem, twój kod jest trwały .
Generalnie przekłada się to na krótkie funkcje, które pobierają dane z parametrów przekazywanych przez program wywołujący, oraz użycie publicznych interfejsów dla konsumentów - metod abstrakcyjnych, opakowań, pośredniczenia, interfejsów typu COM itp. - zamiast funkcji zawierających konkretny kod implementacyjny.
źródło
Solidny kod to po prostu kod, który dobrze radzi sobie z awariami. Nie więcej nie mniej.
W przypadku awarii istnieje wiele typów: niepoprawny kod, niekompletny kod, nieoczekiwane wartości, nieoczekiwane stany, wyjątki, wyczerpanie zasobów, ... Solidny kod dobrze sobie z nimi radzi.
źródło
Rozważę kod, który podałeś jako przykład programowania obronnego (przynajmniej tak, jak używam tego terminu). Częścią programowania obronnego jest dokonywanie wyborów, które minimalizują założenia dotyczące zachowania reszty systemu. Na przykład, który z nich jest lepszy:
Lub:
(W przypadku problemów z zauważeniem różnicy sprawdź test pętli: pierwsze użycie
!=
, drugie użycie<
).Teraz, w większości przypadków, obie pętle będą zachowywać się dokładnie tak samo. Jednak pierwsze (w porównaniu z
!=
) przyjmuje założenie, żei
będzie zwiększane tylko raz na iterację. Jeśli pominie wartość,sequence.length()
wówczas pętla może kontynuować się poza granice sekwencji i spowodować błąd.Można zatem argumentować, że druga implementacja jest bardziej niezawodna: nie zależy ona od założeń dotyczących zmiany treści pętli
i
(uwaga: w rzeczywistości nadal przyjmuje założenie, którei
nigdy nie jest ujemne).Aby uzasadnić, dlaczego nie chcesz przyjąć takiego założenia, wyobraź sobie, że pętla skanuje ciąg znaków i wykonuje pewne przetwarzanie tekstu. Piszecie pętlę i wszystko jest w porządku. Teraz zmieniają się twoje wymagania i decydujesz, że musisz obsługiwać znaki zmiany znaczenia w ciągu tekstowym, więc zmieniasz treść pętli tak, że jeśli wykryje znak zmiany znaczenia (powiedzmy, odwrotny ukośnik), zwiększa się,
i
aby pominąć znak bezpośrednio po wyjściu. Teraz w pierwszej pętli występuje błąd, ponieważ jeśli ostatnim znakiem tekstu jest ukośnik odwrotny, treść pętli będzie się zwiększać,i
a pętla będzie kontynuowana poza końcem sekwencji.źródło
Osobiście opisuję kod jako „solidny”, który ma ten jeden, ważne atrybuty:
Teraz przez przerwę mam na myśli albo doprowadzenie systemu do niestabilnego stanu, albo spowodowanie wyjątku UNHANDLED . Wiesz, czasami dla prostej koncepcji, możesz zrobić złożoną definicję i wyjaśnienie. Ale wolałbym proste definicje. Użytkownicy są całkiem dobrzy w znajdowaniu solidnych aplikacji. Jeśli użytkownik aplikacji wyśle Ci wiele żądań dotyczących błędów, utraty stanu, nieintuicyjnych przepływów pracy itp., Oznacza to, że coś jest nie tak z twoim programowaniem.
źródło