bookmark_borderRozkład oprogramowania

Od lat zajmuje mnie rozkład. Jędrne, gładkie ciała obumierają w zmarszczone, poplamione truchła. Imperia pełne chwały i potęgi rozpadają się na symetryczne zgliszcza gruzu, obmywane deszczem i spiekane słońcem. Lśniące atomowym blaskiem gwiazdy zapadają się w parujące oddechem wymarłych światów czarne dziury. Nic nie jest wolne od rozkładu, bo panta rhei kai ouden menei. Również to, co spod naszych palców wyrasta – oprogramowanie.

Kod oczywiście jest doskonały, cyfrowy, nie poddaje się entropii. Tak samo jak nie poddaje się jej choćby Odyseja Homera. Tyle, że stworzony dawno temu program, jak i wieki temu spisana Odyseja nie żyją w próżni. Powstały dla odbiorców. I tak jak dziś inni są Europejczycy od Greków sprzed tysiącleci, tak samo ewoluuje ekosystem, w którym przychodzi egzystować oprogramowaniu.

Choć jest właściwie jeszcze gorzej. Programy bowiem powstają latami. Rozwijane są nowe funkcjonalności, aktualizowana jest ich integracja ze światem zewnętrznym, czasami wręcz zmianie ulegają technologie, z jakich aplikacje się składają.

I tu pojawia się rozkład. I błędne koło.

Na początku program tworzy się szybko. Stąd pewnie zachwyt wielu początkujących programistów (oraz gorycz doświadczonych). Im więcej kodu, tym więcej zmartwień, jak i w przysłowiowym lesie więcej drzew im głębiej. Zaś im więcej ludzi w projekcie tym już piekło jest wybrukowane…

Pierwsi programiści w projekcie często są bardzo doceniani. Siadają, piszą, działa. Można wdrażać na produkcję albo pokazać klientom lub przełożonym. Sukces. Za ten sukces zwykle są nagradzani. Awansują albo są kierowani do kolejnych nowych projektów. Ci mniej lubiani trafiają zaś do projektów już istniejących. Im niżej w hierarchii dziobania tym do starszych i bardziej zapleśniałych.

Sęk w tym, że bodźce kształtujące zachowanie są zwykle rozłożone niekorzystnie. Tworzy to koszmarne zjawiska i prowadzi do wspomnianego błędnego koła. Błędnego koła tworzenia, próchnienia i burzenia.

Programiści tworzący projekty greenfield (nowe, od zera) nie zmagają się z konsekwencjami swoich czynów. Mogą pisać co chcą, jak chcą, byle wytrwać z rok, czy dwa, do odkorkowania szampana i wdrożenia projektu. Mogą eksperymentować, ale też nagradzani będą ci, którzy jak najszybciej i jak najtaniej zaimplementują zestaw podstawowych funkcjonalności. Bodźce więc są proste – pisz szybko, byle jak (choć nie za bardzo), używaj nowych technologii, które ładnie wyglądają w CV, a jak napiszesz to oczekuj, że trafisz do nowego projektu albo poszukaj go na rynku pracy. Co dalej z kodem? Pal licho, nie twój interes.

Programiści utaplani w błocie legacy z kolei mają małe albo wręcz żadne – w zależności od stopnia rozkładu – szanse na poprawienie sytuacji. Jeśli projekt jest jeszcze względnie nowy, mogą próbować coś ratować, ale nie mogą uciekać od tworzenia nowych funkcjonalności. W każdym wypadku wypadną gorzej od pierwotnych twórców – będą zawsze pisać wolniej. Dlatego, że twórcy narobili bałaganu, w którym teraz trzeba się odnaleźć albo dlatego, że kodu jest więcej i jest trudniej nawet jak jest schludnie albo też jest średnio i trzeba posprzątać, więc również robi się to wolniej, niż na początku. Bodźce są takie, że najlepiej nic nie sprzątać i nie usprawniać, bo za to chwały nie będzie. Więc zwykle się nie sprząta. Tym bardziej, że biznes również tym sprzątaniem nie jest zainteresowany – kosztuje to, skutki bałaganu nadejdą za rok, dwa, pięć, gdy już ich nie będzie, a poza tym trzeba klepać nowe ficzery, bo to przynosi zysk.

