Wytwarzanie oprogramowania jest zagadnieniem złożonym, bo zmienność i nieprzewidywalność dotyka wszystkich istotnych jego aspektów: od technologii, poprzez zaangażowanych w ten proces ludzi, po trendy na rynku doprowadzające do zmiany potrzeb, jakie produkt ma spełniać. Najmniej stabilnym elementem stają się wymagania, w których przypadku jednego można być pewnym: na pewno ulegną zmianie, nieznany jest tylko zakres tej zmiany i moment, w którym ona nastąpi.

Udajemy, że słonia nie ma w pokoju

Największą słabością tradycyjnie realizowanych projektów jest to, że nie ma w nich sensownych mechanizmów radzenia sobie ze zmiennością. Opierają się one na dokładnych analizach problemów, projektowaniu na ich podstawie docelowych rozwiązań i szczegółowym planowaniu ich realizacji. Wszelkie założenia obłożone są stosownymi „marginesami bezpieczeństwa” na wypadek, gdyby coś trzeba było w planie zmienić.

Zwolennicy tych metod stawiają tezę, że każdą potrzebę zmiany da się przewidzieć, a wtedy można z góry zaplanować sposób zareagowania na nią. Negują istnienie problemów złożonych, bo uważają, że trudności w realizacji planów wynikają zawsze z błędów w przygotowaniu projektu (np. źle przeprowadzonej lub niewystarczającej analizy). Twierdzą, że dysponując odpowiednią ilością czasu i niezbędnymi kompetencjami zawsze da się stworzyć taki plan, który nie będzie wymagał żadnych istotnych zmian na etapie realizacji.

Niestety, rzeczywistość co rusz udowadnia, że niektóre domeny są obciążone niestabilną złożonością niezależnie od tego, czy funkcjonujący w niej ludzie są gotowi to zaakceptować, czy będą temu zaprzeczać. Jedną z nich jest właśnie wytwarzanie oprogramowania, które samo w sobie, jako produkt, jest bardzo skomplikowane, korzysta z szybko zmieniającej się technologii i musi być poddawane częstym modyfikacjom, by nadążyć za ciągle pojawiającymi się nowymi potrzebami użytkowników. Nikt rozsądny nie postawi raczej tezy, że jest w stanie zaplanować rozwój software’u z rocznym lub dwuletnim wyprzedzeniem.

Dlatego niewiele projektów informatycznych udaje się zrealizować w tradycyjny sposób bez wprowadzania fundamentalnych zmian w ich trakcie. Zwykle dotyczy to bardzo specyficznych rozwiązań, np. zamkniętej technologicznie linii produkcyjnej, w której wprowadza się drobne usprawnienia. Poziom stabilności takiego środowiska jest na tyle wysoki, a zakres możliwych zmian na tyle niewielki, że planowanie długoterminowe okazuje się skuteczne. To jednak wyjątek, a nie reguła przy budowaniu software’u.

Z kronikarskiego obowiązku trzeba jednak powiedzieć, że udają się czasami również bardzo duże projekty o ogromnej złożoności, która mocno wpływa na przebieg działań. Jakim cudem? Otóż te projekty są przygotowane na radzenie sobie ze złożonością, której zaprzeczają. Nie przewidują zbudowania jednego konkretnego rozwiązania wedle zoptymalizowanego planu, ale dopuszczają kilka lub kilkanaście wariantów przebiegu prac i różne możliwe stany docelowe produktu. Przy odrobinie szczęścia jeden z nich okaże się tym właściwym, czyż nie?

Generuje to oczywiście pokaźne koszty, zabiera sporo czasu i wymaga niebagatelnych środków, więc na ten sposób postępowania pozwolić sobie mogą nieliczne organizacje. Ich kierownictwo często mówi, że „działamy jak dobrze naoliwiona maszyneria, dlatego potrafimy świetnie planować projekty i żadnej domniemanej złożoności się nie boimy”. Za pierwszym razem, gdy zetknąłem się z czymś takim, wydawało mi się to nawet zabawne. Potem jednak zacząłem być przerażony poziomem marnotrawstwa środków, czasu i możliwości, jaki to powoduje.

Jedzmy słonia po kawałku

