Tuesday, February 26, 2013

DDD checklist

Gdzieś mi przemknął taki test ma to, czy DDD na sens w Twoim projekcie:

JEŚLI: Myślisz o swojej aplikacji jako o warstwach
I JEŚLI: Na samej górze jest zawsze web
I JEŚLI: Na samym dole jest zawsze baza danych
TO: Nie masz do czynienia z czymś takim jak "domena"
TO: DDD jest Ci niepotrzebne

[update: 28.02]
JEŚLI: Wyobrażasz sobie architekturę swojej aplikacji jako "stos"
I JEŚLI: Na samej górze tego stosu jest zawsze web
I JEŚLI: Na samym dole tego stosu jest zawsze baza danych
TO: To prawdopodobnie dziedzina, którą informatycznie wspierasz nie jest zbyt złożonna
TO: Wprowadzenie Domain Model za pomocą DDD będzie nieadekwatne do potrzeb i zbyt kosztowne

Ponieważ uwielbiam metafory, szukałem jakiejś trafnej historyjki dla tego kejsu i przyszło mi do głowy coś takiego:

Serce: Słuchaj, Mózg, czy armata to dobra broń?
Mózg: Hmmm, no bardzo dobra...jeśli chcesz zdobyć Fort Knox, ale jeśli chcesz po prostu przetrzepać skórę paru cwaniakom, to powinieneś zastanowić się nad czymś tańszym.

Saturday, February 23, 2013

Obiektowo orientowana pogoń za własnym ogonem

W tym poście. Sławek napisał komentarz:

W tym wszystkim brakuje mi jednej rzeczy, bez której refaktoryzacja przypomina szalony bieg z tasakiem. Refaktoryzacja do czego? Nie do wzorców, nie do SOLID, nie do Clean Code...

Do postaci, która oddaje zrozumienie problemy (dziedziny). Z moich obserwacji tego właśnie brakuje, ten i problem w pewnych klasach systemów próbuje rozwiązać DDD.

(...)


Zacząłem odpisywać i się rozpisałem, wiec przerzucam do posta.

A wiecie, że w systemach a'la Oracle Forms, bez obiektowej warstwy aplikacyjnej, rzadko występują problemy właściwe systemom, w których się tę warstwę wepchnęło? Z pewnością widziałem mniej systemów niż niektórzy z czytelników, trochę jednak widziałem, i takich i takich.

Nie trzeba zastanawiać się nad rozszerzalną (czy to cokolwiek sensownego znaczy?) architekturą, bo architektura jest mocno narzucona; nie trzeba kombinować jak uzyskać możliwość szybkiego wdrażania, bo mamy to prawie out of the box.

Programiści PL/SQL raczej nie narzekają na jakość kodu, wydajność, złożoność przypadkową i intencjonalną. Radzą sobie. Czasem nawet dziwią się, że w OO-świecie są tego typu problemy. Już słyszę obiekcje, że OOP nadaje się do dużych systemów. Akurat te największe, z którymi miałem do czynienia, były db-centric.

Być może ludzie wymyślili DDD, frameworki (sygnalizowałem to wcześniej) inne takie tam tylko po to, aby poradzić sobie z problemem, który sami wygenerowali wymyślając OOP. Po co więc brnąć w coś i rozwijać metody, skoro to coś samo w sobie zostało oparte na niewłaściwych (zbyt skomplikowanych) założeniach?

Dalej, logiczna prostota (i idące za tym ograniczenia) technologii typu Forms, sprawia że łatwiej skonkretyzować oczekiwania użytkowników. Dlaczego? Bo technologia z góry narzuca, co jest dozwolone, a co nie jest i nie pobudza fantazji zbyt wieloma opcjami do wyboru. Może i działa to nieco odwrotnie: oto już IT nie "modeluje" świata biznesu, lecz daje biznesowi parę formatek, nazwy pól, a biznes sobie projektuje co i jak ma działać.

