niedziela, 14 lipca 2013

W stronę kultury "wartości" - część pierwsza - pojęcie kultury

Model - Tribal Lidership

Lubie, po prostu uwielbiam szczerze teorie, które starają się zgłębić pełen kontekst danego zjawiska zamiast częstować czytelników prostymi pseudo-rozwiązaniami w stylu "oto nasza niezwyciężona praktyka" albo "oto narzędzie ostatecznej chwały!".

W tym odcinku zobaczymy bardzo ciekawy model kultury a jak zwykle podczas opisywania modelu bywa, warto przypomnieć co oznacza słowo "model". Otóż (poza kolesiem reklamującym nowy model pantalonów na sklepowej wystawie) jest to zazwyczaj pewne uproszczenie rzeczywistości, które pomaga z czymś tak złożonym jak rzeczywistość pracować. A mówię o tym dlatego, że w przypadku każdego uproszczenia łatwo znaleźć jakiś jeden przypadek i trolować "niee, niee ja znam taki przypadek..." (oczywiście model staje się niezwykle nieskuteczny gdy znajdziemy więcej przypadków, które mu zaprzeczają niż go potwierdzają - patrz np. "wodospadowy model rozwoju oprogramowania" ;))

Tribal Lidership to model stworzony przez profesora Davida Logana (nie, że fakt iż jest profesorem ma jakieś znaczenie). Prowadził on razem ze swoimi naukowymi ziomkami badania w wielu firmach w okresie ładnych kilkunastu lat a tematem jego pracy były tak wyszukiwane efektywność i produktywność. To co wyróżnia jego podejście od podejść, które do tej pory spotykałem to koncentracja na ludziach a nie bezpośrednio na procesach czy narzędziach. A zwrot "tribal" nie powinien się kojarzyć z bojownikami o wolność wybrzeża kości słoniowej a jedynie jest to takie marketingowe sformułowanie dla grupy społecznej.

Model wyróżnia 5 faz rozwoju kultury, które można szybko podchwycić w obrazkach zapożyczonych poniżej. (jak ktoś chce niech przewinie na sam dół - są tam dwa ciekawe videła z objaśnieniem która faza jest co i po co)


No dobra to pojedli, popili, popatrzyli na tabelki - tera jedziemy zobaczyć..

faza 2 i 3 w naszych kochanych korporacjach

"Moje życie jest do dupy" - " ja jestem zajebisty a ty nie". - akurat te dwie fazy znakomicie koegzystują obok siebie w niektórych firmach. Np. jak często słyszymy takie oto cytaty :

  • "Pracujemy w totalnym syfie"
  • "Nikt poza mną nie martwi się o jakość kodu"
  • "Mój zespół jest niedojrzały"
  • "Gdybym tylko miał lepszych ludzi" - do tego przykrego sformułowania "moi ludzie" jeszcze wrócimy
  • "Pracujemy w chujowych technologiach" - tego akurat ja często używałem w zamierzchłych czasach
  • "{Ktoś tam} jest debilem"
  • itd

Faza dwa i trzy tak doskonale istnieją obok siebie ponieważ doskonale wpisują się w popularny w korpo model zatytułowany "chwalebny lider (i te risorsy co tam klepią w klawiature)"

I tera tak strzałka z D oznacza delegacje - tak łądnie nazywa się w korpo rozkazyweanie. Strzałka z V oznacza wartość - czyli sytuacje, w której pracownik coś faktycznie wyprodukował. Tzw. "lider" agreguje wartość i raportują ją dalej. Model zakłada, że całość kreatywności i planowania leży po jego stronie. Można tutaj wskazywać na wiele wad tego modelu (jego prostota ma też pewne pozorne zalety - pozwala stosować uproszczone modele zarządzania - resoursa łatwo przerzucić pomiędzy kratkami w excelu - nieliniowy element grupy społecznej już nie) ale tutaj skupmy się na relacjach społecznych.

Równie dobrze wspomniany model można rozrysować używając Diad - czyli najmniejszego z możliwych zbiorów społecznych.

Taki związek doskonale wpasowuje się w model "I'm great (but you are not)". Osoba obsadzona w pozycji lidera dostaje od organizacji narzędzia do wydzielania organizacyjnej wartości - pochwały, bonusy itd - osoba na drugim końcu ma zadania "wykazywać się". Jeśli wartość od kropki czerwonej to tylko wartość organizacyjna to wtedy mamy klasyczny "model najemnika", czyli zrobić to co trzeba i do domu.

Oczywiście tak nie zawsze musi być. Osoba "niebieska" może czerpać także wartość z nauki ciekawych technologii (tak to jest element świata IT, z którego zrozumieniem management wychowany na pruskich fabrykach ma ogromny kłopot). Również osoba czerwona może podzielić się wartością, którą otrzymał od organizacji - być może od czasu do czasu można usłyszeć "to nie moja zasługa to oni odwalili kawał dobrej roboty".

Innymi mniej etycznymi sposobami na złamanie syndromu "najemnika" przy zachowaniu klasycznej struktury hierarchicznej są tzw. techniki zarządzania. Tutaj mamy cały wachlarz chwytów w stylu "zapierdalaj a będziesz doceniony" - czasem to prawda a czasem nie - albo manipulowanie emocjami pracownika aby uzyskać tzw. commitmenmt. Inną cechą rozpoznawalną takich struktur jest obecność techniki w stylu "kanapki" - czyli opakowywanie gówna w małe komplementy. Mamy tutaj typowe nastawienie na manipulację emocjami słuchacza. Również w środowisku resoursowo-manipulacyjnym często pojawiają się te zwroty w stylu "moi ludzie, twoi ludzie, wasi ludzie" (autor tutaj pragnie czytelnikowi skromnie przypomnieć, że niewolnictwo jest ustawowo zakazane - chyba)

Oczywiście taki model przynosi rezultaty. Znam kilka firm, które doskonale sobie radzą finansowo kultywując wspomniane struktury. Ich koncept organizacyjny zazwyczaj polega na zatrzymaniu "rdzenia" pracowniczego i wymiany muchomorków jak zaczną za dużo narzekać. Może także pojawić się sytuacja gdzie w pozycji liderującej będzie faktycznie ktoś zajebisty, że akurat taki model okaże się niezwykle efektywny. Cóż trudno dyskutować z czymś co w zestawieniach finansowych wydaje się działać i przynosić zysk. Nie każda firma chce i musi zmieniać świat. ale jeśli już..

Faza 4 - jesteśmy zajebiści

Do tego tanga trzeba przynajmniej trojga. W przypadku Diady bardzo trudno jest osiągnąć sytuację gdzie obydwie osoby są na tym samym poziomie wartości. W przypadku trzech osób szansa, że będą się wzajemnie uzupełniać rośnie. Ze swoich młodych lat na zleceniach przez net pamiętam, że często było tak iż albo grafik zlecał coś programiście albo programista grafikowi. Dopiero gdy pojawiała się triada programista,grafik i jakiś tam handlowiec - wtedy pojawia się szansa, że każde dwie osoby mają większą wartość niż osoba trzecia. (wiem, że trochę przekombinowany przykłąd ale jak byłem młody to dużo piłem i tylko tyle pamiętam)

Podobnie jak w procesie zamarzania lodu tworzą się tzw. zarodki (czy coś takiego wokół czego narastają bryły lodu) - tak i tutaj wokół zarodka narasta kultura czwartego poziomu. I specjalnie piszę o kulturze czwartego poziomu a nie o kulturze "MY" bo kurwa już te słowa "we,team,our" są tak nadużywane w korpo, że o niczym nie mówią. To tak jakby kazać strażnikom w więzieniu kazać mówić "miłość" i udawać, że zakład karny zamienił się w rajską wyspę.

A wracając do naszego teamu...

Ostatni obrazek specjalnie nie zawiera samych słodkości aby pokazać, że team w całości nie musi być idealny aby zapoczątkować w jego części kulturę 4 poziomu. I zobaczymy np. że

  • Ludzie sobie nawzajem pomagają aby osiągnąć wspólny cel i wspomóc grupę w rozwoju. I teraz ważna rzecz. W kulturze 2/3 poziomów jak ktoś dostanie przykaz, żeby pomagał to będzie pomagać - ale jedynie dla swoich egoistycznych potrzeb spełnienia celów krótkoterminowych. Czyli pojawi nam się znowu model najemnika - tym razem w kontekście pracy zespołowej. Różnica może nie wydawać się duża ale jest kolosalna - tylko w pierwszym przypadku dana osoba zrezygnuje z własnego krótkoterminowego zysku dla zysku grupy/projektu/organizacji ( a w przyszłym odcinku dowiemy się dlaczego mimo wszystko osoba na 4 poziomie kieruje się nadal osobistymi korzyściami ;) )
  • W przypadku triad i kultury 4 poziomu rośnie szansa na zjawisko tzw "emergence" (niestety żadne polskie tłumaczenie nie brzmi tak fajnie jak angielskie słówko) czyli sytuacja gdzie system modyfikuje swoje zachowanie niezależnie od układu sterującego - chociaż bardziej zrozumiałe może być zjawisko "inicjatywy oddolnej" czyli np. ludzie sami z siebie zaczynają dbać o jakość kodu a nie dlatego, ze im ktoś kazał. Ludzie sami się dokształcają a nie dlatego, że ktoś im kazał. Ludzie sami, wprowadzają nowe rozwiązania a nie dlatego, że ktoś im kazał (niebezpieczne acz chwalebne). Ludzie sami wychodzą a z inicjatywą a nie dlatego, że im ktoś kazał itd.
