Sztuka podejmowania decyzji - okładka

Czy później znaczy lepiej? O sztuce podejmowania decyzji

Opublikowano Kategorie Praca w ITCzas czytania 11min

W życiu pewne są tylko śmierć i podatki. Zmienność w projektach IT, szczególnie tych, których początki nie sięgają pamięcią do czasów Cobola czy Fortrana, sprawia, że zbyt wczesne podejmowanie decyzji może skończyć się zbędną pracą i kosztami. Rozwijanie projektów w IT, w przeciwieństwie do np. architektury budowlanej pozwala na odkładanie decyzji w czasie, z czego zdecydowanie warto korzystać. Więcej mówi o tym Jakub Kubryński na swojej prezentacji na Confiturze 2022 – Architektura i architekt AD2022.

Ten artykuł przyda Ci się, szczególnie gdy będziesz miał(a) okazję pracować z greenfieldem. Mam jednak nadzieję, że i w przypadku brownfieldów przedstawione praktyki będą dla Ciebie przydatne. Opisane podejście staram się wykorzystywać w swojej codziennej pracy.

Niektóre decyzje w projekcie są już podjęte lub muszą być podjęte na samym początku. Może to być język programowania, niektóre wykorzystane technologie, miejsce działania aplikacji, czy inne decyzje, które obwarowane są np. wymogami klienta czy polityką firmy. W tym artykule zakładam, że mowa o decyzjach, na które Ty jako programista/programistka masz wpływ.

Odwlekanie decyzji w czasie na przykładzie

Decyzją, którą moim zdaniem warto odłożyć w czasie, jest wybór zewnętrznych komponentów, z jakimi pracuje aplikacja. Mam tu na myśli komponenty takie jak baza danych, system kolejek, storage, technologia do zarządzania cache itd.

Weźmy pierwszy przykład z brzegu, czyli bazę danych. Na samym początku nie zawsze wiadomo jakie dane będą przechowywane w bazie, jaki format danych będzie przechowywany, czy będą to dane ustrukturyzowane czy nie oraz ile będzie danych. Nawet jeśli mamy pewne założenia, to w trakcie implementacji poszczególnych funkcjonalności może okazać się, że założenia zostaną poddane weryfikacji i konieczne będzie dostosowanie się. Startując od pustej kartki rozwiązaniem, które można wykorzystać, jest zastąpienie fizycznej bazy tymczasową implementacją w pamięci (in-memory).

Oczywiście wadą jest ulotność danych przy restarcie aplikacji, pytanie jednak brzmi czy na etapie developmentu nas to boli? Nawet jeśli na środowisko developerskie wpuścimy zespół QA i zaczną testować niegotową aplikację, to na potrzeby testów i developmentu takie rozwiązanie jest według mnie wystarczające. Dopiero mając aplikację na etapie MVP, można wrócić do myślenia o wyborze silnika bazy danych i faktycznej implementacji. Mając już DZIAŁAJĄCY produkt mamy do dyspozycji znacznie więcej wiedzy i informacji o specyfice danych i potrzebach samej aplikacji.

Decydując się na to by pójść w konkretne rozwiązanie w ciemno i bazując na „wydajemisię” ryzykujemy tym, że zaczniemy pierwsze duże refaktory jeszcze przed pierwszym releasem produkcyjnym. W międzyczasie może zmienić się struktura danych, co w przypadku np. baz relacyjnych wymaga zmian w tabelach, relacjach czy formacie danych. W przypadku bazy in-memory sprowadza się to często do drobnych zmian w implementacji. Może się też okazać w trakcie postępu prac, że relacyjna baza danych nie nadaje się do tego konkretnego problemu. Ponownie, w przypadku bazy in-memory to nie będzie problem.

Nawet patrząc na sam czas developmentu, jaki trzeba poświęcić na wdrożenie rzeczywistej bazy danych, a na napisanie implementacji in-memory, to to drugie wychodzi korzystniej. Optymalizując czas dowiezienia funkcjonalności biznesowych sprawiamy, że jesteśmy w stanie zwalidować rozwiązania biznesowe na prawdziwej aplikacji, a nie na papierze czy w głowie. Jesteśmy to w stanie zrobić bez poświęcenia kupy czasu na rzeźbienie tabel czy stawianiu i konfiguracji bazy.

Kolejnym argumentem za tym podejściem jest to, jak postrzega nasz szeroko rozumiany „biznes”. Przeanalizuj dwa warianty sytuacji:

  1. Zespół poświęcił dwa tygodnie na rzeźbienie tabel w SQL-u i po skończonej iteracji pokazuje piękny diagram baz danych.
  2. Zespół poświęcił dzień na napisanie implementacji bazy danych in-memory a resztę czasu na implementację funkcjonalności kluczowych z punktu widzenia biznesu. Po skończonej iteracji zespół może pochwalić się prototypem realizującym podstawowe założenia biznesowe. Oczywiście taki prototyp jest zabugowany, klejony taśmą i trytkami, ale działa i pozwala zebrać feedback od biznesu i pracować dalej.

