Pisanie obiektowe a myślenie obiektowe przy refaktoryzacji z testowaniem

Celem artykułu jest pokazanie kilku rozwiązań problemów obecnych przy refaktoryzacji kodu zmierzającego do testów kodu aplikacji.

Artykuł docenią programiści pracujący na co dzień z kodem odziedziczonym (Legacy Code) lub kodem drogim w utrzymaniu (w którym wprowadzenie drobnych zmian zajmuje wiele wysiłku i/lub skutkuje pojawieniem się wielu błędów)

W poniższym artykule dodałem również wskazówki odnośnie zespołu, gdyż często bariery i problemy tworzą się w głowach a potem w czynach kolegów, którzy nie zawsze mówią jasno o tym co myślą na temat kodu i rozwiązań.

Warto rozumieć, że programista najpierw jest człowiekiem, pracownikiem i jego cele z reguły dotyczą tej warstwy, czyli nie liczy się tylko fakt, ale również oddziaływanie w zespole oraz rachunek zysków i strat na poziomie pozycji społecznej.

Być może warto spokojnie poczekać na odpowiedni czas dla zespołu, aż większość zrozumie jak w praktyce lokalnie szybko implementować to co wydaje się dziś tak trudne i pracochłonne w skali całego systemu.

Legacy code

Contents

Termin legacy code pierwotnie oznaczał on kod, który jest zależny od niewspieranej wersji systemu, języka, frameworka, etc.

Cechy profesjonalnej zmiany kodu

Zmieniamy kod, gdyż chcemy uzyskać nową jakość lub funkcjonalność.

Jesteśmy profesjonalistami, tzn. że nie mamy czasu na eksperymentowanie.

Mamy cel i wiemy jak go uzyskać.

Stosujemy małe precyzyjne kroki.

Miarą sukcesu nie jest ilość kroków, ale brak błędów podczas zmian, które wykluczyłyby cel w ogóle lub na jakiś czas.

Jakość kodu

Chcesz być dobrym programistą, dlatego troszczysz się o jakość kodu.

Działajcy kod, który rozwiazuje problem

Nigdy nie piszesz kodu, który tylko wydaje się, że działa,  Starasz się tworzyć prawidłowo działajacy kod i masz dobre testy pozwalające to udowodnić. kod jest prawidłowy gdy rzeczywiście rozwiązuje problem. Nie ograniczasz się do tego, aby program tylko wyglądał na działający).

Kod łatwy w zrozumieniu i utrzymaniu

Dla kogo piszesz kod? dla innych ludzi. Piszesz kod, który jasno pokazuje swoje przeznaczenie, dlatego inni programiści mogą go szybko zrozumieć, przez to jest łatwy w konserwacji .Dobrze współpracujesz z innymi programistami. Uwzględniasz innych programistów i tworzysz czytelny dla nich kod. Twoim celem jest przecież to, aby zespół tworzył jak najlepsze oprogramowanie, a nie popisywanie się przed innymi.

Refaktoryzacja przy każdej okazji

Za każdym razem, gdy modyfikujesz jakiś fragment kodu, starasz się go ulepszyć, nadać mu lepszą strukturę, dokładniej go przetestować, ułatwić jego zrozumienie itd.). Zależy Ci na jakości kodu i programowania, dlatego nieustannie poznajesz nowe języki, idiomy i techniki. Stosujesz je jednak tylko wtedy, gdy ma to sens.

Dobra struktura kodu

Pisz kod w taki sposób, jakbyś pisał artykuł, ksiazke lub inna publikację dla ludzi, nie dla procesora.

  • Dziel kod na rozdziały, akapity i zdania.
  • Łącz podobne elementy ze sobą i rozdzielaj różne rzeczy.
  • Funkcje są odpowiednikiem rozdziałów. W każdym rozdziale może znajdować się kilka odrębnych, ale powiązanych fragmentów kodu. Podziel je na akapity za pomocą pustych wierszy.

Testowanie

Według Feathersa („Working Effectively with Legacy Code”) testy są tak istotne, jak możliwość sprawdzenia, czy po zmianie kodu, system nadal będzie działał w sposób oczekiwany.

Wraz z próbą przetestowania chociaż jednego elementu systemu informatycznego wchodzimy w nową warstwę możliwości i problemów.

https://pl.wikipedia.org/wiki/Testowanie_oprogramowania

Typy i sposoby testowania

  • Funkcjonalne testy
  • Jednostkowe testy

Proces testowania

kwestia procesu odnosi się do samego momentu tworzenia testu

  • utworzonie testu
  • warunek określający poprawne funkcjonowanie
  • więcej testów w odniesieniu do tej samej funkcjonalności