Na razie są obiecanki cacanki a gdzie jakieś konkrety! Nie bez powodu w tytule jest "część pierwsza ". Apetyt mam nadzieję nabrany a już niedługo...

  • Jak wycinka lasów państwowych może zaprowadzić nas do czytelnego kodu
  • Jak mamuty doprowadziły do narodzin ekonomii
  • Jak delfiny gwałciciele opracowały użyteczny model współpracy
  • Jak walczyć z wesołymi Cześkami (a.k.a "free rider")
  • I najważniejsze - jak zbudować zespół wokół pojęcia "wartości"

Materiały

niedziela, 7 lipca 2013

Złożoność kodu w realnym świecie - opowiadanie

Przedmowa

Poniżej umieszczam opowiadanie, które napisałem jakiś czas temu pod wpływem impulsu gdy na jednym z forów natrafiłem na kolejny temat z cyklu "mam receptę na idealny świat IT". Zasady działania świata nie wydaje się proste ani tym bardziej logiczne a dla tych którzy w to nie wierzą, polecam poszukać sobie w necie dokumentu (ok 70 stron) o Prawie Parkinsona .

A co do opowiadania to dodałem trochę treści gdzie starałem się opisać rozleglej stany wewnętrzne i motywy bohaterów. Ponadto dodałem zajebistą grafikę. No i wniosek na zakończenie - obok problemów, które opisywałem w kilku poprzednich odcinkach - niestety złożoność kodu rodzi się najszybciej z powodu ludzkiej głupoty i ignorancji...

PROLOG - MROCZNE WIDMO

Rysiek - prezes największego w mieście warsztatu samochodowego - stał lekko pochylony na przeciw wielkiej przeszklonej ściany, którą kazał zbudować w swoim biurze aby móc podziwiać rozległą panorame miasta. Ale nie budynki czy też zachodzące słońce przyciagnęło uwagę Ryśka tego wieczoru. Z poziomu 23 piętra jego budynku spacerujący ludzie wyglądali niczym małe mrówki, małe punkciki próbojące dostać się z jednego miejsca do drugiego bez wydawałoby się żadnego konkretnego celu. W głębi duszy Rysiek trochę im zazdrościł. Każdy z punkcików wydawał się żyć w swojej małej mikro-rzeczywistości odizolowanej mentalną ścianą od praw rządzących tym prawdziwym światem. Nieważne. Na jego twarzy pojawił się uśmiech gdy tak jak miliony razy wcześniej do świadomości przebiła się myśl, że przecież on jest tutaj na górze a oni tam na dole.

- Panie Romanie, przeszedł Stefan, ten ze spółdzielni - Głos pani Ilonki - sekretarki Ryśka - wyrwał go z głębokich rozmyślań.
- Niech wejdzie

To będzie dobry dzień. Rysiek lubił kiedy przychodził do niego worek pieniędzy rozmawiać o kolejnym projekcie motoryzacyjnym. W świecie gdzie o pieniądzach decydują ludzie, którzy o motoryzacji słyszeli tylko z książek i prezentacji handlowych, Rysiek umiał sprawić aby wszystkie drogi prowadziły do niego...

ROZDZIAŁ 1 - ODLEGŁE MARZENIA

- Proszę za mną panie Stefanie, prezes może teraz pana przyjąć.

Głos pani Ilonki wzbudziły w Stefanie uczucie lekkiego podniecenia. Stefan był o krok od otrzymania pokaźnej premii od prezesa spółdzielni i zakupu swojego wymarzonego dębowego kredensu na werandę, którego na pewno będą zazdrościć mu wszyscy sąsiedzi. Marzenie o kredensie zrodziło się w głowie Stefana bardzo dawno. Być może jego źródło lezały gdzieś w jego młodości gdy koledzy nie chcieli grać z nim w piłkę z pwoodu jego dziurawych trampek?

A może przyczyną było to, że nigdy nie stać go było na bardziej wytrawne wina co prawie przypłacił utratą życia i płukaniem żołądka.

Wszystko zaczęło się dwa lata temu kiedy Zbyszek - prezes spółdzielni mieszkaniowej im. Bohaterów Stadionu Narodowego - wezwał do siebie Stefana i przedstawił mu wizję swojego nietuzinkowego projektu.


- panie Stefanie przedstawię teraz panu wizję swojego nietuzinkowego projektu. Otóż po ostatnich podwyżkach czynszu nasz komornik Wacław nie wyrabia się ze zbieraniem zaległości. Wacław w przyszłym tygodniu kończy 60 lat i niestety nie ma już tych sił co kiedyś gdy mógł eksmitować trzy rodziny dziennie. A, że Wacka wymienić nie możemy bo Wacek to wie dobrze jak u nas w spółdzielni rzeczy działają to wymyśliłem, że zbudujemy mu wózek - wie pan taki elektryczny bo benzyna jest droga - wydatki podciągniemy jakoś pod budżet na ochronę środowiska i będzie git. I tutaj na scenę wchodzi pan panie Stefanie. Znajdzie pan jakiś porządny warsztat i niech no nam zbudują taki wózek a jak mnie pan panie Stefanie nie zawiedzie to i jakaś nagroda się znajdzie.

Stefan popytał tu i ówdzie, poszukał w ogłoszeniach i znalazł solidnego wykonawcę - „Mech Ryszard Solutions” - duża, więc na pewno solidna firma bo duża. I chociaż początki nie były łatwe bo przez pierwsze pół roku pracownicy Ryśka pokazywali mu jakieś bloczki, rysunki kółek i jakichś dziwnie pokręconych śrubek. Po co mu to pokazywali? nie wiedział. Gówno go interesowały jakieś śrubki. Koniec końców gdy zobaczył pierwszy jeżdżący model wysłał fotę do prezesa, który ostatnio robił się coraz bardziej nerwowy. Stefan pamiętał ten dzień gdyż właśnie wtedy po raz pierwszy w wyobraźni

poczuł zazdrość sąsiadów na widok jego zajebistego kredensu...

ROZDZIAŁ 2 - PLANY A RZECZYWISTOŚĆ

Masywne drzwi gabinetu pana Ryszarda otworzyły się ukazując wszechstronne wnętrze wykończone w stylu gotycko-greckim.


- Panie Stefanie zapraszam do środka, co pana do mnie dzisiaj sprowadza? Jak sprawuje się nasz wózek.

- Dzień dobry panie Ryśku. Wózek? Wózek sprawuje się świetnie ale nastąpiło coś czego nie przewidzieliśmy. Ostatnio gdy pan Wacek wracał sobie spokojnie po uczciwie przepracowanym dniu z zebranymi pieniędzmi - nie uwierzy pan co się stało - jakieś niecne draby wpadły na wózek pana Wieśka i wyrwały mu torbę z pieniędzmi. Rozmawiałem z prezesem i stwierdziliśmy, że musimy zamontować na wózek jakiś system ochrony dla pana Wieśka , wie pan jakieś miotacze ognia czy coś w tym stylu.
- Świetnie panie Stefanie, świetnie. Zawołam Cześka, naszego specjalistę od skręcania i wkręcania tego i owego.

Stefan znał Cześka. To ten, który zawsze mówił, że wszystko jest pod kontrolą nawet wtedy gdy termin oddania wózka przesuwali po raz trzeci. Znał go i trochę nawet obawiał się rozmowy z nim. Nie chodziło o jakieś kłótnie czy coś podobnego. Wręcz przeciwnie. Czesiek umiał opisać bardzo plastycznie jak wszystkie części pojazdu będą współdziałać gdy osiągnie on ostateczną wersję. Stefan się go bardzo obawiał gdyż zawsze po rozmowie z nim wychodził pełen spokoju ale zawsze tak gdzies po godzinie przypominał sobie, że przecież nie ma pojęcia o motoryzacji, częściach i jak to wszystko działa.

W drzwiach pojawił się pewny siebie jak zwykle Czesiek. Przez 5 minut nasłuchiwał się opowieścią Stefana i nowym wymaganiom co do miotaczy ognia.

- rozumiem panie Stefanie , to będzie kosztować bańkę.

Stefan przypomniał sobie o zaległych badaniach u laryngologa, przez chwilę wydawało mu się, że usłyszał "bańkę".

- ile to będzie kosztować?
- bańkę!

Stefan opadł na fotel. Wyobrażenie kredensu zaczęły przesłaniać twarze kolegów wyśmiewających jego dziurawe trampki.

- czy was coś popierdoliło!
- o co panu chodzi panie Stefanie?
- jak to bańkę?
- ano tak to. Pana wózek nie jest przystosowany do montowania miotaczy ognia. W zasadzie pana wózek nie jest przystosowany do żadnego systemu obrony.
- (prezes mnie zajebie) ludzie czy was pojebało. Myślałem, że pracuję z profesjonalistami!
- niech się pan uspokoi, pierwotna specyfikacja nie zakładała montowania żadnego systemu obrony.
- skąd miałem wiedzieć, że będzie potrzebne coś takiego. Myślałem, że to wy jesteście specjalistami od wózków.
- cóż miotacze ognia to nie jest standardowe wyposażenie wózków...
- ale bańka?!?
- panie Stefanie terminy były mocno napięte. Mechanicy musieli na szybko powkręcać śrubki tu i ówdzie. Teraz będzie trudno to poodkręcać.
- co?!
- gdyby tylko zapłacił pan za pełną 5 letnią analizę to mielibyśmy czas porządnie zaplanować każdy gwint panie Stefanie

kurwa, kurwa, kurwa - myśli Stefana były dosyć prostolinijne w tym momencie. Jak prezes się dowie to go zajebie! Nie ma mowy żeby zamówić nowy samochód - drugi raz wałek z dotacjami unijnymi nie przejdzie. Zresztą nie ma czasu. Ale zaraz zaraz, dlaczego wkręcanie śrubek to taki problem. Stefan widział kiedyś jak mechanicy siedzieli w swoich boksach i skręcali śrubki - nie wydawało się to trudne. To Rysiek! Ta pijawka chce zedrzeć z niego ostatni grosz. Trzeba poszukać kogoś innego - na pewno jest ktoś inny...

