Powiedzmy, że mam klasę MediaPlayer, która ma metody play () i stop (). Jakiej najlepszej strategii użyć przy wdrażaniu metody zatrzymania w przypadku, gdy metoda odtwarzania nie była wcześniej wywoływana. Widzę dwie opcje: wyrzuć wyjątek, ponieważ odtwarzacz nie jest w odpowiednim stanie, lub po cichu zignoruj wywołania metody stop.
Jaka powinna być ogólna zasada, gdy nie należy wywoływać metody w pewnej sytuacji, ale jej wykonanie nie szkodzi ogólnie programowi?
exceptions
methods
state
x2bool
źródło
źródło
Odpowiedzi:
Nie ma reguły. Wszystko zależy od tego, jak chcesz, aby Twój interfejs API „czuł się”.
Osobiście w odtwarzacz muzyki, myślę, że przejście od stanu
Stopped
, abyStopped
za pomocą metodyStop()
jest całkowicie poprawny stan przejściowy. Nie ma to większego znaczenia, ale jest ważne. Mając to na uwadze, wprowadzenie wyjątku wydawałoby się pedantyczne i niesprawiedliwe. Sprawiłoby to, że API wydawałoby się społecznym odpowiednikiem rozmowy z irytującym dzieckiem w szkolnym autobusie. Utkniesz w irytującej sytuacji, ale możesz sobie z tym poradzić.Bardziej „towarzyskim” podejściem jest przyznanie, że przejście w najgorszym przypadku jest nieszkodliwe i że należy na nie pozwolić życzliwym deweloperom.
Gdyby to zależało ode mnie, pominąłbym wyjątek.
źródło
MediaPlayer.stop()
metodę, która rzuciłaIllegalStateException
zamiast godzin debugowania kodu i zastanawiała się, dlaczego do cholery „nic się nie dzieje” (tj. „To po prostu nie działa”).StopOrThrow()
brzmi okropnie. Jeśli idziesz tą alejką, dlaczego nie zastosować standardowego wzoruTryStop()
? Ponadto, ogólnie rzecz biorąc, gdy zachowanie API jest niejasne (jak większość z nich), nie oczekuje się, że programiści po prostu zgadną lub eksperymentują, powinni przejrzeć dokumentację. Dlatego tak bardzo lubię MSDN.Istnieją dwa różne rodzaje działań, które możesz chcieć wykonać:
Jednocześnie przetestuj, czy coś jest w jednym stanie i zmień to na inny.
Ustaw coś na konkretny stan, bez względu na poprzedni stan.
Niektóre konteksty wymagają jednej akcji, a inne wymagają drugiej. Jeśli odtwarzacz multimedialny, który osiągnie koniec zawartości, pozostanie w stanie „odtwarzania”, ale z zamrożoną pozycją na końcu, wówczas metoda, która jednocześnie zapewnia, że odtwarzacz był w stanie odtwarzania, ustawiając go na „zatrzymanie” stan może być przydatny, jeśli kod chciałby się upewnić, że nic nie spowodowało niepowodzenia odtwarzania przed żądaniem zatrzymania (co może być ważne, jeśli np. coś nagrywało odtwarzaną zawartość jako sposób konwersji multimediów z jednego formatu na inny) . W większości przypadków ważne jest jednak to, że po operacji odtwarzacz multimedialny jest w oczekiwanym stanie (tj. Zatrzymany).
Jeśli odtwarzacz multimediów automatycznie zatrzyma się, gdy osiągnie koniec multimediów, funkcja, która zapewnia, że odtwarzacz był uruchomiony, byłaby prawdopodobnie bardziej irytująca niż pomocna, ale w niektórych przypadkach wiedza o tym, czy odtwarzacz działał w momencie zatrzymania, mogłaby być użytecznym. Być może najlepszym podejściem do zaspokojenia obu potrzeb byłoby zwrócenie wartości funkcji wskazującej poprzedni stan odtwarzacza. Kod dbający o ten stan może sprawdzać wartość zwracaną; kod, który go nie obchodzi, może go po prostu zignorować.
źródło
bool TryStop()
ivoid Stop()
przykład kodu sprawiłby, że jest to naprawdę doskonała odpowiedź.TryStop
sugeruje, że wyjątek nie powinien zostać zgłoszony, jeśli gracz nie może zostać zmuszony do zatrzymania. Próba zatrzymania odtwarzacza, gdy jest już zatrzymany, nie jest wyjątkowym warunkiem, ale jest stanem, który może być zainteresowany dzwoniącym.isPlaying()
.Nie ma ogólnej zasady. W tym konkretnym przypadku intencją użytkownika interfejsu API jest powstrzymanie odtwarzacza przed odtwarzaniem multimediów. Jeśli odtwarzacz nie odtwarza multimediów,
MediaPlayer.stop()
może nic nie robić, a cel metody wywołującej nadal zostanie osiągnięty - media nie odtwarzają.Zgłoszenie wyjątku wymagałoby od użytkownika interfejsu API sprawdzenia, czy gracz aktualnie gra lub złapania i obsługi wyjątku. Sprawiłoby to, że interfejs API byłby większym obowiązkiem.
źródło
Wyjątki nie mają na celu zasygnalizowania, że stało się coś złego; ma to sygnalizować
Jeśli dzwoniący próbuje
Stop
uzyskać stan,MediaPlayer
który jest już w stanie zatrzymania, nie jest to problem, któregoMediaPlayer
nie można rozwiązać (może po prostu nic nie zrobić). Nie jest to problem, który, jeśli będzie kontynuowany, doprowadzi do uszkodzenia lub danych korupcja, ponieważ może być skuteczna po prostu przez nic nie robienie. I nie jest tak naprawdę problemem, że rozsądniej jest oczekiwać, że rozmówca będzie w stanie rozwiązać problem niżMediaPlayer
.Dlatego nie powinieneś zgłaszać wyjątku w tej sytuacji.
źródło
Spójrz na to w ten sposób:
Jeśli klient wywoła Stop (), gdy odtwarzacz nie gra, wówczas Stop () automatycznie zakończy się powodzeniem, ponieważ odtwarzacz jest obecnie w stanie zatrzymania.
źródło
Zasadą jest, że robisz to, czego wymaga umowa twojej metody.
Widzę wiele sposobów definiowania znaczących umów dla takiej
stop
metody. Może to być całkowicie poprawne, jeślistop
metoda nie robi absolutnie nic, jeśli gracz nie gra. W takim przypadku definiujesz swój interfejs API według jego celów. Chcesz przejść dostopped
stanu, astop
metoda robi to - po prostu przechodząc zestopped
stanu do siebie bez błędu.Czy jest jakiś powód, dla którego chcesz zgłosić wyjątek? Jeśli kod użytkownika będzie wyglądał tak na końcu:
to nie ma żadnej korzyści z posiadania tego wyjątku. Pytanie brzmi: czy użytkownikowi zależy na tym, że odtwarzacz został już zatrzymany? Czy on ma jeszcze jedno pytanie, żeby to zapytać?
Gracz może być obserwowalny i może już powiadamiać obserwatorów o pewnych rzeczach - np. Powiadamiając obserwatorów, kiedy przechodzą od
playing
dostopping
dostopped
. W takim przypadku zgłoszenie wyjątku przez metodę nie jest konieczne. Wywołaniestop
nic nie zrobi, jeśli gracz jest już zatrzymany, a wywołanie go, gdy nie zostanie zatrzymane, powiadomi obserwatorów o przejściu.w sumie zależy to od tego, gdzie skończy się twoja logika. Ale myślę, że są lepsze opcje projektowania niż rzucenie wyjątku.
Powinieneś rzucić wyjątek, jeśli dzwoniący musi interweniować. W tym przypadku nie musi, możesz po prostu pozwolić, aby gracz był taki, jaki jest i nadal działa.
źródło
Istnieje prosta ogólna strategia, która pomaga w podjęciu tej decyzji.
Zastanów się, jak zostałby obsłużony wyjątek, gdyby miał zostać zgłoszony.
Więc wyobraź sobie swój odtwarzacz muzyki, kliknięcia użytkownika zatrzymaj, a następnie zatrzymaj ponownie. Czy chcesz wyświetlić komunikat o błędzie w tym scenariuszu? Nie widziałem jeszcze takiego gracza. Czy chcesz, aby zachowanie aplikacji było inne niż kliknięcie raz zatrzymaj (np. Zaloguj zdarzenie lub wyślij raport o błędzie w tle)? Prawdopodobnie nie.
Oznacza to, że wyjątek musiałby zostać gdzieś złapany i połknięty. A to oznacza, że lepiej nie rzucać wyjątkiem, ponieważ nie chcesz podejmować innych działań .
Nie dotyczy to tylko wyjątków, ale wszelkiego rodzaju rozgałęzień: w zasadzie jeśli nie ma zauważalnej różnicy w zachowaniu, nie ma potrzeby rozgałęziania.
To powiedziawszy, nie ma żadnej szkody w definiowaniu
stop()
metody zwracaniaboolean
wartości enum lub wskazującej, czy zatrzymanie zakończyło się powodzeniem. Prawdopodobnie nigdy go nie użyjesz, ale wartość zwracaną można łatwiej i naturalnie zignorować niż wyjątek.źródło
Oto jak to robi Android: MediaPlayer
Krótko mówiąc,
stop
kiedystart
nie został wywołany, nie stanowi problemu, system pozostaje w stanie zatrzymania, ale jeśli zostanie wezwany do gracza, który nawet nie wie, w co gra, zostanie zgłoszony wyjątek, ponieważ nie ma dobrego powodu, aby zadzwonić Zatrzymaj się tam.źródło
Widzę, co może być niewielką sprzecznością w twoim oświadczeniu, które może wyjaśnić ci odpowiedź. Dlaczego nie należy wywoływać metody, a mimo to jej wykonanie nie szkodzi ?
Dlaczego metoda „nie powinna być wywoływana”? To wydaje się być narzuconym przez siebie ograniczeniem. Jeśli nie ma „szkody” ani wpływu na interfejs API, nie ma wyjątku. Jeśli metoda naprawdę nie powinna zostać wywołana, ponieważ może tworzyć niepoprawne lub nieprzewidywalne stany, należy zgłosić wyjątek.
Na przykład, jeśli podszedłem do odtwarzacza DVD i wcisnąłem „stop” przed uderzeniem w „start” i to się zawiesiło, nie miałoby to dla mnie sensu. Powinien po prostu tam usiąść lub, co gorsza, potwierdzić „zatrzymanie” na ekranie. Błąd byłby irytujący i nie byłby w żaden sposób pomocny.
Czy mogę jednak wprowadzić kod alarmu, aby wyłączyć go, gdy jest już wyłączony (wywołanie metody), co może spowodować, że przejdzie w stan uniemożliwiający prawidłowe uzbrojenie go później? Jeśli tak, wyślij błąd, chociaż technicznie „nie doszło do szkody”, ponieważ może wystąpić problem później. Chciałbym o tym wiedzieć, nawet jeśli tak naprawdę nie przejdzie do tego stanu. Wystarczy taka możliwość. To byłoby
IllegalStateException
.W twoim przypadku, jeśli maszyna stanu może obsłużyć niepoprawne / niepotrzebne wywołanie, zignoruj je, w przeciwnym razie wyrzuć błąd.
EDYTOWAĆ:
Zauważ, że kompilator nie wywołuje błędu, jeśli ustawisz zmienną dwa razy bez sprawdzania wartości. Nie zapobiega to również ponownej inicjalizacji zmiennej. Istnieje wiele działań, które można uznać za „błąd” z jednej perspektywy, ale są one po prostu „nieefektywne”, „niepotrzebne”, „bezcelowe” itp. Próbowałem podać tę perspektywę w mojej odpowiedzi - robienie tych rzeczy nie jest ogólnie brane pod uwagę „błąd”, ponieważ nie powoduje niczego nieoczekiwanego. Prawdopodobnie powinieneś podejść do problemu w podobny sposób.
źródło
Co dokładnie zrobić
MediaPlayer.play()
iMediaPlayer.stop()
zrobić - są one zdarzeń słuchaczy do danych wprowadzonych przez użytkownika lub są oni faktycznie metody, które rozpoczynają jakiegoś strumienia mediów w systemie? Jeśli są tylko słuchaczami wprowadzanymi przez użytkownika, to jest całkowicie rozsądne, aby nic nie robili (chociaż dobrym pomysłem byłoby ich gdzieś zalogować). Jednakże, jeżeli mają one wpływ na modelu Twój UI kontroluje, a następnie mogli rzucaćIllegalStateException
ponieważ gracz powinien mieć jakiśisPlaying=true
lubisPlaying=false
stan (to nie musi być rzeczywisty Boolean flagę jak napisane tutaj), a więc, gdy dzwoniszMediaPlayer.stop()
, kiedyisPlaying=false
The metoda nie może faktycznie „zatrzymać”,MediaPlayer
ponieważ obiekt nie jest w odpowiednim stanie do zatrzymania - zobacz opisjava.lang.IllegalStateException
klasa:Dlaczego wprowadzanie wyjątków może być dobre
Wydaje się, że wiele osób uważa, że wyjątki są złe, ponieważ nigdy nie należy ich rzucać (por. Joel w sprawie oprogramowania ). Powiedzmy jednak, że masz
MediaPlayerGUI.notifyPlayButton()
wywoływanieMediaPlayer.play()
, któreMediaPlayer.play()
wywołuje wiele innych kodów i gdzieś poniżej łączy się z np. PulseAudio , ale ja (programista) nie wiem, gdzie, ponieważ nie napisałem całego tego kodu.Pewnego dnia podczas pracy nad kodem klikam „zagraj” i nic się nie dzieje. Jeśli
MediaPlayerGUIController.notifyPlayButton()
coś loguje, przynajmniej mogę zajrzeć do logów i sprawdzić, czy kliknięcie przycisku zostało zarejestrowane ... ale dlaczego nie gra? Powiedzmy, że coś jest nie tak z opakowaniami PulseAudio, któreMediaPlayer.play()
wywołują. Ponownie klikam przycisk „Odtwórz” i tym razem otrzymujęIllegalStateException
:Patrząc na ślad stosu, mogę zignorować wszystko przed
MediaPlayer.play()
debugowaniem i poświęcić czas na zastanawianie się, dlaczego np. Nie jest przekazywana wiadomość do PulseAudio w celu uruchomienia strumienia audio.Z drugiej strony, jeśli nie wyrzucisz wyjątku, nie będę miał nic do pracy poza: „Głupi program nie odtwarza żadnej muzyki”; Chociaż nie jest tak zły, jak jawne ukrywanie błędów, takie jak faktyczne połykanie wyjątku, który został faktycznie zgłoszony , wciąż potencjalnie marnujesz czas innych, gdy coś pójdzie nie tak ... więc bądź miły i rzuć im wyjątek.
źródło
Wolę zaprojektować interfejs API w sposób, który utrudnia lub uniemożliwia konsumentowi popełnienie błędów. Na przykład zamiast
MediaPlayer.play()
iMediaPlayer.stop()
możesz podać,MediaPlayer.playToggle()
które przełącza między „zatrzymaniem” a „graniem”. W ten sposób metoda jest zawsze bezpieczna do wywołania - nie ma ryzyka przejścia w stan nielegalny.Oczywiście nie zawsze jest to możliwe lub łatwe do zrobienia. Podany przykład jest porównywalny z próbą usunięcia elementu, który został już usunięty z listy. Możesz być albo
if (list.contains(x)) { list.remove(x) }
, potrzebujesz tylkolist.remove(x)
). Ale może również ukrywać błędy.Jeśli wywoływanie,
MediaPlayer.stop()
gdy jest już zatrzymane, nie ma żadnej szkody w twojej aplikacji, pozwoliłbym mu działać cicho, ponieważ upraszcza kod i mam coś do idempotentnych metod. Ale jeśli jesteś absolutnie pewien,MediaPlayer.stop()
że nigdy nie zostanie wywołany w tych warunkach, zwrócę błąd, ponieważ może być błąd w innym miejscu kodu, a wyjątek pomoże go wyśledzić.źródło
playToggle()
łatwiejszy w użyciu: Aby osiągnąć pożądany efekt (grający lub nie grający), musisz znać jego obecny stan. Tak więc, jeśli dostaniesz od użytkowników prośbę o zatrzymanie odtwarzacza, najpierw musisz zapytać, czy gracz aktualnie gra, a następnie przełączyć jego stan, czy nie, w zależności od odpowiedzi. Jest to o wiele bardziej kłopotliwe niż zwykłe wywoływaniestop()
metody i wykonywanie jej.playToggle
byłoby bardzo kłopotliwe w użyciu. Ale jeśli użytkownik otrzymałby również przycisk przełączania,playToggle
byłby łatwiejszy w użyciu niż odtwarzanie / zatrzymywanie.