wtorek, 23 marca 2010

Koszt wykorzystania wzorców projektowych

Podczas dzisiejszej prezentacji na temat Software Craftsmanship i Wzorców Projektowych, którą przeprowadziłem w ramach lubelskiego JUG padło szereg pytań odnośnie:
- kosztu posługiwania się w kodzie wzorcami
- presji czasu
- komplikacji

Jest to pewien ogólny i często powtarzający się zestaw pytań, dlatego sądzę, że warto rozwiać kilka wątpliwości i nieporozumień.

Część programistów widzi wykorzystanie wzorca jako niepotrzebną komplikację, która wprowadza dodatkowy narzut na wytworzenie kodu.

Generalnie jeżeli chodzi o poziom komplikacji to wzorce powinny redukować złożoność przypadkową oraz zwiększać czytelność złożoności właściwej. Jeżeli mamy sytuację odwrotną to wzorzec został wykorzystany niepoprawnie lub niepotrzebnie.

Odnośnie narzutu czasowego to obserwuję dziwne przeświadczenie jakoby dopisanie interfejsu czy kilku linijek kodu potrzebnych na podzielenie kodu na większą ilość pudełek (klas) było jakoś niezwykle czasochłonne;) Sam proces myślowy również nie powinien się znacząco wydłużyć. Albo wiem jaki wzorzec użyć albo nie wiem. To nie jest tak, że zastanawiam się nad tym godzinami.

Warto zwrócić uwagę na fakt, że katalog Wzorców Projektowych Ogólnego Przeznaczenia jest dosyć bogaty.

Owszem, część z nich jest dosyć wyrafinowana pod względem koncepcyjnym lub pod względem konstrukcji kodu. Przykładowo taki Visitor, Component Tree, Bridge, Proxy, Builder, Flyweight, Memento prowadzają pewien narzut, ale są to narzędzia, które wybieramy do misji specjalnych. Na pewno nie należą do codziennego wachlarza technik.

Natomiast Strategia, Stan, Dekorator, Template Method, Command to lekkie "skróty myślowe" wynikające wprost w paradygmatu Object Oriented. Nie ma w nich doprawdy nic nadzwyczajnego.

Osobiście traktuję część z nich jako "atomowe klocki", z których powinno budować się rozwiązania. Są to byty niemal tej samej klasy ciężaru co IFy, pętele czy metody. Biegłe opanowanie lekkich wzorców sprawia, że poszerzają one nasz zestaw podstawowych "klocków myślowych". Budujemy z nich najpierw mentalne modele, a później naturalnie pojawiają się w kodzie.

Operując lekkimi wzorcami na poziomie podstawowych klocków nie mamy narzutu czasowego podczas developmentu. Natomiast poziom komplikacji ulega znacznemu obniżeniu, ale tylko dla tych czytelników kodu, który posiadają taki sam zasób skojarzeń jak jego twórca - czyli operują tym samym słownictwem branżowym.

//=====================================

Potrzeba posiłkowania się wzorcami "lekkiego" kalibru wynika ze słabej siły wyrazu języków wywodzących się z C++ takich jak np Java. Przykładowo brak domknięć kompensujemy sobie strategiami. Doprawdy nie jest niczym strasznym i pracochłonnym opracowanie interfejsu z jedną metodą oraz kilku jej implementacji. Ilość kodu "biznesowego" nie ulega zwiększeniu (ew redukcji) natomiast pojawiają się jedynie dodatkowe "opakowania" dla niego w postaci zgrabnych i eleganckich klas.

Jeżeli ktoś aż tak bardzo nie pała chęcią do pisania kodu to może powinien zostać trubadurem;)

Ciężki kaliber wzorców również może czasem przydać się do emulacji brakujących elementów składni, takich jak:
- double dispatch emulowany przez wzorzec Visitor
- lazy evaluation emulowany przez wzorzec Proxy.

wtorek, 9 marca 2010

Wyginanie Javy



Dzisiejsza prezentacja Wojtka Gomoła o wyrażeniach Lambda (C# i "emulacja" w Javie) na Lubelskim JUG przypomniała mi o kilku ciekawych zabawkach, które jakiś czas temu znalazłem i zapomniałem o nich napisać.

Ciekawą cechą dynamicznych języków funkcyjnych jest:
- możliwość tworzenia konstrukcji zbliżonych do języka naturalnego
- możliwość rozszerzania standardowych, często używanych klas (zwykle kolekcji, kontenerów) o wygodne metody
- nazwijmy to w sumie roboczo "techniczny DSL"

Przykładowo takie oto sprawiające radość uproszczenia:

kolekcja.wyrzucZNullemWPolu("name").posortuj({e1.age >e2.age}).wytnijPierwszych(10);


Jak dla mnie nie ma się czym ekscytować ponieważ jest to jedynie lukier składniowy i w statycznych językach z silną kontrolą typów jest to tylko kwestia stworzenia jakiegoś starego, dobrego, proceduralnego Utilsa z interfejsem strategii komparacji:

MyAlmightyUtils28.wyrzucZNullemWPolu(kolekcja, "name")

oraz odpowiednia sekwencja wywołań.

/*Oczywiście wszyscy wiemy, że Utilsy są złe i brzydkie, ale co zrobić*/

No ale już nie będzie tak pięknie jak we wcześniejszym przykładzie z jakiegoś sexi-skryptowego-języka.


Na szczęście istnieje kilka ciekawych zabawek zaimplementowanych w Javie. Dzięki cwanym trikom na generykach i zastosowaniu odpowiedniego nazewnictwa dają nam coś więcej niż namiastkę konstrukcji znanych z dynamicznych, słabo otypowanych języków.

op4j - genialna biblioteka, która sprawia, że żmudny kod staje się bardziej czytelny i zwięzły jednocześnie zachowując typowanie.
Bardzo podstawowy przykład:

String[] emailArray = Op.on(userEmails).toArrayOf(Types.STRING).forEach().exec(FnString.toLowerCase()).get();


lambdaj - podobna zabawka ze stajni google. Można rzucić okiem również na google collections.

Mockito - nasza rodzima produkcja. Kto jeszcze nie używał, ten powinien spróbować. Warto zajrzeć choćby do kodu ponieważ stanowi on przykład wzorcowej dokumentacji. Cały manual wraz z przykładami znajduje się w JavaDocu głównej "klaski"! Wzór do naśladowania dla innych twórców bibliotek - bo na pewno nie dla developerów komercyjnych produktów;)

//==================

Czekamy na Javę 1.7 z jej nową konstrukcję closures. Ale forma w jakiej pojawi się ona w nowym wydaniu języka będzie praktycznie niczym innym niż uproszczonym wydaniem anonimowej klasy wewnętrznej. Czyżby ukłon w stronę tych hakerów, którzy nie mogą połapać się w konstrukcji anonimowych klas albo Wzorca Strategii?

No i na pewno na certyfikatach pojawią się kolejne arcyistotne pytania mające na celu sprawdzenie czy delikwent jest w stanie odróżnić: domknięcie od klasy wewnętrznej i od niekompilującego się kodu;P