ROZDZIAŁ 3 - OKRUTNY ŚWIAT

"Firma Mietek Mechanics Consulting to pewny partner z bogatymi tradycjami". Taki napis zauważył Stefan nad drzwiami zakładu mechanicznego, który polecił mu znajomy na forum. To byłą ostatnia deska ratunku dla Stefana - ostatnia nadzieja na kredens.

Opinie, które słyszał o Mietku napawały go nadzieją przemieszaną jednak z pewnymi obawami. Pomimo, że Mietek miał opinie "złotej rączki" to jednak znajomi wspominali, że lubi on strasznie filozofować komplikując proste rzeczy. Stefan nie chciał wysłuchiwać o jakichś kodeksach, super wydajnych sposobach pracy czy innych takich. Centralnie go to pierdoliło. Chce mieć swój wózek i swój kredens.


- panie Stefanie - głos Mietka nie przejawiał oznak entuzjazmu - niestety nie mam dobrych wiadomości. Pojazd, który nam pan przesłał to istny labirynt. Gdy próbowaliśmy odkręcić lewy reflektor włączyły się wycieraczki a próba wymiany bezpieczników spowodowała wyparowanie płynu hamulcowego.
-(Stefan nie wiele rozumiał z tych słów)

-Ponadto te śrubki. Nie wiem kto wpadł na to aby tak to poskręcać. A tutaj widzi pan tę śrubkę - nikt nie używa takiej technologii wkręcania śrubek od 15 lat. O a żeby odkręcić tę tutaj potrzebny będzie klucz 69 - niestety licencja na klucz 69 kosztuje masę pieniędzy.

-(Stefan nadal nie wiele rozumiał z tych słów)

- A teraz najgorsze. Gdy moi pracownicy dotarli do silnika to kombinacja śróbek jaką napotkali sprawiła, że jeden oznajmił iż pierdoli tę robotę i odchodzi aby założyć punkt skręcania długopisów. Drugi zaś zamknął się w magazynie i od dwóch godzin pomimo próśb nie chce otworzyć drzwi.

-Czy.. da.. się.. coś .. zrobić..

- Cóż przyznam szczerze, że będziemy musieli porozkręcać i poprzekręcać jakieś 80%-90% wózka.

- Ile...

- dwie bańki

Stefan opadł na podłogę. Nawet było mu wszystko jedno czy podjedzie po niego Łódzkie pogotowie...

ROZDZIAŁ 4 - OCALENIE

Być może Stefan potrzebował odpoczynku a być może to uderzenie o podłogę sprawiło, że jego umysł sam odnalazł rozwiązanie. "Spółdzielczy budżet na nowe technologie". Trzeba tylko zamontować dodatkowy monitor, który będzie sterował miotaczem ognia, nazwie się to "ochrona 2.0" i rada spółdzielni na pewno to kupi!


- panie Stefanie cieszę się, że pan do nas wrócił - Rysiek w głębi duszy śmiał się szyderczo gdyż wiedział, że oni zawsze wracają - nie mają wyboru Zapoznaliśmy się z pana koncepcja monitora sterującego - teraz to będzie kosztować półtorej bańki
- nie ważne. Uzbieramy kasę - podniesiemy czynsz albo wymyślimy jakieś opłaty za wywóz śmieci czy coś takiego. Róbcie, róbcie jak najszybciej , Wacław nie może zbierać odsetek
- tylko tutaj jeden podpis i zaczynamy
- czy tym razem będę mógł wcześniej wypróbować wózek?
- my tak nie pracujemy panie Stefanie...

Stefan nie miał wyjścia. To będzie długa współpraca. Nie wiadomo jakie jeszcze niebezpieczeństwa niesie ze sobą świat rzeczywisty, świat tak bardzo nieprzewidywalny. Stefan zrozumiał, ze to będzie długa współpraca....

EPILOG

Rysiek spoglądał zadowolony poprzez szklaną ścianę na spacerujace 20 pięte3r niżej punkty. Ten cały Stefan z tym dziwnym pomysłem na wózek. To będzie kolejny długoletni klient – oni wracają zawsze wracają.


- panie prezesie – Rozmyślania przerwał Czesiek – mamy mały problem. Okazało się, że blachy nie da się nagwintować tak jak planowaliśmy.
- To niech kręcą jak mogą
- Tak też im powiedziałem
- Więc w czym jest problem?
- Bolek z dziewiątki ma znowu rozterki natury moralnej. Narzeka, na bajzel wśród śrubek i chce wprowadzać jakieś dziwne narzędzia czy praktyki do kontroli jakości wkręcania.
- Hoho kurwa jeszcze co! Jak ma aspiracje do układania procesów wytwórczych to może powinien sobie poszukać nowej pracy
- Wydaje mi się, że Bolek ma w tym temacie duże doświadczenie może warto by się chociaż tym razem trochę temu przyjrzeć?
- Czesław, Czesław kiedy ty się nauczysz. Oni są tutaj od napierdalania a nie od myślenia. To najniższa kasta, oni maja się słuchać i nie marudzić. Weź go wyjeb.
- Bolka? A kto go zastąpi?
- Czesław – standardowo - zastąpi go skończona liczba praktykantów. Powiedz Ilonce żeby wrzuciła jakieś ogłoszenie o rekrutacji do młodej dynamicznej firmy pełnej wyzwań i rozwijającej się.

Czesław obrócił się na pięcie i wyszedł realizować zalecenia prezesa. Rysiek odetchnął i spoglądając na panoramę miasta przykrytą łuną zachodzącego słońca pomyślał w zadumie – ciekawe czy oni kiedyś zrozumieją, ciekawe czy kiedyś zrozumieją...

niedziela, 30 czerwca 2013

Jak rodzi się złożoność kodu - księga trzecia

Kiedy rozpoczynaliśmy pracę nad obecnym projektem mieliśmy już trochę doświadczeń i przemyśleń odnośnie tego co czyni kod niezwykle nieczytelnym i trudnym w rozwoju. Wśród głównych przyczyn leżały ogromne klasy opakowujące kod proceduralny, długie metody czy masa zagnieżdżonych warunków - a wszystko przypieczętowane zmiennymi o słodko brzmiących nazwach temp czy zzz.

Na tym polu odnieśliśmy zauważalny sukces gdyż nasza największa klasa ma chyba 400 linii, żadna metoda nie przekracza 30 linii a 90% kodu nie ma więcej niż jednego poziomu zagnieżdżenia. Klęskę ponieśliśmy za to na innym froncie, którego istnienia kilka lat tamu nawet się nie domyślałem. Otóż kiedy tworzyliśmy nasze małe czytelne klasy zawsze musieliśmy je gdzieś umieścić. DAO szło zazwyczaj do paczki costam.dao kontrolery do paczki costam.controlers itd.

Istnieje specjalna metryka w sonarze, która bada tego typu spierdoliny : I chociaż nie mam jeszcze w pełni intuicyjnego wyczucia tej metryki to wskazane wartości pokazują, że może być lepiej.

A co da nam to, że paczki nie będą tak poprzeplatane? Aby to zrozumieć złamiemy (ale tylko pozornie) jedną z zasad dobrego kodu obiektowego

Jak OOP tworzy Klasę Blob/Good

Klasa Money będzie dobrym przykładem bo w dzisiejszych czasach każdy potrzebuje pieniędzy. Na początek zobaczmy co się stanie kiedy przedstawimy pieniądze jako zwykłą strukturę danych:

 

public class Money {

    private final BigDecimal amount;
    
    private final Currency currency;

    public Money(BigDecimal bigDecimal, Currency currency) {
        this.amount = bigDecimal;
        this.currency = currency;
    }

    public BigDecimal getAmount() {
        return amount;
    }

    public Currency getCurrency() {
        return currency;
    }
}

Niestety powyższa forma jest zaproszeniem do tworzenia klas typu Utils czy Helper. A pod tym linkiem ----> http://nemo.sonarsource.org/ możecie zobaczyć, że klasy utils to jedne z bardziej złożonych tworów.

No to spróbujmy, żeby było bardziej obiektowo :

 

public class Money {
    private final BigDecimal amount;

    private final Currency currency;

    public Money(BigDecimal bigDecimal, Currency currency) {
        this.amount = bigDecimal;
        this.currency = currency;
    }
    
    public Money add(Money moneyToAdd){
        checkTheSameCurrency(moneyToAdd);
        return new Money(moneyToAdd.amount.add(this.amount),currency);
    }
    
    public boolean isGreaterThan(Money moneyToCompare){
        checkTheSameCurrency(moneyToCompare);
        return this.amount.doubleValue()> moneyToCompare.amount.doubleValue();
    }

    private void checkTheSameCurrency(Money moneyToAdd) {
        if(moneyToAdd.currency!=this.currency){
            throw new RuntimeException("zle");
        }
    }
}

Wydaje się być ok. Jest enkapsulacja, nie zdradzamy wewnętrznej implementacji, jest "tell don't ask" i inne takie dobre praktyki.

A co gdy domena się rozwija i muszę obliczyć podatek? "Tell don't ask" ?

 

public class Money {

    private final BigDecimal amount;

    private final Currency currency;

    public Money(BigDecimal bigDecimal, Currency currency) {
        this.amount = bigDecimal;
        this.currency = currency;
    }
    
    //!!!!!!
    public Tax calculateTax(double percentage){
        return new Tax(amount.multiply(BigDecimal.valueOf(percentage)));
    }

No i pytanie co tak naprawdę symbolizuje klasa Money? Jeśli reprezentuje pieniądz to polityka podatkowa średnio tam pasuje. Oczywiście można powiedzieć - "no to dodajmy tam operacje mnożenia czy też wyciągania procentu". Tak a za chwilę dodamy operacji całki i obliczania pola. A co jeśli będę chciał przeliczać waluty - czy w ramach enkapsulacji dodać funkcjonalność kantoru do klasy Money albo Currency?

Jeśli mówić o smrodzie w kodzie to powyższe opcje dla mnie niezwykle śmierdzą. A co dyby tak wykorzystać modyfikator dostępu, którego nie ma?

 

public class Money {

