Jak wyjść z awarii skończonego stanu?


14

Moje pytanie może wydawać się bardzo naukowe, ale myślę, że jest to powszechny problem i mam nadzieję, że doświadczeni programiści i programiści udzielą porady, aby uniknąć problemu, o którym wspominam w tytule. Przy okazji, to, co opisuję poniżej, to prawdziwy problem, który staram się proaktywnie rozwiązać w moim projekcie iOS, chcę go za wszelką cenę uniknąć.

Przez maszynę stanów skończonych rozumiem to> mam interfejs użytkownika z kilkoma przyciskami, kilka stanów sesji związanych z tym interfejsem użytkownika i to, co reprezentuje ten interfejs, mam dane, których wartości są częściowo wyświetlane w interfejsie użytkownika, odbieram i obsługuję niektóre zewnętrzne wyzwalacze (reprezentowane przez wywołania zwrotne z czujników). Stworzyłem diagramy stanu, aby lepiej odwzorować odpowiednie scenariusze, które są pożądane i możliwe w tym interfejsie użytkownika i aplikacji. Gdy powoli wdrażam kod, aplikacja zaczyna zachowywać się coraz bardziej tak, jak powinna. Nie jestem jednak pewien, czy jest wystarczająco solidny. Moje wątpliwości wynikają z obserwowania własnego procesu myślenia i wdrażania. Byłem pewien, że wszystko załatwiłem, ale wystarczyło, że wykonałem kilka brutalnych testów w interfejsie użytkownika i szybko zdałem sobie sprawę, że w zachowaniu nadal są luki. Poprawiłem je. Jednak, ponieważ każdy komponent zależy i zachowuje się na podstawie danych wejściowych z innego komponentu, określone dane wejściowe od użytkownika lub źródła zewnętrznego wyzwalają łańcuch zdarzeń, zmiany stanu ... itd. Mam kilka składników i każdy z nich zachowuje się tak, jak Trigger otrzymany na wejściu -> wyzwalacz i jego analizowany nadawca -> wypisuje coś (komunikat, zmianę stanu) na podstawie analizy

Problem polega na tym, że nie jest to całkowicie samowystarczalne, a moje komponenty (element bazy danych, stan sesji, stan niektórych przycisków) ... MOGĄ zostać zmienione, poddane wpływowi, usunięte lub w inny sposób zmodyfikowane, poza zakresem łańcucha zdarzeń lub pożądany scenariusz. (telefon się zawiesza, akumulator nagle się rozładowuje) Spowoduje to wprowadzenie do systemu nieważnej sytuacji, z której system NIE MOŻE BYĆ MOŻLIWY do odzyskania. Widzę to (chociaż ludzie nie zdają sobie sprawy, że to jest problem) w wielu aplikacjach konkurencji, które są w sklepie Apple, klienci piszą takie rzeczy> „Dodałem trzy dokumenty i po przejściu tam i tam nie mogę ich otworzyć, nawet jeśli je zobacz ”. lub „Nagrywam filmy codziennie, ale po nagraniu zbyt logowanego wideo nie mogę włączyć na nich napisów ... a przycisk do napisów nie

To tylko skrócone przykłady, klienci często opisują to bardziej szczegółowo .. z opisów i zachowań opisanych w nich zakładam, że konkretna aplikacja ma awarię FSM.

Tak więc ostateczne pytanie brzmi: jak mogę tego uniknąć i jak chronić system przed samymi blokowaniem?

EDYCJA> Mówię w kontekście jednego widoku kontrolera widoku przez telefon, mam na myśli jedną część aplikacji. Rozumiem wzorzec MVC, mam osobne moduły dla różnych funkcji. Wszystko, co opisuję, dotyczy jednego obszaru roboczego w interfejsie użytkownika.


2
Brzmi jak walizka do testów jednostkowych!
Michael K,

Odpowiedzi:


7

