Zapomnij o legacy code, czyli refaktoryzacja kodu PHP z wykorzystaniem Rector

Marek Karmelski | Rozwój oprogramowania | 02.03.2022

Praca z kodem otrzymanym „w spadku” lub z kodem jeszcze nieprzetestowanym może rodzić wiele obaw. Nie zawsze pozbycie się takiego kodu jest możliwe, często w pewnym momencie potrzebna jest refaktoryzacja za pomocą odpowiednich narzędzi. Już od ponad 15 lat programiści PHP mogą wykorzystywać rozwiązania, które ułatwiają walkę z legacy code w codziennej pracy. Ale czym właściwie jest legacy code i jak z nim pracować? Jak pozbyć się długu technologicznego? W niniejszym artykule chciałbym przybliżyć wam narzędzie, jakim jest Rector.

Legacy code – co to takiego?

Zanim przejdę do opisania możliwości Rectora, chciałbym w kilku zdaniach nakreślić, czym jest legacy code i jakie są najczęściej wybierane sposoby radzenia sobie z tymże problemem. W sieci trafimy na wiele definicji legacy code. W dosłownym tłumaczeniu legacy code oznacza nic innego, jak „kod dziedziczony”, czyli kod, który de facto otrzymujemy w spadku po innych programistach, na przykład w momencie dołączenia do nowego projektu. Taki kod budzi lęki i obawy – kod działa, lecz nie znamy jego zawiłej logiki. Szczególny postrach sieje trudny do zrozumienia i okiełznania spaghetti code. Legacy code to również kod, który… piszemy w danej chwili, ale nie został jeszcze pokryty testami, zatem nie jest przygotowany na zmiany w przyszłości. Według mnie każda z powyższych definicji opisuje istotę problemu.

kod legacy to też kod pisany pod nową funkcjonalność

Legacy code – co zrobić z takim kodem?

W pracy z legacy code mamy do wyboru podejścia, które albo minimalizują jego wystąpienie, albo usuwają taki kod całkowicie. Możemy zatem zastosować takie rozwiązania jak refactoring kodu lub całkowicie go przepisać. Refactoring kodu dotyczy optymalizacji jego małych fragmentów, bez zmiany podstawowej funkcjonalności, drugie podejście wymaga przepisania całego kodu aplikacji na nowo, z uwzględnieniem aktualnych standardów.

Refactoring kodu – narzędzia

Jak już wspomniałem we wstępie, mamy obecnie wiele narzędzi umożliwiających walkę z długiem technologicznym w kodzie PHP. Wśród narzędzi do refaktoryzacji można wymienić takie jak:

  • PHP_CodeSniffer,
  • PHP CS Fixer,
  • PHP-Parser,
  • PHPStan,
  • Psalm,
  • Rector.

Które z narzędzi wybrać? Wszystko zależy od tego, czego zespół programistów potrzebuje w danym momencie oraz tego, czy narzędzie spełnia nasze oczekiwania. Jednym z decydujących czynników będzie też znajomość danego narzędzia przez członków zespołu developerskiego. Jedne z powyższych narzędzi przeprowadzają analizę statyczną kodu, drugie modyfikują go zgodnie z aktualnymi standardami, a jeszcze inne umożliwiają obie te rzeczy – tak jak właśnie Rector.

Rector PHP – pogromca legacy code?

Rector pojawił się w 2017 roku, a inicjatorem jego powstania był Tomasa Votruba. Rector to program CLI (Command Line Interface) na licencji open source, oparty na  komponentach

Symfony. To narzędzie, które poza analizą statyczną kodu dokonuje też jego zmiany. Podstawowe zastosowania Rectora to sprawna i szybka aktualizacja i refaktoryzacja kodu, a także zmiana architektury aplikacji.

Refaktoryzacja – jakie mamy możliwości w takim przypadku?

W przypadku aktualizacji i refaktoryzacji możemy tutaj wymienić takie możliwości jak:

  • migracja z PHP 5.3 do PHP 8.1,  
  • migracja z Symfony 2.8 do Symfony 4.4,  
  • usuwanie tzw. martwego kodu (ang. dead code, czyli kodu, który nigdy nie będzie wykorzystany),
  • zmiana nazw klas, metod, parametrów.
dobra wiadomość - można opanować kod legacy

Zmiana architektury kodu