Finalna faza rozkładu oprogramowania to legacy dojrzałe. Taka gorgonzola cremoso. Już nie tylko pleśń zielonymi żyłkami przecina nam kod jak w Matriksie, ale też sama struktura jest miękka i z lekka galaretowata. Poprawić nic się nie da, jak nie da się skleić szklanki. Można ją tylko – cytując klasyka – pogryźć i połknąć. I to też robią całymi dniami deweloperzy skazani na opiekę paliatywną nad projektami u progu śmierci. Bug za bugiem, debug za debugiem, w beznadziei dnia codziennego. Nikt nie porywa się tam na zmiany. Zmienić można co najwyżej pracę, ale i o to trudno, bo pracuje się przecież w starych technologiach, a i zapomniało się już nawet jak pisać przyzwoicie, patrząc przez dłuższy czas na starcze wynaturzenia – zrakowiałe struktury danych, zdegenerowane algorytmu i ułomne, antyczne narzędzia o finezji cepa bojowego, ewentualnie wekiery…

Programowanie w ostatniej fazie czeka na decyzję. Na decyzję o przepisaniu go od nowa. I tak zamyka się błędne koło – projekt dostają programiści z ładnym CV, którzy zawsze robią greenfieldy, potem ci mniej bohaterscy, którzy utrzymują systemy wieku średniego, aż w końcu trafia on znów do pielęgniarzy i pielęgniarek, którzy ostatnim ticketem zamkną mu oczy i wyślą na wieczny odpoczynek do krainy wiecznych pętli.

Tyle, jeśli chodzi o diagnozę. Pytanie – co z receptą? Nie umiem sobie do tej pory na nie odpowiedzieć. Wydaje się, że większość systemów informatycznych kończy właśnie w taki sposób i jest to jakimś naturalnym biegiem w cyklu życia oprogramowania. A wy, znacie jakieś alternatywy?

bookmark_borderDług techniczny / technologiczny – co to jest i czym grozi?

Jak mówi przysłowie – jeden obraz wart jest więcej niż tysiąc słów. Czym jest dług techniczny? Tym właśnie co na obrazku powyżej. Jest tymczasowym rozwiązaniem, które w miarę upływu czasu stało się problemem.

Czasami dług techniczny jest małym, czasami dużym zaniedbaniem. Im dłużej jednak zwlekamy z doprowadzeniem spraw do porządku, tym większe ryzyko, że nastąpi eksplozja, w wyniku której nasz projekt albo nasza firma pokryje się warstwą cuchnących ekskrementów. Bowiem dług technologiczny (czy też dług techniczny) jest w istocie jak dług – im dłużej odroczymy płatność, tym więcej odsetek przyjdzie nam zapłacić.

Można by zapytać – kto byłby tak bezmyślny, nieroztropny, niedbały i beztroski, by pozostawiać jakieś tymczasowe rozwiązanie na dłuższy czas! I słuszne byłoby to pytanie. I uzasadnione byłoby to oburzenie. Tyle tylko, że jak mówi programistyczna mądrość – tymczasowe rozwiązania są najbardziej trwałe. Czemu?

Bywa, że nie ma czasu. Bywa, że nie ma zasobów, sił. Albo jednych i drugich brak jednocześnie. Gdy tymczasowe rozwiązanie już powstanie, nie ma znów sił i czasu oraz zasobów, by się go pozbyć i zrobić rzecz zgodnie ze sztuką. Goni nas kolejny deadline, pojawia się następny projekt, a krótkotrwałe obejście, „hack”, żyje swoim życiem… Przez lata czasami, aż nie stanie się niebezpieczne, aż nie zagrozi biznesowi.