Co właściwie, wymienione na początku DDD próbuje rozwiązać (mowa tu o tej części książki, w której mamy bloki budujące)? DDD chce stworzyć koncepty, dzięki którym będzie można łatwiej opisywać rzeczywistość i jednocześnie przekładać ten opis na model obiektowy. Trochę nadużywamy słowa "model". Model to model - opis działania rzeczywistości taki jak np. poniższy. Same prostokąciki, strzałki, napisy itd. Tak jak na rysunku.



Teraz ten model można przenieść do świata IT na wiele sposobów. Można za pomocą obiektów (co robi DDD), można przełożyć na związki encji, nawet na pliki - wszystko jedno. DDD to tylko narzędzie do przekładania modelu do IT w jakiś specyficzny sposób. DDD to tylko jedna możliwości. Czy aby na pewno OOP jest najprostszym sposobem na przenoszenie modelu do IT?

Prócz standardowego trójkąta projektu, zespół podlega jeszcze jednemu ograniczeniu: kompetencje programistów. Gdybyśmy w każdym zespole mieli samych Ericków Evansów, to w warstwę aplikacyjną, OOP, DDD można wchodzić w ciemno. Najczęściej jednak raz na jakiś czas zdarzy się jakiś "quasi-Evans", a większość to jednak Janowie Kowalscy.

Jakiś czas temu, 60-letni uczestnik szkolenia Wzorce Projektowe. Powiedział: "Nie bardzo rozumiem, po co te wszystkie klasy? Kiedyś to, co się pisało, trzeba było porządnie nazwać i to zazwyczaj rozwiązywało problemy" I o to chodzi: grunt to prostota.

Tuesday, February 19, 2013

Strategiczna refaktoryzacja

Dlaczego "strategiczna"? Ponieważ od kiedy po raz pierwszy zobowiązałem się do "Niech Pan coś zrobi z tym kodem", stało się całkowicie dla mnie jasne, że techniki refaktoryzacji to za mało. Refaktoryzacje refactoring.com, czy Refactoring to Patterns są genialne i precyzyjnie opisują jak transformować kod z jednej postaci w inną, lecz są to zbyt niskopoziomowe przekształcenia o zbyt małej "sile rażenia", aby mogły przynieść wymierny efekt w mocno zagmatwanym i wciąż zmieniającym się kodzie.

O czym dokładnie mówię? Mówię o projektach rozwijanych od dziesięciu lub więcej lat. Ilość wierszy kodu liczona jest wtedy w milionach. Wyobraź sobie ten projekt, a w nim plik *java (zawierajacy jedną klasę) o rozmiarze 2.8MB, co przekłada się na około 60k wierszy kodu w klasie. Dalej wyobraź sobie, że podobnych klas jest kilkaset w tym projekcie. W takim razie jaką konkretnie zmianę spowoduje chociażby Replace Conditional with Polymorphism? Albo inaczej: ile pieniędzy w kontekście najbliższego kwartału pozwoli to zaoszczędzić? Wydaje mi się, że takiego bilonu w Polsce nawet się nie bije. Wprowadzona zmiana utonie w odmętach wszechogarniającego spaghetti. A system wciąż się rozwija, kodu wciąż przybywa.

Kluczowe założenie: To ma sens

Szczegóły na ten temat umieściłem w poście Megasoftwarecraftsmanshipper. Moim zdaniem nie da się sensownie przeprowadzić znaczącej refaktoryzacji, jeśli w pierwszej kolejności nie zrozumiemy dokładnie przyczyn, dla których kod znajduje się obecnie w takiej a nie innej postaci. Bez takiej analizy najczęściej podejmowane są refaktoryzacje, które od dawana chodzą zespołowi po głowie, są ciekawe od strony technicznej, bywają czasochłonne i jednocześnie nie prowadzą do znaczącej poprawy.

Może jest to banalne, ale tego typu analizę najłatwiej przeprowadzić przy założeniu, że architektura kodu ma sens, że komuś kto ją tworzył, przyświecały racjonalne przesłanki do podjęcia decyzji, które podjął. Zamiast zadawać sobie pytania: co to $^&#@#$ jest!? , itp., stawiasz następujące: "co musiałbym myśleć, aby stworzyć taką architekturę? w jakiej sytuacji musiałbym się znajdować?". Ponoć indiańskie przysłowie mówi, że "możesz kogoś oceniać dopiero wtedy, gdy przejdziesz milę w jego mokasynach". Coś w tym jest.

