Często zwracam wartość logiczną z metody używanej w wielu lokalizacjach, aby zawrzeć całą logikę wokół tej metody w jednym miejscu. Wszystkie (wewnętrzne) metody wywoływania muszą wiedzieć, czy operacja się powiodła, czy nie.
Używam Pythona, ale pytanie niekoniecznie jest specyficzne dla tego języka. Są tylko dwie opcje, o których mogę myśleć
- Zgłaszaj wyjątek, chociaż okoliczności nie są wyjątkowe, i pamiętaj, aby wychwycić ten wyjątek w każdym miejscu, w którym wywoływana jest funkcja
- Zwróć wartość logiczną, tak jak ja.
To jest naprawdę prosty przykład, który pokazuje, o czym mówię.
import os
class DoSomething(object):
def remove_file(self, filename):
try:
os.remove(filename)
except OSError:
return False
return True
def process_file(self, filename):
do_something()
if remove_file(filename):
do_something_else()
Chociaż jest funkcjonalny, naprawdę nie podoba mi się ten sposób robienia czegoś, „pachnie”, a czasami może powodować wiele zagnieżdżonych ifów. Ale nie mogę wymyślić prostszego sposobu.
Mógłbym przejść do bardziej filozofii LBYL i użyć go os.path.exists(filename)
przed próbą usunięcia, ale nie ma gwarancji, że plik nie zostanie w międzyczasie zablokowany (jest mało prawdopodobne, ale możliwe) i nadal muszę ustalić, czy usunięcie zakończyło się powodzeniem, czy nie.
Czy jest to „akceptowalny” projekt, a jeśli nie, jaki byłby lepszy sposób zaprojektowania tego?
Twoja intuicja jest prawidłowa, istnieje lepszy sposób: monady .
Co to są monady?
Monady to (parafrazując Wikipedię) sposób łączenia operacji łańcuchowych przy jednoczesnym ukrywaniu mechanizmu łączenia; w twoim przypadku mechanizmem łańcuchowym są zagnieżdżone
if
s. Ukryj że i Twój kod będzie zapach dużo ładniejszy.Jest kilka monad, które to zrobią („Może” i „Albo”) i na szczęście są częścią naprawdę ładnej biblioteki monad Pythona!
Co mogą zrobić dla twojego kodu?
Oto przykład z użyciem monady „Albo” („Niepowodzenie” w połączonej bibliotece), w której funkcja może zwrócić sukces lub niepowodzenie, w zależności od tego, co się wydarzyło:
Teraz może to nie wyglądać inaczej niż teraz, ale zastanów się, jak by to było, gdybyś miał więcej operacji, które mogłyby spowodować awarię:
W przypadku każdego
yield
sprocess_file
funkcji, jeśli wywołanie funkcji zwróci błąd, wówczasprocess_file
funkcja wyjdzie w tym momencie , zwracając wartość błędu z funkcji błędu, zamiast kontynuować przez resztę i zwracającSuccess("All ok.")
A teraz wyobraź sobie robienie powyższego z zagnieżdżonymi literami
if
s! (Jak poradziłbyś sobie z wartością zwracaną !?)Wniosek
Monady są fajne :)
Uwagi:
Nie jestem programistą Python - użyłem biblioteki monad, do której odsyłam, w skrypcie, którego ninja użyłem do automatyzacji projektu. Rozumiem jednak, że ogólnie preferowanym, idiomatycznym podejściem jest stosowanie wyjątków.
IIRC zawiera literówkę w skrypcie lib na stronie, do której prowadzi link, choć nie pamiętam, gdzie jest ATM. Zaktualizuję, jeśli pamiętam.I diff'd moją wersję na stronę i stwierdził:def failable_monad_examle():
->def failable_monad_example():
- wp
wexample
brakowało.Aby uzyskać wynik funkcji dekorowania awaryjnego (np.
process_file
), Musisz uchwycić wynik w avariable
i zrobić,variable.value
aby go uzyskać.źródło
Funkcja jest umową, a jej nazwa powinna sugerować, jaką umowę będzie wypełniać. IMHO, jeśli nadasz mu nazwę,
remove_file
powinien usunąć plik, a jego brak powinien spowodować wyjątek. Z drugiej strony, jeśli go nazwiesztry_remove_file
, powinien „spróbować” usunąć i zwrócić wartość logiczną, aby stwierdzić, czy plik został usunięty, czy nie.Prowadziłoby to do innego pytania - powinien być
remove_file
albotry_remove_file
? To zależy od Twojej strony połączeń. W rzeczywistości możesz mieć obie metody i używać ich w różnych scenariuszach, ale myślę, że usunięcie pliku jako takiego ma duże szanse powodzenia, więc wolę mieć tylkoremove_file
ten wyjątek rzucania, gdy się nie powiedzie.źródło
W tym konkretnym przypadku warto zastanowić się, dlaczego usunięcie pliku może nie być możliwe. Powiedzmy, że problem polega na tym, że plik może istnieć lub nie. Powinieneś mieć funkcję
doesFileExist()
zwracającą wartość true lub false oraz funkcję,removeFile()
która po prostu usuwa plik.W swoim kodzie najpierw sprawdzisz, czy plik istnieje. Jeśli tak, zadzwoń
removeFile
. Jeśli nie, zrób inne rzeczy.W takim przypadku nadal
removeFile
możesz zgłosić wyjątek, jeśli pliku nie można usunąć z innego powodu, na przykład uprawnień.Podsumowując, należy rzucać wyjątki na rzeczy, które są, cóż, wyjątkowe. Jeśli więc całkowicie normalne jest to, że plik, który próbujesz usunąć, może nie istnieć, nie jest to wyjątkiem. Napisz predykat boolowski, aby to sprawdzić. Z drugiej strony, jeśli nie masz uprawnień do zapisu dla pliku lub jeśli znajduje się on w zdalnym systemie plików, który nagle jest niedostępny, mogą to być bardzo wyjątkowe warunki.
źródło