Nasza strona używa cookies. Dowiedz się więcej o celu ich używania i zmianie ustawień w przeglądarce. Korzystając ze strony, wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki. Rozumiem

Dość narzekania na dług techniczny

Frank Zickert IT-Manager / Aperto GmbH
Spójrz na dług techniczny z innej strony i przestań na niego narzekać oraz poznaj sposoby na jego kontrolowanie.
Dość narzekania na dług techniczny

My, programiści, nie bez powodu łapiemy się over-engineeringu. Staramy się unikać długu technicznego. To ukryty koszt przebudowania kodu w przyszłości, spowodowany wyborem drogi na skróty, zamiast stosowania lepszego podejścia, które zajęłoby więcej czasu..

Rozważmy następujący przykład. Chcesz wygenerować dzisiejszą datę (na dzień pisania artykułu) jako ciąg z pierwszą i ostatnią godziną (0 i 23). To dość proste. Spójrz na następujący kod źródłowy.

/** THE EASY WAY */
const today = new Date();
console.log("today is from " 
    + today.getFullYear()
    + "-" + today.getMonth()
    + "-" + today.getDate()
    + "-" + 0
    + " to "
    + today.getFullYear()
    + "-" + today.getMonth()
    + "-" + today.getDate()
    + "-" + 23
);

Prosty sposób

Alternatywnie, moglibyśmy napisać kod z podejściem inżynierskim (z over-engineeringiem?). Umieścilibyśmy wszystkie elementy w osobnych funkcjach.

/** THE (OVER-?) ENGINEERED WAY */
const dateToString = (d) => (
 d.getFullYear()
 + "-" + d.getMonth()
 + "-" + d.getDate()
 + "-" + d.getHours()
);
 
const setDate = (d, hours) => (
 new Date(d.getFullYear(), d.getMonth(), d.getDate(), hours)
);
console.log("today is from " 
 + dateToString(setDate(new Date(), 0))
 + " to "
 + dateToString(setDate(new Date(), 23))
);

Sposób over-engineered

Które rozwiązanie jest lepsze? Łatwy sposób jest krótszy. Sposób over-engineered jest dłuższy i tworzy więcej obiektów Date w czasie wykonywania. Czy to przesada? Oba rozwiązania mają taką samą wydajność (nawiasem mówiąc, gdy to piszę, jest 15 października 2019 r.): today is from 2019–9–15–0 to 2019–9–15–23

Ale momencik, ma być październik! Nie wrzesień.

Powodem błędu jest to, że funkcja getMonth() z obiektu Date w JavaScript zaczyna zliczać od 0. Więc musimy dodać do niej 1.

W kodzie "inżynierskim" możemy poprawić wynik za pomocą jednej zmiany: parseInt(d.getMonth()+1).

W łatwiejszym kodzie musimy poprawić błąd w dwóch miejscach: parseInt(today.getMonth()+1).

To więcej pracy. A zmiana w dwóch miejscach jest potencjalnie źródłem błędów. Możemy łatwo przegapić zmianę w jednym miejscu. Otrzymujemy wtedy zupełnie inny wynik. Tak jak today is from 2019–10–15–0 to 2019–9–15–23 na wypadek, gdybyśmy zmienili tylko pierwsze wystąpienie.

Ta dodatkowa praca i to źródło błędów to nasz dług techniczny! To nie tak dużo. Ale to również prosty przykład.

Dług techniczny można porównać do długu pieniężnego. Brak spłaty długu technicznego utrudnia późniejsze wprowadzenie zmian. Kiedy coś zmieniasz, musisz uporać się z niedoskonałymi konstrukcjami, które zastosowałeś za pierwszym razem. Dług rośnie z czasem i kumuluje odsetki.

Jeśli wystarczająco długo będziesz ignorować dług techniczny, możesz technicznie zbankrutować. Stanie się tak, gdy nie będzie już można zmieniać kodu bez jego ryzykownego komplikowania.

A co, jeśli zechcemy otrzymać pierwszą i ostatnią godzinę dnia, ale w innej strefie czasowej. Załóżmy, że jesteśmy w Nowym Jorku. Jest 7 rano (-5 do UTC). Jaki jest nasz dzień w Nowym Jorku w UTC?

Możemy po prostu zastąpić wywoływane funkcje ich wersjami UTC, np.: d.getUTCDate() zastępuje d.getDate().

Kod “over-engineered” nie wymaga wielu zmian.

