Często przechodzę do pozycji w moim kodzie, gdzie ciągle sprawdzam określony stan.
Chcę dać ci mały przykład: załóżmy, że istnieje plik tekstowy zawierający linie zaczynające się od „a”, linie rozpoczynające się od „b” i inne linie, a tak naprawdę chcę pracować tylko z dwoma pierwszymi rodzajami linii. Mój kod wyglądałby mniej więcej tak (używając Pythona, ale czytałbym go jako pseudokod):
# ...
clear_lines() # removes every other line than those starting with "a" or "b"
for line in lines:
if (line.startsWith("a")):
# do stuff
elif (line.startsWith("b")):
# magic
else:
# this else is redundant, I already made sure there is no else-case
# by using clear_lines()
# ...
Możesz sobie wyobrazić, że nie sprawdzę tutaj tylko tego warunku, ale może także w innych funkcjach i tak dalej.
Czy myślisz o tym jako o szumie, czy może to wnosi dodatkową wartość do mojego kodu?
coding-style
clean-code
marktani
źródło
źródło
assert()
tam, aby pomóc w testowaniu, ale poza tym jest to prawdopodobnie nadmierne. To powiedziawszy, będzie się różnić w zależności od sytuacji.elif (line.startsWith("b"))
? przy okazji, możesz bezpiecznie usunąć te otaczające nawiasy pod warunkiem, że nie są idiomatyczne w Pythonie.Odpowiedzi:
Jest to wyjątkowo powszechna praktyka, a sposobem radzenia sobie z nią są filtry wyższego rzędu .
Zasadniczo przekazujesz funkcję do metody filter wraz z listą / sekwencją, według której chcesz filtrować, a wynikowa lista / sekwencja zawiera tylko te elementy, które chcesz.
Nie znam składni Pythona (chociaż zawiera taką funkcję, jak widać w powyższym linku), ale w c # / f # wygląda to tak:
do#:
f # (zakłada, że jest niepoliczalny, w przeciwnym razie użyłby List.filter):
Tak więc, żeby być jasnym: jeśli używasz sprawdzonego kodu / wzorców, jest to zły styl. To i mutowanie listy w pamięci w sposób, w jaki wyglądasz za pomocą clear_lines (), traci bezpieczeństwo wątków i wszelkie nadzieje na paralelizm, który mógłbyś mieć.
źródło
(line for line in lines if line.startswith("a") or line.startswith("b"))
.clear_lines
jest naprawdę złym pomysłem. W Pythonie prawdopodobnie użyłbyś generatorów, aby uniknąć ładowania całego pliku do pamięci.lines
jest generowaną kolekcją.skip
,take
,reduce
(aggregate
w .NET),map
(select
w .NET), a tam więcej, ale to jest naprawdę dobry początek.Niedawno musiałem wdrożyć programator oprogramowania układowego w formacie S-record Motoroli , bardzo podobny do tego, co opisujesz. Ponieważ mieliśmy niewielką presję czasu, mój pierwszy szkic zignorowałem zwolnienia i wprowadziłem uproszczenia w oparciu o podzbiór, którego faktycznie potrzebowałem w mojej aplikacji. Z łatwością przeszedł moje testy, ale zawiódł, gdy tylko ktoś go spróbował. Nie było pojęcia, na czym polega problem. Przeszedł całą drogę, ale na końcu się nie udało.
Nie miałem więc wyboru, jak wdrożyć wszystkie zbędne kontrole, aby zawęzić problem. Po tym czasie znalezienie problemu zajęło mi około dwóch sekund.
Zajęło mi to może dodatkowe dwie godziny, aby zrobić to we właściwy sposób, ale zmarnowałem także dzień innych ludzi na rozwiązywanie problemów. Bardzo rzadko kilka cykli procesora jest wartych jednego dnia zmarnowanego rozwiązywania problemów.
To powiedziawszy, jeśli chodzi o odczytywanie plików, często korzystne jest zaprojektowanie oprogramowania do pracy z odczytem i przetwarzaniem go pojedynczo, zamiast wczytywania całego pliku do pamięci i przetwarzania go w pamięci. W ten sposób nadal będzie działać na bardzo dużych plikach.
źródło
Możesz zgłosić wyjątek w
else
przypadku. W ten sposób nie jest to zbędne. Wyjątki to rzeczy, które nie powinny się zdarzyć, ale i tak są sprawdzane.źródło
"c"
, może być mniej jasny.Przy projektowaniu na podstawie umowy zgaduje się, że każda funkcja musi wykonywać swoją pracę zgodnie z opisem w dokumentacji. Tak więc każda funkcja ma listę warunków wstępnych, czyli warunków na wejściach funkcji, a także warunków końcowych, czyli warunków wyjścia funkcji.
Funkcja musi gwarantować swoim klientom, że jeśli dane wejściowe spełniają warunki wstępne, to wynik będzie taki, jak opisano w warunkach końcowych. Jeśli co najmniej jeden z warunków wstępnych nie jest przestrzegany, funkcja może zrobić, co chce (zawiesić się, zwrócić dowolny wynik, ...). Dlatego warunki wstępne i końcowe są semantycznym opisem funkcji.
Dzięki umowie funkcja ma pewność, że jej klienci używają jej poprawnie, a klient ma pewność, że funkcja poprawnie wykonuje swoje zadanie.
Niektóre języki obsługują kontrakty natywnie lub poprzez dedykowane ramy. Dla pozostałych najlepiej sprawdzać warunki wstępne i końcowe dzięki zapewnieniom, jak powiedział @Lattyware. Ale nie nazwałbym tego programowaniem defensywnym, ponieważ moim zdaniem koncepcja ta koncentruje się bardziej na ochronie przed wkładem użytkownika (człowieka).
Jeśli wykorzystasz kontrakty, możesz uniknąć warunku nadmiarowo sprawdzonego, ponieważ albo wywoływana funkcja działa doskonale i nie potrzebujesz podwójnego sprawdzania, albo wywoływana funkcja jest dysfunkcyjna, a funkcja wywołująca może zachowywać się tak, jak chce.
Najtrudniejsze jest zatem określenie, która funkcja jest odpowiedzialna za co i ścisłe udokumentowanie tych ról.
źródło
Na początku nie potrzebujesz clear_lines (). Jeśli linia nie jest ani „a”, ani „b”, warunki warunkowe po prostu się nie uruchomią. Jeśli chcesz się pozbyć tych linii, ustaw inną w clear_line (). W tej chwili robisz dwa przejścia przez dokument. Jeśli pominiesz funkcję clear_lines () na początku i zrobisz to jako część pętli foreach, skrócisz czas przetwarzania o połowę.
To nie tylko zły styl, ale i złe obliczenia.
źródło
"a"
/"b"
. Nie mówię, że to prawdopodobne ( wyraźna nazwa oznacza, że są odrzucane), tylko istnieje możliwość, że jest to potrzebne. Jeśli ten zestaw linii będzie wielokrotnie powtarzany w przyszłości, warto je wcześniej usunąć, aby uniknąć wielu bezcelowych iteracji.Jeśli naprawdę chcesz coś zrobić, jeśli znajdziesz niepoprawny ciąg (na przykład wyjściowy tekst debugowania), powiedziałbym, że to absolutnie w porządku. Kilka dodatkowych wierszy i kilka miesięcy później, gdy przestanie działać z nieznanego powodu, możesz sprawdzić wyniki, aby dowiedzieć się, dlaczego.
Jeśli jednak można go po prostu zignorować lub wiesz na pewno, że nigdy nie otrzymasz niepoprawnego ciągu, to nie potrzebujesz dodatkowej gałęzi.
Osobiście jestem zawsze za wprowadzeniem przynajmniej danych wyjściowych śledzenia dla każdego nieoczekiwanego stanu - znacznie ułatwia życie, gdy masz błąd z dołączonym wyjściem, który mówi dokładnie, co poszło nie tak.
źródło
Nienawidzę
if...then...else
konstrukcji. Unikałbym całego problemu:źródło