    private final BigDecimal amount;

    private final Currency currency;

    public Money(BigDecimal bigDecimal, Currency currency) {
        this.amount = bigDecimal;
        this.currency = currency;
    }
    
    BigDecimal getAmount() {
        return amount;
    }

...
}
public class TaxCalculator {

    private static final double standardTaxPercentage=0.23;
    
    public Tax calculateTax(Money money){
        return new  Tax(money.getAmount().multiply(BigDecimal.valueOf(standardTaxPercentage)));

    }
}
Dla wielu osób wynik tego niezwykle wciągającego eksperymentu myślowego może być czymś oczywistym ale ja na swojej drodze napotykam wiele osób, które potrzebują takiej wiedzy (w tym mnie). Ładujemy Money i TaxKalkulator do jednej paczki i mamy enkapsulację pieniędzy w ramach tej paczki. Powinno nam to pomoc w uniknięciu problemu klasy BLOBa ale co jeśli owa paczka niezwykle nam się rozrośnie? Tutaj trudno mi określić granicę kiedy "jest już za dużo".

Bonus - skąd się bierze fundamentalizm obiektowy

Znam pewnych ludzi, którzy kłócili by się, że powyższy przykład można rozwiązać jakimś wizytatorem czy innymi cudami. Problem polega na tym, że czasem mogą mieć rację a czasem nie ale sam fakt uznawania zasady rodem z wikipedii typu "tell don't ask" czy "DRY" za święte i nienaruszalne doprowadza mnie do szalonego płaczu. A w ogólności dobija mnie traktowanie OOP jako panaceum na wszystko - czyli zarzuty typu "ten kod jest zły bo nie obiektowy".

Jeśli ktoś ma niech zerknie sobie do rozdziału nr 6 "Czystego kodu" pod tytułem "struktury danych". Są tam dwa przykłady obliczania pól figur - jeden obiektowy a drugi proceduralny. Nie chce mi się kopiować tutaj źródeł ale wniosek postawiony przez autora jest jasny: wersja bardziej proceduralna z ifami jest lepsza jeśli dojdą nam nowe funkcje (jedno miejsce do zmiany vs wszystkie klasy figur do zmiany) - ale wersja z polimorfizmem jest lepsza jeśli spodziewamy się nowych kształtów - wniosek: obiektowy nie znaczy najlepszy.

Bo nawet czy jest sens toczyć spory odnośnie tego czy kod z ogromną klasą ale idealna enkapsulacją jest bardziej obiektowy niż kod z małymi klasami, które jednak nie starają się robić wszystkiego jednocześnie udostępniając gdzieniegdzie szczegóły implementacyjne?

A może jednak przychodzi czas aby nie ograniczać się do racjonalizacji swoich wyborów prostymi zasadami z wikipedii ale zgłębić dokładniej jakie te wybory niosą konsekwencje? Są różne modele rozwoju zdolności : Shu Ha Ri , SU HA RY , SŁU CHAJ STARY - opisujące jak to od prostych reguł należy przechodzić do zrozumienia zasad poprzez magiczną transcendencję. Na samym początku jest bagno, totalnie zjebany kod i tutaj jest miejsce na stosowanie zasad "Tell don't ask" czy "DRY" bez myślenia bo cokolwiek się nie zrobi to będzie lepiej. Ale jeśli zostanie się na tym etapie to jesteśmy na prostej ścieżce do Obiektowego Fundamentalizmu

O a teraz złamię jeszcze jedną zasadę - święte DRY

 

class PracownikUczelni{
private String tytuł;
}

class Film{
private String tytuł;
}

No i DRY jest złamane. Można oczywiście zrobić też tak:
 

class abstract PosiadaczTytułu{
private String tytuł;
} 

class PracownikUczelni extends PosiadaczTytułu{}

class Film extends PosiadaczTytułu{}

No i DRY jest spełnione a przy okazji mamy zjebaną hierarchię klas. NA początek zasady z wikipedii są dobre ale później trzeba zacząć używać mózgu - nie ma wyjścia. A jeśli ktoś potrzebuje potwierdzenia od jakiejś gwiazdy światowego kalibru to Dan North w jednej ze swoich prezentacji napisał "DRY is an enemy of decoupled" (A jeśli dla kogoś Dan North nie jest gwiazdą światowego kalibru albo zastanawia się "kto to k***a jest?" to niech delektuje się moimi wnioskami)

niedziela, 9 czerwca 2013

Jak rodzi się złożoność kodu - sezon drugi

W poprzednim odcinku opowiadaliśmy sobie o złożoności cyklomatycznej i również w tamtej części znajduje się rozbudowany wstęp epicko opowiadający o tym dlaczego należy się interesować jakością kodu i takie tam. Aby nie tracić teraz miejsca i czasu idziemy prosto do kolejnych metryk.

Spójność klasy - LCOM i LCOM4

Gdy po raz pierwszy zobaczyłem metrykę LCOM4 w sonarze do głowy przyszło mi, że to musi mierzyć ilość materiałów wybuchowych w kodzie albo coś takiego. W zasadzie to wysokie wskazania tej metryki mogą objawiać nieuchronną eksplozję projektu gdyż mierzy ona jak spójne są nasze klasy. Niech kod przemówi ...

ProductManager

Manager, Helper, Procesor - podobno to są skróty od zdania "nie mam pojęcia co dokładnie ma robić ta klasa i dlatego nie wiem jak ją nazwać". Niestety widziałem na własne oczy jak takie klasy rozrastają się do ponad 10000 linii bo jak coś nazywa się "Manager" to w zasadzie wszystko tam pasuje.
Na początek mamy :

 

public class ProductManager {
private List products = newArrayList();

 public List showProducts() {
  List currentlyAvailableProducts = newArrayList();
  for (Product product : products) {
   addProductIfAvailable(currentlyAvailableProducts, product);
  }
  return currentlyAvailableProducts;
 }

 private void addProductIfAvailable(
   List currentlyAvailableProducts, Product product) {
  if (product.isAvailable()) {
   currentlyAvailableProducts.add(product);
  }
 }
}

Taka sobie klasa opakowująca listę produktów. Na razie poza nazwą nie widać innych niebezpieczeństw. Zobaczmy co wskazują metryki :

  • (Metrics plugin) LCOM - 0
  • (Sonar) LCOM4 - 1

Nadszedł czas przyjrzeć się co to są te LCOM i LCOM4. Pierwsza - LCOM - jest dosyć dyskusyjna za co została podobno dosyć skrytykowana - ale o tym za chwilę. LCOM weźmie każdą parę metod i sprawdzi czy chociaż jedna z danych na których one operują jest wspólna. Na razie mamy dwie metody jedną publiczną jedną prywatna ale obydwie działają na produktach. Wynik zero oznacza, że nie ma żadnych metod rozłącznych działających na różnych danych czyli jest zajebiście.

LCOM4 z drugiej strony sprawdza czy wszystkie metody i dane są połączone strzałkami tak jak na rysunku poniżej (fachowo to się nazywa ilość niezależnych grafów).

Na razie wszystko jest połaczone czyli wartości metryki to 1 czyli jest zajebiście.

Wiele odpowiedzialności

Dodajmy teraz nową odpowiedzialność do klasy : obliczanie cen produktów ( w końcu to product manager)

 
public class ProductManager {

 private List products = newArrayList();


 private Set vipUsers=newHashSet();
 
 private BigDecimal vat=new BigDecimal("0.23");

 
 public List showProducts() {
  List currentlyAvailableProducts = newArrayList();
  for (Product product : products) {
   addProductIfAvailable(currentlyAvailableProducts, product);
  }
  return currentlyAvailableProducts;
 }

 private void addProductIfAvailable(
   List currentlyAvailableProducts, Product product) {
  if (product.isAvailable()) {
   currentlyAvailableProducts.add(product);
  }
 }
 
 public BigDecimal calculateGrossPrice(Product product,User user){
  if(vipUsers.contains(user)){
   return product.getNetPrice();
  }
  
  BigDecimal netPrice = product.getNetPrice();
  return netPrice.add(netPrice.multiply(vat));
 }

}

  • (Metrics plugin) LCOM - 1
  • (Sonar) LCOM4 - 2


Ponieważ metody obliczania ceny brutto oraz wyświetlania produktów korzystają z zupełnie innych zestawów danych, więc na rysunku dostaniemy dwa rozłączne grafy - co zwyczajnie oznacza, że możemy te funkcjonalności spokojnie rozdzielić.

Problemy z LCOM4 i Sonarem

Pierwsza wersja kalsy z LCOM4=2 korzystała tylko z pola vat i wyglądała mniej więcej tak :

public BigDecimal calculateGrossPrice(Product product,User user){
BigDecimal netPrice = product.getNetPrice();
return netPrice.add(netPrice.multiply(vat));
}

Pomimo, że w teorii powinno to wystarczyć aby podbić LCOM4 do dwójki to jednak sonar z premedytacją zlewał tę metodę. Po małych poszukiwaniach okazało się, że problem nie jest trywialny gdyż czasami metody takie jak gettery, toString() czy jakieś dziedziczenia narzucone przez frameworki sztucznie podbijają LCOM4. Programiście Sonara wyłączają samotne metody jednak jak się okazuje trochę tutaj przekombinowali w druga stronę i czasami takie małe niezależne metody nie są brane pod uwagę przy obliczaniu LCOM4. Więcej w ticketach :
* SONARJAVA-65
* SONAR-2941
* SONAR-2934
* dyskusja - " A getter/setter is defined as method which accesses a single field" - właśnie to miało chyba miejsce w powyższym przypadku, nawet jak technicznie nie był to getter ale taka metodka utilsowa.

A wracając do wartości LCOM=1 w pluginie metrics. Generalnie wartosć 1 to z tego co zrozumiałem najgorsza wartość jaką możemy uzyskać gdyż zakres dopuszczalnych wartości to [0,1]. Oznacza to ni mniej ni więcej jak to, że możemy dalej psuć klasę a wartość LCOM będzie cały czas wynosić 1. Chyba stąd ta cała krytyka. LCOM4 będzie się zwiększać razem z kolejnymi nadmiarowymi funkcjonalnościami (minus optymalizacje sonara)

Rozwiązanie

Tutaj wystarczy jakoś rozsądnie podzielić managera na odrębne kalsy. Np. część finansowa może pójść do :
 
public class FinancialCalculator {