Zidentyfikuj obszary refaktoryzacji

Nie wszystkie fragmenty systemu są jednakowo ważne. Refaktoryzuj te, które są najważniejsze. Jako atrybuty do klasyfikacji możesz wziąć pod uwagę:
  • najczęściej zmieniające się fragmenty
  • fragmenty, na które raportowanych jest najwięcej błędów
  • fragmenty, z którymi programiści mają najwięcej problemów
  • fragmenty najmniej/najbardziej pokryte testami

Pomocnym narzędziem do analizy w/w atrybutów są raporty z Twojego traca albo repozytorium. Kilka informacji znajdziesz w artykule Your VCS – the forgotten feedback loop

Najpierw zatrzymaj degradację kodu

Idę tę opisałem na na przykładzie radzenia sobie z workarounds w kodzie.
Innym, bardziej powszechnego użytku przykładem, może być skorzystanie z zasady Interface Paritioning.



Gdy mamy wielgachne (60k w pliku txt; rozmiar metod od 20 wierszy od 1k) klasy jak na rysunku, od których zależy wiele innych, to pierwszym problemem nie jest ich rozmiar, lecz to, że sytuacja wciąż się pogarsza ze względu na trwające prace, a więc stale przyrasta ilość klas zależnych od naszego potwora. Jeśli zabierzesz się za rozkładanie tej klasy na mniejsze kawałki, to może to zająć tyle czasu, że w końcu spotkasz się z Jeźdźcami Apokalipsy. Jeśli jednak na najpierw posegregujemy metody z potwora pomiędzy interfejsy, to spowoduje to zatrzymanie postępującego pogarszania się kodu.



Skupiamy się, na zatrzymaniu powstawania nowych zależności. Empirycznie sprawdziłem, że poszatkowanie na interfejsy klasy zawierającej ok 1k metod, dwóm osobom, które w miarę często z niej korzystają zajmuje jakieś 4-6 godzin. Po takim zabiegu można sobie już zaplanować spokojne wydzielanie mniejszych komponentów z potwora.

Drugim przykładem na powstrzymanie postępującego bałaganu w kodzie jest Replace Method with Method Object. Ten refaktoring kojarzy mi się z pączkowaniem drożdży.



Chodzi tu przede wszystkim o to, aby "wypchnąć" dużą ilość kodu poza wielgachną klasę i tam sobie już spokojnie rozkminiać dany fragment i go porządkować.

Te dwa sposoby działania bardzo dobrze ze sobą współpracują:
  1. Interface Partitioning - tworzy w okół potwora skorupę, dzięki której nie może on już wpływać na inne klasy
  2. Replace Method with Method Object - pomaga rozczłonkować potwora na kwałki



Zarządzaj refaktoryzacją również na poziomie mikro

Oczywiście umiejętności programistów są jednym z kluczowych blokad przeciw pogarszaniu się jakości kodu.



Mam tu na myśli powtarzalny proces pracy z kodem, który nazywam Naturalnym Porządkiem Refaktoryzacji. Szczegółowo możesz się zapoznać z tą koncepcją tutaj:

Hipoteza o pewnym Punkcie:)

Przez analogię do Promienia Schwarzschilda mam taką hipotezę, że dla każdego systemu istnieje taki poziom bałaganu w kodzie, po przekroczeniu którego, zespół nie jest już w stanie kompetencyjnie podołać refaktoryzowaniu systemu i każde podjęte działanie pogarsza tylko jakość kodu.

Żeby znaleźć rozwiązanie w tej sytuacji, należy zacząć inaczej myśleć o refaktoryzowaniu kodu oraz o jego jakości. Z mojego punktu widzenia architektura jest emanacją relacji pomiędzy biznesem a IT. Jeśli relacja ta jest nieekologiczna, to żadna magiczna sztuczka nie poprawi designu. Dalej, refaktoryzacja jest dla mnie problem organizacyjnym, a nie technicznym, czy kompetencyjnym.

