Przewrotnie nadałem temu wpisowi tytuł nawiązujący do książki Christophera Alexandra, gdyż od niego zaczęła się ta cała zabawa i chyba już nigdy się nie skończy.
Trudno powiedzieć czy Alexander odkrył naturę powtarzalności czy też tylko uświadomił ludziom to, co czynili od zawsze.
Architektura oprogramowania
Jak na złość koncept powtarzalnych wzorców zyskał największy posłuch wśród inżynierów oprogramowania. Gdy się zastanowić, to są ku temu ważkie powody. Po pierwsze znakomita większość projektów programistycznych przekracza budżet i/lub założone ramy czasowe. Po drugie tworząc milion razy ten sam system można to zrobić na milion różnych sposobów. Naturalnie powstaje pytanie: który ze sposobów jest najlepszy? Bez wahania można odpowiedzieć, że ten który najlepiej spełnia założone kryteria będące w tym przypadku funkcją oceny produktu. Utożsamienie wspomnianej funkcji oceny oprogramowania z, tylko i wyłącznie, wymaganiami użytkowników jest poważanym uproszczeniem. Rzeczona funkcja ma wiele składowych: wymagania użytkowników, założony koszt, ramy czasowe, oczekiwania każdego z członków zespołu, itd. Jedne z nich są specyfikowane jawnie, inne manifestowane zupełnie nieświadomie. Mamy zatem pewne kryterium ewaluacji sposobu programowania: końcowy produkt ma być optymalny ze względu na przyjętą funkcję oceny.
Wzorce projektowe
Szansę na optymalizację funkcji oceny oprogramowania dostrzeżono właśnie w konceptach Christophera Alexandra. Założenia były proste: wiele systemów jest do siebie podobnych, podobieństwo dotyczy różnych poziomów abstrakcji i różnych części systemu. Dodatkowo systemy tworzone są za pomocą języka programowania. Stąd pytanie: czy istnieją takie konstrukcje językowe, które w optymalny sposób rozwiązują problemy związane z programowaniem?
Wspomniane problemy dotyczą sposobu w jaki współpracują obiekty w systemie w celu realizacji określonego zdania. (Część zagadnień opisałem w artykule Organizowanie logiki biznesowej.) W 1995 światło dzienne ujrzała książka Design Patterns katalogująca 23 wzorce projektowe dotyczących inżynierii oprogramowania. Niezwykle ważną rzeczą, prócz samych wzorców, którą wniosła wspomniana publikacja, był aparat pojęciowy umożliwiający rozpoznawanie oraz klasyfikowanie nowych pojawiających się wzorców projektowych. Od tego momentu nastąpiła gwałtowna eksploracja tego tematu przez różnych autorów.
Design Patterns została napisana przez czterech autorów i przyjęło się określać ich jako Gang of Four (GoF), natomiast wzorce przez nich skatalogowane jako Wzorce GoF.
Ich charakterystyczną cechą jest, że dotyczą konstrukcji programistycznych używanych do poprawy jakość tworzonego kodu. Wzorce te dotyczą współpracy obiektów w obrębie jednego systemu.
Umiejętne posługiwanie się tymi wzorcami jest absolutnym minimum kompetencji programisty i bazą dzięki której może on efektywnie korzystać z kolejnych technologii. Wzorce GoF są podstawą piramidy, na której opiera się praca i rozwój programisty. Istnieje możliwość wzięcia udziału w Weekendowych Warsztatach z Wzorców GoF, o których wspominałem w poprzednim wpisie.
Klienci stawiali przed programistami coraz większe wymagania. Systemy informatyczne musiały mieć coraz większą funkcjonalność, stawały się coraz większe. Dało to impuls do rozwoju technologii takich jak J2EE czy .NET. Technologie dostarczają wiele możliwości, czasem zbyt wiele,...technologie bywają niedoskonałe. W przypadku J2EE te niedoskonałości i problemy z użytkowaniem spowodowały powstawanie wzorców projektowych w odniesieniu do tej konkretnej technologii, czego egzemplifikacją stała się przełomowa publikacja Core J2EE Patterns.
Z drugiej strony skala systemów informatycznych, niezależnie od użytej technologii rodziła problemy, których nie rozwiązywały klasyczne Wzorce GoF (GoF, jak pamiętamy, dotyczyły przede wszystkim efektywnej współpracy obiektów pomiędzy sobą w obrębie systemu). Zastosowanie Wzorców GoF w dużym systemie, rzeczywiście ułatwiało pracę i czyniło go bardziej elastycznym, lecz już sama jego rozległość wymagała dodatkowego podejścia - podejścia globalnego, podejścia od strony architektury całego systemu. System miał wykonywać pewne zadania, prezentować dane użytkownikowi i trwale przechowywać wyniki swoich działań. Rozwinęły się: koncepcja warstw w systemie informatycznym oraz pomysły na udostępnianie danych klientom oraz na ich trwałe przechowywanie. Klasyką w tym obszarze jest publikacja Martina Fowlera Patterns of Enterprise Application Architecture, w której autor opisuje wzorce projektowe w aplikacjach klasy enterprise. Pozycja ta daje globalny pogląd na architekturę systemów informatycznych. Wzorce tam opisywane, na własny użytek i dla odróżnienia od GoF, nazywam Wzorcami Architektonicznymi. Warto nadmienić, że Martin Fowler przy gotowuje kolejną publikację na temat Wzorców Architektonicznych. Nie wiadomo kiedy się ona ukaże, jednak z postępem prac można zapoznać się na blogu Fowlera.
Problemy związane z architekturą aplikacji, z użytkowaniem technologii J2EE sprowokowały co najmniej trzy bardzo wartościowe następstwa. Po pierwsze: "stworzyły" środowisko sprzyjające rozwojowi wzorców projektowych. Po drugie: jako implementacja konkretnych Wzorców Architektonicznych powstały frameworki takie jak: Struts, Hibernate, JDO, Spring Framework, WebWork, iBatis i wiele innych. Po trzecie: w konsekwencji popularności frameworków rozpoczął się proces standaryzacyjny JCP, który zaowocował specyfikacją JEE5.
Dalszy rozwój systemów informatycznych uświadomił wszystkim zaangażowanym, że żadna z istniejących technologii jest w jakiś szczególny sposób uprzywilejowana. Jakkolwiek by nie argumentować każda będzie miała swoich zwolenników i przeciwników. Można się z tym poglądem nie zgadzać, ale nie sposób zaprzeczyć, że istnieje jego namacalna konsekwencja: w strukturze informatycznej przedsiębiorstwa działają systemy różnego typu stworzone w różnych technologiach. I choć najbardziej popularne rozwiązania dostarczają sposobów na obsługę całego infrastruktury informatycznej, to jednak każde przedsiębiorstwo ma swoją historię i nikt przy zdrowych zmysłach nie będzie zmieniał sprawnie działającego systemu tylko dlatego, że został napisany w Fortranie a nie w Javie.
Zatem każdy nowy system musi sprawnie komunikować się z istniejącą infrastrukturą. Wymaganie to spowodowało odkrywanie wzorców dotyczących integracji systemów informatycznych. Tę gałąź wzorców projektowych nazywam Wzorcami Integracyjnymi, a jako programową publikację można podać Enterprise Integration Patterns napisaną przez Gregora Hohpe'a i Bobbyego Woolfa.
Poszukiwanie pewnej powtarzalności w tworzeniu systemów informatycznych, mające na celu optymalizowanie określonej wcześniej funkcji oceny, pojawia się na każdym etapie złożoności nie zależnie od tego, czy jest to oprogramowanie desktopowe czy system obsługujący wielką korporację.
Okazuje się, że stosowanie konstrukcji architektonicznych to jedna część układanki. Pracując w różnych projektach programiści wypracowali swego rodzaju wiedzę plemienną, która pomaga im w pracy. Zaznaczam, że nie chodzi tutaj o wzorce projektowe, które rozwiązują konkretne problemy, chodzi raczej o sposób posługiwania się wzorcami w możliwie efektywny sposób.
Odkrycie strategii postępowania programistów wiąże się ze spostrzeżeniem, iż kod źródłowy jest częściej czytany niż pisany. Zatem sposób pisania kodu, konwencje, standardy, w tym sposób używania wzorców projektowych jest kolejnym obszarem, w którym można poszukiwać powtarzalności.
Doświadczony programista przechodząc z projektu do projektu będzie przenosił pewne pomocne nawyki mające ułatwić mu tworzenie oprogramowania. Dalej, doświadczony lider będzie dbał o to, aby uspójniać te nawyki wśród zespołu. Po co? Celem jest tu efektywna komunikacja. Jeden programista sprawniej pracuje posługując się sposobem kodowania który zna, zespół sprawnie pracuje posługując się tym samym stylem kodowania, nowa osoba w projekcie łatwiej wdroży się do zadań jeśli będzie mogła czytać kod wg pewnych, z góry określonych, zasad.
Wzorce wyodrębnione na tym poziomie zostały zaprezentowane przez Kenta Becka w książce
Implementation Patterns.
Wzorce Implementacyjne to kolejny kamień milowy w poszukiwaniu powtarzalnych schematów podczas wytwarzania oprogramowania. Dotyczą one sposobów posługiwania się językiem oraz wzorcami projektowymi, a nie konkretnej architektury oprogramowania. Używając porównania do budownictwa: Wzorce Projektowe mówią jakich cegieł należy używać, Wzorce Implementacyjne wskazują jak najlepiej pchać taczkę.
Dalsza dekompozycja
Richard Dawkins w swojej książce Samolubny gen stawia hipotezę, że rozwój nie jest progresywny lecz ekspansywny. Nie przebiega zgodnie z odgórnie ustalonym planem lecz odbywa się poprzez najlepsze dopasowanie do sytuacji bieżącej. Jednostką rozwoju nie jest istota wysokiego rzędu np. człowiek, lecz najbardziej elementarna jednostka - gen. Gen maksymalizuje swoje korzyści w danej chwili, a rozwój osobników wyższego rzędu jest jedynie skutkiem "samolubnych" zachowań genu.
Zastanawia mnie czy ta sama zasada obowiązuje, jawnie bądź w ukrytej postaci, podczas wytwarzania oprogramowania? Bytem wyższego rzędu jest system informatyczny, lecz kto jest samolubnym genem? Obiekt, czy może...programista? Kto dąży to maksymalizowania swoich korzyści w danej chwili? Dawkinsowy gen zachowuje się samolubnie, gdyż pragnie przetrwać, przekazać swój materiał genetyczny. Rozważając programowanie obiektowe musimy zrezygnować z przypisywania obiektom cech genów, gdyż jest obiekt jest ze swej strony bierny w obszarze replikacji. A zatem programista? Czy programista jest samolubnym genem? Zaznaczam, że nie chodzi to celowe działanie na szkodę, lecz o nieświadomy mechanizm funkcjonowania. Jeśli programista spełnia funkcję genu, to materiałem genetycznym musi być kod, który jest przez niego pisany!
Końcowy produkt jest zatem wypadkową: wymagań klienta, budżetu, ram czasowych oraz samolubności programisty. O ile z zewnątrz, w działaniach objawiających się bezpośrednio użytkownikowi, pierwsze cztery czynniki mają znaczącą rolę, o tyle wewnątrz systemu największe znaczenie ma samolubność programisty. Na czym miałaby ona polegać? Na przekazaniu jak największej ilości swoich koncepcji, rozwiązań, pomysłów do kodu systemu. Z kolei koncepcje te zależą od indywidualnych predyspozycji, umiejętności stosowania Wzorców Projektowych i Implementacyjncych,...humoru, itd. Stąd najkorzystniej dla projektu jest dbać o jakość przekazywanych koncepcji dbając o rozwój programistów.
Wspomniany Chirstopher Alexander w książce Timeless Way of Building przedstawia, podobny w idei, koncept, który nazywa porządkiem naturalnym.
(Przykład za Christopher Alexander czyli w poszukiwaniu doskonałości) Wyobraźmy salę wypełnioną krzesłami w uporządkowany sposób. Chcąc umieścić na sali jak najwięcej osób, można identyczne krzesła ustawić równo w rzędach optymalnie wypełniając salę. Na sali zmieści się wiele osób, ale nie wszystkim będzie wygodnie.
Z drugiej strony można poprosić osoby aby przyszły z własnymi krzesłami i usiadły możliwie najbliżej siebie. W tej sytuacji na sali będą krzesła różnego typu: duże i małe, odległości między nimi będą różne - w zależności od potrzeb siedzących, lecz wszyscy będą się starali, aby na sali zmieściło się najwięcej osób. Takie uporządkowanie Alexander nazywa porządkiem naturalnym. Charakteryzuje się on tym, każda osób ma tyle miejsca ile potrzebuje oraz występuje sprzężenie zwrotne z całością systemu (chęć zmieszczenia jak największej ilości osób). Charakterystyczną cechą porządku naturalnego jest to, że za każdym razem będzie przebiegał według tych samych reguł i za każdym razem efekt końcowy będzie nieco inny od poprzedniego.
W obu przedstawionych koncepcjach kształt ostatecznego systemu jest sterowany przez działania programisty. Alexander dokłada jednak jedną drobną, acz kluczową rzecz: sprzężenie zwrotne.
Można zatem wnioskować, że jeżeli każdy programista ma dokładną świadomość ostatecznego celu, ostatecznego kształtu systemu i dbając przekazywanie własnych koncepcji w kodzie, weźmie pod uwagę to sprzężenie zwrotne, to jakość wytworzonego oprogramowania wzrośnie.
Koncept sprzężenia zwrotnego uwidacznia się w programowaniu między innymi w takich podejściach jak Continuous Integration, Continuous Testing, czy Test-Driven Development. Całość podejścia do wytwarzania oprogramowania wywiedzionego z porządku naturalnego uwidacznia się w metodykach Agile.
Należy podkreślić jednak niezwykle istotną rzecz. We wspomnianym przykładzie nt. porządku naturalnego, to ludzie przynosili własne krzesła i ustawiali je optymalnie do własnych potrzeb, biorąc pod uwagę całość systemu. Również w podejściu Agile ciężar odpowiedzialności przeniesiony został na programistę. Już nie procedury sterują projektem, lecz ludzie. Wymaga to od programisty wiele uwagi, dojrzałości, a przede wszystkim odpowiedzialności. Wymaga również kompetencji innego rodzaju niż biegłość techniczna w stosowaniu określonych technologii, Wzorców Projektowych, czy Implementacyjnych.
Poza kodem źródłowym
W poszukiwaniu powtarzalności podczas wytwarzania oprogramowania przeszliśmy ścieżkę od Wzorców GoF, poprzez Wzorce Architektoniczne, Implementacyjne, aż do powtarzalnych procesów Agile, gdzie programista tworząc kod nieustannie bierze pod uwagę końcowy kształt systemu.
Każdy programista wie, że nauka wymienionych rzeczy zabiera wiele czasu. Moim zdaniem minimum 2 lata, a to i tak w odpowiednich warunkach. Jednym zabiera to wspomniane 2 lata, innym dłużej, a może i krócej, wielu zniechęca się po drodze. Lecz ci, którym się udało zaczęli programować w inny sposób. Jaka jest zatem różnica pomiędzy stanem początkowym, a końcowym w ścieżce rozwoju programisty i gdzie tej różnicy poszukiwać?
Z pewnością różnica nie polega na znajomości wzorców, bo wiedza teoretyczna może nie zmienić się przez ten czas. Ale przecież różnica istniej! Widać ją w sposobie kodowania, w sposobie podejścia do problemu, w jakości wytwarzanego oprogramowania.
Być może chodzi o to, że taki programista czuje programowanie, myśli poprzez wzorce, widzi je i potrafi zastosować w praktyce. Następuje zatem zmiana w sposobie myślenia (o pewnych jej aspektach pisałem w artykule Metaprogramy w tworzeniu oprogramowania).
Skoro programista czujący programowanie ma pewien nowy sposób myślenia, a jest wielu takich programistów, to można wnioskować, że istnieje pewien zbiór sposobów postępowania czy myślenia, charakterystyczny dla tych ludzi. Z pewnością każdy robi to w nieco inny sposób, ale skoro potrafią otrzymywać identyczne wyniki w postaci fantastycznie napisanego kodu, to jest jakaś część wspólna tych strategii. Zatem istnieją pewne powtarzalne wzorce, coś jak najlepsze praktyki skutecznych programistów, które charakteryzują ludzie osiągających sukcesy w naszej branży.
Odkrycie tych wzorców jest moim zdaniem kolejnym wyzwaniem przed inżynierią oprogramowania, kolejnym etapem odkrywania i stosowania optymalnych metod pracy.
Kolejne kroki usprawniania pracy wiodą od udoskonalania narzędzi i metod do rozwoju programisty. Tak już jest, że jakkolwiek skomplikowany by system nie był, w centralnym punkcje i tak znajduje się człowiek.