Category

Agile

Category

Rodzice zawsze mi powtarzali – „ucz się dziecko systematycznie”. Ale dzieci zawsze wiedzą lepiej. Z czasem każdy z nas przekonuje się jednak, że systematyczne podejście ma większy sens niż walka za pięć dwunasta. Co to ma wspólnego z wytwarzaniem oprogramowania? To właśnie temat tego wpisu.

Życie bez ciągłej integracji

Klasyczny proces rozwoju oprogramowania polega na tworzeniu osobnej gałęzi dla każdego projektu. Największym plusem takiego podejścia jest możliwość odłożenia w czasie decyzji, które projekty wejdą w skład wdrożenia. Zamiast planować długofalowo, możemy w oparciu o stan zaawansowania developmentu i testów integrować poszczególne projekty. A jakie są minusy? Scalenie (merge) gałęzi kodu, w których wykonano dużo zmian, jest dość czasochłonne. Zawsze pojawiają się konflikty, wynikające z tego, że jeden fragment kodu został w różny sposób zmodyfikowany w ramach kilku projektów. Liczba takich konfliktów i czas ich rozwiązywania są trudne do przewidzenia. W zależności od wielkości projektu i czasu życia scalanych gałęzi, potrafi to zająć kilka, a nawet kilkanaście dni. Co więcej, proces rozwiązywania konfliktów nie jest bezbłędny. Dość często dochodzi do przypadkowej ingerencji w poprawność procesów biznesowych. Pół biedy, jeżeli po połączeniu projektów wykonujemy pełną procedurę testową. Wtedy tracimy „tylko” czas. Niestety z reguły firmy ograniczają się do weryfikacji regresji. A błędy związane z nowymi zmianami odkrywane są dopiero po wdrożeniu na środowisko produkcyjne.

Co integrujemy?

Ciągła integracja to w zasadzie podstawa nowoczesnego prowadzenia projektów. Zakłada ona, że cały rozwój odbywa się w jednej gałęzi (branchu) kodu. Takie podejście bardzo ogranicza liczbę konfliktów i praktycznie do zera eliminuje problematyczne merge. Szansa, że podczas dwu-, trzymiesięcznej fazy developmentu, dwóch programistów zmodyfikuje tę samą metodę jest bardzo duża. Szansa, że zrobią to tego samego dnia, jest już zdecydowanie mniejsza. Właśnie na takiej systematyce opiera się ciągła integracja. Dodatkowo, skoro mamy już wszystkie zmiany w jednej gałęzi, możemy iść o krok dalej. Standardem jest regularne (nawet po każdej integracji) kompilowanie aplikacji i uruchamianie choćby podstawowego zestawu testów. Zbudowaną paczkę można od razu wdrożyć na środowisko stage’ingowe (preprodukcyjne). Tutaj można na bieżąco weryfikować implementowane zmiany.

Co znaczy ciągle?

Tutaj ważny jest zdrowy rozsądek. Integrowanie zmian co miesiąc jest lepsze niż co dwa miesiące. A robienie tego raz w tygodniu jest lepsze niż raz w miesiącu. Ideałem, do którego dążymy, jest wykonywanie tego codziennie. Standardowy proces polega na pracy w branchu utworzonym specjalnie na potrzeby danej zmiany. Następnie tworzymy pull-request (czyli żądanie integracji). Zmieniony kod jest przeglądany (proces code-review), w celu wykrycia potencjalnych błędów, niespójności czy możliwych usprawnień. W zależności od wyniku przeglądu wraca do poprawy lub jest integrowany. Czy koniecznie trzeba to robić na koniec każdego dnia pracy? Oczywiście nie. Jeżeli wykonanie sensownej zmiany zajmie dwa dni, to właśnie po takim czasie rozpoczynamy proces.

Duże zmiany

A jak radzić sobie z dużymi zmianami? Nie wszystkie prace da się przecież podzielić na małe zadania. Czy wtedy rezygnujemy z ciągłej integracji? Oczywiście, że nie. Zastanówmy się, czy wrzucenie nieskończonego lub nawet niedziałającego kodu jest problematyczne? Na pierwszy rzut oka tak. Jednak czy na pewno? Problem stanowi tylko użycie takiego kodu. Tak długo, jak nie jest on wywoływany, wszystko jest w porządku. Najprostszym sposobem byłoby jego zakomentowanie, jednak to wyklucza nawigowanie po nim czy testowanie go. Zdecydowanie lepszym pomysłem jest zastosowanie feature flag. Działają one na zasadzie przełącznika, który aktywuje lub dezaktywuje określone fragmenty kodu. To z kolei daje nam realną możliwość zastosowania ciągłej integracji, nawet w przypadku długo trwających implementacji.

Jeżeli myślimy o pracy w metodykach zwinnych, to właśnie od wdrożenia praktyki ciągłej integracji powinniśmy zacząć. Bez tego uzyskanie szybkiego feedbacku, który jest podstawą agile, nie będzie możliwe.

Wyobraź sobie, że piszesz książkę. Zarywasz noce, zalewasz zmęczenie kolejnymi kubkami kawy, przemywasz przekrwione oczy zimną wodą… Po kilku miesiącach takiego maratonu – jest! Wysyłasz do najbliższych przyjaciół i z niecierpliwością czekasz na wyrok. Kilka rozmów później już wiesz, że lektura jest ciekawa, ambitna, nowatorska… ale? Ale byłaby znacznie lepsza, gdyby główny bohater był dojrzałą kobietą, a nie młodym chłopakiem. I co teraz? Przepisywać całą książkę? Ach, gdybyś tylko wiedział to wcześniej! Może po pierwszym rozdziale? Zaraz, zaraz, to czemu nie wysłałeś pierwszego rozdziału zaraz po jego ukończeniu?