Dług techniczny, dług technologiczny istnieje w każdym projekcie IT. W innych formach zjawisko to pojawia się wszędzie. W naszym domu, kiedy zaczyna istnieć jakiś kąt, gdzie rośnie nieporządek – jak w przysłowiowym zamiataniu pod dywan – rośnie, rośnie, aż w pewnym momencie orientujemy się, że w całym mieszkaniu jest bałagan. W naszym ciele, kiedy kilka komórek wymyka się spod kontroli i zaczyna mnożyć, a po latach okazuje się, że lekarz diagnozuje nowotwór. Jak również w biznesie – na przykład, kiedy pracownik w podeszłym wieku, zajmujący się od lat częścią jakiegoś istotnego procesu biznesowego przechodzi na emeryturę lub umiera, a wraz z nim ginie wiedza i umiejętność wykonywania zadań, które okazują się kluczowe. I nagle cała firma wpada w tarapaty.

Dług technologiczny to ziarno chaosu. Odpowiedzialnością programistów oraz kierowników IT jest kontrolowanie go, niedopuszczanie do jego nadmiernego rozrostu. W przeciwnym wypadku, jeśli tylko pozwolimy mu wypuścić pędy, zapuścić korzenie, dług techniczny nas zniszczy – naszą pracę, naszą firmę, nasze życie. Nie pozwólmy mu rosnąć…

bookmark_borderProblem nazw w programowaniu

Czytanie kodu źródłowego jest trudniejsze, niż jego pisanie. Przyzna to każdy kto pracował z odziedziczonym repozytorium i próbował zrozumieć jego działanie. Przebijanie się przez tysiące linii kodu, przez setki definicji funkcji i zmiennych, w próbie zrozumienia co autorzy mieli na myśli jest ciężkim, męczącym, okrutnie wyczerpującym zadaniem.

Dlaczego tak jest?

Czy zrozumienie przepisu kuchennego jest trudne?

Czy jest trudniejsze od napisania go?

Czy zrozumienie przepisu kuchennego sprzed trzydziestu lat jest trudniejsze od zrozumienia przepisu sprzed tygodnia?

Nie sądzę.

Czemu więc czytanie kodu źródłowego staje się trudniejsze z każdym kolejnym miesiącem od jego powstania? Dlaczego nowy kod jest czytelny, a stary niezrozumiały? Jaka może być przyczyną “gnicia oprogramowania” i nienawiści do legacy code?

Zastanówmy się nad czynnością czytania kodu źródłowego. Co robimy próbując zrozumieć program, wgryźć się w kod?

Działamy jak komputery, tylko mniej wydajnie. Przeglądamy kod, czytamy go, napotykamy na zmienne i funkcje. Nie będąc maszynami nie jesteśmy w stanie spamiętać każdej wartości oraz ciągu konstrukcji. Próbujemy więc “zrozumieć” kod. Napotykając na nazwę zmiennej staramy się domyślić, co oznacza i w jakim kontekście jest używana. Nazwy funkcji lub procedur również próbujemy zrozumieć, najlepiej bez wnikania w ich treść.

Dokładnie tak samo działamy w świecie rzeczywistym. Gdy w przepisie napotykamy na słowo “marchew” wyobrażamy sobie marchew. Rozumiemy ideę marchewki bez zapoznawania się że szczegółami takimi jak jej masa, kolor, DNA, czy temperatura.

Kod źródłowy jednak – mimo starań obozu oprogramowania zorientowanego obiektowo – nie jest jak świat rzeczywisty. Odstaje od niego znacząco.

