Posty oznaczone etykietą programowanie

Prosty automat skończony (FSM) w Python

Na GitHub i PyPi wrzuciłem implementację prostego automatu skończonego https://github.com/marcinn/dsm

Instalacja

pip install dsm

Przykład 1: Stany zamówienia w sklepie

Załóżmy, że mamy do oprogramowania automat stanów zamówienia w sklepie:

Deklaracja

Wystarczy zdefiniować przejścia za pomocą listy krotek ([stan], [wartość], [nowy stan]), aby automat spełniał swoją rolę.

Deklaracja automatu z przykładu wygląda następująco:

import dsm

class OrderFSM(dsm.StateMachine):
    class Meta:
        transitions = (
            ('new', 'accept', 'accepted'),
            ('new', 'cancel', 'cancelled'),
            ('accepted', 'mark_ready', 'ready'),
            ('accepted', 'cancel', 'cancelled'),
            ('ready', 'send', 'sent'),
            ('ready', 'cancel', 'cancelled'),
            ('sent', 'finalize', 'finalized'),
        )
        initial = 'new'

Symulacja

Kod symulacji:

fsm = OrderFSM()

print 'State: `%s`, trying to accept' % fsm.state

fsm.process('accept')
print 'State: `%s`' % fsm.state

print "Can I send order now? - %s." % ('Yes' if fsm.can('send') else 'No')
print 'Marking as ready.'
fsm.process('mark_ready')

print "Can I send order now? - %s." % ('Yes' if fsm.can('send') else 'No')

fsm.process('send')
print "Order is %s" % fsm.state

print "Trying to cancel order..."
fsm.process('cancel')

Wynik:

State: `new`, trying to accept
State: `accepted`
Can I send order now? - No.
Marking as ready.
Can I send order now? - Yes.
Order is sent
Trying to cancel order...
Traceback (most recent call last):
  File "bin/python", line 78, in <module>
    exec(compile(__file__f.read(), __file__, "exec"))
  File "dsmtest.py", line 39, in <module>
    fsm.process('cancel')
  File "/home/marcin/src/projekty/archikat/eggs/dsm-0.2-py2.7.egg/dsm.py", line 97, in process
    new_state = self._transitions.execute(value, self.state)
  File "/home/marcin/src/projekty/archikat/eggs/dsm-0.2-py2.7.egg/dsm.py", line 46, in execute
    raise UnknownTransition('Can not find transition for `%s` in state `%s`' % (value, current_state))
dsm.UnknownTransition: Can not find transition for `cancel` in state `sent`

Użycie z Django

Moduł nie ma żadnych zależności od Django, ale można go łatwo zintegrować z modelem. Na przykład tak:

class Order(models.Model):
    status = models.CharField(max_length=32)

    def change_status(self, operation):
        # process() zwraca nowy stan
        # nowa instancja OrderFSM() zapewnia prawidłowy stan początkowy
        self.status = OrderFSM(initial=self.status).process(operation) 

Wywołanie zmiany stanu zamówienia może być następujące:

order = Order.objects.get(pk=666)

try:
    order.change_status('send')
except OrderFSM.UnknownTransition:
    print "Buuu... :("

Inne zastosowania

W module dsm.py zawarłem przykład automatu sumującego wprowadzane na wejściu cyfry jako przykład użycia w innych celach niż tylko manipulacja stanami jakichś tam dokumentów. DSM emituje zdarzenia i dzięki możliwości rejestrowania callbacków można zbudować coś znacznie ciekawszego.

Przykład 2: Parzysta ilość zer w ciągu

Jako kolejny przykład zaimplementowałem automat sprawdzający, czy liczność zer w ciągu wejściowym jest parzysta, opisany na Wikipedii.

import dsm

class ParityChecker(dsm.StateMachine):
    class Meta:
        transitions = (
                ('even', '0', 'odd'),
                ('even', '1', 'even'),
                ('odd', '1', 'odd'),
                ('odd', '0', 'even'),
            )
        initial = 'even'


pc = ParityChecker()


while True:
    pc.reset()

    digits = raw_input('Podaj ciag zlozony z zer i jedynek: ')

    if not digits:
        break

    pc.process_many(digits)

    print "%sparzysta ilos zer (%s)" % ('Nie' if pc.state=='odd' else '',
            digits.count('0'))

REST po ludzku

Znalazłem ostatnio dwa ciekawe wpisy traktujące o architekturze REST. Pierwszy z nich jest autorstwa niezastąpionego Martina Fowlera, który ciekawie przedstawił drogę od prostych usług typu RPC aż po podstawy RESTful (ale nie samego REST-a). Zdecydowanie ułatwia przejście od jednej do drugiej koncepcji: http://martinfowler.com/articles/richardsonMaturityModel.html

Z kolei drugą perełką jest wpis Ryana Tomayko, programisty GitHuba i Heroku, który wytłumaczył idee RESTful (wirtualnej?) żonie. Niestety środowisko gender zaatakowało Ryana i autor usunął artykuł-wywiad, ale na szczęście (w tym wypadku) Internet zachował jego kopie. Znajdziecie takową m.in. na archive.org: http://web.archive.org/web/20130116005443/http://tomayko.com/writings/rest-to-my-wife

Miłej lektury!

Międzynarodowy maraton programistyczny Deadline24