Czemu nie uczymy się na cudzych błędach? Nie wyciągamy pomocnych wniosków, zanim doprowadzimy siebie i otoczenie do łez i frustracji? I jak możemy działać inaczej?

Spróbujmy przyjrzeć się popularnym podejściom do przyrostowego rozwoju produktu. Główną siłą napędową metodyk zwinnych jest szybka informacja zwrotna. Użytkownik otrzymuje „wydmuszkę” zaślepionej funkcji, która jednak pozwala mu choćby w minimalnym zakresie ją wykorzystać czy przetestować. Taką właśnie rolę pełni pierwszy napisany rozdział książki. Podobnie będzie wyglądało projektowanie środków komunikacji pokazane na poniższym rysunku.

Zakładając, że potrzebą użytkownika jest przemieszczenie się z miejsca na miejsce, możemy uznać, iż przyjęty harmonogram prac jest daleki od ideału. Pierwsze użycie produktu może nastąpić dopiero po ostatniej fazie projektu, czyli montażu kół. Analogicznie w projektach programistycznych jest to koncentracja na kwestiach technicznych. Zaczynamy od utworzenia modelu bazy danych, później implementujemy warstwę serwisową, a na samym końcu dostarczamy interfejs użytkownika. Dopiero teraz pozwalamy mu zapoznać się z budowanym (a w zasadzie już zbudowanym) systemem.

Jak inaczej zatem można planować pracę? Najczęściej spotykanym modelem iteracji jest ten przedstawiony poniżej.

Tutaj sytuacja pozornie wygląda znacznie lepiej. Już od samego początku użytkownik jest w stanie realizować swoją najważniejszą potrzebę, czyli przemieszczenie się z miejsca A do miejsca B. Czemu zatem pozornie? Problem w powyższym podejściu polega na porzucaniu wyników poprzednich iteracji. Oczywiście, w mocy pozostają retrospekcje i informacja zwrotna od użytkowników, jednak sam produkt jest realizowany poniekąd od zera. W przypadku projektów informatycznych problem taki wynika głównie z braku strategicznego planowania produktu, które broni się w zasadzie jedynie w przypadku niezwykle innowacyjnych projektów, przy których nie do końca jesteśmy w stanie przewidzieć zachowania, czy nawet potrzeb potencjalnych klientów.

Rozwiązanie idealne? Oczywiście wiemy, że takie nie istnieje. Natomiast w większości przypadków najlepiej sprawdza się sposób pokazany poniżej.

Klient otrzymuje podstawowy wariant realizacji wymagań już po pierwszej iteracji projektu. Co prawda, jechać można tylko do przodu i w dodatku „na pych”, ale worki z cementem możemy przetransportować na drugi koniec hali dość sprawnie. Po kolejnej iteracji możemy już kierować (okazało się to dość dużym brakiem pierwszej wersji) i siedzieć. Może nawet zakończymy projekt, bo klient nazwie to „buggy” i oceni, że całkowicie spełnia jego potrzeby? Jeżeli później przyjdzie jesień i okaże się, że karoseria i szyby chroniące przed deszczem jednak nie są przereklamowane, nic nie stoi na przeszkodzie, aby je „zaimplementować”.

Przenieśmy to teraz na grunt projektów software. Otrzymując jedynie interfejs użytkownika formularza rejestracji konta (zwracający zawsze informację, że wszystko zakończyło się sukcesem i nawet nie próbujący zapisywać nic do bazy danych), jesteśmy w stanie ocenić ergonomię rozwiązania. Jeżeli teraz okaże się, iż pól jest zbyt dużo i należy podzielić formularz na dwa rozdzielone kroki (np. jeden dostępny ze strony głównej, a drugi wyświetlany po pierwszym zalogowaniu do aplikacji), to dowiedzieliśmy się o tym, zanim wykonaliśmy jakąkolwiek implementację. Zmiana kodu, który jeszcze nie istnieje, nic nie kosztuje. Jeżeli jednak o tym samym dowiedzieliśmy się po dostarczeniu całej funkcji, podział formularza na dwa, zmiana walidacji i przepisanie API łączyłoby się z większym nakładem pracy i prawdopodobnie potraktowane zostanie jako zmiana wymagań. To właśnie takie modyfikacje opóźniają projekty i powodują dostarczanie rozwiązań dalekich od optymalnych.

Przytoczony na początku przykład z książką wyraźnie pokazuje, że im wcześniej uzyskamy informację zwrotną dotyczącą wykonanego produktu, tym bardziej efekt będzie dopasowany do potrzeb końcowego użytkownika. Co równie ważne, satysfakcja z wykonanej pracy będzie znacznie większa, a koszt jej wykonania zauważalnie niższy niż przy klasycznym podejściu, które przeprowadzenie ewaluacji zostawia na sam koniec. Oczywiście, nawet najbardziej zwinny system pisania książki nie pomoże, o ile recenzent odmówi czytania pojedynczych rozdziałów i postanowi czekać na ukończoną powieść. Ale warto próbować!

Pamiętajmy – miesiąc prac i planowania może zaoszczędzić nam godzinę poświęconą na uzyskanie opinii użytkownika 🙂