Szkolenia Konferencje Publikacje Klienci Zespół
 
  Książki    Artykuły     
 
Michał Bartyzel
Metaprogramy w tworzeniu oprogramowania

Każdy krok w kierunku rozwoju własnego warsztatu pracy uświadamia, że istnieje coś poza technologią, coś co porządkuje poszczególne umiejętności i pozwala korzystać z ich jeszcze efektywniej. Odkrywanie tych zasobów jest fascynującą podróżą ku doskonałości.

Jeśli jesteś programistą albo w jakikolwiek inny sposób związany z branżą IT, to najprawdopodobniej po przeczytaniu tytułu, spodziewałeś się porcji informacji technicznych. Muszę Cię zaskoczyć, artykuł będzie o innych programach i o innym programowaniu niż sobie wyobrażasz.

Ludzie posługują się swoimi myślami w zorganizowany sposób. Dzieje się to często na poziomie nieświadomym. Gdy o czymś myślimy, w umyśle pojawiają się różnego rodzaju obrazy, odczucia czy dźwięki. Choć nie zawsze zdajemy sobie z tego sprawę, w ten właśnie sposób przetwarzamy informacje docierające z zewnątrz (i z wewnątrz).

Psycholingwistyce zawdzięczamy badania nad tą wewnętrzną organizacją codziennych doświadczeń. Okazuje się, że można wyróżnić pewne schematy, że ludzie posługują się pewnymi wzorcami zachowań, które nazwano metaprogramami. Metaprogramy zostały pogrupowane w przeciwstawne, uzupełniające się pary scenariuszy w następujący sposób:

  • Unikanie – Dążenie
  • Autorytet wewnętrzny – Autorytet zewnętrzny
  • Podobieństwa – Różnice
  • Ja – Inni
  • Informacje szczegółowe – Informacje globalne
  • Proaktywne – Reaktywne
  • Opcje – Procedury

Dla osoby skoncentrowanej na unikaniu, bodźcem do działania będzie chęć uniknięcia niepożądanych sytuacji. Przeciwnie, dla osoby posługującej się metaprogramem Dążenie, ważniejsze będzie osiągnięcie wyznaczonego celu. Czytelników zainteresowanych dokładnym poznaniem metaprogramów odsyłam do literatury.

Preferowane metaprogramy

W praktyce nigdy nie jest tak, aby dany scenariusz np. Unikanie występował w czystej formie. Jest tak przede wszystkim dlatego, że procesy myślowe są w swej naturze zbyt skomplikowane (i jeszcze wiele jest do odkrycia), by opisać je za pomocą tak prostego modelu jakim są metaprogramy. Koncept metaprogramów należy traktować zatem jako pewne przybliżenie rzeczywistości o ograniczonym zakresie stosowalności.

Używanie metaprogramu jest zależne od kontekstu (kontekstualizm), środowiska w którym znajduje się człowiek. Ktoś na co dzień skoncentrowany na Dążeniu może radykalnie zmienić sposób myślenia, gdy ucieka przed goniącym go psem – wtedy najprawdopodobniej skupi się na uniknięciu niebezpieczeństwa, bez znaczenia w jaki sposób.

Bardzo istotne jest to, że metaprogramy nie podlegają ewaluacji. Żaden z nich nie jest lepszy lub gorszy – są inne. Jedyne co można powiedzieć to, że dany scenariusz może być mniej lub bardziej użyteczny w określonej sytuacji. Zadaniem artykułu jest, przede wszystkim, pobudzenie do refleksji nad własnym stylem pracy.

Choć obserwując zachowania ludzi, można wskazać pewne przejawy każdego z metaprogramów to jednak poszczególne osoby mają osobiste preferencje. Najczęściej 1 do 3 metaprogramów dominuje, pozostałe pojawiają się sporadycznie. W dalszej części zastanowimy się, które z metaprogramów są najistotniejsze w pracy programisty.

Zbieranie wymagań