/** THE (NO LONGER OVER-) ENGINEERED WAY */
const dateToString = (d) => (
  d.getUTCFullYear()
  + "-" + parseInt(d.getUTCMonth()+1)
  + "-" + d.getUTCDate()
  + "-" + d.getUTCHours()
);

const setDate = (d, hours) => (
  new Date(d.getFullYear(), d.getMonth(), d.getDate(), hours)
);

console.log("today is from "
  + dateToString(setDate(new Date(), 0))
  + " to "
  + dateToString(setDate(new Date(), 23))
);


Ze względu na nasze zadłużenie techniczne, w prostym kodzie musimy zastosować zmianę w większej liczbie miejsc niż w przykładzie “over-engineered”.

/** THE EASY WAY */
const today = new Date();
console.log("today is from " 
  + today.getUTCFullYear()
  + "-" + parseInt(today.getUTCMonth()+1)
  + "-" + today.getUTCDate()
  + "-" + 0
  + " to "
  + today.getUTCFullYear()
  + "-" + parseInt(today.getUTCMonth()+1)
  + "-" + today.getUTCDate()
  + "-" + 23
);


Ale jeśli spojrzymy na wyniki, zauważymy, że oba są różne:

today is from 2019–10–15–0 to 2019–10–15–23
today is from 2019–10–15–5 to 2019–10–16–4


Łatwiejszy kod nie wyświetla naszego lokalnego dnia, jak ma to miejsce w UTC. Bierze tylko daty i sztywno koduje godziny. Musimy dopisać więcej kodu, aby działał poprawnie. To nasze odsetki od długu technicznego.

Spójrz na następujący przykład:

/** THE EASY WAY WITH INTEREST ON TECHNICAL DEBT */
const today = new Date();
console.log("today is from "
  + today.getUTCFullYear()
  + "-" + today.getUTCMonth()
  + "-" + today.getUTCDate()
  + "-" + 5
  + " to "
  + today.getUTCFullYear()
  + "-" + parseInt(today.getUTCMonth()+1)
  + "-" + parseInt(today.getUTCDate()+(today.getHours()+5 > 23 ? 1 : 0))
  + "-" + 4
);


Co tam zrobiłem? (Poza ponownym wprowadzeniem starego błędu polegającego na tym, że przegapiłem dodanie +1 do miesiąca… To prawdziwy błąd, który popełniłem. Myślę, że zbyt często kopiowałem w te i z powrotem…)

Ponieważ jesteśmy w Nowym Jorku, mogę zahardkodować godziny dnia UTC (5 do 4). Ale w zależności od aktualnej godziny, w UTC może być już jutro! W takim przypadku dodajemy do daty 1.

Ten kod działa na bieżący dzień (w przykładzie 15 października). Ale co, jeśli byłby 31 października? Musielibyśmy dodać również kolejny miesiąc. Co powiesz na 31 grudnia? Musielibyśmy dodać kolejny rok.

To za dużo pracy, nie mam czasu… Muszę zaimplentować kolejne user stories… Nie jest źle, jeśli wynik jest nieprawidłowy przez 5 godzin przez 12 dni w roku, prawda?


Czy jestem bankrutem technicznym?

Dług techniczny spowodował nie tylko więcej pracy na pierwszym etapie. Zsumował się również w drugim kroku. Przy okazji wkradł mi się błąd przy wprowadzaniu zmian. Co więcej, „łatwe” rozwiązanie działa tylko w Nowym Jorku. Nie działa w żadnej innej strefie czasowej.

Zbyt duże zadłużenie techniczne uniemożliwi dowożenie funkcji i poprawek błędów w dostępnym czasie. Jeśli dług techniczny jest aż tak zły, to jak możemy nazwać dobrą inżynierię, która zapobiega długowi technicznemu, over-engineeringiem?

Problem z kodem polega na tym, że nigdy nie przewidzisz, które części mogą wymagać zmiany w przyszłości. Zarówno wymagania, jak i technologia, zmieniają się z prędkością, która po prostu nie pozwala wywróżyć przyszłości.

Kiedy po raz pierwszy napisaliśmy powyższy przykład kodu, jak mogliśmy przewidzieć, że będziemy chcieli obliczyć lokalny czas dnia w Nowym Jorku w innej strefie czasowej? Nie mogliśmy! Naszym rozwiązaniem był kod “over-engineered”. Mieliśmy szczęście, że go przygotowaliśmy.