 private Set vipUsers = newHashSet();

 private BigDecimal vat = new BigDecimal("0.23");
 
 
 public BigDecimal calculateGrossPrice(Product product,User user){
  if(vipUsers.contains(user)){
   return product.getNetPrice();
  }
  
  BigDecimal netPrice = product.getNetPrice();
  return netPrice.add(netPrice.multiply(vat));
 }
}


No i teraz trzeba wykazać się niesamowitymi pokładami braku inteligencji aby do takiej klasy wrzucić metodę "showProducts" gdzie do "Managera" czy "Helpera" nawet by pasowała bo tam wszystko pasuje.

RFC - Response For Class

Problem z tą metryką jest taki, iż trudno stwierdzić czy dana wartość to już za dużo czy jeszcze nie. Te wskazania traktować należy jako sygnał, że coś tutaj może być nie tak.

Wpłynąć można na nią raczej dosyć prosto - redukując liczbę wywołań innych klas ale także redukując liczbę wywołań siebie przez inne klasy. I już na początek niespodzianka. Poniższa pusta klasa ma RFC=2. Według dokumentacji pierwsze "+1" Bierze się z okazji domyślnego konstruktora. Skąd się bierze +2 - pojęcia nie mam - widocznie poza domyślnym konstruktorem sonar znajduje tam coś jeszcze.

 
public class BlogCreator {
}

Po dorzuceniu węgla :

 

public class BlogCreator {

 private Browser browser;
 private Keyboard keyboard;

 public void writeBlog() { +1
  browser.loginToBlogger();  +1
  Topic postTopic = workOnTopic();
  Post typedPost = keyboard.type(postTopic); +1
  review(typedPost); 
  browser.submit(typedPost); +1
 }

 private Topic workOnTopic() { +1
  return new Topic(); +1
 }

 private void review(Post typedPost) { +1
  //coś tam
 }
}

Mamy RFC=9. Powtórzonych wywołań się nie liczy czyli jakbym użył raz jeszcze browser.loginToBlogger() to RFC byłoby stałe. Podobnie tylko raz liczy się metody wewnątrz klasy czyli workOnTopic() jest zliczane przy deklaracji i można to sobie wywoływać ile razy tam się chce.

Czy RFC=9 jest duże? Kod powyżej jest raczej czytelny i paradoksalnie wrzucenie wszystkiego w jedną wielką metodę mogłoby zmniejszyć jednocześnie RFC jak i czytelność kodu (mniej metod ~ mniejsze RFC). Także osobiście polecałbym traktować tę metrykę jako taki wskaźnik pomocniczy a nie wartość absolutną.

Jak można zmniejszyć RFC zwiększając czytelność kodu? Trzeba pobawić się poziomami abstrakcji i przykryć koncepcje bardzo szczegółowe czymś bardziej ogólnym :

 
public class BlogCreator2 {

 private Medium internetMedium;
 
 public void writeBlog() {
  MentalCreation ideaForBlog=awakeDivineSparkAndTrascendenOverYourHumanLimitation();
  internetMedium.publishForHumanity(ideaForBlog);
 }

 private MentalCreation awakeDivineSparkAndTrascendenOverYourHumanLimitation() {
  return new MentalCreation();
 }
}

W tym przypadku się okazało się, że RFC spadło do 6. Dobrze? Niedobrze? Nie wiadomo. Generalnie wiele zależy tutaj od percepcji tych, którzy będą kod czytać. Generalnie po wpisaniu słowa "abstraction" w Amazonie na pierwszych miejscach wyskakują pozycje o sztuce, malarstwie i psychologii. Chyba jednak pewne rzeczy łatwiej będzie ogarnąć na polu matematyki i programowania funkcyjnego.

Z drugiej strony jak gdzieś w projekcie RFC wyskoczy na poziomie 50 to jednak coś może być mocno zjebanego. ProductManager z tego odcinka ma RFC=17. FinancialCalculator już tylko RFC=9; ProductProvider RFC=10 (zrodla tego nie wklejałem ale to jest reszta, która została w ProductManagerze po ekstrakcji FinancialKalkultora). Czyli każdą z tych klas teoretycznie łatwiej przeczytać ale razem mają RFC większe od RFC samego ProductManagera.

Klasa CyclomaticExample z poprzedniego odcinka w swojej najbardziej zjebanej formie ma RFC=21. Po refaktoringu RFC=11. Czyli w jakimś tam stopniu można założyć, że im RFC mniejsze tym czytelniej

Materiały

niedziela, 2 czerwca 2013

Jak rodzi się złożoność kodu (part łan)

Był rok 2011 - konkretnie lato lub jesień. W tle rozpoczynała się restrukturyzacja firmy a na pierwszym planie trwały batalię o termin oddania kolejnej wersji produktu. Standardowo jak to w takich sytuacjach bywa funkcjonalność na deadline była absolutnym priorytetem a kod to tam sobie posprzątamy później w wolnym czasie. Zaczęły pojawiać się nawet takie dziwne i egzotyczne pomysły w stylu "nie piszcie testów to według obliczeń dostarczycie 30% szybciej" albo " teraz szybko zaimplementujcie a jak będzie sukces to później zrobi się sprint lub dwa na refaktoring". Oczywiście gdy kończy się implementować jeden "urgent ficzer" wtedy okazuje się, że to był tylko taki przedsionek dla kolejnej niezbędnej funkcjonalności.

Cóż, każdy z nas(w zespole) przeżył pracę z klasami liczącymi 10000+ linii kodu i dobrze wiedzieliśmy do czego prowadzi patologia "poprawimy później". Dlatego praktyką zespołu było i jest ciągłe sprzątanie po sobie. Dzięki temu byliśmy w stanie dostarczyć kolejne super urgent ficzery w 2012 i 2013. Jednocześnie pojawił się postęp kulturowy kiedy to użytkownicy zwani biznesowymi zaczęli przejawiać zrozumienie w kierunku konceptu "dostarczamy w miarę regularnie bo pilnujemy wysokiej jakości kodu". To był taki krok pierwszy w kierunku wysokiej higieny kodu - zrozumienie, że jak się nie posprząta gówna to będzie śmierdzieć. Czas teraz na kolejny krok czyli dlaczego tak często trzeba latać do kibla - (kontynuując metaforę - trzeba zrozumieć co jemy i jak to wpływa na żołądek)

Tyle tytułem epicko-kulinarnego wstępu.

Jaka jest właściwie złożoność kodu?

Oczywiście każdy z nas czytał "czysty kod" nawet po kilka razy. Mimo tego co jakiś czas musimy odwiedzić wyniki Sonara aby zobaczyć czy naruszenia kodu znowu nie pikują w okolicach akceptowalnych granic. W naszym projekcie sformułowanie "o kurwa" pojawia się przy około 50 naruszeniach - w innych projektach punkt niemiłego zaskoczenia może znajdować się gdzieś indziej. I chociaż wspomniany "Czysty kod" to doskonała podstawa zawierająca wiele reguł, które każdy programista powinien wbić sobie do głowy (może wtedy zmienne w stylu "temp1" czy "zzz" zniknęłyby z kodu) to jednak aby uzyskać intuicyjne zrozumienie złożoności kodu być może musimy zejść trochę głębiej.

Tutaj dla analogii także przemycę fakt żywieniowy. Podobnie jak ogólne rady "pisz czysty kod, małe metody i czytelne nazwy" wyznaczają ogólny kierunek poszukiwań tak samo rady "jedz zdrowo i dużo się ruszaj" są tylko początkiem. Trzeba zejść głębiej aby zrozumieć jak działają poszczególne składniki. Przykładowo na półce w sklepie stoi sobie taka buteleczka soku z napisem slim za 2,99. Producent mógł tam napisać duże "slim" czy "odchudzanie" ponieważ dodał śladowe ilości L-karnityny, która w niezależnych badaniach naukowych faktycznie ujawniła właściwości odchudzające ALE... po pierwsze L-karnityna pomaga w transporcie tłuszczy w trakcie wysiłku czyli jeśli nie ruszy się dupy to i tak ten soczek w niczym nie pomoże... a co śmieszniejsze aby polepszyć smak i odczucie konsumenta w napoju znajdują się różne cukry i słodziki, które spowodują wyrzut insuliny, która z kolei zablokuje tłuszcz w komórkach... Wpieprzamy w dobrej wierze taką buteleczkę słodzików i później niestety trzeba robić refaktoring organizmu.

Ale wracając do kodu

Badanie złożoności poprzez metryki

Kwestia z metrykami jest nieco skomplikowana bo mogą być one pomocne ale niestety często są wykorzystywane do oceny ludzi co całkowicie je dyskwalifikuje. Jeśli nie użyjemy jej jako targetu czy innego gówna to może pomóc bo zobaczmy. Jeśli np. ktoś mi powie "będzie do przeniesienia 100kg cementu" to mniej więcej czuję o jaką ilość i materiału i pracy tu chodzi. Jeśli zaś ktoś powie "musimy dodać logowanie do kodu o złożoności cyklomatycznej 17" to ilu z was tak naprawdę "CZUJE" z czym ma do czynienia. Dodatkowo - ilu z was wie jak się doprowadza kod do złożoności cyklomatycznej 17? No właśnie. Dlatego dzisiaj zerknijmy na tę metrykę.

Jest sobie kod będący przepisem na wyciągnięcie pieniędzy z bankomatu.

 
public void wyciągnijPieniadzeZBanku(){
  ubierzSie();
  WezKarte();
  IdzDoBankomatu();
  UzyjKarty();
  SchowajKase();
  WracajDoDomu();
 }

Do obliczenia złożoności cyklomatycznej wykorzystamy plugin "metrics" dla eclipse co w praktyce wygląda tak:

Dlaczego wartość tej metryki wynosi 1? Złożoność cyklomatyczna to tak naprawdę odpowiedź na pytanie "W ilu miejscach kod może zmienić swój bieg"

 
public void wyciągnijPieniadzeZBanku(){
  if(padaDeszcz){
   wezParasol();
  }
  ubierzSie();
  WezKarte();
  IdzDoBankomatu();
  UzyjKarty();
  SchowajKase();
  WracajDoDomu();
 }

Możemy wziąć parasol ale nie musimy - są dwie drogi wykonania metody - złożoność cyklomatyczna wzrosła do 2. Czy kod stał się mniej czytelny? Cóż to jest indywidualna ocena każdego czytelnika. Dla mnie nadal jest czytelny. Co do czytelności to tutaj jest jeszcze jedna kwestia. Zerknijmy na dwa kawałki kodu

 
public void wyciągnijPieniadzeZBanku(){
  if(padaDeszcz){
   wezParasol();
  }
  ubierzSie();
  WezKarte();
  IdzDoBankomatu();
  UzyjKarty();
  SchowajKase();
  
  if(portfel.isEmpty()){
   WracajDoDomu();
  }else{
   IdzZKolegamiNaBrowara();
  }
 }
oraz
 
public void wyciągnijPieniadzeZBanku(){
  if(padaDeszcz){
   wezParasol();
  }
  ubierzSie();
  WezKarte();
  IdzDoBankomatu();
  UzyjKarty();
  
  for (int i = 0; i < banknoty.size(); i++) {
   portfel.schowaj(banknoty.get(i));
  }
  
  WracajDoDomu();
 }

Obydwa kawałki kodu maja złożoność cyklomatyczną na poziomie 3 chociaż można się spierać czy oba są czytelne/nieczytelne w tym samym stopniu. Dlatego też nie ma się co spinać, że sama złożoność na danym poziomi dostarcza nam 100% potrzebnych informacji. Druga kwestia. Można by zastosować w kodzie pętlę "ForEach" ale niestety plugin metrics liczy tę złożność cyklometryczną zliczając ilość warunków logicznych dlatego zupełnie ignoruje konstrukcję "foreach" - sonar dla odmiany dał sobie radę.

 
public void wyciągnijPieniadzeZBanku(){
  if(padaDeszcz){
   wezParasol();
  }
  ubierzSie();
  WezKarte();
  IdzDoBankomatu();
  UzyjKarty();
  
  for (int i = 0; i < banknoty.size(); i++) {
   portfel.schowaj(banknoty.get(i));
  }
  
  if(portfel.isEmpty() || nieChleje){
   WracajDoDomu();
  }else{
   IdzZKolegamiNaBrowara();
  }
 }

Tutaj dodaliśmy jeszcze jeden warunek logiczny (niechleje) i Cyclomatic Complexity wynosi już 5. Pomału zaczyna się robić bajzel.Ile mamy teraz puntów decyzyjnych?