Zbieranie wymagań oraz modelowanie systemu są czynnościami o tyle ważnymi, że kierunkują dalsze prace zespołu. Prace nad projektem rozpoczynają się (powinny się rozpoczynać) od stworzenia jego wizji. Wizja, w postaci metafory, jest metacelem, do którego zmierzać będzie projekt w kolejnych fazach rozwoju. Dalej wyłania się zbiór wymagań, zestaw procesów, w których może brać udział użytkownik. Pojawia luźny koncept rozwiązania i będzie uszczegóławiany w dalszych krokach.

W opisanym wyżej procesie projektowania zauważamy, że prace odbywają się na poziomie ogólnym. Programiści/projektanci wykazują w tym miejscu tendencję do skupiania się na konkretnym rozwiązaniu. Podczas rozmowy z potencjalnym użytkownikiem, od którego należy zebrać wymagania, deweloper stara się podążać za tokiem myślowym rozmówcy. Jednocześnie zastanawia się, czy i jak konkretne wymaganie zostanie zrealizowane. Myślenie o rozwiązaniu jest jak najbardziej w porządku tyle, że nie na to jest miejsce podczas zbierania wymagań, gdy należy się skoncentrować na zrozumieniu wyobrażeń użytkownika o sposobie pracy z przyszłym narzędziem. Z tego względu zbieranie wymagań warto przeprowadzać w sesjach, np.: godzinę rozmowy z użytkownikiem, a następnie pół godziny na analizę wymagań i formułowanie dodatkowych pytań.

W artykule pt: „Klient, który wie, czego chce” rozważałem różnice w sposobie myślenia analityka i klienta. Głównym wnioskiem było to, że klienci jednak wiedzą, czego chcą. Opisują to jednak w postaci procesów, natomiast analityk widzi system jako strukturę. Podążając za procesem łatwiej dokonywać w nim zmian. Z kolei zmiany wprowadzane w strukturze wymagają przeanalizowania całej koncepcji od nowa. Podczas zbierania wymagań klient często zastanawia się nad pożądanym sposobem pracy. Próbuje różnych alternatywnych przebiegów tego samego procesu i oczekuje od analityka pomocy w wyborze najlepszego rozwiązania. Jeśli analityk nie podąża w procesie za klientem, lecz percypuje system jako strukturę, to ma nie lada kłopot. Alternatywne ścieżki procesu klienta zazwyczaj mają istotny wpływ na domniemaną strukturę systemu i/lub implementację.

Dochodzimy do kolejnego, istotnego w pracy programisty, metaprogramu: Opcje – Procedury. Najczęściej klient chce próbować różnych możliwości – jest nastawiony na opcje. Analityk powinien wyjść naprzeciw tym oczekiwaniom. Należy przypomnieć fakt, że nastawienie na opcje może nie być głównym metaprogramem klienta. Jednak w kontekście zbierania wymagań, gdy zastanawia się on nad sposobem pracy z narzędziem i analizuje różne procesy, najczęściej chce mieć możliwość wybrania najlepszego rozwiązania spośród dostępnych.

Podsumowując część o zbieraniu wymagań stwierdzamy, że w tym kontekście pracy programisty ważne jest nastawienie na Ogół oraz na Opcje. Te sposoby myślenia pozwalają pozostać na odpowiednim poziomie abstrakcji i skupić się na wizji i celu istnienia systemu zamiast brnąć w konkretne technologie i rozwiązania tylko po to, aby w następstwie późniejszych decyzji w pośpiechu się z nich wycofywać.

Programowanie

Po zebraniu wymagań i zaplanowaniu prac przychodzi czas na implementację. W chwili obecnej role projektanta oraz programisty przenikają w projekcie. Myślę, że to dobrze, gdyż można w pełni wykorzystać potencjał tkwiący w programistach. W tej sytuacji deweloperzy dostają pewne wytyczne z projektem systemu i mają dość dużą dowolność w implementowaniu poszczególnych klas oraz metod.