Co do zmian architektury, zmiana fasady w Laravel na DI czy przeniesienie aplikacji z TYPO3 na Symfony. Oczywiście współczesne IDE umożliwiają refaktoryzację kodu, jednak mogą działać wolno i być skomplikowane w obsłudze. W przypadku wykorzystywania wyrażeń regularnych do wyszukiwania fragmentów kodu mogą znajdować nie wszystkie wystąpienia lub wręcz przeciwnie – wyszukiwać znacznie więcej. Kopiowanie i wklejanie przez programistę także może być obarczone pewnym błędem wynikającym ze zmęczenia czy rozkojarzenia.

Dlaczego Rector, a nie IDE?

Rector nie zrobi nic, na co nie pozwoli programista! Opiera się na regułach (to zdefiniowane, pojedyncze „przepisy”, które dokonują pojedynczej zmiany w kodzie), zgrupowanych w zestawy reguł wykonujących zmiany o zbliżonej charakterystyce.

Pojedyncze reguły:

  • ArrayKeyFirstLastRector,
  • IsCountableRector,
  • JsonThrowOnErrorRector.

Zestawy reguł:

  • PSR4,    
  • Php70,
  • Php71, TypeDeclaration,
  • DowngradePhp71.

W momencie pisania artykułu Rector udostępniał około 660 reguł, zebranych w ponad 48 zestawów.

Rector – konfiguracja

Zasada działania Rectora jest bardzo prosta – zaczynamy od zainstalowania narzędzia i jego konfiguracji. Rector w pierwszej kolejności wyszukuje wszystkie pliki wskazane przez programistę, następnie, analizując każdy z nich z osobna, buduje dla nich AST (Abstract Syntax Tree), tzw. drzewo abstrakcji składniowej. Do każdego takiego drzewa stosuje zdefiniowane przez programistę w pliku konfiguracyjnym reguły. Gdy cały proces zostanie przeprowadzony, w konsoli otrzymujemy raport dotyczący naniesionych zmian. Warto wspomnieć, że Rector nie opiera się wyłącznie na domyślnych regułach – narzędzie umożliwia tworzenie własnych, bardziej wyspecjalizowanych. 

/** @var SplFileInfo[] $fileInfos */
foreach ($fileInfos as $fileInfo)  {
    // 1 file => nodes
    /** @var Parser $phpParser */
    $nodes = $phpParser->parse(file_get_contents($fileInfo->getRealPath()));
    // nodes => 1 node
    foreach ($nodes as $node) { // rather traverse all of them
        /** @var PhpRectorInterface[] $rectors */
        foreach ($rectors as $rector)  {
            foreach ($rector->getNodeTypes() as $nodeType)  {
                if (is_a($node, $nodeType, true))  {
                    $rector->refactor($node);
                }
            }
        }
    }
}
return static function (
    ContainerConfigurator $containerConfigurator
): void {
    // get parameters
    $parameters = $containerConfigurator->parameters();
    $parameters->set(Option::PATHS, [
        __DIR__ . '/src'
    ]);
    // Define what rule sets will be applied
    $containerConfigurator->import(SetList::CODE_QUALITY);
};


ChangeArrayPushToArrayAssignRector
class SomeClass
  {
      public function run()
      {
          $items = [];
-         array_push($items, $item);
+         $items[] = $item;
      }
  }
CombinedAssignRector
-$value = $value + 5;
+$value += 5;


DateTimeToDateTimeInterfaceRector
class SomeClass {
-    public function methodWithDateTime(DateTime $dateTime)
+    /**
+     * @param DateTime|DateTimeImmutable $dateTime
+     */
+    public function methodWithDateTime(
         DateTimeInterface $dateTime
     ){
         return true;
     }
}


SimplifyArraySearchRector
-array_search("searching", $array) !== false;
+in_array("searching", $array);

Kod legacy – podsumowanie

Pracy z legacy code nie sposób uniknąć, prędzej czy później każdy programista spotka się z nim w projekcie. Dobrze jest znać możliwości, jakie w zakresie refaktoryzacji kodu dają dostępne narzędzia, takie jak Rector. Mam nadzieję, że przekonałem was do poznania jego możliwości – a jeśli chcecie dowiedzieć się więcej, zachęcam do obejrzenia mojego wystąpienia na DrupalCamp Poland 2021:

TOPICS