7 przysłów programisty. Co cię nie zabije, to cię wzmocni
Każdego dnia spotykamy się z problemami, które trzeba rozwiązać. Są to incydenty, które szybko trzeba skończyć: goniący termin wykonania projektu, implementacja nowej, nieznanej nam technologii, czy problem w zmotywowaniu i komunikacji z zespołem. Niektóre wydają się proste, ponieważ już się kiedyś z nimi mierzyliśmy. Inne natomiast powodują, że musimy wyjść ze swojej strefy komfortu i zaryzykować.
Jakub Saleniuk. Programowaniem zajmuje się od 10 lat. Aktualnie pracuje w firmie Przelewy24. W swoim życiu przeczytałem dużo książek, dokumentacji oraz artykułów. W tym samym życiu napisałem dużo złego kodu oraz popełniłem mnóstwo błędów. Czy to czyni ze mnie złego programistę? Udowodnię Wam, że wręcz przeciwnie!
Z doświadczeniem wypracowujemy sobie małe rzeczy, które ostatecznie mają ogromny wpływ na naszą pracę. Jako programiści popełniamy niezmierzoną liczbę błędów. Ale czy każdy z nich źle o nas świadczy?
Spis treści
Gdzie kucharek sześć, tam nie ma co jeść
Biznes ma często bardzo ciekawe podejście do zarządzania zasobami ludzkimi. Jeśli jedna kobieta rodzi dziecko w 9 miesięcy, to co zrobić, aby urodziło się ono w 1 miesiąc? Trzeba zaangażować 9 kobiet. W teorii większa liczba osób przekłada się na szybsze wykonanie projektu. W praktyce może to wyglądać zupełnie inaczej. Szczególnie jeśli brak nam doświadczenia w prowadzeniu dużego zespołu. Miałem okazję pracować nad projektem, w który zaangażowanych było 10 programistów. Chodziło oczywiście o przyspieszenie całego procesu. Niestety jedyne co przyszło z czasem to wiedza, że mniejsza liczba osób wykonałaby ten projekt znacznie szybciej. Wynikało to z kilku problemów, które generował większy zespół oraz popełnionych przez nas błędów.
Dodatkowym atutem, oprócz czasu, przemawiającym za dużym zespołem było, to, że projekty nie są zależne od konkretnych osób. W razie urlopu lub odejścia, którego z programistów projekt dalej miał być realizowany przez resztę zespołu. Dzięki temu mieliśmy uniknąć konieczności poświęcania czasu na wdrażanie nowych osób oraz ciągłego wydzwaniania do nieobecnych osób w sprawie projektu. Niestety w praktyce wyglądało to tak, że każdy z nas był skupiony na części systemu i nikt nie miał wiedzy na temat całego projektu.
Założyliśmy, że każdy w zespole jest sobie równy, co z założenia nie jest złe. Code Review nie było wykonywane przez określone osoby mające największą wiedzę na temat projektu tylko przez dowolną w zespole. Straciliśmy kontrolę nad projektem. Każda osoba ma nieco inne podejście. Architektura projektu ulegała zmianie. Kod zmieniał się tak szybko, że każdego dnia trzeba było uczyć się go na nowo. Większość czasu poświęcaliśmy na zastanawianie się jak uruchomić kod, który u innych działał bez problemu.
Straciliśmy chęć i siły do rozwijania tego projektu. Każda mała zmiana trwała bardzo długo. Presja czasu oraz frustracja powodowały, że przymykaliśmy oko na kod, którego w normalnych warunkach nigdy byśmy nie przepuścili. Brak osoby decyzyjnej powodował, że planowania dalszej pracy trwały godzinami. Kłóciliśmy się między sobą, komunikacja była na kiepskim poziomie. Zaangażowanie praktycznie całych zasobów ludzkich na jeden projekt sprawiało, że dodatkowo byliśmy odrywani od pracy, gdy przychodziły incydenty do innych projektów, którymi musieliśmy się zajmować.
Im mniejsze zespoły, tym produktywność ich pojedynczych członków jest większa. Nauczeni doświadczeniem stworzyliśmy mniejsze zespoły, którymi znacznie łatwiej było zarządzać. Najbardziej doświadczone osoby są osobami decyzyjnymi oraz przekazują wiedzę i uwagi mniej doświadczonym. Komunikacja przebiega bezproblemowo. Nikt nie jest odrywany od zadań i może skupić się na swojej pracy. Architektura oraz podejście do pisania kodu są stałe i znane całemu zespołowi.
Jak sobie pościelisz, tak się wyśpisz
Każdy z nas chce pisać kod wysokiej jakości z testami oraz korzystając z najnowszych technologii. Zazwyczaj problem polega na tym, że pomimo umiejętności brakuje nam czasu, aby realizowany przez nas projekt posiadał najlepsze standardy. Jest to bardzo frustrujące. Z doświadczenia wiem, że presja czasu potrafi sprawić, że nawet najlepsi programiści piszą kod niskiej jakości. Nie jest to do końca ich wina, ponieważ termin jest nie do przesunięcia. Ale czy na pewno tak jest, że nie mamy na to wpływu?
Trzeba umieć wywalczyć sobie możliwość napisania projektu według najwyższych standardów. Z tego powodu tak ważne są miękkie umiejętności. Spotkałem na swej drodze różne osoby. Osoba pewna siebie oraz zmotywowana nauczy się wszystkiego, a współpraca z taką osobą układa się dużo lepiej. W kontaktach z biznesem oraz szefostwem trzeba być stanowczym i nieustępliwym.
Znam przypadki osób, które dostając termin nie do zrealizowania będą pociły się i stresowały, aby wyrobić się z projektem. Kiedyś myślałem, że to świadczy o sile takich osób, teraz wiem, że o słabości. Takie zachowania prowadzą do sytuacji, w których kod powstaje na szybko i generuje ogromną liczbę błędów. Ostatecznie, przez powstające błędy, termin oddania projektu się przedłuża. Oczywiście uważam, że każdy z nas powinien starać się na 120% własnych możliwości niezależnie od zadania. Jednak możemy pozwolić, aby praca wykańczała nas psychicznie. Biznes bardzo często nie posiada wiedzy technicznej. Trzeba więc przyjąć to do wiadomości, że ciężko jest mu czasami zrozumieć, że pewna funkcjonalność, która wydaje się prosta zajmuje dużo czasu. Należy uzbroić się w argumenty, aby umieć się obronić i wytłumaczyć skąd bierze się taki nakład pracy.
Niektórym wydaje się, że muszą wykonywać wszystkie polecenia. Inni potrafią mieć swoje zdanie i standardy, których będą poszukiwać i o nie walczyć. Wasz kod reprezentuje Was, więc musicie postarać się pisać go jak najlepiej, ale i umieć wywalczyć sobie czas, aby mieć w ogóle taką możliwość. Możecie zostać znielubieni za wolniejsze terminy chcąc wykonać pracę dobrze. Nigdy nie powinniście jednak zmieniać swoich standardów, aby dostosować się do złych nawyków oraz braku organizacji. Nauczcie się cenić swoje umiejętności oraz wypracujcie sobie umiejętność mówienia “nie”. Pamiętajcie, że to Wy ponosicie odpowiedzialność za kod, który tworzycie i w razie błędów to na Was spadnie wina za problemy, które on generuje.
Jeśli nie potrafisz, nie pchaj się na afisz
Jako programiści chcemy pokazać nasze umiejętności. Często decydując się na użycie nowej, nieznanej nam technologii. Jest to oczywiście poparte argumentami o tym w jaki sposób może ona wpłynąć pozytywnie na projekt. Wychodzenie ze strefy komfortu jest bardzo ciekawe i rozwijające. Musimy jednak pamiętać o tym, że w wielu przypadkach sama wiedza teoretyczna nie jest wystarczająca. W bardzo dobrym przypadku po zakończeniu projektu dojdziemy do tego co zrobiliśmy dobrze, a co moglibyśmy zrobić inaczej. Niestety najczęściej nie ma już czasu na zmianę. W gorszym wypadku w trakcie pisania projektu dojdziemy do punktu, w którym okazuje się, że wdrażana technologia nie rozwiązuje naszych problemów lub nie wiemy jak coś wykonać. Termin goni, zespół nie wie jak wykonywać dalej zadania. W innym przypadku zgłosilibyśmy się do kogoś starszego stażem. W tym przypadku to my jesteśmy specjalistą w tej dziedzinie. Bierzemy odpowiedzialność za ten projekt.
W naszym wypadku wdrożoną technologią był Event Sourcing. Nikt z nas wcześniej tego nie robił. Nie mieliśmy najmniejszego doświadczenia z tą technologią. Zdecydowaliśmy się zaimplementować to rozwiązanie ze względu na niezaprzeczalne korzyści, które ze sobą niesie. Podczas implementacji wiele razy docieraliśmy do punktów, w których musieliśmy poświęcić dużo czasu na szukanie rozwiązań. Na szczęście w naszym przypadku błędy, które popełniliśmy nie powodowały problemów w samym działaniu aplikacji. Sprawiły jednak, że rozwijanie aplikacji było bardzo uciążliwe. Niepotrzebnie próbowaliśmy zaimplementować wszystkie elementy Event Sourcingu takie jak snapshoty oraz projekcje. Wtedy wydawało nam się to konieczne. Wynikało to z braku wiedzy. Z czasem doszliśmy także jak zbudować lepszą architekturę aplikacji, w jaki sposób przechowywać dane oraz obsługiwać zmieniającą się strukturę zdarzeń.
Gdybyśmy poświęcili czas na zaimplementowanie tego rozwiązania w testowym projekcie to przystępując do tworzenia głównego produktu bylibyśmy bogatsi o tę wiedzę. Wynikało to oczywiście z braku czasu, którego sobie nie wywalczyliśmy oraz zbytniej pewności siebie co do nieznanej nam technologii. Jest to bardzo cenna nauczka, z którą przyszło nam się zmierzyć. Wiem, że jako programiści pragniemy sławy. Być wychwalani jako odkrywcy i pionierzy, ale odradzam z całego serca wdrażania nowych, niepoznanych technologii na polu walki.
Z tego powodu tak bardzo cenieni są specjaliści, posiadający przede wszystkim doświadczenie a nie tylko wiedzę książkową. Ludzie, którzy wykonali wiele projektów i popełnili masę błędów, z których wyciągnęli wnioski. Bardzo cenna jest także pozycja CTO w firmie, który powinien wyszukać oraz poznać technologię, aby być gotowym do wdrażania jej w produkcyjnym środowisku.
Nie ilość, lecz jakość
Kiedy zacząłem pisać testy i poznałem TDD wydawało mi się, że świat się zmienił. Myślałem, że to rozwiąże wszystkie problemy, głód na świecie, wojny, kłótnie i pozwoli pisać bezpieczniejszy kod. Chciałem pisać testy dosłownie wszystkiego. Wtedy nie widziałem w tym żadnego problemu. W końcu jeśli wszystko będzie przetestowane to system będzie bezpieczniejszy i łatwiejszy do rozwijania. Niestety duża liczba testów powoduje problemy z ich utrzymywaniem. Czy naprawdę uważacie, że w API trzeba testować wszystkie gettery i settery klas? Czy nie ważniejsze jest przetestowanie działania funkcjonalności? Testy powinny być tak samo dobrze napisane jak reszta oprogramowania. Nie rozwiązują one wszystkich problemów. Żadna technologia czy rozwiązanie tego nie robi. Nie ważne ile testów napiszecie i tak oprogramowanie może mieć błędy. Utrzymywanie dużej ilości testów tylko generuje dodatkowe problemy zamiast je rozwiązywać.
Kiedyś czytając książkę o BDD natknąłem się na krótki, komiksowy przykład, w którym nowy programista przybiega zmartwiony i mówi, że wrzucił kod i żadne testy nie przeszły. Reszta zespołu na spokojnie na to zaregowała odpowiadając, żeby się nie przejmował, nic nie zepsuł. Po prostu te testy nie przechodzą od pierwszego sprintu. Lepiej posiadać małą liczbę testów, które dostarczają nam ważnych informacji niż wiele testów, które nic nie wnoszą. Bardzo częstym problemem z nadużyciem jakiegoś rozwiązania jest utrata zaufania co do wartości, którą wnosi. Może to doprowadzić do sytuacji, w której nikt nie przejmuje się aktualizacją testów oraz tym, że nie przechodzą, ponieważ dbanie o nie jest zbyt czasochłonne. W pewnym momencie doszło do takich przypadków, w testy były po prostu usuwane lub zakomentowane, ponieważ nie było czasu ich aktualizować, a kod musiał trafić na produkcję. To jest niedopuszczalne, ale wynikało ze zbyt dużej ilości testów co doprowadziło do ich niskiej jakości.
W taki sam sposób możemy skomplikować system poprzez nadużycie innych rozwiązań. Wspominałem wcześniej o wadze umiejętności wywalczenia sobie czasu na napisanie kodu według najlepszych standardów. Nie oznacza to jednak, że musimy pakować do systemu wszystkie znane nam technologie i rozwiązania. Trzeba zachować złoty środek. To, że znamy Event Sourcing nie oznacza, że powinniśmy stosować go we wszystkich projektach. Serwis posiadający tylko newsletter nie potrzebuje takich rozwiązań. Asynchroniczność w systemie wystarczy do ograniczenia się do miejsc, w których rzeczywiście jest potrzebna.
Trzeba dokonać analizy jakie rozwiązania są konieczne w naszym systemie. To samo tyczy się bezpieczeństwa systemu. Jeśli zaznajomicie się z dokumentami OWASP to przeczytacie tam, że zalecane standardy bezpieczeństwa różnią się w zależności od skomplikowania oraz danych, którymi system zarządza. Nie ma potrzeby wdrażania wszystkich możliwych zabezpieczeń w prostych systemach, które nie przechowują wrażliwych danych. W ten sposób niepotrzebnie komplikujemy sobie pracę. Projekt powinien być na tyle skomplikowany, aby rozwiązywał wszystkie problemy i na tyle prosty jak to tylko możliwe.
Tylko krowa zdania nie zmienia
W projekcie najważniejsza jest logika biznesowa. Infrastruktura jest tylko szczegółem. Dla biznesu nie ma znaczenia czy skorzystamy z MySQL, czy MongoDB. Serce aplikacji powinno być więc od niej niezależne. Czy to jednak oznacza, że powinniśmy wykorzystywać wielowarstwowe architektury już od początku powstawania projektu? W idealnym świecie logika biznesowa jest bardzo dobrze znana już na początku projektu. Wówczas zastosowanie skomplikowanej technologii w pierwszym etapie ma sens. W prawdziwym świecie logika biznesowa zmienia się, szczególnie na początku powstawania projektu. Implementowanie złożonych rozwiązań od początku spowalnia tylko naszą pracę. Podejście takie jest też zgodne z TDD oraz zwinnymi metodykami rozwijania oprogramowania. Dostarczamy kod, który pozwala rozwiązać problem w jak najszybszym tempie. Następnie go refaktoryzujemy. Lepiej spędzić dwie godziny na napisanie kodu w kontrolerze i później go zmienić niż cały dzień na napisanie pięknego kodu, który okaże się niepotrzebny lub musi zostać radykalnie przebudowany.
Infrastruktura się zmienia i naturalnie należy być na to przygotowanym. Jeśli naprawdę znamy jakąś technologię to nic nam nie przeszkodzi we wdrożeniu jej w późniejszej fazie, gdy projekt ma już ustabilizowaną logikę biznesową. Wówczas na spokojnie możemy refaktoryzować kod, aby przygotować go do szybkiej zmiany infrastruktury. Wdrażanie zaawansowanych rozwiązań w celu przygotowania się na wszystkie możliwe okoliczności jest jak rozwiązywanie problemów, których nie ma i może nigdy nie być. Należy dostosować się do potrzeb. Poświęcanie czasu na budowanie systemu cache w systemie, który nie ma żadnego problemu z wydajnością jest stratą czasu. Większość napisanych projektów na zawsze zostanie osadzona w infrastrukturze, w której powstała, ponieważ nie będzie potrzeby jej zmiany.
W naszym przypadku po poznaniu wielowarstwowej architektury Clean Architecture zaczęliśmy stosować ją we wszystkich projektach już na pierwszym etapie powstawania oprogramowania. Zamiast skupiać się na zapewnieniu działania systemu i logice biznesowej skupiliśmy się na tym, aby kod był w 100% niezależny od infrastruktury. Potrafiliśmy analizować całymi godzinami czy nasz kod jest zgodny z modelem na kartce. W rezultacie funkcjonalności dostarczane były bardzo powoli. Informacja zwrotna wracała do nas późno, co nie jest zgodne ze zwinnymi metodykami. W momencie, w którym okazywało się, że trzeba radykalnie zmienić lub zrezygnować z pewnej funkcjonalności byliśmy w najlepszym przypadku kilka godzin w plecy.
Chciałbym tutaj jasno zaznaczyć, że nie propaguję pisania kodu niskiej jakości. Kod w kontrolerze może być takiej samej jakości jak ten ukryty za abstrakcją. Podstawą tutaj są testy, które sterują logiką biznesową i pozwalają w łatwy sposób oraz bez ryzyka refaktoryzować kod w późniejszym etapie. Pamiętajcie tylko o tym, aby wywalczyć sobie czas na refaktoryzację u biznesu. Jest to jedna z trudniejszych rzeczy, ponieważ trzeba przekonać biznes, aby poświęcić czas na przepisanie czegoś co działa.
Tłumaczyć z polskiego na nasze
Możemy mieć bardzo dobrą wiedzę i doświadczenie w tworzeniu oprogramowania, lecz jeśli w zespole nie będzie odpowiedniej komunikacji to projekt będzie rozwijał się bardzo powoli. W zespole współpracują osoby o różnej wiedzy i różnym charakterze. W celu zapewnienia wysokiej jakości kodu zespół musi współpracować. Osoby powinny dzielić się swoim doświadczeniem, problemami oraz rozwiązaniami. Często winę za brak komunikacji zrzuca się na osoby, które są ciche i zamknięte w sobie. W wielu przypadkach osoby takie boją się dzielić swoimi wątpliwościami, ponieważ są traktowane z pogardą przez bardziej doświadczonych.
Znalezienie przyczyny braku komunikacji oraz rozwiązanie tego problemu nie jest łatwym zadaniem. Ważną rolę odgrywają w takim zespole np. Scrum Masterzy, którzy potrafią motywować zespół oraz sprawiać, aby każdy czuł się komfortowo i miał możliwości do rozwijania się. Oczywiście komunikacja w zespole zależy od wszystkich osób. Jest to temat, którego rozwijanie większość programistów uznaje za zbędne, uważają każdy umie rozmawiać, więc czym się tutaj przejmować. Czasami bardzo ciężkim zadaniem jest wyciąganie ludzi z błędu. Zapominamy o tym, że my też kiedyś borykaliśmy się z brakiem wiedzy i wątpliwościami. Trzeba umieć wziąć głęboki oddech, ugryźć się w język i na spokojnie postarać się wytłumaczyć coś po raz kolejny. Trzeba umieć przyznać się do błędu, jeśli taki popełnimy. To zdecydowanie buduje zaufanie. Trzeba umieć kogoś pochwalić, ale też zganić i zmotywować w taktowny sposób. Każda osoba wnosi coś do zespołu i ze zdaniem każdego należy się liczyć. Tylko w taki sposób można zbudować solidny zespół.
Każda osoba popełnia błędy i należy o tym pamiętać. Czy jest na świecie programista, który nigdy nie zrobił czegoś głupiego i szalonego. Pamiętam, że kiedy siedziałem strasznie zestresowany po tym jak wyrzuciłem w kosmos bazę klienta. Gdy udało się już ją przywrócić podszedł do mnie jeden ze starszych programistów i zaczął opowiadać swoje historie, przy których moja to była przechadzka po lesie. Nie potraktował mnie pogardliwie czy z brakiem szacunku. Wiedział, że pojedyncze błędy zdarzają się każdemu i nie wpływają na ocenę jakimi programistami jesteśmy. Oczywiście gdybym wyrzucał bazę klienta średnio raz na miesiąc to nie zagrzałbym miejsca w żadnej firmie. Na błędach trzeba się umieć uczyć i wyciągać z nich wnioski. Każdy miewa gorszy dzień, trzeba o tym pamiętać. Szczególnie, że kiedyś i nam może się taki przydarzyć.
Nie od razu Kraków zbudowano
Wszystko zaczyna się od poznania projektu, który ma zostać zrealizowany. Na początku zawsze pojawią się czarne strefy, których nikt nie jest pewny. To na tych rzeczach powinniśmy się skupić zadając jak najwięcej pytań biznesowi. Najlepszym sposobem jest wymaganie od biznesu podania przykładów do konkretnych zadań. Z przykładami nie da się dyskutować, ponieważ są jasne i konkretne. Rozmawiamy na temat technologii, które powinny zostać użyte w danym projekcie. Korzystamy z rozwiązań, których jesteśmy pewni, ponieważ są przez nas sprawdzone. Oczywiście bierzemy pod uwagę umiejętności oraz uwagi całego zespołu. Wszelkie nowe podejścia oraz technologie zostają przez nas zapamiętane oraz przeanalizowane do wykorzystania w późniejszym czasie.
Następnie zabieramy się do działania. Skupiamy się na podstawowych funkcjonalnościach, które są najważniejsze dla działania projektu. Bardzo ważną rolę odgrywa tutaj biznes, który analizuje wpływ każdej funkcjonalności na produkt. Jeśli coś jest niejasne zapisz to jako pytanie do Product Ownera i postaraj się jak najszybciej to wyjaśnić. Implementowanie niejasnych funkcjonalności może się okazać stratą czasu, ponieważ w wielu przypadkach trzeba je później zmieniać. Piszemy testy dla funkcjonalności i tylko tyle kodu, aby zapewnić ich działanie. Nie staramy się rozwiązywać problemów, których nie ma. Budujemy wszystko jak najprościej i jak najszybciej. Cachowanie, kolejki, asynchroniczność? Bardzo dobrze, że posiadasz tę wiedzę. Będzie Ci przydatna w implementowaniu tych rzeczy, jeśli rzeczywiście zajdzie taka potrzeba. Na tym etapie są to rozwiązania zupełnie zbędne.
Podczas review prezentujemy pierwszą wersję oprogramowania. Oczywiście pojawiają się kolejne pytania, lecz z każdym dniem wiedza na temat projektu rośnie. Nanosimy poprawki i budujemy kolejne funkcjonalności. Po zaakceptowaniu i wydaniu pierwszej wersji oprogramowania możemy przystąpić do etapu refaktoryzacji. Oczywiście należy ten temat przedyskutować i wywalczyć z biznesem. Potrzebny jest czas na przeanalizowanie aplikacji pod względem zgodności z logiką biznesową, bezpieczeństwa oraz na zapewnienie łatwego rozwijania projektu. Na tym etapie możemy skupić się na tworzeniu architektury niezależnej od infrastruktury. Dzięki testom możemy w łatwy sposób dokonać zmiany architektury naszej aplikacji bez obawy o to, że coś przestanie działać. Kolejnym etapem jest reagowanie na błędy oraz informacje od klientów. Na podstawie zebranych od nich informacji możemy stwierdzić czy aplikacji jest dla nich użyteczna oraz budować kolejne funkcjonalności, które są im najbardziej potrzebne. Jest to bardzo ważny etap w tworzeniu oprogramowania, ponieważ tworzone jest ono właśnie dla nich. To dzięki informacji zwrotnej od użytkowników powstają najlepsze aplikacje.
Wnioski końcowe
Każdy projekt uczy nas czegoś nowego. Dzięki temu wszyscy posiadamy unikalną wiedzę opartą na własnych doświadczeniach i od każdego możemy się czegoś nauczyć.
Wezwanie do działania: Nie bójcie się robić rzeczy po swojemu. Popełniajcie błędy i wyciągajcie z nich wnioski.
Zdjęcie główne artykułu pochodzi z pexels.com. Zdjęcie profilowe autora wykonał Błażej Pszczółkowski.