Implementacja złożonego systemu informatycznego, gdzie współpracuje ze sobą wiele niezależnych bytów, jest skomplikowanym zadaniem. Na tym etapie programujemy już konkretne zachowania i procesy. Od programisty oczekujemy, że będzie potrafił uzasadnić wybór tego, a nie innego rozwiązania. Dodatkowo należy przestrzegać obowiązujących w zespole konwencji kodowania, zasad projektowania, wytycznych pisania testów, itp. Nie ma tu miejsca na swobodną twórczość albo raczej ma ona miejsce drugorzędne. Ważniejsze jest przestrzeganie określonych zasad – procedur. Ujawnia się nam  kolejny scenariusz pomocny programiście: Procedury. Osoba, sprawnie posługująca się procedurami, rozważa rzeczywistość jako ciąg logicznych następstw, gdzie każde zdarzenie ma swoją przyczynę oraz konsekwencje. Intuicyjnie czujemy, że Procedury to jeden największych sprzymierzeńców programistów. Możemy być dumni z faktu, że wykorzystujemy metaprogram do tworzenia wspaniałego oprogramowania.

Warto jeszcze poruszyć jeszcze jeden aspekt programowania, istotny w kontekście rozważania metaprogramów. Mowa o abstrahowaniu. Języki obiektowe dają sposobność budowania złożonych zależności pomiędzy klasami dzięki np. dziedziczeniu, polimorfizmowi, interfejsom. Możliwości te, aż same zachęcają do ich korzystania. Istota kwestii leży w sposobie korzystania z tych udogodnień. Gdy programista implementuje daną funkcjonalność ma tendencję do tworzenia całej gamy abstrakcyjnych bytów „na wyrost”. Efekt jest taki, że w projekcie każda klasa implementuje specyficzny interfejs (nie używany nigdzie indziej) oraz posiada, do niczego nie potrzebną, klasę abstrakcyjną. Z kolei podczas używania danych klas i tak wszędzie następuje rzutowanie na obiekty konkretne. Przyczyną takiego tworzenia kodu jest „zachłyśnięcie się” paradygmatem reużywalności i elastyczności komponentów. Oczywiście paradygmat ten jest jak najbardziej słuszny, lecz sposób jego egzekwowania już nie. Błąd programisty polega na próbie zaprojektowanie stworzenia maksymalnie rozszerzalnego kodu i „zapomina” że każde rozwiązanie ma ściśle określone granice stosowalności. Programista próbuje stworzyć komponenty możliwe do zastosowania wszędzie (Golden Hammer). Popatrzmy na poniższy kod:

public interface Problem {
	//...
}
public interface Solution {
	//...
}
public interface UniversalProblemSolver {
	public Solution solve( Problem problem );
}

Właściwie każda klasa mogłaby implementować interfejs UniversalProblemSolver, prawda? Gdy tworzymy kod, który ma dostarczać określoną funkcjonalność i koncentrujemy się przede wszystkim na jego uniwersalności, zamiast na konkretnym zadaniu które ma wykonać, popadamy w opisaną wyżej pułapkę. W takiej sytuacji myślimy poprzez Ogół i tworząc abstrakcyjne byty, zmierzamy do celu wyjątkowo krętą ścieżką.

Optymalne programowanie wymaga podejścia od strony Szczegółu. Najpierw tworzymy konkretne rozwiązanie implementujące żądaną funkcjonalność. Jeśli w trakcie tworzenia kodu zajdzie potrzeba abstrahowania: wydzielenia hierarchii dziedziczenia, interfejsów – refaktorujemy kod. Praca odbywa się zatem od Szczegółu do Ogółu – w odwrotnym kierunku niż w przypadku zbierania wymagań i modelowania systemu. Ten styl programowania wymaga od deweloperów wewnętrznej samodyscypliny. Warto skorzystać z technik programowania np. Test-Driven Development, które wspomagają ten styl pracy. TDD, dzięki filozofii pisania testu funkcjonalności przed jej implementacją, zabezpiecza przed nadmiarowym „rozdmuchiwaniem” klas i dokładaniem kodu, być może użytecznego, lecz zbędnego z punktu widzenia tworzonego systemu.

