Test driven development: Część 2 – Model pracy w TDD

utworzone przez | 20/11/2019 | architektura, TDD

Każdy, kto zetknął się z TDD zna trzy magiczne słowa: Red, Green, Refactor. W praktyce przekłada się to na cykl zaplanuj, wykonaj, przeanalizuj i popraw. To oznacza, że TDD zachęca nas do:

  • rozbicia problemu na jak najmniejsze składowe
  • rozwiązania jednej z nich w prostacki sposób, bez oglądania się na jakość
  • poprawienia tego rozwiązania
  • podejścia do kolejnej składowej w ten sam sposób

Kiedy zaczynałem interesować się tematem TDD zawsze zastanawiały mnie dwie rzeczy: skąd wiedzieć jaki kawałek kodu powinien przypadać na jeden test (czyli jak zdefiniować najmniejszą składową) i dlaczego nie można po prostu napisać raz a dobrze. Do tego pierwszego wrócimy w kolejnych tekstach, a na razie skupimy się na pytaniu po co?

Po co?

Refactoring się robi jak przejmujesz kod po kimś albo jak kod nieuchronnie zarośnie brudem ze starości. Konieczność wrócenia do napisanego kodu i poprawienia go to osobista porażka.

Ja, wiele lat temu. Koloryzowane.

Okazuje się, że bardzo się myliłem. Napisanie wstępnej implementacji “na brudno” pomaga dostrzec i zrozumieć problemy, których inaczej byśmy nie zauważyli. Uwalnia też zasoby mózgu, który nie musi skupiać się jednocześnie na implementacji logiki i jakości kodu. Kiedyś nie było to dla mnie oczywiste, ale te dwie rzeczy są zupełnie odrębnymi kwestiami.

W zrozumieniu powyższego pomogła mi książka, która nie ma nic wspólnego z programowaniem – Jedna Rzecz, Gary’ego Kellera i Jay’a Papasana. Swoją cegiełkę dołożyli też Kevlin Henney, Miłosz Brzeziński i autorzy różnorakich tekstów dot. psychologii i neurologii.

Ludzka wielowątkowość jest pozorna. Jasne, potrafimy jednocześnie iść, popijać kawę i myśleć o mechanice kwantowej, ale czy to oznacza, że potrafimy robić 3 rzeczy na raz? Do pewnego stopnia tak, przy założeniu że dwie z nich nie angażują świadomości – jak chodzenie i picie kawy. To są “no-brainery”, które, w idealnych warunkach, wykonujemy automatycznie.

Zadajcie sobie pytanie, która z wymienionych poniżej czynności kwalifikuje się jako no-brainer:

  • Doprecyzowanie i zrozumienie wymagań biznesowych
  • Analiza nowej logiki w kontekście starej
  • Projektowanie interfejsu
  • Implementacja logiki biznesowej
  • Szukanie dobrych, gotowych rozwiązań wśród dostępnych bibliotek albo w obrębie języka
  • Analiza pod kątem bezpieczeństwa
  • Optymalizacja
  • Pozbywanie się duplikacji z kodu

Odpowiedź jest prosta – żadna. Czyli nie możemy ich robić jednocześnie, bo będą konkurować o naszą uwagę. Będziemy stale przełączać świadomość z jednego tematu na drugi, a to nie różni się niczym od sytuacji, w której ktoś ciągle pinguje nas na Slacku.

Jedna idea, wiele skali

Najbardziej powszechnym cyklem naszej pracy jest scrumowy sprint. Jego idea jest prosta – możesz sobie mieć swój plan pięcioletni, ale warto wyznaczać sobie krótkoterminowe cele i stale weryfikować naszą pracę względem obranego kierunku i kierunek względem świata zewnętrznego.

Jak wygląda scrum? Najpierw mamy planowanie, na którym ustalamy co będziemy robić. Zauważcie, że gdybyśmy na tym etapie zastanawiali się nad szczegółami implementacji, to nigdy byśmy nie skończyli. Potem następuje faktyczny sprint, kiedy pracujemy nad poszczególnymi zadaniami. Na koniec mamy retrospekcję, która pozwala nam spojrzeć na zakończoną pracę i wyciągnąć wnioski na przyszłość.