Koszt i Amortyzacja Testów

Przeprowadzanie testów jest oczywiste dla producentów srzętu elektronicznego, ale mogą być abstrakcyjne dla kogoś kto widzi działający produkt i nie zna całego cyklu produkcji i związanym z nim ryzykiem.

 

skoro 'wszystko’ działa to testowanie jest zbędne?

Tak może zapytać każda osoba nieznająca kodu i procesu jego tworzenia, więc jak uzasadnić ponoszenie kosztów na coś co nie jest finalnym produktem?

Warto wobec tego zadać pytanie:

Co jest rezultatem tworzenia testów?

  • zrefaktoryzowany, uproszczony kod, bardziej zrozumiały oparty o standardy i wzorce
  • kod testów, która wskazuje czy aplikacja działa prawidłowo
  • gdy testy już istnieją można wprowadzić automatyzację testowania
  • programiści uczą się nowych umiejętności, które pozwalają pisać świadomie lepszej jakości kod
  • kod jest mniej zależny i łatwiej o jego zrozumienie i rozbudowę

 

Zespół programistów a wprowadzenie refaktoryzacji w celu pokrycia kodu testami

Code Review, przegląd kodu
Code Review, przegląd kodu

Czy w waszym zespole jest czas na przegląd kodu?

Nic bardziej nie zmusi programisty do przemyślenia tego, co i jak napisać niż perspektywa kontroli. Jest to podobna ale idąca od góry droga prowadząca  do podobnych rezultatów w jakości pisanego kodu przez programistów.

Co kontroluje się w przeglądzie kodu (Code Review)?

  • wzorce i standardy
  • warto zacząć CR od samego szkicu, komentarzy, by już na początku określić strukturę i przeznaczenie kodu

 Testować czy nie testować?

Prościej zadać sobie pytanie:

  • jaka jest twoja odpowiedzialność w projekcie?

Jeśli do obowiązków nie należy testowanie, to nie musisz się tym martwić.

Czy warto współpracować z zespołem, który nie rozumie specyfikacji tworzenia oprogramowania?

Każdy się rozwija w swoim tempie a nie każdy ma szefa który mu dozgonnie ufa co do propozycji zmian.

Czy warto tworzyć kolejne części aplikacji a potem zmuszać zespół do tworzenia i użcia nowych standardów, tylko dla tego projektu?

Kto wówczas będzie odpowiedzialny za tworzenie nowego standardu i jak długo?

Finalnie nie do programisty należy decyzja tylko do osoby odpowiedzialnej za koszty tworzenia kodu, więc nie zawsze można zrobić odpowienie testy.

Warto mieć na uwadze, że lepszy jest brak testów, gdy świadomość zespołu jest niska, niż gdy, przy tak niskiej świadomości, każdy programista będzie próbował tworzyć kod, który nie będzie zgodny z duchem i myślą w zespole. Wówczas może okazać się że implementacja testów będzie musiała być odroczona, lub na nowo wprowadzana z uwagi na brak wcześniejszych standardów i idącej z nimi jakości, a to będzie niosło ze sobą zbyt wysoki koszt w porównaniu do innej formy edukacji.

Alternatywa w perspektywie

Gdy mimo chęci i możliwości czasowych nie zostaje udzielona programiście lub całemu zespołowi możliwość wykonywania testów, warto iść drogą edukacji i inspiracji.

Kilka lat temu… starałem się przekonać szefa projektu, aby umożliwił nam używanie lepszego IDE. Nie przekonała go moja wiedza i doświadczenie z PHPStorm. Jednak po miesiącu sam zainicjował zmiany, pokazał mi nawet dlaczego… po prostu przeczytał w pewnej książce o Magento (systemie do sklepów internetowych), że PHPStorm jest polecany.

Właśnie dlatego, że nie każdy ma autorytet, zwłaszcza gdy pracuje w firmie krótko i uczy się nowego systemu. Dlatego warto próbować szukać publikacji i książek, które pozwolą nam przekonać osobę decyzyjną. Kilka pozycji jest na końcu tego artykułu.

Problemy i Rozwiązania

Koszty wytworzenia i zmiany kodu

Często wiele zmian, które czekają w poczekalni powstrzymuje koszt ich wdrożenia.

Wynika to bardziej z tego, że trudno przewidzieć ile czasu będzie potrzebne w rzeczywistoiści w odniesieniu do prawdopodobnej kalkulacji.