Co z wynika z powyższych dywagacji. Bardzo prosta rzecz. Możliwe staje się zastosowanie wytycznej: nie szukaj rozwiązania problemu na poziomie, na którym się on rozgrywa, przejdź poziom wyżej. Zatem po przekroczeniu wymienionego w H3 Punktu, jedyne co może poprawić jakość kodu to refaktoryzacja na poziomie organizacyjnym. Właśnie tak, zmień sposób zorganizowania prac nad projektem, a przełoży się to na poprawę architektury i jakości kodu. Możesz zacząć od przyjrzenia się następującym relacjom:
  • współpraca między zespołami deweloperskimi
  • współpraca między zespołami deweloperskimi a zespołami testującymi
  • współpraca między zespołami deweloperskimi a biznesem

Podam przykład, w pewnej firmie postanowiono wprowadzić Zespół ds. Trudnych i Beznadziejnych. Zajmowali się oni naprawianiem problemów, których nikt inny nie potrafił naprawić. Przez pewien czas rozwiązanie sprawdzało się świetnie. Przez pewien czas, bo po ośmiu miesiącach zaczęły pojawiać się następujące objawy:
  • najlepsi programiści znaleźli się w Zespole
  • najlepsi programiści nie mieli czasu na uczenie innych jak rozwiązywać trudne problemy
  • zwykli programiści przykładali mniejszą wagę do jakości kodu, bo wiedzieli, że Najlepsi wychwycą każde niedociągnięcie
  • Zespół Najlepszych miał coraz więcej pracy
  • zwykli programiści coraz mniej dbali o jakość kodu
  • jakość kodu drastycznie spadła
  • Najlepsi programiści nie mieli czasu na refaktoryzację, zwykli programiści nie potrafili jej przeprowadzić
Paradoksalnie można stwierdzić, że kod był pisany po to, aby Zespół ds. Trudnych i Beznadziejnych miał co robić:)


Najważniejsze założenia przestawione w tym artykule

  • Pierwszym celem strategicznej refaktoryzacji jest powstrzymanie postępującej degradacji w kodzie; drugim celem jest poprawa jego jakości
  • Aby zaproponować sensowny plan refaktoryzacji trzeba w pierwszej kolejności zrozumieć przyczyny powstania legacy code
  • Umiejętności programisty to kluczowa rzecz, która poprawia jakość kodu
  • Refaktoryzację można prowadzić na poziomie technicznym tylko do pewnego momentu, potem należy przenieść się na poziom organizacyjny

Megasoftwarecraftsmanshipper

Pojawił się nowy rodzaj programisty:

megasoftwarecraftsmanshipper - podchodzi do odziedziczonego kodu z wyższością ocierającą się o pogardę; z kpiącym uśmiechem patrzy na nazwy zmiennych, design, stan testów; sam dla siebie jest największym autorytetem; podstawowymi narzędziami poprawy jakości czyjegoś kodu jest naprzemienne stosowanie wyśmiewania i poniżania; megasoftwarecraftsmanshipper to powiernik i głosiciel Prawdy objawionej przez Święte Community; według najnowszych badań postawa taka jest objawem choroby zwanej megasoftwarecraftsmanshippentem

A więc jest nadzieja! Można to leczyć! Zebrałem kilka receptur ozdrowieniowych, więc jeśli znasz jakiegoś megasoftwarecraftsmanshippera, to koniecznie poleć mu jedną z nich, bądź nawet wszystkie. Skutków ubocznych nie stwierdzono.

Receptura Szczepana i Bartka

Szczepan Faber i Bartosz Bieńkowski w swoim wystąpieniu nt. TDD dają następującą recepturę: "musicie pozbyć się swojego Ego". Dalej wyjaśniają, że chodzi to pozbycie się zdradliwej myśli, że "mój kod jest najlepszy". Zaleca się powtarzanie tego rozumowania na głos rano i wieczorem.

Receptura Lequiera