Osobiście wolałbym być w zespole numer 2. Trzeba tu podkreślić, że biznes musi być świadom dokonanych uproszczeń i skrótów, na jakim etapie jest projekt, co trzeba będzie zrobić przez uruchomieniem aplikacji itd.

Podobnie jak do opisanego przykładu z bazami danych pochodziłbym do innych wspomnianych wcześniej komponentów, elementów infrastruktury, a nawet architektury. Co więcej, dobra architektura powinna uwzględniać możliwość odwlekania decyzji w czasie. Pisał o tym Robert C. Martin w Clean Architecture:

The purpose of a good architecture is to defer decisions, delay decisions. The job of an architect is not to make decisions, the job of an architect is to build a structure that allows decisions to be delayed as long as possible. Why? Because when you delay a decision, you have more information when it comes time to make it.

Na zakończenie tej części artykułu, po raz kolejny odsyłam do prezentacji Jakuba Kubryńskiego Architektura i architekt AD2022. Szczególnie w pamięci zapadły mi słowa Jakuba:

Decyzje mają to do siebie, że im później się je podejmuje, tym rzadziej się je zmienia.

Optymalizacja

Będąc przy temacie odkładania decyzji w czasie i optymalizacji byłoby faux pas, by nie podać cytatu Donalda Knutha:

Premature optimization is the root of all evil.

Oczywiście, żeby nie było zbyt kolorowo, to nie uważam tematu przedwczesnej optymalizacji za zero-jedynkowy. To zależy 🙂

Przykłady, gdy optymalizacja na bardzo wczesnym etapie ma sens, widzę dwa. Pierwszym z nich jest nawet nie tyle optymalizacja, ile testowanie efektywności danego rozwiązania. Jeśli projektujemy jakiś algorytm, to jednym z kluczowych aspektów będzie jego wydajność. Załóżmy, że mamy do przygotowania algorytm parsujący tablicę z daną strukturą danych. Testy wykazały, że algorytm zaczyna „mulić” już przy kilku elementach. W takim przypadku nie ma mowy o przedwczesnej optymalizacji, tylko o nieefektywnym rozwiązaniu, które nie powinno wylądować na produkcji.

Inaczej moim zdaniem sytuacja by wyglądała, gdyby się okazało, że algorytm staje się nieefektywny przy kilkuset elementach. Pytanie brzmi, jak często algorytm będzie uruchamiany z tak dużym zestawem danych i jak nieefektywność algorytmu będzie wpływała na użytkownika końcowego. W takim wariancie optowałbym za podejściem „jeździć i obserwować”. Będzie wtedy czas na zebranie danych, wyciągnięcie wniosków i podjęcie decyzji o dalszych krokach. Może się okazać, że problem będzie występował dla promila użytkowników lub jest niezauważalny dla klienta. Wtedy czas poświęcony na optymalizację tego rozwiązania, to moim zdaniem czas zmarnowany.

Drugim przypadkiem, gdy przedwczesna optymalizacja ma sens to przypadki proste lub oczywiste. Jeśli widzimy, że niskim kosztem można zoptymalizować proponowane rozwiązanie, to nie widzę powodu, by tego nie robić. Czasami zmiana kilku linii kodu, która zajmie minutę lub dwie może np. znacznie obniżyć złożoność obliczeniową.

W pozostałych sytuacjach stosowałbym wspomniane już podejście „jeździć i obserwować”. Wstępne przetestowanie wydajności da nam ogólny widok na sytuację, jednak faktyczne miejsca do optymalizacji wyjdą prawdopodobnie po pojawieniu się prawdziwych użytkowników. Kluczowe jest tu zadbanie zawczasu o poprawne observability usług: alerty, metryki i logi, dzięki którym będziemy w stanie takie sytuacje wykryć i zgromadzić niezbędne dane.

Do przedwczesnych optymalizacji zaliczyłbym też przygotowywanie kodu „na zaś”. Nie zawsze potrzebujesz robić wszystko fancy, skalowalne, elastyczne i tak dalej. Czasami prosta funkcja czy klasa napisana „na pałę” wystarczy. Kod można zrefaktoryzować, gdy pojawi się taka potrzeba, a czasu np. na rzeźbienie skalowalnego rozwiązania, które się nigdy nie wyskaluje, nikt Ci nie odda. Chowanie dwóch ifów za strategią, fabryką i pierdylionem innych rozwiązań to moim zdaniem gra niewarta świeczki, chyba że pracujesz nad konkurencją dla FizzBuzzEnterpriseEdition.