Istnieje bardzo dużo czynników mających wpływ na wykonanie zadania, dlatego można wiele kalkulacji określić intuicyjnie z małym błędem, tylko nie każdy ma taki dar, dlatego też warto te kwestie rozpatrywać razem w zespole, gdyż często ważniejsze jest zaangażowanie zespołu, niż techniczne prawdopodobnieństwo.

Kontrola jakości standardów i automatyzacja

Kod tworzony przez programistę ma strukturę i opiera sie o zdefiniowane standardy, np OOP. Skoro oczekujemy powtarzalności standardów, to również oczekujemy przestrzegania jakości tych standardów.

Celem Testu Jednostkowego w pierwszej kolejności jest forma a potem treść.

Dlaczego?

Wynika to z faktu iż najpierw trzeba zdefiniować instancję obiektu a dopiero potem jej użyć, by uzyskać w drodze porównania określoną wartość.

Jak to zrobić?

Gdy chcemy przeprowadzić kontrolę jak najszybciej to tylko w sposób możliwie zautomatyzowany. Miejmy na uwadze, że obecnie kontrola jakości i tak jest przeprowadzana ale manualnie i szczątkowo.

Refaktorzyacja kodu polega na zmianie struktury kodu, bez modyfikowania jego zachowania.

W refaktoryzacji w odróżnieniu od optymalizacji, nie jest naszym celem poprawienie funkcjonowania samego kodu ze względu na szybkość działania czy zajętość w pamięci.

Architektura

Nie ma sensu na siłę budować nowego lepszego świata, gdy kod zamykamy w modułach, bilbiotekach, API on może być już dziś lepszy w małej części.
Każda architektura ma jakieś cechy, wady, zalety, stąd zamykamy ją w małych boxach i idziemy dalej.

Efektem stworzenia sieci modeli jest bardziej użyteczna forma każdego modelu wchodzącego w skład systemu

Wzorce

Warto od razu nakreślić początek i koniec odpowiedzialności dla danej części aplikacji z wykorzystaniem zdefiniowanych i znanych już w zespole wzorców.

Nowa funkcjonalność korzystająca ze starego kodu może być nowym modułem, jest to możliwe przy tworzeniu z użyciem wzorców DI, API

Bo to programiści determinują czas i jakość a nie wzorzec.

Single Responsibility

Piszesz klasę tylko dla jednego celu

Dependency Injection

Bazujesz na programowaniu zorientowanemu objektowo, więc używasz klas jako dane wejścia i wyjścia, dzięki czemu każda dana jest ściśle zdefiniowana

Standardy w zespole

Warto zastosować standardy, które są znane każdemu programiście piszącego kod w naszym zespole.

  • wzorzec
  • biblioteka
  • framework
  • API

Warto zamykać różne elementy aplikacji, gdzie są stosowane różne technologie i wzorce jako autonomiczne moduły. Niech moduł jest określony jasno przez jedną architekturę.

Nie wszystko od razu

Wiele spraw trzeba przedyskutować:

  • realizację kolejnych zmian
  • priorytety

Gdzie zostawić informację, listę TODO dla kodu?

Najlepiej w komentarzach, np #TODO piszesz w komentarzu w kodzie, wtedy każdy widzi zmiany przy commitach/review.

 

Struktura kodu, zależności i modularyzacja

Struktura kodu jest jedną z przeszkód przy wprowadzaniu testów, stąd konieczność refaktoryzacji.

Powody zmiany wiążą się m. in. z:

Rozwiązanie:

  • zamiast testować jedną dużą klasę, warto ją rozbić na kilka klas, zgodnie z zasadą Single Responisibility.

Wnioski:

  • Wzrasta czytelność kodu
  • Przeznaczenie kodu jest jasno określone, chociażby nazwą oraz użtymi metodami. W związku z tym nie potrzeba tyle opisowej dokumentacji, bo dodatkowo testy dają informację o użyciu kodu.
  • wzrasta prawdopodobieństwo ponownego użycia klasy

Zależności

Zależności w kodzie źródłowym aplikacji występują w naturalny sposób, gdy nie dbamy o modularyzację, czyli nie dbamy o autonomiczność i modułowość elementów aplikacji.

Warto również określić co oznacza modularyzacja, gdyż podobnie jak pisanie i myślenie obiektowe może być uproszczone i źle zrozumiane.

Perspektywa zmian

Zamiast myśleć o całej perspektywie zmian, warto skupić się na myśleniu lokalnym tu i teraz.

  • Kierunkowo i fachowo.

Więcej na: http://ronjeffries.com/xprog/articles/refactoring-not-on-the-backlog/

Modularyzacja

Modularyzacja jest przeciwieństwem zależności

Zależność jest cechą Spaghetti Code.