który rzekł: "Gdy się wierzy, że posiadło się prawdę, trzeba wiedzieć, że się w to wierzy, a nie, że się to wie". Na podstawie tej światłej myśli zaleca się rozważanie następującego zdania: "Konwencja kodowania, architektura, frameworki, język programowania to tylko moje przyzwyczajenia i moje ulubione podejścia. Sprawdzają się w bardzo określonych kontekstach, a poza nimi się nie sprawdzają. Wszystkie inne alternatywy mają tyle samo sensu co te, wyznawane przeze mnie". Oczywiście powtarzamy rano i wieczorem.

Receptura percepcji błędu

Jeśli coś nie działa, w pierwszej kolejności szukaj błędu we własnym kodzie. Zadawaj sobie pytanie: "Co mogłem zrobić albo czego nie zrobić, że to przestało działać?" Rano i wieczorem to dawka minimalna. Optymalna to: przy każdorazowym wystąpieniu błędu.

Receptura "To ma sens"

Megasoftwarecraftsmanshipper przede wszystkim powinien sobie tak głęboko, że aż do szpiku kości uświadomić, że ludzie którzy stworzyli kod odziedziczony naprawdę NIE byli kretynami. Dla wielu megasoftwarecraftsmanshipperów będzie to trudne do zaakceptowania, ale jeśli tylko założą, że "istniała racjonalna przyczyna, dla której ten kod wygląda tak a nie inaczej, że ci którzy go napisali zrobili wszystko, co mogli w sytuacji w jakiej musieli pracować", to być może zaczną zauważać, że: była jedna doświadczona osoba, która dostała "darmowych" (obecnie już chyba nie:)) studentów, kod został przeportowany 1:1 z jakiegoś egzotycznego języka, to jest poprawiona wersja, która wyglądała jeszcze gorzej, itd. Megasoftwarecraftsmanshipper może wtedy zacząć uświadamiać sobie, że "to naprawdę ma sens"!

Wednesday, February 6, 2013

Jak radzić sobie z błędami ludzkimi?

Ostatnio na grupie WJUG pojawił się wątek Jak zmienić człowieka?, w którym do pewnego momentu dyskutowano o programiście, który nie trzyma się standardów zespołu. Potem, jak to na forach bywa, dyskusja zahaczyła o tematy metafizyczne, by po jakimś czasie odbić w kierunku swobodnych skojarzeń z tematem.

Choć jestem zdania, że lepiej (taniej) nie zmieniać ludzi, a zamiast tego zatrudniać właściwych, to pozwoliłem sobie pominąć dominującą w wątku myśl jak właściwie znaleźć konsensus, aby nie wylać programisty z kąpielą i zastanowiłem się nad pytaniem: Jak radzić sobie z osobami, które masz już w zespole i według Ciebie nie działają tak, jakbyś tego oczekiwał. Na przykład popełniają błędy, których nie akceptujesz. Oczywiście takie rzeczy zdarzają się każdemu, ale przypuśćmy, że akurat u tej osoby charakterystyka błędów mocno odbiega od dopuszczalnej.

Współpraca z ludźmi, to zawiła sztuka i konkretne sposoby postępowania nie są remedium na wszystko, aczkolwiek poniższe pomysły mogą pomóc.

Sklasyfikuj błędy

Dowolną ilość czasu możesz spędzić dyskutując, czy ktoś popełnił błąd, czy nie. Dlatego, prócz standardowych zasad definiowanych dla zespołu, proponuję od razu wprowadzić pewną klasyfikację błędów i określić sposoby postępowania na wypadek każdego z nich. A zatem mamy błędy:

Przypadkowe Zdarzają się w wyniku okoliczności, na które mieliśmy znikomy wpływ. Nie mamy pretensji o takie błędy.
  • Uwidocznij
Wynikające z podjętej decyzji Programista podjął decyzję i była to błędna decyzja. Dobrze, że ludzie podejmują decyzje. O te błędy też nie mamy pretensji.
  • Udziel informacji zwrotnej (@see poniżej)
Symptomatyczne Pojawia się regularnie w podobnych okolicznościach. Nie mamy o niego pretensji, ale coś z nim trzeba zrobić
  • Zdiagnozuj problem
  • Napraw błąd lokalnie, a następnie przemodeluj proces, aby błąd więcej nie występował
  • podejmij coaching z programistą