Agile nie próbuje walczyć z rzeczywistością ani przewidywać odległej przyszłości. Skoro zmienność jest czymś nieuniknionym, należy rozwijać produkt małymi kroczkami, po każdym z nich sprawdzając cztery rzeczy:

  • Czy to, co udało się zrobić, jest właściwym rozwiązaniem?
  • Czy dzięki temu rozwiązaniu udało się zbliżyć do osiągnięcia wytyczonego celu?
  • Czy cel nadal jest aktualny, a jeśli nie, co jest tym celem teraz?
  • Czy plany dalszego rozwoju produktu są aktualne, a jeśli nie, jak należy je zmienić?

W ten sposób podąża się w stronę przyjętego celu bez prób określenia z góry, jaka droga najszybciej do niego doprowadzi. Planowanie szczegółowe dotyczy tylko bieżącej iteracji, która jest na tyle krótka, że konieczność dokonania jakiejś fundamentalnej ich zmiany jest niewielkie. A nawet jeśli trzeba jej będzie dokonać, czyli jeśli iteracja okaże się „źle zaplanowana”, straty są ograniczone do kosztów przeprowadzenia tej jednej iteracji. W kolejnej można pójść w zupełnie inną stronę.

Istnieje też oczywiście plan długofalowy, on jednak nie jest przesadnie precyzyjny. Rzeczy, które będą realizowane już za moment, są zdefiniowane dość szczegółowo. Pozostałe – całkiem ogólnie, bo przecież nie wiadomo, czy nie okażą się zbędne, jeśli zmienność środowiska wymusi dostosowanie planów.

Co steruje rozwojem planu długofalowego i decyzjami Zespołu odnośnie do zakresu prac w kolejnych krótkich iteracjach? Nie założenia i szczegółowe analizy, które mogą okazać się błędne, ale to, co wydarzyło się do tej pory. Zespół porusza się do przodu, gromadząc wiedzę i doświadczenia przed zrobieniem każdego z nich.

Jeśli pojawi się duży problem, który zawiera wiele niewiadomych, zamiast kompleksowej analizy i próby wymyślenia rozwiązania, Zespół dokonuje dekompozycji tego problemu. W ten sposób jedno zagadnienie rozkładane jest na serię mniejszych, z którymi można sobie łatwiej poradzić. To zwiększa prawdopodobieństwo, że podjęte decyzje będą trafne.

Co więcej, po zrealizowaniu każdego takiego małego elementu – w ramach krótkiej iteracji – da się sprawdzić, jaki efekt udało się w ten sposób uzyskać. Zdarza się, że w wyniku dekompozycji złożonego problemu i rozwiązywania go poprzez serię małych zmian w produkcie, ujawni się coś, o czym nikt wcześniej nie wiedział. Im szybciej Zespół to odkryje, tym wcześniej dopasuje do tego plan dalszego rozwoju produktu.

W szczególności to pozwala szybciej wycofać się z błędnych decyzji, ale też często wcześniej zidentyfikować sposób prostszego rozwiązania skomplikowanych problemów. Co oznacza, że zmienność i nieprzewidywalność, która w metodach klasycznych jest w najlepszym razie ignorowana, a najczęściej stanowi problem, w podejściu zwinnym jest czymś nie tylko normalnym, ale może czasami wręcz przynosić wymierne korzyści, o ile Zespół potrafi po nie sięgnąć.

Agile nie wymaga też pozostawiania żadnych „marginesów bezpieczeństwa”, które służą zwykle do radzenia sobie z niespodziewaną zmianą. Są one zbędne w procesie, w którym zmiana jest czymś naturalnym i spodziewanym.

Czy można zrobić sobie krzywdę na własne życzenie?

Agile działa dzięki rozłożeniu złożonego problemu na serię zagadnień prostszych, z którymi Zespół poradzić sobie może łatwiej. Nie jest natomiast prawdą, że Agile redukuje złożoność – czyli że usuwa ją w jakiś magiczny sposób.

To oznacza, że rozwój oprogramowania pozostanie złożonym problemem również wtedy, gdy odbędzie się to z użyciem metod zwinnych. To, co one dadzą, to empiryczna kontrola procesu, w którym software powstaje, dzięki czemu Zespół albo Zespoły będą sobie ze złożonością radzić lepiej.

Piszę o tym, bo zdarza mi się spotykać osoby posługujące się taką oto logiką: skoro zmagamy się ze złożonością i nie da się niczego planować długoterminowo, użyjmy metod Agile, żeby tę złożoność usunąć i dzięki temu planowanie długoterminowe umożliwić. Cóż, to nie zadziała w ten sposób…

Można też utonąć w złożoności mimo zastosowania metod zwinnych, jeśli sposób działania Zespołu i organizacji będzie generował niepotrzebny chaos. A w dużych firmach zdarza się to często. Przykładem może być wymuszanie na wszystkich Zespołach, by „działały tak samo”, choć przecież każdy z nich składa się z innych ludzi, zajmujących się różnymi problemami. Jakim cudem „jedynie słuszna jedna metoda”, choćby zwinna, miałaby pasować im wszystkim? Prawdopodobnie nie będzie pasować nikomu, przez co możliwości skutecznego działania wszystkich Zespołów będą ograniczone, a potencjał używanych (w marny sposób) metod zwinnych zostanie całkowicie zmarnowany.

Przepisów na katastrofy tego rodzaju można zresztą podać więcej: narzucanie Zespołom narzędzi i praktyk developerskich, sterowanie rozwojem produktu za pomocą miar produktywności, zamiast miar wartości, odcięcie Zespołów (a zwłaszcza Developerów w nich) od kontaktu z interesariuszami… długo można by wymieniać. Agile jest narzędziem, które musi zostać dobrze użyte, jeśli ma przynieść wymierne korzyści.

Raz-a-dobrze!

Pozostańmy przy katastrofalnych błędach, jakie popełniają organizacje i Zespoły.

Pierwszym gwoździem do trumny oprogramowania jest zwykle raz-a-dobrzyzm, czyli próba takiego definiowania wymagań, by zrealizować opisaną nimi funkcjonalność ciągiem, bez konieczności dokonywania dalszych zmian. Zamiast zrobić najprostszą działającą wersję szybko, po czym empirycznie ją udoskonalać, wymagania w Backlogu Produktu są polerowane dotąd, aż każde z nich będzie opisywać finalną wersję jakiegoś rozwiązania.

A gdy już nastąpi ich realizacja, powstaje rozwiązanie „gotowe na przyszłe wyzwania” – w znacznej mierze zbyt rozbudowane, jak na bieżące potrzeby, kosztowne w utrzymaniu oraz… ostatecznie niewłaściwe i wymagające zmiany, gdy przyszłe wyzwania złośliwie rozminą się z „gotowością” na nie.

Zachowując mechanikę metody zwinnej (bo wciąż pracujemy iteracyjnie, wciąż niby rozwijamy produkt inkrementalnie), tak naprawdę rugujemy w ten sposób empiryzm z procesu. Wciąż bowiem tworzony jest kompletny plan działania, ma jedynie inną formę (np. Backlogu Produktu w Scrumie). Tymczasem to nie forma zapisu wymagań czyni proces zwinnym, ale sposób pracy z tymi wymaganiami.

Koszt takiego postępowania? Mnóstwo czasu poświęcone zostanie na analizy i dyskusje oparte na projektach i założeniach, a nie na czymś, co już działa i co wiadomo na pewno. Do tego pojawia się ryzyko, że gdy już ukończona zostanie ta pozornie finalna wersja, wtedy i tak trzeba będzie ją poprawiać – bo zupełnie inaczej ocenia się projekty niż działający, dający się użyć kawałek oprogramowania.

Ten sposób pseudozwinnego działania generuje też dodatkowe problemy. Można w nim zapomnieć o krótkich iteracjach, bo wymagania są zbyt opasłe. Rośnie też złożoność, ponieważ raz-a-dobrze zdefiniowanych rozwiązań nie buduje się przyrostowo (począwszy od najprostszej wersji), tylko od razu w formie docelowej. A to wymaga sporo wiedzy, koordynowania prac wielu Developerów, skomplikowanych testów, ciężkiej integracji złożonych systemów itd.

Rozwiązaniem jest oczywiście takie dekomponowanie dużych wymagań, by najpierw zrobić najprostsze działające rozwiązanie, które potwierdzi, że w ogóle da się to zrobić i warto kontynuować. Potem dodawać do niego coraz to nowe cechy i funkcjonalności, tak by wartość biznesowa rosła, a produkt cały czas działał. A gdy już będzie dość dobry, należy go wydać i sprawdzić, jak zostanie przyjęty na rynku (lub przez klientów, użytkowników, odbiorców).

Tyraliera

Innym pomysłem na zabicie zwinności jest realizacja wymagań nie sekwencyjnie w kolejności, w jakiej umieszczone były w Backlogu Produktu, ale równolegle – tyle na raz, ile tylko się da. Daje to pozory uzyskania szybszego postępu prac, ponieważ – w początkowych etapach iteracji – rzeczywiście dużo się dzieje, praca wre. Taki na przykład Kanban wymaga, by świadomie kontrolować ilość pracy wykonywanej równocześnie, ale metody zwinne nie wymagają posługiwania się w nich Kanbanem. Więc skoro można robić wszystko na raz, to wiele Zespołów tak właśnie robi.

Warto wszakże zadać sobie trud zrozumienia stosowanej metody, nie zaś jedynie jej mechaniki. Przykładowo, ograniczanie ilości pracy w toku jest wpisane w DNA najpopularniejszej metody Agile, jaką jest Scrum, jedną z jego wartości jest bowiem skupienie (ang. focus). Poza tym Backlog Produktu jest uporządkowaną listą określającą kolejność realizacji umieszczonych na niej elementów również nie bez przyczyny – te elementy powinny być realizowane po kolei, a nie hurtowo…

Wracając do rozwinięcia się Developerów w tyralierę: w końcu prowadzi to do kłopotów, bo nie sztuka zacząć realizację wielu wymagań naraz, bywa sztuką wszystkie ukończyć. Im więcej zmian w produkcie dokonywanych jest równolegle, tym większa jest szansa na pojawienie się zależności lub interferencji między wykonywanymi przez różnych Developerów czynnościami. Jeśli pracują oni wspólnie nad jakimś oprogramowaniem, nieuniknione jest, że niektóre zmiany w kodzie lub konfiguracji będą powodować konflikty wymagające rozwiązania. Konieczne też będzie konkurowanie o zasoby takie jak środowiska testowe, dostęp do narzędzi, moc obliczeniową maszyn i tak dalej.

Koszt takiego postępowania? Pojawi się praca związana z rozwiązywaniem zależności i koordynowaniem równolegle prowadzonych działań. Do tego wspólnota pracy, tak istotna zwłaszcza w metodzie Scrum, może stać się zupełnie niepotrzebna – bo każdy robi co innego. Spada przejrzystość, bo Developerzy robią zbyt wiele rzeczy naraz i niekoniecznie rozumieją „nie swoje” wymagania. Rośnie więc złożoność, której i bez tego mieli w nadmiarze (dlatego zapewne sięgnęli po Scrum lub inną metodę Agile).

Skutki? Ukaskadowiony zostaje proces, ponieważ całość testowania zostanie zepchnięta na ostatnie dni iteracji. To powoduje, że niemal do samego końca nie wiadomo, czy produkt uda się poskładać w działające rozwiązanie – jedno, przetestowane, spełniające jakieś kryteria biznesowe i techniczne (w Scrumie powiemy, że spełniające wymogi Definicji Ukończenia).

Rozwiązaniem jest sekwencyjne realizowanie wymagań; model idealny to skupienie się całego Zespołu nad jednym wymaganiem na raz. Dobrą zasadą może być zadawanie sobie przez Developerów prostego pytania: czy jeśli nie rozpocznę pracy nad kolejnym wymaganiem, będę siedział bezczynnie? Prawie zawsze okaże się, że jest jeszcze sporo do zrobienia z tym wymaganiem, które już jest realizowane, nie ma więc powodu, by dokładać złożoności, zaczynając kolejne.

Wszystkie łapki na klawiaturę!

Pewnym wariantem pracy tyralierą jest presja organizacji na to, by wszyscy byli nieustannie zajęci. Czasami wynika to z przyzwyczajeń samych Developerów, którzy nauczeni (aż kusi mnie napisać „wytresowani”) w różnych firmach, nieswojo czują się, gdy przez moment – kilka godzin – nie mają nic do zrobienia.

Jeśli każdy musi być nieustannie zajęty, pojawia się ochota, by pracować wspomnianą wcześniej tyralierą. Jeśli wszakże Zespół jest na tyle dojrzały, żeby tego nie robić, wciąż w dążeniu do pełnej zajętości zacznie kopać grób na swoją zwinność. Aby zapewnić sobie tę zajętość, będzie na przykład brał do iteracji więcej wymagań, niż realnie jest w stanie ukończyć – bez wątpienia to spowoduje, że pracy będzie aż nadto, niekoniecznie natomiast powstanie w jej wyniku działający produkt.