W świecie rzeczywistym dysponujemy ogromną, lecz ograniczoną ilością słów w naszych słownikach. Języki naturalne przetwarzane są przez ludzkie mózgi zupełnie inaczej niż kod źródłowy przez kompilatory. Krzesło na przykład to dla człowieka nie tylko konkretne krzesło, ale idea krzesła jako takiego. W większości przypadków zbędne nam są szczegółowe informacje na temat obiektów by przetwarzać i rozumieć język naturalny, w którego zdaniach te obiekty, reprezentowane przez słowa, występują. Tam z kolei gdzie jest nam potrzebna precyzja definicji (jak w prawie na przykład) napotykamy na spore problemy.

Świetnym przykładem ilustrującym, co by było gdybyśmy przetwarzali język naturalny tak jak komputery przetwarzają kod źródłowy jest ten filmik:

Wróćmy do czynności czytania kodu przez programistów. Cóż robi developer? Czyta nazwę zmiennej lub funkcji i próbuje zgadnąć, co ona oznacza. Póki kod jest “czysty” i niezbyt stary instynktowne zrozumienie jest stosunkowo poprawne. Czytanie idzie mu dobrze i praca z kodem jest sprawna.

Kłopot zaczyna się, gdy nie jest w stanie poprawnie domniemać znaczenia nazw.

Problem jest jednak znacznie poważniejszy. Jest to jedna z fundamentalnych trudności w rozwoju oprogramowania. Częściowo oddaje to poniższy cytat:

There are only two hard things in Computer Science: cache invalidation and naming things

Phil Karlton

Chodzi o nazewnictwo.

Nazewnictwo jest punktem styku pomiędzy światem maszyn i ludzi. Jest jednocześnie przepaścią między jednym, a drugim. W świecie rzeczywistym, gdzie język ludzki rozumiany jest przez mózgi, które są w stanie instynktownie zrozumieć klasy obiektów / idee przedmiotów rzadko tworzone są nowe słowa. W kodzie źródłowym nowe słowa tworzone są nieustannie.

Co dzieje się, gdy tworzymy nowe słowo w ludzkiej mowie? Uczymy się go. Powstaje nowe słowo – na przykład „komputer” – i wszyscy ludzie na świecie uczą się, że oznacza ono taką, a nie inną rzecz. Nie ma znaczenia, czy mowa o MacBooku, Dellu XPS, ENIACu, czy PC-cie. Nie tworzymy odrębnych słów opisujących komputery stojące w każdym z departamentów firmy, nie tworzymy innych słów na różne modele MacBooka mające inną wielkość pamięci RAM. Nie ma takiej potrzeby. Nowe słowa tworzymy rzadko. Za nowym słowem kryją się duże grupy obiektów. 

Inaczej jest w przypadku kodu źródłowego. „Użytkownik” znaczy co innego w każdym omal programie, jaki do tej pory napisano. W jednych jest to imię i nazwisko, w innych również data urodzenia, w jeszcze innych płeć. W niektórych imię i nazwisko może się składać tylko z liter łacińskich, w pewnych mieć maksymalnie 20 znaków. I tak dalej, etc.

Mówiąc krótko: w kodzie źródłowym niczego nie da się nazwać poprawnie. Żadna nazwa, jak dobrej byśmy nie wybrali, nie będzie odpowiadała znaczeniu słów wyjętych z języka naturalnego, ze świata ludzi.

Możemy być tylko zbyt precyzyjni („user”) lub zbyt ogólni („userWithNameAndSurnameAndSexAndDateOfBirth). Nigdy omal nie będziemy idealnie precyzyjni w nazywaniu zmiennych, czy funkcji. Nigdy te nazwy nie będą znaczyły tego, co nam się wydaje. Zawsze musimy nawigować się do definicji i czytać implementację. Za każdym razem, gdy dołączamy do istniejącego projektu musimy „nauczyć się jego języka”. Nauka nowego języka jest zawsze trudna, żmudna i męcząca. Dlatego właśnie, ze wszystkich wymienionych powyżej przyczyn, czytanie kodu źródłowego jest trudne.