Robiąc zooma na pojedynczą dobę w sprincie otrzymujemy kolejny cykl, od jednego daily do następnego, i ten sam schemat – planujemy, robimy, a na kolejnym daily sygnalizujemy problemy i staramy się skorygować trajektorię zespołu.

Kiedy mówimy o prędkości zespołu warto pamiętać, że prędkość to wartość wektorowa – ma kierunek i zwrot. Można poruszać się bardzo szybko, ale w niewłaściwym kierunku.

Kevlin Henney

Dalej mamy cykl pracy nad pojedynczym zadaniem. I znowu: dostajemy wymagania, które przekuwamy w kod, a review to retrospekcja. Ponownie widzimy tu ten sam schemat: zaplanuj, wykonaj, przeanalizuj, popraw.

Dla dużej części programistów skala jednego taska to maksymalne przybliżenie. Siadamy do roboty, realizujemy założenia, puszczamy do review. Ten proces może trwać godzinę, a może też kilka sprintów (to jest temat na inną dyskusję 😃).

Można jednak pociągnąć ten zoom dalej i wtedy dochodzimy do mikro skali i cyklów kilkuminutowych, które co chwilę korygują nasz kurs niczym silniczki manewrowe statku Progress podczas dokowania w Międzynarodowej Stacji Kosmicznej. To jest właśnie TDD.

Rozrysuj mi to…

Dobre pomysły mają tendencję do uniwersalności – trzeba je tylko właściwie zastosować do danego problemu. Kevlin Henney i Greg Young z tego powodu radzą przyglądać się innym, starszym branżom.

Henney architektom oprogramowania radzi kupić książkę o architekturze budynków, a Young reklamuje pogaduchy z księgowym jako najlepszy sposób na zrozumienie i przekonanie się do event sourcingu (z programowaniem funkcyjnym też działa – księgowi używają najbardziej popularnego języka funkcyjnego na świecie: Excela). Ja polecam spojrzeć na rysowników.

szkic twarzy

Rysownik tworząc portret nie zaczyna od narysowania perfekcyjnego kącika oka, następnie przechodząc do idealnej tęczówki i doskonałego nozdrza. Przeciwnie. Najpierw rysuje całą twarz, bez żadnych detali, żeby dobrze ocenić proporcje i kształt. Często kolejne kreski szkicu poprawiają poprzednie, zbliżając artystę do finalnego dzieła.

Podsumowanie

Jak widzisz sama idea pracy w cyklach nie jest wyjątkowa dla TDD, podobnie jak świadomość, że powinniśmy iść od ogółu do szczegółu, a nie odwrotnie.

Pisząc test określamy co ma robić nasza logika z punktu widzenia jej użytkowników – czyli jej API. To wyznacza nam cel na najbliższe minuty. Następnie tworzymy szkic implementacji pomagający nam zrozumieć problem, a później poprawiamy, żeby zamysł z testu zrealizować najlepiej jak się da.

Proces projektowy składa się z następujących etapów: opisz jak rozumiesz problem, napisz kod żeby się dowiedzieć, że nie rozumiesz problemu, popraw opis, powtarzaj do skutku

Hollis Kinslow, 1968 NATO Software Engineering Conference


To jest fundamentalne rozdzielenie odpowiedzialności – oddzielamy co robi od jak robi. Dzięki czemu nie myślimy o obu tych aspektach na raz i nie konkurują o nasz jednowątkowy mózg. Obecność testu oraz niewielki czas cyklu sprawiają, że ciągle trzymamy się wyznaczonego kierunku.

W kolejnym tekście poznamy lepiej cykl PDSA i dowiemy się jak rozpoznać skalę, w której powinniśmy działać. Co, nawiasem mówiąc, zbuduje nam fundament pod dyskusję o unit testach i testowalności kodu.

O autorze

O autorze

Piotr Podgórski

Praktyk TDD, bez którego nie wyobraża sobie pracy nad czymkolwiek. Propagator metodyk lekkich i zwinnych, bywalec Zwinnej Łodzi. Nie straszna mu praca z kodem legacy, gdzie przyświeca mu odwracanie zależności i myśl Davida Wheelera: Every problem in computer science can be solved by another layer of indirection.

Czysta architektura okiem ekipy migawka.it

Jakie problemy napotkaliśmy na początku? W czym nam pomogła? Kiedy na pewno z niej nie skorzystamy?