  • Możemy brać parasol albo i nie
  • możemy schować pieniądze do portfela albo i nie
  • Jak nie mam kasy w portfelu to idę do domu
  • Ale nawet jak mam kasę i nie chleję to i tak idę do domu
Jeśli chcielibyśmy przetestować wszystkie scenariusze to też ich tam chyba jest już z kilkanaście. No to dorzućmy coś jeszcze :

 
public void wyciągnijPieniadzeZBanku(){
 if(padaDeszcz){
   wezParasol();
  }
  
  ubierzSie();
  
  if(jestZimno){
   ZalozDodatkowoSportowyDres();
  }
  
  WezKarte();
  
  if(golf.jestZatankowany()){
   podjedzDoBanku();
  }else{
   IdzDoBankomatu();
  }
  
  if(pin==null){
   pin=telefon.dzwonDo(ZIOMALE.WIESIEK);
  }
  
  banknoty=UzyjKarty(pin);
  
  for (int i = 0; i < banknoty.size(); i++) {
   portfel.schowaj(banknoty.get(i));
  }
  
  if(portfel.jestPusty() || nieChleje){
   WracajDoDomu();
  }else{
   IdzZKolegamiNaBrowara();
  }

Złożoność cyklomatyczna tego kawałeczka wynosi już 8. Sądzę, że zdolności percepcyjne większości czytelników są na takim poziomie, że każdy dostrzeże w tym kodzie naturę gówna. I na koniec ulubiona praktyka w pewnych kręgach czyli arrow code.

 
public void wyciągnijPieniadzeZBanku(){
 if(padaDeszcz){
   wezParasol();
  }
  
  ubierzSie();
  
  if(jestZimno){
   ZalozDodatkowoSportowyDres();
  }
  
  WezKarte();
  
  if(golf.jestZatankowany()){
   podjedzDoBanku();
  }else{
   IdzDoBankomatu();
  }
  
  if(pin==null){
   pin=telefon.dzwonDo(ZIOMALE.WIESIEK);
  }
  
  banknoty=UzyjKarty(pin);
  
  for (int i = 0; i < banknoty.size(); i++) {
   portfel.schowaj(banknoty.get(i));
  }
  
  if(portfel.jestPusty() || nieChleje){
   WracajDoDomu();
  }else{
   while(telefon.mamSrodki()){
    for (ZIOMALE ziomal: ZIOMALE.values()) {
     if(telefon.dzwonDo(ziomal)!=null){
      if(zabkaJEstOtwarta()){
       IdzZKolegamiNaBrowara();
      }
     }
    }
   }
  }

Plugin Metrics określi wartość złożoności cyklometrycznej na 9 zaś sonar na 12. Jest to dowód na to, że nie ma co się co przywiązywać do konkretnych wartości tej metryki. Generalnie to co pokazuje sonar bardziej do mnie przemawia gdyż ten "arrow code" dosyć tutaj namieszał.

Teraz zostaje nam już tylko to gówno refaktorować. Na szybko może wyjść coś takiego :

 
public void wyciągnijPieniadzeZBanku(){
 przygotujSie(padaDeszcz,jestZimno);
  WezKarte();
  SrodekTransportu srodekTransportu=uzyskajSrodekTransportu();
  srodekTransportu.udajSieDoBankomatu();
  pin=znajdzPin();
  banknoty=UzyjKarty(pin);
  portfel.schowaj(banknoty);
  OpcjaNaWieczor opcjaNaWieczor=zbadajMojeOpcjeNaWieczor(portfel);
  opcjaNaWieczor.zrealizuj();

Złożoność cyklomatyczna tej metody wynosi znowu 1. Mamy również chyba jeden spójny poziom abstrakcji. Niektóre koncepcje jak Telefon czy Ziomal zostały przykryte przez bardziej abstrakcyjne twory jak OpcjaNaWieczor. Gdzie się podziała złożoność? Nie zniknęła ale została równomiernie rozłożona a powstałe konstrukcje obiektowe mogą (lub nie) zwiększy "reużywalność" kodu i zmniejszyć koszt utrzymania.

Podsumowując: Im większa wartość złożoności cyklometrycznej (które lepiej brzmi z angielska Cyclomatic complexity) tym gorzej ale z drugiej strony konkretne wartość nie mówi nam wszystkiego i jest zależna od narzędzia. W kolejnych odcinach przedstawię naturę innych metryk. Mam nadzieję, że ten sposób tłumaczenia był bardziej efektywny niż pisanie wzoru na CC "M = E − N + 2P" i nurkowanie w teorię grafów (której sam do końca nie rozumiem"

Dodatkowe źródła :

  1. http://en.wikipedia.org/wiki/Cyclomatic_complexity
  2. http://www.guru99.com/cyclomatic-complexity.html

niedziela, 26 maja 2013

Od Zera Do Matchera

To jest materiał podstawowy, który przygotowałem do wewnętrznej prezentacji w pracy. Jeśli ktoś jest obcykany w matcherach niech nie traci czasu i poczyta sobie lepiej jakieś dzieła w stylu trzynastej księgi Pana Tadeusza.

Jeśli jednak ta tematyka jest Ci obca to zapraszam do lektury

Domena

Domenę biznesową tworzą dwie klasy - piosenka i techno mikser

 

public class Song {

 private String title;

 private int numberOfBits;
 
 public Song(String title,int numberOfBits) {
  this.title = title;
  this.numberOfBits = numberOfBits;
 }

 public void play(){
  System.out.println("plying "+title);
 }
 
 public String getTitle() {
  return title;
 }
 
 public int getNumberOfBits() {
  return numberOfBits;
 }
}

 

public class TechnoMixer {
 public List makeTechnoMix(Song... originalSongs){
  int technoMultiplier = 2;
  String technoPostfix = "_techno_mix";
  return mixSongs(technoMultiplier, technoPostfix, originalSongs);
 }
 
 public List makeThunderdomeMix(Song... originalSongs){
  int thunderdommeMultiplier = 4;
  String thunderdommePostfix = "_thunderdomme_mix";
  return mixSongs(thunderdommeMultiplier, thunderdommePostfix,
    originalSongs);
 }

 private List mixSongs(int bitsMultiplier,
   String titlePostfix, Song... originalSongs) {
  List mixedSongs=new ArrayList<>();
  for (Song originalSong : originalSongs) {
   int newNumberOfBits=originalSong.getNumberOfBits()*bitsMultiplier;
   String newTitle=originalSong.getTitle()+ titlePostfix;
   mixedSongs.add(new Song(newTitle, newNumberOfBits));
  }
  return mixedSongs;
 }
}

Działanie jest proste - TechnoMixer zwyczajnie zwiększa ilość bitów znacząco podnosząc komfort słuchania piosenki :) No i odpowiednio modyfikuje tytuł utworu oznajmując wszem i wobec, że słuchamy lepszej jej formy

Spróbujmy to przetestować...

Test próba pierwsza

 

@Test
public void shouldMixSongsToTechno() {
 //given
 Song equador=new Song("Equador", 100);
 Song onaTanczyDlaMnie=new Song("Ona Tanczy Dla Mnie", 50);
 Song harlemShake=new Song("Harlem Shake", 150);

 //when
 List mixes = technoMixer.makeTechnoMix(equador,onaTanczyDlaMnie,harlemShake);
 
 //then
 Assert.assertEquals(mixes.get(0).getNumberOfBits(), 200);
 Assert.assertEquals(mixes.get(1).getNumberOfBits(), 100);
 Assert.assertEquals(mixes.get(2).getNumberOfBits(), 300);
 
 Assert.assertEquals(mixes.get(0).getTitle(), "Equador_techno_mix");
 Assert.assertEquals(mixes.get(1).getTitle(), "Ona Tanczy Dla Mnie_techno_mix");
 Assert.assertEquals(mixes.get(2).getTitle(), "Harlem Shake_techno_mix");
}

Testy przechodzi ale wygląda kiepsko - a pamiętajcie, że to tylko prosty przykład. Przy bardziej skomplikowanych obiektach będzie jeszcze więcej syfu.

Test próba druga - zewnętrzna metoda

 

@Test
public void shouldMixSongsToTechno2() {
 //given
 Song equador=new Song("Equador", 100);
 Song onaTanczyDlaMnie=new Song("Ona Tanczy Dla Mnie", 50);
 Song harlemShake=new Song("Harlem Shake", 150);

 //when
 List mixes = technoMixer.makeTechnoMix(equador,onaTanczyDlaMnie,harlemShake);
 
 //then
 assertThatSongsHaveBits(mixes,200,100,300);
 assertThatSongsHaveTitles(mixes,"Equador_techno_mix","Ona Tanczy Dla Mnie_techno_mix","Harlem Shake_techno_mix");
}


private void assertThatSongsHaveBits(List mixes, int... expectedBites) {
 for (int currentSongNumber = 0; currentSongNumber < expectedBites.length; currentSongNumber++) {
  Assert.assertTrue(mixes.get(currentSongNumber).getNumberOfBits()==expectedBites[currentSongNumber]);
 }
}

private void assertThatSongsHaveTitles(List mixes, String... expectedTitles) {
 for (int currentSongNumber = 0; currentSongNumber < expectedTitles.length; currentSongNumber++) {
  Assert.assertEquals(mixes.get(currentSongNumber).getTitle(),expectedTitles[currentSongNumber]);
 }
}

Test wygląda już czytelniej. Metody są bliźniaczo podobne ale nie będziemy ich jeszcze refaktorować. Na razie zastanówmy się nad kilkoma rzeczami :

  • Czy wywołanie może być jeszcze czytelniejsze
  • Czy można gdzieś przenieść metody pomocnicze aby nie zaśmiecały testu
  • Zakłądając, że w bardziej skomplikowanej domenie obiekt Song mógłby wystąpić w większej ilości testów - czy można zrobić jakoś tak aby metod "reużyć"?

Można!

Matcher - wersja pierwsza

 

@Test
public void shouldMixSongsToTechnoWithMatcher() {
 //given
 Song equador=new Song("Equador", 100);
 Song onaTanczyDlaMnie=new Song("Ona Tanczy Dla Mnie", 50);
 Song harlemShake=new Song("Harlem Shake", 150);

 //when
 List mixes = technoMixer.makeTechnoMix(equador,onaTanczyDlaMnie,harlemShake);
 
 //then
 SongMatcher.assertThat(mixes).haveBits(200,100,300).and().titles("Equador_techno_mix","Ona Tanczy Dla Mnie_techno_mix","Harlem Shake_techno_mix");
}

 

public class SongMatcher {

 private List songs;

 public SongMatcher(List songs) {
  this.songs = songs;

 }

 public static SongMatcher assertThat(List songs) {
  return new SongMatcher(songs);
 }

 public SongMatcher haveBits(int... expectedBits) {
  for (int currentSongNumber = 0; currentSongNumber < expectedBits.length; currentSongNumber++) {
   Assert.assertEquals(expectedBits[currentSongNumber],songs.get(currentSongNumber).getNumberOfBits());
  }
  return this;
 }

 public SongMatcher titles(String... expectedTitles) {
  for (int currentSongNumber = 0; currentSongNumber < expectedTitles.length; currentSongNumber++) {
   Assert.assertEquals(expectedTitles[currentSongNumber],songs.get(currentSongNumber).getTitle());
  }
  return this;
 }

 public SongMatcher and() {
  return this;
 }

}

Test wygląda już ok ale w matcherze mamy te jakże podobne do siebie metody. A Gdyby tak obiekt miał 10 własności? Czas na jakiś refaktoring - na tyle na ile znam refleksję to wyjdzie coś takiego....

Matcher - wersja druga

 

public class SongMatcher2 {
 private List songs;

 public SongMatcher2(List songs) {
  this.songs = songs;

 }

 public static SongMatcher2 assertThat(List songs) {
  return new SongMatcher2(songs);
 }

 public SongMatcher2 haveBits(Integer... expectedBits) {
  assertValueOnProperty("NumberOfBits",expectedBits);
  return this;
 }
 
 public SongMatcher2 titles(String... expectedTitles) {
  assertValueOnProperty("Title",expectedTitles);
  return this;
 }


 private void assertValueOnProperty(String fieldName, Object[] expectedBits) {
  Assert.assertNotNull(songs);
  Assert.assertFalse(songs.isEmpty());

  try {
   assertValueOnPropertyUnsafely(fieldName, expectedBits);
  } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
   throw new RuntimeException(e);
  }
  
 }

 private void assertValueOnPropertyUnsafely(String fieldName,
   Object[] expectedBits) throws NoSuchMethodException,
   IllegalAccessException, InvocationTargetException {
  Method methodToInvoke = songs.get(0).getClass().getDeclaredMethod("get"+fieldName);
  for (int currentNumberOfValue = 0; currentNumberOfValue < expectedBits.length; currentNumberOfValue++) {
   Song currentSong = songs.get(currentNumberOfValue);
   Object invokeResult = methodToInvoke.invoke(currentSong,new Object[0]);
   Assert.assertEquals(expectedBits[currentNumberOfValue], invokeResult);
  }
 }

 

 public SongMatcher2 and() {
  return this;
 }
}

No i uważne oko zauważy, że wykształcił nam się taki matcher w matcherze. Zakładając, że obiektów w domenie będzie więcej to być może znowu jakoś tę logikę z refleksją da się gdzieś tam ładnie udostępnić. Możemy alb o delegować albo dziedziczyć. Niby książki mówią, żeby delegować ale tutaj z powodów nad którymi teraz nie chce mi się rozwodzić będziemy dziedziczyć.

Matcher - wersja trzecia

 

public class BaseMatcher {

 protected List objectsUnderTest;
 
 public BaseMatcher(List objectsUnderTest) {
  this.objectsUnderTest = objectsUnderTest;
 }

 protected void assertValueOnProperty(String fieldName, Object[] expectedValues) {
  Assert.assertNotNull(objectsUnderTest);
  Assert.assertFalse(objectsUnderTest.isEmpty());

  try {
   assertValueOnPropertyUnsafely(fieldName, expectedValues);
  } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
   throw new RuntimeException(e);
  }
  
 }

 private void assertValueOnPropertyUnsafely(String fieldName,
   Object[] expectedBits) throws NoSuchMethodException,
   IllegalAccessException, InvocationTargetException {
  Method methodToInvoke = objectsUnderTest.get(0).getClass().getDeclaredMethod("get"+fieldName);
  for (int currentNumberOfValue = 0; currentNumberOfValue < expectedBits.length; currentNumberOfValue++) {
   Object currentObject = objectsUnderTest.get(currentNumberOfValue);
   Object invokeResult = methodToInvoke.invoke(currentObject,new Object[0]);
   Assert.assertEquals(expectedBits[currentNumberOfValue], invokeResult);
  }
 }

 

 public BaseMatcher and() {
  return this;
 }
}

 

public class SongMatcher3 extends BaseMatcher{

 public SongMatcher3(List songs) {
  super(songs);
 }

 public static SongMatcher3 assertThat(List songs) {
  return new SongMatcher3(songs);
 }

 public SongMatcher3 haveBits(Integer... expectedBits) {
  assertValueOnProperty("NumberOfBits",expectedBits);
  return this;
 }
 
 public SongMatcher3 titles(String... expectedTitles) {
  assertValueOnProperty("Title",expectedTitles);
  return this;
 }
}

Na razie możemy testować tylko listy ale to na potrzeby przykładu i jeszcze do tego wrócimy. Wygląda całkiem obiecująco ale pojawia się pewien problem otóż kompilator zgłasza błąd w teście :

 

@Test
public void shouldMixSongsToTechnoWithMatcher3() {
 //given
 Song equador=new Song("Equador", 100);
 Song onaTanczyDlaMnie=new Song("Ona Tanczy Dla Mnie", 50);
 Song harlemShake=new Song("Harlem Shake", 150);

 //when
 List mixes = technoMixer.makeTechnoMix(equador,onaTanczyDlaMnie,harlemShake);
 
 //then
 SongMatcher3.assertThat(mixes).haveBits(200,100,300).and();// O TUTAJ JEST BŁĄD .titles("Equador_techno_mix","Ona Tanczy Dla Mnie_techno_mix","Harlem Shake_techno_mix");
}

Dzieje się tak dlatego, że nasz matcher bazowy nic nie wie o metodach matchera domenowego. Ostatni szlif i ostatnia sztuczka własnie przed nami. Musimy jakoś tę wiedzę do nadklasy przekazać.

Matcher - wersja czwarta

 

public class BaseMatcher2 {

 private K self;
 
 protected List objectsUnderTest;
 
 public BaseMatcher2(List objectsUnderTest,Class selfClass) {
  this.objectsUnderTest = objectsUnderTest;
  this.self=selfClass.cast(this);
 }

 protected void assertValueOnProperty(String fieldName, Object[] expectedValues) {
  Assert.assertNotNull(objectsUnderTest);
  Assert.assertFalse(objectsUnderTest.isEmpty());

  try {
   assertValueOnPropertyUnsafely(fieldName, expectedValues);
  } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
   throw new RuntimeException(e);
  }
  
 }

 private void assertValueOnPropertyUnsafely(String fieldName,
   Object[] expectedBits) throws NoSuchMethodException,
   IllegalAccessException, InvocationTargetException {
  Method methodToInvoke = objectsUnderTest.get(0).getClass().getDeclaredMethod("get"+fieldName);
  for (int currentNumberOfValue = 0; currentNumberOfValue < expectedBits.length; currentNumberOfValue++) {
   Object currentObject = objectsUnderTest.get(currentNumberOfValue);
   Object invokeResult = methodToInvoke.invoke(currentObject,new Object[0]);
   Assert.assertEquals(expectedBits[currentNumberOfValue], invokeResult);
  }
 }

 public K and() {
  return self;
 }
}

 

public class SongMatcher4 extends BaseMatcher2{