Deadline24 to międzynarodowy maraton programistyczny organizowany przez firmę Future Processing, który polega na rywalizacji trzyosobowych drużyn zmagających się z zadaniami algorytmicznymi. Już po raz siódmy pasjonaci informatyki będą mieć szansę wykazać się swoją wiedzą, kreatywnością oraz wytrwałością, pracując nad zadaniami konkursowymi przez 24 godziny. Rejestracja trzyosobowych drużyn potrwa do 26 lutego 2015 roku. Zapisy odbywają się za pośrednictwem formularza na stronie https://deadline24.pl/rejestracja/. Eliminacje rozpoczną się 1 marca 2015 roku punktualnie o 9.00. Przez 5 godzin zegarowych drużyny będą zmagać się z rozwiązywaniem zadań i generowaniem odpowiedzi, ocenianych przez serwer sprawdzający. Ten etap przebiega zdalnie.

Do finału zapraszane są najlepsze drużyny spośród tych, które wzięły udział w eliminacjach. Finał odbędzie się w dniach 7-8 kwietnia 2015 roku i potrwa nieprzerwanie przez 24 godziny. Wyróżnikiem maratonu Deadline24, poza nietypową formułą zadań konkursowych, których głównymi bohaterami są żukoskoczki, jest organizowanie go w miejscach wpisujących się w tradycję województwa śląskiego. Edycja 2012 i 2013 miała swój finał w Zabytkowej Kopalni Węgla Kamiennego Guido, 320 metrów pod ziemią, a w roku ubiegłym konkurs odbywał się w Zabytkowej Kopalni „Ludwik” w Zabrzu. Organizatorzy zapewniają, że tegoroczna edycja także zaskoczy uczestników wyjątkową oprawą oraz cennymi nagrodami!

Zeszłoroczna edycja maratonu zgromadziła uczestników z Polski, USA, Holandii, Niemiec, Wielkiej Brytanii, Estonii, Ukrainy, Rosji, Nigerii, Indii, Pakistanu, Tunezji oraz Indonezji. Zawodnicy reprezentowali między innymi Uniwersytet Warszawski, Uniwersytet Jagielloński, Carnegie Mellon University, czy National University of Lviv. Do rywalizacji stanęli również przedstawiciele takich firm jak Google, InsERT, czy Flytronic.

Organizatorem Deadline24 jest firma Future Processing, która działa na globalnym rynku oprogramowania od 2000 roku, tworząc systemy informatyczne dedykowane między innymi branży przemysłu, mediów elektronicznych, opieki medycznej, sektora finansowego oraz transportu. Firma jest Certyfikowanym Partnerem Microsoftu i posiada tytuły „Independent Software Vendor” oraz „Software Development” na poziomie Gold.

Aby być na bieżąco zapraszamy do śledzenia naszego fanpage na Facebooku https://www.facebook.com/Deadline24pl.

Źródło: inf. prasowa Future Processing

Co boli MVC

Prowadziliśmy swego czasu dyskusje o logice biznesowej i szukaliśmy rozwiązania starając się znaleźć jej miejsce w MVC. Stosując bowiem ten paradygmat logika naturalnie "rozmywa się" po kontrolerach i modelach (ekstremiści wstawiają ją nawet do widoków). Typowy przykład złego podejścia to kiepskie implementacje ORM (Propel-PHP, DjangoORM - Python):

class User(Model):
   def save(..):
      ...

W tym przypadku Model jest jednocześnie persistance managerem i wykonuje operacje, których nie powinien. Prawidłowe podejście to

class UserManager:
   def save(self, user):
       ....

Który kod łatwiej przetestować? Gdzie znajduje się logika zapisu stanu encji?

Inny przykład:

class Product(Model):
    def calculate_tax(self, tax):
       ...

vs

class TaxCalculator:
    def calculate_tax(self, product):
       ...

Jak widać w drugim podejściu to TaxCalculator zawiera logikę obliczania podatku dla produktu. Możemy ją łatwiej przetestować (bez uruchamiania całego środowiska i persistance) oraz możemy ją wymieniać wprowadzając różne kalkulatory, np. USATaxCalculator, VATTaxCalculator, etc.

Frameworki narzucają konwencje, ale nie ograniczają aż tak naszych działań. Dlatego w moich projektach, ostatnio opartych o Django, stosuję minimalny kod kontrolerów, operacje zamykam w formularzach (mimo, że nie są renderowane) lub funkcjach/metodach wykonujących proces na rzecz jakiegoś obiektu. W ten sposób modele są pozbawione metod będących operacjami na ich samych. Taki kod jest przede wszystkim łatwiej testowalny (mowa o unit tests), oraz nie wymaga uruchamiania i konfigurowania całego środowiska, co czasem bywa problematyczne i jest wolniejsze.

Jeśli już mowa o Django, to wyraźnie widać, gdzie popełniono błędy projektowe. Model już na wstępie posiada dwie operacje, których nie powinien mieć - model.save() i model.delete(). Zapis i odczyt stanu oraz usuwanie modelu powinny być wykonywane przez obiekt zewnętrzny (konkretnie persistence manager). Z kolei formularze są ściśle związane z widgetami HTML, co przy stosowaniu ich jako obiektów utility (np. do walidacji danych) pociąga za sobą niechciane zależności.

Warto zastosować się do kilku reguł:

  • ograniczyć ciało kontrolerów do wywoływania operacji sprowadzając je do roli przetwarzania żądań i generowania odpowiedzi; nie implementować w nich logiki biznesowej (tj. use cases/sekwencji)
  • każdy use case powinien mieć swoją implementację w oddzielnym bycie, co wyraźnie odseparuje warstwę logiki biznesowej
  • nie implementować operacji w klasach modeli, które zawierają logikę biznesową; tj. ograniczać do operacji modyfikujących lub odczytujących stan (w znakomitej większości pozostaną akcesory i mutatory lub po prostu properties)

Ciekawe artykuły: