"Możesz wydzielić część zadania innej osobie, aby było szybciej?". Wielu programistów słyszało w swoim życiu podobne pytanie co najmniej raz, a najpewniej takich razów było kilka. Natomiast nie tylko osoby techniczne spotykają się z koniecznością odpowiedzi na ten dylemat - równie często, a może i nawet częściej w podobnej sytuacji znajdują się osoby z kadry kierowniczej, którzy rozmawiają z klientem i tłumaczą mu, że realizacji pewnych rzeczy nie da się przyspieszyć. A zleceniodawca pyta "dlaczego?".
No właśnie - jak to jest, że są zadania, które można wykonać szybciej poprzez przydzielenie większej liczby osób, ale są też takie, w których nijak nie da się tego zrealizować? Od czego zależy to, że są tematy, które muszą zostać poprowadzone przez jedną osobę, ale bywają takie, w których mimo ogromnego nakładu pracy, może ona rozłożyć się na kilku programistów? Czy w ogóle są jakieś "wzory" na obliczanie takich rzeczy?
Czy dziewięć kobiet urodzi jedno dziecko w miesiąc?
Na początku trochę teorii, jednak spokojnie - związanej z dzisiejszym tematem, dodatkowo wyjaśniającej słynne zdanie znajdujące się w tytule tego podrozdziału. A jest ono często przywoływane właśnie w kontekście równoczesnej pracy wielu osób i na jego podstawie tłumaczy się absurd w przydzielaniu kilku programistów do zadania, które nijak nie może być podzielone. Aby podejść do tego bardziej naukowo, przywołamy sobie dwa prawa, które pojawiają się w obszarze informatyki zajmującej się problemami zrównoleglania obliczeń. I od razu zastrzegam - będę to tłumaczył tak, że nie zdacie na tej podstawie żadnego egzaminu, więc lepiej uczcie się z poważnych źródeł, a poniższy opis co najwyżej jako metaforę.
Pierwsze z tych praw to prawo Amdahla. Pokazuje ono, że przyspieszenie wykonywania obliczeń (czyli działania programu) ma granicę wynikającą z części, która musi być wykonywana sekwencyjnie. Intuicyjnie pewnie każdy to rozumie, ale na przykładzie - jeśli normalnie obliczenia wykonują się przez 10 godzin, ale fragment 2-godzinny musi być realizowany sekwencyjnie (czyli instrukcja po instrukcji, bez czegoś dziejącego się "w tle"), to niezależnie od tego, ile procesorów zostanie przydzielone, minimalny czas wykonania programu nigdy nie spadnie poniżej tych 2 godzin. Sama teoria związana z tym prawem jest bardziej skomplikowana, ale przywoływanie wzorów nie jest tutaj moim celem.
Bardziej chodzi o przełożenie tego na wykonanie zadań - w tym przypadku procesorami są programiści, a obliczeniami zadanie do realizacji. Załóżmy, że jego czas został oszacowany na 60 godzin i możemy je podzielić na 3 podpunkty. Pierwszy z nich zajmie 30 godzin i musi być wykonany na początku, jednym ciągiem (sekwencyjnie). Druga część wymaga poświęcenia 20 godzin, a trzecia 10 i te fragmenty akurat mogą być realizowane równolegle, gdyż są od siebie niezależne. Pomijając czas poświęcony na scalenie zmian i ich weryfikację, widzimy, że przy działaniu dwóch programistów, całość najmniej wyniesie 30 + 20 = 50 godzin (tutaj jeszcze można zainteresować się tematem metody ścieżki krytycznej, CPM). Dość małe przyspieszenie. Pytanie, co by się działo, gdybyśmy te dwa podpunkty mogli podzielić na jeszcze mniejsze cząstki wykonywane równolegle - być może przy zastosowaniu większej liczby programistów przyspieszenie byłoby większe, jednak nigdy nie spadłoby poniżej 30 godzin. Idąc tym tropem i odpowiadając na pytanie z tytułu sekcji - poproszenie 8 dodatkowych kobiet nie spowoduje, że jedno dziecko urodzi się szybciej, gdyż ta jedna kobieta musi "wykonać to zadanie" sekwencyjnie. To nie jest tak, że jedna kobieta urodzi głowę, druga ręce, trzecia nogi i po połączeniu wysiłków pań powstanie jedno dziecko. Makabryczny przykład, ale mam nadzieję, że rozumiecie, o co mi chodzi.
Drugie z interesujących nas praw, to prawo Gustafsona, które bywa nazywane także prawem Gustafsona-Barsisa. Niesie ono ze sobą bardziej pozytywne hasło - każdy problem może być efektywnie zrównoleglony, o ile jest wystarczająco duży. Brzmi abstrakcyjnie (i w teorii informatyki takie jest), ale ma również odniesienie do praktyki.
Załóżmy, że jako s oznaczymy część wykonywaną sekwencyjnie, a p to część dająca się zrównoleglić przy użyciu n procesorów. W tym przypadku na jednym komputerze czas wykonania będzie wynosić s + n * p, gdyż to, co normalnie robiłoby n procesorów, tutaj musi być realizowane przez jedno CU. Natomiast cała idea polega na tym, że jeśli cały problem będzie wystarczająco duży (co oznacza, że część sekwencyjna będzie w niej coraz mniejsza, gdyż nieprawdopodobne jest, aby zwiększenie rozmiaru projektu powodowało wydłużenie jednego ciągłego procesu), to przyspieszenie w wyniku równoległości będzie dążyło do nieskończoności.
Idąc tropem poprzedniego przykładu - jeśli zadanie dane programistom, które wcześniej wynosiło 60 godzin i było podzielone na jedną część sekwencyjną oraz (następujące po nim) dwie równoległe, zostałoby znacznie rozszerzone i wynosiłoby teraz 1000 godzin, ale mogłoby zostać podzielone na wiele równoległych części, to mimo iż sekwencyjny fragment nadal by istniał (lub doszłyby jego nowe elementy), to stanowiłby mniejszość prac. Tym samym można rzucić więcej programistów do jeszcze większego zadania i uzyskać większe przyspieszenie niż przy mniejszym zadaniu. Ktoś w tym momencie powie, że przecież to oznacza dłuższy czas oczekiwania niż 50 godzin, ale tutaj chodzi o coś innego - im więcej wymagań dorzucimy do projektu, to ten czas 50 godzin nie będzie rósł zgodnie z "dodatkami", tylko przyrost będzie mniejszy. Wracając do przypadku 9 kobiet - prawo Gustafsona oznacza, że po 9 miesiącach 9 kobiet urodzi 9 dzieci. Więcej dzieci oznacza zwiększenie zakresu zadania i, faktycznie, w ten sposób przyspieszenie zostało osiągnięte - nie trzeb czekać 81 miesięcy, tylko 9.
Dlaczego w ogóle o tym piszę? Dlatego, aby pokazać, że problem zrównoleglania prac nie polega tylko na rzuceniu w bój N dodatkowych programistów - dużo zależy także od rodzaju problemu, zidentyfikowaniu części, której nie da się podzielić i musi ją realizować jedna osoba. Prawa Amdahla oraz Gustafsona pokazują, że ma to naukowe podstawy i chociaż oryginalnie te terminy wiążą się z obliczeniami równoległymi oraz procesorami, to w łatwy sposób mogą zostać przeniesione na grunt tworzenia oprogramowania i zarządzania zespołem. Tylko że jest jeszcze coś...
Charakterystyczne cechy zadań programistycznych
Realizacja wymagań funkcjonalnych to nie tylko czas spędzony nad samym projektowaniem i kodowaniem. Wspomniałem wyżej o tym, że nawet w przypadku zrównoleglania prac (a może właśnie z tego powodu) istnieje jeszcze potrzeba ich scalenia oraz sprawdzenia, czy po integracji działają. Kolejna rzecz to przeprowadzenie testów, co też zajmuje czas i wymaga obecności "wszystkiego na miejscu" od programistów (albo przynajmniej znaczącej części). Ale to jeszcze nie jest pełen obraz.
Klienci często zapominają, że developerzy są różni i nie każdy z nich dysponuje odpowiednim doświadczeniem pozwalającym na wykonanie danego zadania efektywnie. Co więcej, poszczególne jednostki znają lepiej jedną część systemu, a gorzej inną. Czasem trzeba też najpierw zaprojektować fragment architektury wykorzystywany przy kilku zadaniach lub mimo równoległości, te zadania w jakiś sposób z siebie "korzystają" (choć to może być objaw błędu w zarządzaniu przydziałem wymagań). Istnieją też wymagania, których nijak nie da się sensownie podzielić, aby nie powodowało to większego zamieszania. Dlatego np. przy ustalaniu zakresu sprintów unikamy proponowania zadań z jednego modułu, nawet jeśli jest on priorytetowy. Istnieje bowiem ryzyko, że poszczególne zadania będą ze sobą tak związane, iż nie będzie możliwe ich rozdzielenie na 2-3 programistów, przez co i tak nie zmieści się w sprincie (i to mimo że oszacowanie czasowe jest mniejsze niż liczba programistów * liczba godzin pracy programisty), a dodatkowo niektórzy będą musieli dostać zadania spoza projektu, aby się nie nudzić. Dlatego często bywa tak, że umawiamy się, iż jeden moduł robimy w połowie w danym sprincie, ale dokładamy do tego połowę drugiego priorytetowego modułu. A w następnym sprincie robimy drugie połowy obu, dzięki czemu mamy zastosowane prawo Gustafsona w praktyce - w ciągu 2 sprintów "urodziliśmy" 2 moduły, ale - tym razem zgodnie z prawem Amdahla - nie bylibyśmy w stanie stworzyć 2 modułów w 1 sprincie wykorzystując 2 razy więcej programistów.
A żeby tego wszystkiego było mało, to przecież realizacją tych zadań i monitorowaniem ich progresu musi ktoś zarządzać. Od tego jest kierownik projektu czy product owner, których pracą jest nadzorowanie, reagowanie na problemy i planowanie przydziału tak, aby dowieźć wartość biznesową w efektywny sposób. Tylko czy zwiększając zespół np. dziesiękrotnie nie powodujemy nawału pracy także u osoby zarządzającej, która ma nagle wiele osób do nadzorowania i odpowiadania na pytania? Być może wówczas pion kierowniczy również powinien zostać zwiększony, ale przecież te osoby też musiałyby się wtedy podzielić oraz wzajemnie informować się o postępach (i to w bardziej złożony sposób niż poprzez stand-up meetingi). Jakby było mało problemów...
Oczywiście, to wszystko zależy od samego projektu i zadań. To bardzo indywidualna kwestia i bywają sytuacje (nawet częste), kiedy jedną dużą funkcjonalność udaje się "upchnąć" w sprincie. Natomiast trzeba pamiętać, że nie zawsze jest to takie proste i możliwe.
Czy podział zadania zawsze jest pozytywny?
Czytając poprzednią sekcję prawdopodobnie wiecie już, że tak nie jest - nie zawsze szukanie na siłę równoległych prac w zadaniu przyniesie oczekiwany skutek. Przykładowo, jeśli tworzymy moduł powiadomień i mamy pięć wymagań związanych z pięcioma różnymi rodzajami powiadomień, to mimo iż w jakiś sposób różnią się one od siebie, to bardzo prawdopodobne jest, że korzystają z podobnych koncepcji, a co za tym idzie - rozwiązań w kodzie. I teraz, rozdzielając te zadania wśród pięciu różnych programistów, oni mogą to zrobić nawet w jeden dzień zamiast w tydzień. Ale jest zagrożenie, że zostanie to okupione powtarzającym się kodem czy różnym sposobem rozwiązania tego samego dylematu, występującego w kilku z tych zadań. Oczywiście, można z tym sobie poradzić bardziej dzieląc punkty funkcjonalne i wyróżniając wspólną część w postaci interfejsu, którego implementacją zajmie się jedna osoba, natomiast nie zawsze warunki pozwalają na to, aby można było się w ten sposób bawić.
Podsumowanie
Oczywiście, nie zrozumcie mnie źle - odpowiedni podział zadań jest kwestią kluczową i jak najbardziej pozwala szybciej tworzyć oprogramowanie bez utraty jakości. Duże projekty są tworzone przez zespoły, a nie jedną osobę i siłą rzeczy wymagania muszą dać się zrównoleglić. Tym niemniej, warto pamiętać o tym, że istnieją naukowe przesłanki mówiące o tym, że to nie jest tak, iż rzucenie kilku dodatkowych programistów magicznie przyspieszy tworzenie oprogramowania w każdej sytuacji. Trzeba umiejętnie zidentyfikować wąskie gardła i rozsądnie gospodarować zasobami. Nie mówiąc już przy tym o kwestiach czysto biznesowych, wiedzy o projekcie, doświadczeniu czy tak prozaicznej rzeczy jak zdolność do wspólnego działania w grupie. Zwyczajnie - nie wszystko da się robić jednocześnie. Ale warto próbować.
Przy pisaniu artykułu wykorzystałem materiały dr. inż. Rafała Walkowiaka z Politechniki Poznańskiej (z przedmiotu "Przetwarzanie równoległe"). Przyznaję, że czasem zaskakuje mnie, jak często wracam do niektórych tematów omawianych na uczelni, które teraz zaczynają układać mi się w całość. Poza tym, standardowo wspomogła mnie Wikipedia oraz takie artykuły, jak choćby ten.
Pozdrawiam i dziękuję - Jakub Rojek.
Komentarze