Późniejsza decyzja to lepszy ADR

ADR, czyli Architecture Decision Record to dokument opisujący decyzje architektoniczne podjęte w projekcie. Dzięki niemu, w przyszłości można do niego wrócić i przypomnieć sobie:

  • Przed jakimi decyzjami w projekcie stali programiści?
  • Jakie były czynniki decyzyjne?
  • Jaką decyzję podjęli?
  • Dlaczego podjęli taką decyzję?
  • Jakie były alternatywy?
  • Dlaczego alternatywy nie zostały wybrane?

Zwykle taki dokument ma zdefiniowaną strukturę i opiera się na wcześniej przygotowanym szablonie.

Wróćmy do przykładu z wyborem bazy danych. Załóżmy, że decyzja o wyborze bazy danych została podjęta na samym początku projektu. Problem pojawia się już na samym początku ADR-a. Rzetelne opisanie czynników decyzyjnych będzie trudne. W momencie, gdy jest wiele nieznanych czynników, to ADR albo będzie pisany na zasadzie „wydajemisię” albo nie będzie uwzględniał potencjalnie istotnych czynników.

Jeśli z decyzją o wyborze bazy poczekalibyśmy, aż struktura danych się ustabilizuje, wyklarują się wszystkie założenia i wymagania, to ADR przyniesie o wiele większą wartość. Wtedy ADR pisany jest na bazie faktów, a nie domysłów i teorii.

Lean Software Development

Prawdę mówiąc, o tym, że opisany przeze mnie sposób pracy jest zawarty w podejściu Lean Software Development, dowiedziałem się w trakcie robienia researchu pod ten artykuł. Warto wiedzieć, pod jaką frazą możesz dowiedzieć się czegoś więcej na ten temat. Podstawowym założeniem tego podejścia jest eliminowanie strat, czyli elementów niedodających produktowi żadnej wartości, a celem jest jak najszybsze dostarczenie klientowi wartości. Lean Software Development opiera się na siedmiu wartościach:

  1. Eliminacja strat — na przykład implementacja niepotrzebnych funkcji i minimalizowanie wykonywania zbędnych prac. Do strat można też zaliczyć przedwczesną optymalizację czy nadmierne testowanie. Niekoniecznie od samego początku fazy developmentu warto testować aplikację od A do Z wraz ze wszystkimi przypadkami brzegowymi.
  2. Wzmacnianie procesu uczenia — stosowanie krótkich iteracji w celu weryfikacji założeń i szybszego uzyskiwania feedbacku.
  3. Opóźnianie decyzji — patrz cytat z prezentacji Jakuba 🙂
  4. Szybkie dostarczanie rezultatów, co wiąże się z punktem poprzednim.
  5. Upoważniony zespół — zakładamy, że w projekcie pracują ludzie kompetentni, którzy wiedzą co robią i nie trzeba im patrzeć na ręce. Zespół powinien też mieć odpowiedni poziom decyzyjności i móc decydować autonomicznie.
  6. Tworzenie jakości i spójności — program powinien nie tylko spełniać odpowiednie normy jakościowe, ale też realizować cele biznesowe i spełniać wymagania klienta. Architektura powinna pozwalać na utrzymanie i dalszą rozbudowę programu. Kluczowe jest zachowanie balansu między spełnieniem wymagań funkcjonalnych i niefunkcjonalnych.
  7. Spojrzenie holistyczne — patrzymy na system jako całość, staramy się nadmiernie nie skupiać się ani nie ignorować wybranych aspektów.

Podsumowanie

Jak się człowiek spieszy, to się diabeł cieszy. Z kolei na pewno nie cieszy się programista, klient oraz biznes, którzy będą ponosić konsekwencje. Mam nadzieję, że moje wypociny zainspirują Cię do nieco bardziej leniwego i pragmatycznego podejmowania decyzji. Jestem ciekaw, jakie są Twoje doświadczenia w tym zakresie. Daj znać, jak w Twoim projekcie/zespole/pracy wygląda proces podejmowania decyzji.

Źródła i materiały dodatkowe

Dominik Szczepaniak

Zawodowo Senior Software Engineer w CKSource. Prywatnie bloger, fan włoskiej kuchni, miłośnik jazdy na rowerze i treningu siłowego.

Inne wpisy, które mogą Cię zainteresować

Zapisz się na mailing i odbierz e-booka

Odbierz darmowy egzemplarz e-booka 106 Pytań Rekrutacyjnych Junior JavaScript Developer i realnie zwiększ swoje szanse na rozmowie rekrutacyjnej! Będziesz też otrzymywać wartościowe treści i powiadomienia o nowych wpisach na skrzynkę e-mail.

Subscribe
Notify of
guest

0 komentarzy
Most Voted
Newest Oldest
Inline Feedbacks
View all comments