Jestem pewien, że już to wiesz, ale na wszelki wypadek:

  1. Upewnij się, że każdy węzeł na diagramie stanu ma łuk wychodzący dla KAŻDEGO legalnego rodzaju danych wejściowych (lub podziel dane wejściowe na klasy, z jednym łukiem wychodzącym dla każdej klasy danych wejściowych).

    Każdy przykład maszyny stanu, który widziałem, używa tylko jednego łuku wychodzącego dla KAŻDEGO błędnego wejścia.

    Jeśli nie ma jednoznacznej odpowiedzi na pytanie, co zrobi wejście za każdym razem, jest to albo błąd, albo brakuje innego wejścia (co powinno konsekwentnie skutkować łukiem dla wejść przechodzących do nowego węzła).

    Jeśli węzeł nie ma łuku dla jednego rodzaju danych wejściowych, jest to założenie, że dane wejściowe nigdy nie wystąpią w rzeczywistości (jest to potencjalny błąd, który nie zostałby obsłużony przez maszynę stanu).

  2. Upewnij się, że automat stanowy może odbierać lub podążać tylko JEDEN łuk w odpowiedzi na otrzymany sygnał wejściowy (nie więcej niż jeden łuk).

    Jeśli istnieją różne rodzaje scenariuszy błędów lub rodzaje danych wejściowych, których nie można zidentyfikować w czasie projektowania automatu stanów, scenariusze błędów i nieznane dane wejściowe powinny przejść do stanu z całkowicie oddzielną ścieżką oddzieloną od „normalnych łuków”.

    IE, jeśli w jakimkolwiek „znanym” stanie zostanie odebrany błąd lub nieznany, wówczas łuki zastosowane w wyniku obsługi błędów / nieznanych danych wejściowych nie powinny wracać do żadnych stanów, w których znajdowałby się komputer, gdyby otrzymywały tylko znane dane wejściowe.

  3. Po osiągnięciu stanu końcowego (końcowego) nie powinieneś być w stanie powrócić do nieterminalnego tylko do stanu początkowego (początkowego).

  4. Dla jednej maszyny stanów nie powinien istnieć więcej niż jeden stan początkowy lub początkowy (na podstawie przykładów, które widziałem).

  5. Na podstawie tego, co widziałem, jedna maszyna stanu może reprezentować tylko stan jednego problemu lub scenariusza.
    Na jednym schemacie stanów nigdy nie powinno być wielu możliwych stanów jednocześnie.
    Jeśli widzę potencjał dla wielu współbieżnych stanów, to mówi mi, że muszę podzielić diagram stanów na 2 lub więcej osobnych maszyn stanów, które mają potencjał do niezależnego modyfikowania każdego stanu.


10

Istotą skończonej maszyny stanów jest to, że ma wyraźne reguły dla wszystkiego, co może się zdarzyć w stanie. Dlatego jest skończony .

Na przykład:

if a:
  print a
elif b:
  print b

Jest nie skończony, ponieważ możemy uzyskać wejście c. To:

if a:
  print a
elif b:
  print b
else:
  print error

jest skończony, ponieważ uwzględniane są wszystkie możliwe dane wejściowe. Uwzględnia to możliwe dane wejściowe do stanu , który może być niezależny od sprawdzania błędów. Wyobraź sobie maszynę stanu o następujących stanach:

No money state. 
Not enough money state.
Pick soda state.

W zdefiniowanych stanach wszystkie możliwe dane wejściowe są obsługiwane dla włożonych pieniędzy i wybranych napojów gazowanych. Awaria zasilania występuje poza automatem stanowym i „nie istnieje”. Maszyna stanów może obsługiwać dane wejściowe tylko dla stanów, które posiada, więc masz dwie możliwości.

  1. Zagwarantuj, że wszystko jest atomowe Maszyna może mieć całkowitą utratę mocy i nadal pozostawiać wszystko w stabilnym, prawidłowym stanie.
  2. Rozszerz swoje stany, aby obejmowały nieznany problem, i poprowadź błędy do tego stanu, w którym problemy są rozwiązywane.

W celach informacyjnych artykuł wiki o automatach stanowych jest dokładny. Sugeruję także Code Complete , w rozdziałach dotyczących budowania stabilnego, niezawodnego oprogramowania.


„Moglibyśmy uzyskać dane wejściowe c” - dlatego tak ważne są języki bezpieczne. Jeśli twój typ wprowadzania to bool, możesz dostać truei false, ale nic więcej. Nawet wtedy ważne jest, aby zrozumieć swoje typy - np. Typy zerowalne, zmiennoprzecinkowe NaNitp.
MSalters