 public static SongMatcher4 assertThat(List songs) {
  return new SongMatcher4(songs);
 }

 public SongMatcher4(List objectsUnderTest) {
  super(objectsUnderTest, SongMatcher4.class); 
 }

 public SongMatcher4 haveBits(Integer... expectedBits) {
  assertValueOnProperty("NumberOfBits",expectedBits);
  return this;
 }
 
 public SongMatcher4 titles(String... expectedTitles) {
  assertValueOnProperty("Title",expectedTitles);
  return this;
 }
}

Teraz powinno być ok

Co dalej?

Jak już wcześniej zwróciliśmy uwagę powyższe matchery nadają się jedynie do testowania list. Można by klasę bazową zamienić w fabrykę wyspecjalizowanych matecherów co ma sens bo testowania czy ilość elementów jest większa od zera nie ma sensu dla pojedynczych obiektów. I coś takiego jest już gotowe, nazywa się FEST i gorąco polecam

Co Jeszcze?

Przy okazji pisania artykułu użyłem JUnit 4.11, który ma nowy ciekawy patent : @FixMethodOrder(MethodSorters.NAME_ASCENDING). Od czasu przesiadki na Javę 7 JUnit zaczął wywoływać metody w losowej kolejności co czasami psuje testy integracyjne bo tam np. testując flow transformacji plików dla zaoszczędzenia czasu kolejne testy korzystają ze stanu pozostawionego przez poprzednie fazy. A ta małą adnotacja działa jak do rany przyłóż.

wtorek, 21 maja 2013

GeeCON 2013 - rekonstrukcja zdarzeń

Programowanie Funkcyjne

Ten temat pojawił się trzeciego dnia, więc pamiętam go najlepiej bo jako odpowiedzialny kierowca byłem wtedy trzeźwy i wyspany. Generalnie do czasu tej konferencji programowanie funkcyjne kojarzyło mi się jedynie z przetwarzaniem danych pomiędzy zbiorami gdzieś tam wewnątrz aplikacji (tak jak na matematyce dyskretnej w klasie 1 studiów). Aż tu na jednej prezentacji pojawił się rysunek podobny do poniższego (odtworzone z pamięci) :

Pewnie dla wyjadaczy scali, klołżerów i innych takich ten temat jest oczywisty jak składanie pita37 ale dla mnie to był moment oświecenia, że jako funkcję można przedstawić dowolne "coś" co dostaje na wejściu "cokolwiek" i zwraca "coś innego". I teraz taki Wzorzec Dekorator wydaje się być hakiem na obiekcie gdzie czymś bardziej naturalnym byłoby wywołanie f(g(x)) albo F(f1(x),f2(x),...) - jeśli dobrze zrozumiałem F to funkcja wyższego rzędu a fn funkcje niższego rzędu - czyli tutaj to mogą być np poszczególne walidatory w trakcie jednej wielkiej walidacji.

Jest jeszcze jedna ciekawa a być może nawet ważniejsza korzyść jaką może przynieść programowanie funkcyjne. Generalnie we wszystkich książkach o OOP we wstępie jest napisane, że właśnie ten sposób jest naturalny dla ludzi i dlatego wszystko pójdzie gładko. A to gówno prawda bo ludzie myślą w sposób proceduralny przez co bliżej nam do programowania imperatywnego co ma swoje potwierdzenie w kodzie większości studentów - masa ponakładanych na siebie ifów,forów i innego świństwa zamkniętych w 8000 linijkową metodę.

A jeśli do kogoś to nie przemawia to niech pomyśli sobie co dzieje się w jego głowie gdy idzie pozmywać naczynia po obiedzie:

Czy coś takiego :

Mam do dyspozycji obiekt klasy Zlew, obiekt klasy Ludwik oraz kolekcję obiektów klasy NAczynie oraz obiekt klasy Człowiek (czyli JA)

 
zlew.umieść(naczynia)
ludwik.nalejDo(zlew)
kran.odkręć()
ja.zmyjNaczynia(zlew)


Czy Może naturalniej jednak będzie :

Jest do pozmyawania sterta naczyń
 
JA muszę wrzucić te wszystkie naczynia do zlewu
JA muszę teraz odkręcić wodę 
JA muszę teraz nalać ludwika
JA teraz dla każdego talerza
      muszę go umyć
      jeśli jest już czysty 
          to JA go odłożę na suszarkę
      jeśli nie
          to JA go myję dalej
      W międzyczasie zakręcę wodę
      Dobra zrobię sobie krótka przerwę
      (...)
      
No jeśli ktoś myśli o abstrakcjach i interfejsach w trakcie zmywania naczyń to jakieś dobre zioło namierzył albo po ASPięknych.

Generalnie zarówno programowanie obiektowe jak i funkcyjne nie jest naturalne dla naszego umysłu ALE. Klasy i obiekty to wynalazek greckich filozofów i chyba ktoś ich kalibru ma odpowiednie doświadczenie aby stworzyć poprawny model obiektowy. W przeciwieństwie do humanistycznej obiektówki programowanie funkcyjne opiera się na zrozumieniu ścisłych zasad matematyki. A enkapsulacja i re-używalność jest równie dobra.

?Nowe spojrzenie na dług techniczny

Temat długu technicznego, kompatybilności wstecz, dodawania nowych ficzerów itd pojawił się na kilka prezentacjach pierwszego i drugiego dnia. I w sumie tutaj tez jest mały mindfuck bo do tej pory TechDebt traktowałem jako coś bezpośrednio związanego z jakością kodu i architekturą systemu.

A tutaj pojawia się taka ciekawostka. Jeśli mamy np. takie wymagania :