Innym pomysłem jest przełączanie się między różnymi tematami tak, żeby „maksymalnie wykorzystać umiejętności i czas”. Pomijając już potencjalną szkodliwość takiego postępowania dla Zespołu – który w tym modelu, zamiast rozbijać silosy kompetencyjne, będzie starał się z nich jak najefektywniej korzystać – pojawi się marnotrawstwo wynikające z przełączania między kontekstami.

Rosła też będzie, a jakże, złożoność: nieukończone rozwiązanie jest przekazywane do dalszych prac między ludźmi o różnych kompetencjach na zasadzie kaskady, co znakomicie utrudnia planowanie działań w iteracji. Im bliżej końca iteracji, tym trudniej tak poukładać pracę, żeby z jednej strony wszyscy mieli co robić, z drugiej optymalnie dobrać wykonawców do charakteru realizowanego zadania, z trzeciej strony poskładać produkt w całość.

Koszty takiego postępowania? Dławienie się nadmiarem pracy (wciągniętej do iteracji) prowokuje do zdążenia za wszelką cenę kosztem jakości. Przełączanie między kontekstami, poza marnowaniem czasu, prowadzi do błędów, nieporozumień, przeoczeń. Nierzadko Developerzy na siłę robią coś niepotrzebnego, jeśli tylko w ten sposób będą mieli co robić.

Problemy: chaotyczne końcówki iteracji, gdzie rzutem na taśmę udaje się jakoś poskładać produkt. Poza tym dążenie do pełnej zajętości (w domyśle: zajętości pracą nad wymaganiami biznesowymi) skutkuje często zawieszeniem na kołku wszystkich usprawnień, jakie udało się wymyślić. Bo, paradoksalnie, nie ma na nie czasu.

Rozwiązaniem znów jest ograniczenie liczby rzeczy wykonywanych jednocześnie, ale też takie definiowanie sposobu pracy w iteracji, aby nie były one tworzone „dla Miecia”, tylko dla całego Zespołu. W ten sposób możliwe stanie się, że Developerzy będą pracować nad wymaganiami wspólnie, a nie „każdy nad swoim”. Pozorne przestojów (ang. slack time) można wykorzystać na realizację usprawnień. Dobrym pomysłem jest też wykorzystanie takich praktyk jak pair programming, mob programming czy swarming.

Jak uniknąć błędów?

Próbując uniknąć błędów za wszelką cenę, możemy zostać sparaliżowani strachem przed dokonywaniem zmian i eksperymentowaniem z różnymi rozwiązaniami. Przede wszystkim należy zadbać o przejrzystość, żeby możliwe było zobaczenie rzeczywistego stanu spraw (procesu, produktu, możliwości Zespołu itd.). Scrum i inne metody zwinne mają duży potencjał ujawniania deficytów Zespołu i dysfunkcji organizacyjnych – czasami wręcz uderza ich objawami prosto w twarz. To pozwala usuwać istniejące ograniczenia, o ile zapewnimy przejrzystość, a potem nie zamieciemy widocznych problemów pod dywan.

Drugim kluczowym elementem jest przyjęcie jako pewnik, że rozwój oprogramowania to domena złożona. Stosujmy w niej metody radzące sobie ze złożonością i mające odpowiednie mechanizmy, by to czynić. Szukajmy i eliminujmy z procesu to, co może wpychać nas na powrót w podejście oparte na przewidywaniu, założeniach, ciężkich analizach i detalicznym planowaniu z góry. Nie dokładajmy sobie złożoności sposobem działania, a jeśli tak się stanie, doskonalmy proces, by to eliminować.

Trzecim elementem jest wykorzystanie empiryzmu do poszukiwania najlepszych rozwiązań nie tylko w produkcie, ale również procesowych. Jeśli dziś niekoniecznie robimy coś dobrze, spróbujmy innego podejścia, oceńmy skutki tej zmiany, dokonajmy kolejnej, i jeszcze kolejnej… iteracja po iteracji. Kto powiedział, że tylko produkt można rozwijać iteracyjnie i inkrementalnie?

Wiedza jest czwartym, zupełnie podstawowym elementem. Bez wiedzy i zrozumienia czym jest Agile, ciężko wykorzystać go do radzenia sobie ze złożonością.