5

Wyrażenia regularne są implementowane jako skończone maszyny stanów. Tabela przejścia wygenerowana przez kod biblioteki będzie miała wbudowany stan awarii, aby obsłużyć to, co się stanie, jeśli dane wejściowe nie będą zgodne z wzorcem. Istnieje przynajmniej domniemane przejście do stanu awarii z prawie każdego innego stanu.

Gramatyki języka programowania nie są FSM, ale generatory parsera (takie jak Yacc lub bison) generalnie mają sposób na wprowadzenie jednego lub więcej stanów błędów, tak że nieoczekiwane dane wejściowe mogą spowodować wygenerowanie kodu w stanie błędu.

Wygląda na to, że Twój FSM potrzebuje stanu błędu lub stanu awarii lub ekwiwalentu moralnego, a także jawnych (w przypadkach, których się spodziewasz) i niejawnych (w przypadkach, których się nie spodziewasz) przejścia do jednego ze stanów błędu lub błędu.


Wybacz mi, jeśli moje pytanie brzmi głupio, ponieważ nie mam formalnego wykształcenia w zakresie CS i uczę się programowania od kilku miesięcy. Czy to oznacza, że ​​kiedy powiem metodę obsługi dla zdarzenia push dla przycisku, i w tej metodzie mam umiarkowanie skomplikowaną strukturę warunkującą przełączenie if-else-switch (20-30 linii kodu), że Czy zawsze powinienem jawnie obsługiwać niepożądane dane? CZY masz na myśli to na poziomie „globalnym”? Czy powinienem mieć oddzielną klasę obserwującą ten FSM, a kiedy problem się zdarzy, zresetuje wartości i stany?
Earl Grey

Wyjaśnienie parserów wygenerowanych przez Yacc lub Bison jest poza mną, ale zwykle zajmujesz się znanymi przypadkami, a następnie masz mały blok kodu dla „wszystko inne przechodzi w stan błędu lub awarii”. Kod stanu błędu / awarii wykona wszystkie resetowanie. Być może musisz mieć dodatkową wartość, która mówi, dlaczego doszedłeś do stanu awarii.
Bruce Ediger,

Twój FSM powinien mieć co najmniej jeden stan dla błędów lub wiele stanów błędów dla różnych rodzajów błędów.
whatsisname

3

Najlepszym sposobem na uniknięcie tego jest automatyczne testowanie .

Jedynym sposobem, aby mieć prawdziwą pewność co do tego, co robi Twój kod na podstawie pewnych danych wejściowych, jest ich przetestowanie. Możesz klikać w aplikacji i próbować robić rzeczy niepoprawnie, ale nie skaluje się to dobrze, aby upewnić się, że nie masz żadnych regresji. Zamiast tego możesz utworzyć test, który przekazuje złe dane wejściowe do elementu kodu i zapewnia, że ​​obsługuje go w rozsądny sposób.

Nie będzie to w stanie udowodnić, że automat stanowy nigdy nie może zostać zepsuty, ale powie ci, że wiele typowych przypadków jest obsługiwanych poprawnie i nie psuje innych rzeczy.


2

to, czego szukasz, to obsługa wyjątków. Filozofia projektowania polegająca na unikaniu pozostawania w niespójnym stanie jest udokumentowana jako the way of the samurai: zwróć zwycięstwo lub nie wróć. Innymi słowy: komponent powinien sprawdzić wszystkie swoje dane wejściowe i upewnić się, że będzie mógł je przetworzyć normalnie. Tak nie jest, powinien wystąpić wyjątek zawierający przydatne informacje.

Gdy zgłoszony zostanie wyjątek, powoduje to powiększenie stosu. Powinieneś zdefiniować warstwę obsługi błędów, która będzie wiedziała, co robić. Jeśli plik użytkownika jest uszkodzony, wyjaśnij klientowi, że dane zostały utracone, i ponownie utwórz czysty, pusty plik.