Modularny kod, nawet gdy nie jest testowany pozwala na zastosowanie alternatywy do każdego elementu kodu.

Przykładem modularnego i nie obiektowego kodu może być aplikacja oparta o wiele API, z którym komunikują się elementy aplikacji. Dodatkowa cecha takiego rozwiązania to fakt, że nie ma znaczenia kod jaki jest użyty po stronie API.

Modularyzacja z pewnością jest cechą aplikacji, która posiada niezależne moduły, które mają otwarty interfejs, określony i opisany przez API, Dokumentację, Testy.

Modularyzuj czyli: dziel i rządź!

Dziel i rządź, sprawdza się w kodzie i na ludziach.

Oznacza to, że finalnie zdefiniowaną funkcjonalność zamykamy w modułach, bilbiotekach, API, …

Pisanie obiektowe

Pisanie obiektowe to użycie klas i metod, zamiast plików z funkcjami charakterystycznymi dla kodu strukturalnego.

Myślenie obiektowe

Myślenie obiektowe to przede wszystkim przemyślana struktura, zakładająca dwie podstawowe zasady:

  • Zasada pojedynczej odpowiedzialności (ang. single responsibility principle)
  • Wstrzykiwanie zależności (angDependency Injection, DI) – wzorzec projektowy i wzorzec architektury oprogramowania polegający na usuwaniu bezpośrednich zależności pomiędzy komponentami na rzecz architektury typu plug-in

Myślenie obiektowe nie jest zależne od technologii, tylko od zasad, które pozwalają na powstanie kodu, jest ściśle określonym planem na utwrzenie nowego kodu.

 

Systematyka, czyli małe kroki już dziś!

najbardziej optymalne jest robienie małych usprawnień.

Ponadto małe zmiany są łatwo przyswajalne i inny developer może użyć rozwiązanie już istniejące w mikroskali, by rozwiązać taki sam problem.

Z czasem z kilku helperów można utworzyć małą bibliotekę.

Fakt, że zmiana jest oddolna – czyli mniejsze komponenty uległy zmianie jako pierwsze – przekłada się później na niższy koszt kolejnych usprawnień.

Za każdym razem staraj się zostawić to co zastałeś w lepszym stanie

SOLID

Lokalny refactoring klasy, tak by była bliżej SOLID.

Zamiast wprowadzać kolejne warstwy abstrakcji w całym systemie warto stosować abstrakcję lokalnie, gdy jest na nią zapotrzebowanie, w precyzyjnie określonych typach danych.

Ważne jest pokrywanie tych zmian testami, dzięki którym kolejne pokolenia będą miały ułatwione zadania tego typu.

Przykład prywatnej methody w klasie

Skoro występuje prywatna metoda to znaczy, że nie ma znaczenia na zwenątrz systemu.

Ma lokalne zastosowanie, więc po co ją testować? a jeśli testować, to powinna być zwenętrzną klasą, helperem z publiczną methodą poprzez wstryzknięcie zależności (Dependency Injection).

Literatura

Poniżej kilka pozycji pozwalające na efektywne wprowadzenie jakości we własnym kodzie, aby był szybko zrozumiany przy współpracy z zespołem:

 

Jeśli w projekcie nie zostały stosowane praktyki i techniki zawarte w powyższych książkach to warto sięgnąć po:

Wnioski

  • Testy nie czynią oprogramowania wolnego od błędów;
  • Testy obrazują działanie określonego wycinka aplikacji, pozwalają na symulację działania w inkubatorze, by znaleźć lepsze rozwiązanie starego problemu;
  • Testy otwierają kod na zmiany
  • Aktualne testy opisują działanie, lepiej niż często nieaktualna dokumentacja
  • Testy sygnalizują i informują o działaniu każdego elementu programu, nawet przy tysiącach zmian w kodzie i setek tysięcy testów na godzinę w środowisku continuous delivery
  • Testy wskazują osobę odpowiedzialną gdy powstają błędy (np. przy odpowiednim skonfigurowaniu poprzez oprogramowanie gitlab)

A jakie są Twoje wnioski i pomysły?

 

Tom Sapletta
Facebooktwitterredditpinterestlinkedinmail

Author: Tom Sapletta

Łączę doświadczenie z nowymi technologiami. Od 10 roku życia jestem pasjonatem komputerów i programowania. Moim pierwszym (mikro)komputerem był ZX-Spectrum a językiem programowania: Basic. Od 2010 roku programuję zawodowo, objektowo i funkcjonalnie w architekturach monolitycznych i mikro-usługowych. Obecnie tworzę architekturę ekosystemów dla liderów rynku w firmie Softreck.