Co jeśli ten nowy wymóg się nigdy nie pojawił? Spędzilibyśmy sporo czasu (i pieniędzy) na rozwiązaniu, którego nie potrzebowaliśmy.

Co jeśli jeszcze nie rozumiesz problemu wystarczająco dobrze? Podczas programowania zdobywasz wiedzę. Znajdowanie najlepszego rozwiązania może zająć trochę czasu.
Spędzanie czasu na czymś, czego tak naprawdę nie potrzebujesz, to hazard. Dajesz teraz trochę czasu na poczet szansy wygrania później.

Czasami wygrasz, czasami przegrasz. Otrzymujesz kod, którego tak naprawdę nie potrzebujesz. Ale ten kod jest teraz częścią Twojej aplikacji. Musisz z tym żyć i pracować z nim. To kolejna forma długu technicznego., ponieważ ten kod nie jest potrzebny do bieżącego celu Twojej aplikacji. I nie wiesz, czy służy to przyszłemu celowi, czy nie.

Bez względu na to, co zrobisz, skończysz z długiem technicznym. Nie ma sposobu, aby temu zapobiec. Jednak istnieją sposoby, aby to kontrolować.


Sposoby na kontrolę długu

Po pierwsze

Istnieje najlepsza praktyka w tworzeniu oprogramowania: „You ain’t gonna need it” (YAGNI). Ta praktyka mówi, że nie powinieneś dodawać niczego do swojego kodu, dopóki tego nie potrzebujesz. Wystarczy zaimplementować kod potrzebny do rozwiązania problemu. Nie piszesz teraz tego, czego (może!) będziesz potrzebować później.

Będziemy zaciągać dług techniczny, ale pozwoli nam to szybciej dostarczać oprogramowanie.

Po drugie

Jest jeszcze jedna najlepsza praktyka: „Nie powtarzaj się” (DRY). Ta praktyka ma na celu ograniczenie liczby powtórzeń kodu. Zamiast powielać kod, zastępuj go abstrakcją (taką jak funkcja lub klasa).

To Twoja szansa na znaczące zmniejszenie długu technicznego. Właśnie dowiedziałeś się, że potrzebujesz tego fragmentu kodu. Im bardziej go potrzebujesz, tym więcej czasu w niego inwestujesz. Gdy użyjesz go ponownie, poświęć trochę czasu na implementację kodu od zera. Szansa na ponowne użycie istniejącego kodu pozwoliła Ci zaoszczędzić trochę czasu. Spójrz na kod, w razie potrzeby popraw, a nawet przebuduj go. Ale inwestuj tylko zaoszczędzony czas.

Spłacając w ten sposób swój dług techniczny, masz pewność, że inwestujesz czas w odpowiednim momencie. Ponadto możesz argumentować, że pracowałeś nad funkcją (lub historią użytkownika albo czymkolwiek), nad którą obiecałeś pracować.

Nie musisz prosić o czas na refaktoryzację. Twoja firma zapewne zapyta Cię, dlaczego refaktoryzacja jest w tej chwili konieczna: „Teraz działa. Czyż nie?” Jeśli tak, to poświęcanie czasu (i pieniędzy) nie zwraca się. Jeśli nie działa, będziesz obwiniony za napisanie kodu, który nie działa.

„Ale jeśli nie zmienimy kodu teraz, implementacja funkcji w przyszłości będzie wymagała więcej czasu”. Odpowiedź najprawdopodobniej brzmi: „Byłeś leniwy w momencie jego implementacji. Poszedłeś na skróty. A teraz musimy zapłacić za jego ponowną implementację? ”. Nadal będziesz obwiniany.

Zainwestuj zaoszczędzony czas, ponownie wykorzystując kod do spłaty zadłużenia technicznego. Poprawisz kod, który ma znaczenie. Będziesz mógł dostarczać swoje funkcje, jednocześnie zmniejszając zadłużenie techniczne. I zapobiegniesz niepotrzebnym dyskusjom.

Ten artykuł to fragment z mojej książki: React-Architect: Full-Stack React App Development and Serverless Deployment. Możesz uzyskać dostęp do pierwszego rozdziału za darmo tutaj.


Oryginał tekstu w języku angielskim przeczytasz tutaj.

Lubisz dzielić się wiedzą i chcesz zostać autorem?

Podziel się wiedzą z 130 tysiącami naszych czytelników

Dowiedz się więcej