Ważną częścią jest tutaj powrót do stanu roboczego i unikanie propagacji błędów. Kiedy to zrobisz, możesz pracować nad poszczególnymi komponentami, aby były bardziej niezawodne.

Nie jestem ekspertem od celu c, ale ta strona powinna być dobrym punktem wyjścia:

http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocExceptionHandling.html


1

Zapomnij o swojej maszynie skończonej. To, co tu masz, to poważna sytuacja wielowątkowa . W dowolnym momencie można nacisnąć dowolny przycisk, a zewnętrzne wyzwalacze mogą się w każdej chwili wyłączyć. Przyciski są prawdopodobnie wszystkie na jednym wątku, ale spust może dosłownie zadziałać w tym samym momencie, co jeden z przycisków lub jeden, wiele lub wszystkie pozostałe wyzwalacze.

Co musisz zrobić, to określić swój stan w momencie, gdy zdecydujesz się działać. Zbierz wszystkie przyciski i stany wyzwalające. Zapisz je w zmiennych lokalnych . Oryginalne wartości mogą ulec zmianie za każdym razem, gdy na nie spojrzysz. Następnie działaj tak, jak masz. Jest to migawka tego, jak system wyglądał w jednym punkcie. Milisekunda później mogło to wyglądać zupełnie inaczej, ale w przypadku wielowątkowości nie ma rzeczywistego „teraz”, w którym można by się utrzymać, tylko obraz zapisany w zmiennych lokalnych.

Następnie musisz odpowiedzieć na swój zapisany - historyczny - stan. Wszystko jest naprawione i powinieneś mieć akcję dla wszystkich możliwych stanów. Nie uwzględni zmian wprowadzonych między czasem wykonania migawki a czasem wyświetlania wyników, ale takie jest życie w świecie wielowątkowości. Konieczne może być użycie synchronizacji, aby zapobiec nadmiernemu rozmyciu migawki. (Nie można być na bieżąco, ale można przyjść blisko do uzyskania całego stanu z jednym konkretnym momencie w czasie).

Przeczytaj o wielowątkowości. Musisz się wiele nauczyć. I z powodu tych wyzwalaczy nie sądzę, że można użyć wielu podanych często sztuczek, aby ułatwić przetwarzanie równoległe („Wątki robocze” i tym podobne). Nie wykonujesz „przetwarzania równoległego”; nie próbujesz użyć 75% z 8 rdzeni. Zużywasz 1% całego procesora, ale masz wysoce niezależne, wysoce interaktywne wątki i trzeba będzie dużo przemyśleć, aby je zsynchronizować i powstrzymać synchronizację przed zablokowaniem systemu.

Testuj na maszynach jedno- i wielordzeniowych; Przekonałem się, że zachowują się inaczej w przypadku wielowątkowości. Jednordzeniowe maszyny wykrywają mniej błędów wielowątkowych, ale te błędy są o wiele bardziej dziwne. (Chociaż maszyny wielordzeniowe będą kłębić się w twoim umyśle, dopóki się do nich nie przyzwyczaisz).

Ostatnia nieprzyjemna myśl: nie jest to łatwe do przetestowania. Musisz wygenerować losowe wyzwalacze i naciśnięcia przycisków i pozwolić systemowi na chwilę się uruchomić, aby zobaczyć, co się pojawi. Kod wielowątkowy nie jest deterministyczny. Coś może zawieść raz na miliard przebiegów, tylko dlatego, że opóźnienie dla nanosekundy było opóźnione. Wprowadź instrukcje debugowania (ostrożne instrukcje if, aby uniknąć 999,999,999 niepotrzebnych komunikatów), a musisz wykonać miliard uruchomień, aby uzyskać jedną przydatną wiadomość. Na szczęście w dzisiejszych czasach maszyny są naprawdę szybkie.

Przykro mi, że rzuciłem to wszystko na ciebie w początkowej fazie kariery. Mam nadzieję, że ktoś wymyśli inną odpowiedź, która obejdzie to wszystko (myślę, że istnieją rzeczy, które mogą oswoić wyzwalacze, ale nadal masz konflikt między wyzwalaczem a przyciskiem). Jeśli tak, ta odpowiedź przynajmniej da ci znać, czego brakuje. Powodzenia.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.