Przenoszenie metaprogramów

Powyżej wyróżniłem dwa konteksty pracy programisty, w których pożądane jest posługiwanie się odmiennymi scenariuszami metaprogramów OgółSzczegół oraz OpcjeProcedury. Ta właśnie potrzeba przełączania się pomiędzy różnymi scenariuszami rodzi wiele kłopotów i frustracji. Jest tak przeważnie dla tego, że „uruchamianie” odpowiedniego metaprogramu odbywa się w sposób nieświadomy. Drugą przyczyną, jak na ironię, jest zdolność do uczenia się. Przypuśćmy, że ktoś uczy się programowania. Po jakimś czasie wykształca sobie pewien sposób myślenia i sprawnie posługuje się scenariuszami Procedury oraz Szczegół. W umyśle człowieka rodzi się skrót myślowy: „jeśli TO zadziałało w TYM przypadku, z pewnością zadziała również w TAMTYM”. Ów człowiek, za pomocą metaprogramu, dobrze działającego w jednym kontekście (praca) próbuje wykonywać działanie w zupełnie innym kontekście (np. relacje międzyludzkie). To zjawisko może prowadzić do niepożądanych skutków. Praca nad własnymi metaprogramami uwrażliwia nas na podobne sytuacje i uczy efektywnego funkcjonowania w różnych kontekstach.

Korzyści

Najważniejszą, moim zdaniem, korzyścią ze znajomości i rozwijania określonych metaprogramów jest rozszerzenie spektrum możliwych reakcji na sytuacje pojawiające się podczas prac nad projektem. Umiejętność posługiwania sposobami myślenia innymi niż nasze własne sprawia, że efektywniej współpracujemy z klientami, użytkownikami oraz z innymi członkami zespołu. Poniżej wymieniam dodatkowe korzyści:

  • pomoc w formowaniu zespołu projektowego
  • wyznacznik dla osób rekrutujących programistów
  • skrócenie czasu zbierania wymagań
  • poprawa komunikacji w zespole
  • pomoc podczas projektowania ścieżki rozwoju dla programistów

Podsumowanie

W artykule wyróżniłem metaprogramy Ogół – Szczegół oraz Opcje – Procedury jako bardzo istotne w pracy programisty. Zdaję sobie sprawę, że istnieje wiele punktów w procesie rozwoju oprogramowania, które nie zostały tu wspomniane. W obszarach tych również korzystamy ze specyficznych metaprogramów i wiedza o nich będzie nieocenioną pomocą. Dalszą eksplorację tego zagadnienia rezerwuję sobie na najbliższą przyszłość. Warto wskazać czego potrzeba opracowaniu tego typu, aby stanowiło rzetelną pomoc w pracy dewelopera:

  • kompletny opis procesu wytwarzania oprogramowania z punktu widzenia wykorzystywanych metaprogramów,
  • opis efektywnych technik pracy na każdym z etapów,
  • zestaw ćwiczeń pozwalających rozwijać pożądane metaprogramy.

Każdy krok w kierunku rozwoju własnego warsztatu pracy uświadamia, że istnieje coś poza technologią, coś co porządkuje poszczególne umiejętności i pozwala korzystać z ich jeszcze efektywniej. Odkrywanie tych zasobów jest fascynującą podróżą ku doskonałości.

Literatura

  1. Structure of Magic, R. Bandler, J. Grinder
  2. NLP, J. O'Conor, J. Seymour
  3. Style myślenia, M. Miller
  4. The Object Primer, S. Ambler
  5. Test Driven, L. Koskela
  6. http://www.racjonalista.pl/kk.php/s,4588
  7. http://eqi.org
  8. http://eq.org


 
 
Touching the Void
Radość tworzenia
oprogramowania
Pawel.Wrzesz.cz
BNS IT
Walecznych 12/7
03-916 Warszawa