  • Zrobić funkcjonalność 1
  • Zrobić funkcjonalność 2
  • Zrobić funkcjonalność 3


To one tak naprawdę brzmią

  • Zrobić funkcjonalność 1
  • Zrobić funkcjonalność 2 i nie popsuć funkcjonalności 1
  • Zrobić funkcjonalność 3 i nie popsuć funkcjonalności 1 i też nie popsuć funkcjonalności 2

No to teraz jeśli założymy, że mamy w naszym systemie N (dla uproszczenia) podobnych funkcjonalności, to koszt dodania funkcjonalności N+1 będzie:

  • Tam gdzie jakimś cudem osiągnęliśmy legendarny poziom modularyzacji - Cost~F(n+1)
  • Tam gdzie ficzery są od siebie zależne ale system jest gotowy na rozbudowę - Cost~F(n+1) + M(n) - gdzie M(n) koszt utrzymania przy życiu pozostałej części systemu
  • Tam gdzie jest skomplikowana interakcja (zamierzona lub nie) pomiędzy ficzerami - Cost~F(n+1)+M(n^2)
  • Tam gdzie gówno pływa swobodnie po kodzie, "big ball of mud" i wszystko jest powiązane ze wszystkim w 10000 linijkowych klasach nazwanych "Menażer" - Cost~F(n+1)+M(2^n)

Oczywiście poprzez refkatoringi, metodyki projektowe i inne takie można poprawić charakterystykę funkcji kosztu ALE - można też zmniejszyć pracochłonność zmniejszając N poprzez usunięcie częsci fukncjonalności. Niby szok kulturowy bo funkcjonalność - szczególnie ta działająca - zarabia dla nas pieniądze - no poza momentami kiedy ich nie zarabia!

Kiedyś czytałem w focusie artykuł o ludziach, którzy patologicznie kolekcjonują rzeczy przez co nie mogą się poruszać po swoim mieszkaniu. U mnie w komórce przez 10 lat też leżały jakieś deski bo a nuż kiedyś się przydadzą. Nikt ich nie używał to poszły na przemiał i podobnie trzeba wywalać jakieś dziwne opuszczone ficzery aplikacji, których nikt nie używa. Czyli nowy rodzaj długu technicznego : doskonale działąjąca funkcjonalność, której (praktycznie) nikt nie używa

Inne takie

Inna ciekawa rzecz to : http://code.google.com/p/spock/ w sumie fajna rzecz ale na Groovym . Po PHP trzymałem się z dala od języków dynamicznych ale trzeba się chyba przemów.