Wynikające z zaniedbania Programista wiedział jak postąpić i tak nie postąpił. Powinien mieć pewną wiedzę, ale jej nie pozyskał. Zrobił coś, lecz od początku było wiadomo, że robić tego nie powinien. O takie błędy mamy pretensje.
  • Udziel informacji zwrotnej
  • wyciągnij konsekwencje służbowe

Udzielanie informacji zwrotnej

Tak jak pisałem kiedyś w udzielaniu informacji zwrotnej nie chodzi o to, aby na siłę być milusim i też nie o to, aby być chamem. Dla uproszczenia sprawy na początek trzymaj się następujących kroków:
  1. Udzielaj informacji zwrotnej wyłącznie w cztery oczy i cztery uszy
  2. Przedstaw fakty - fakty, nie twoje oceny; fakty do sformułowania, które odpowiadają na pytania: jak? gdzie? kiedy? z kim? co powiedział? co napisał? co usłyszał? co konkretnie zrobił?; zatem jeśli informacje przekazywane przez Ciebie bardziej odpowiadają na pytania: dlaczego? po co? to nie są to fakty, lecz twoje/czyjeś opinie; lepiej wtedy zakończ spotkanie i na spokojnie spisz fakty na kartce
  3. FeedBACK - Przekaż co najmniej cztery informacje zaczynające się od Dobrze zrobiłeś, że...
  4. FeedFORWARD - Przekaż tyle ile to konieczne informacji zaczynających się od Następnym razem oczekuję, że...
  5. Poproś o podsumowanie, tego co przed chwilą powiedziałeś

Coaching

Większość z nas namiętnie uprawia co najmniej dwa zawody: doradcy i psychologa. Odpuść.
Prowadź coaching tylko, gdy ktoś o to poprosi, albo przynajmniej wyrazi zgodę.
Najprostsze formy "coachingu" to:
  • Wysłuchanie - słuchaj i potakuj. Nic nie mów tylko potakuj
  • Zadawaj pytania konkretyzujące (jak? w jaki sposób? po czym poznajesz, że?) - Zadawaj pytania i czekaj, aż rozmówca dozna olśnienia
  • Zadawaj pytania dlaczego? po co? w jakim celu? - zadawaj tak długo, aż rozmówca dozna olśnienia

I przede wszystkim nie doradzaj i nie oceniaj, bo więcej do Ciebie nie przyjdzie. Jakkolwiek by to nie brzmiało: to co w tym momencie masz do powiedzenia jest absolutnie nieistotne:)

Tuesday, February 5, 2013

Test biznesowy

Od paru wieczorów, moja żona usypiająca naszą paromiesięczną córkę, zgłaszała pewien problem: Klamka w drzwiach skrzypi. Gdy dziś zamknąwszy za sobą drzwi, zobaczyłem na lodówce kartkę w kolorze czerwonym, wiedziałem, że wymaganie otrzymało status blocker.

Z namaszczeniem rozłożyłem skrzynkę z narzędziami, oliwę do maszyn, szmatkę przeciw zabrudzeniom i przystąpiłem do dzieła. Powoli, aby nie stracić żadnej chwili przyjemności rozmontowałem wszystkie śrubki i kropla po kropelce wsączałem oliwę w wadliwy mechanizm.

Nagle przyszło mi do głowy, żeby odstawić popisówkę i zademonstrować żonie, jak to profesjonalnie powinno się współpracować z klientem, jak formułować kryteria akceptacji.
- Słuchaj - powiedziałem - ile razy musisz nacisnąć klamkę, aby się przekonać, że już nie skrzypi i jest naprawiona?
Żona zastanowiła się krótko i odpowiedziała:
- Klamka będzie naprawiona, jeśli Nadia nie będzie się budzić, gdy będę od niej wychodzić.

Morał: Żaden gadżet ani wybajerzona metodyka nie mają uzasadnienia, jeśli klient za pomocą funkcjonalności nie może rozwiązać swoich problemów.

Morał2: Nie zmuszaj klienta do myślenia o technikaliach. Raczej myśl tak jak on.