W kodowaniu bardzo lubię redukować różne problemy do skończonej maszyny stanów. Trzymam stan w zmiennej, bazie danych bądź czymkolwiek co pasuje w danym projekcie, i w zależności od stanu poprzedniego ustalam stan następny. Najczęściej interesujący jest nie tyle stan, co moment zmiany stanów i reakcja na niego.
Najprostszym rozwiązaniem jest wywołanie funkcji A_B() przy zmianie stanu A na stan B. Działa dobrze dopóki maszyna ma kilka stanów i ograniczoną liczbę przejść, ale już przy kilkunastu stanach łatwo się pomylić i można spędzić długie godziny tropiąc błąd w FSM.
Piszę aktualnie prosty projekt z użyciem Django, i rzecz jasna używam FSM. Chciałem mieć łatwy w testowaniu kod, bo FSM rozrósł mi się już na tyle, że zaczęły się w nim pojawiać błędy. Użyłem mechanizmu sygnałów w Django.
Sygnały są niczym innym jak implementacją wzorca Obserwator - jest obiekt reprezentujący wydarzenie w systemie. Obiekt ten udostępnia mechanizm rejestracji callbacków - funkcji, które będą wywoływane jeśli wydarzenie się zdarzy - oraz metodę "odpalającą" wydarzenie, która powoduje wywołanie wszystkich zarejestrowanych callbacków - obserwatorów. W przypadku FSM stworzyłem obiekty odpowiadające przejściom między różnymi stanami, które mnie interesują, i przy zmianie stanu "odpalam" odpowiednie sygnały.
Jakie są zalety takiego podejścia? W moim przypadku, zmiana stanów odbywa się w cronjobie i powoduje wysyłanie różnych typów e-maili bądź wiadomości XMPP. Jeśli uruchamia się cronjob, podpinam zatem pod sygnały funkcje wysyłające odpowiednie maile i mam działające powiadomienia. Jeśli natomiast odpalam testy, podpinam funkcje zliczające ile razy dany sygnał został uruchomiony, a w testach ustawiam asercje nie tylko na stany, ale i na liczniki.
Powstał w ten sposób prosty, elegancki kod, w którym mogę w prosty sposób przetestować dowolny scenariusz bez obaw, że zaspamuję sobie bądź komuś skrzynkę.