Analizatory kodu. Co to i czy warto?

Programowanie jest pewnym działaniem. Na ogół jest to praca wykonywana przez człowieka, polegająca na przekazaniu maszynie zestawu pewnych standardowych instrukcji. Czyli teoretycznie jest to działanie polegające na komunikacji człowiek — maszyna. Czy tylko?

Okazuje się, że maszyny są bardzo cierpliwe i wyrozumiałe. Mają garść zasad, których się trzymają i w zasadzie pozwalają Ci robić wszystko, na co masz ochotę, jeśli jest zgodne z tymi zasadami. Po pewnym czasie orientujesz się, że jako programista, oprócz precyzyjnego dłuta i małego młoteczka dostajesz też pistolet z bardzo czułym spustem do strzelania sobie w kolano.

Nie inaczej sprawa ma się w Pythonie. Język ten jest bardzo prosty do nauczenia się, jednak jest dość trudny do opanowania w sposób mistrzowski. Jednak nawet jeśli jesteś mistrzem, wciąż będzie Ci często brakować pamięci podręcznej w głowie, żeby przeanalizować wszystkie możliwe konsekwencje tworzonego kodu.

Łącznie z tymi, jak na Twój kod będą patrzeć inni i Ty sam za jakiś czas. Co autor miał na myśli? Co JA miałem na myśli? Nasze działanie rozszerza się więc o komunikację człowiek — inny człowiek i człowiek — ten sam człowiek później. Brzmi to nieco pokrętnie. Jednak na pewno nie bardziej niż kod rozbudowanej aplikacji, która przez kilka lat była rozwijana przez więcej niż jednego programistę, bez żadnych dodatkowych kryteriów odbioru oprócz poprawnego działania.

Analizatory kodu — kiedy używać?

W tym momencie na arenę wchodzą analizatory kodu. Analizatory kodu działają sobie poza głównym kodem. Leżą sobie gdzieś w systemie najczęściej w postaci pojedynczych małych programów i patrzą Ci przez ramię na kod. Przynajmniej taki sposób pracy jest najrozsądniejszy. Ustawiamy sobie, żeby analizatory kodu przeglądały kod po każdej zapisanej zmianie. Wtedy od razu dostajemy sygnały, że zbaczamy z właściwej ścieżki w stronę gąszczu niezrozumiałości i potencjalnych błędów. Możemy też uruchamiać je ręcznie raz na jakiś czas, jeśli nie lubimy rozpraszaczy. Albo możemy wymusić stosowanie się do wytycznych takich analizatorów każdego członka naszego projektu, poprzez ustawienie „bramkarza” który nie przepuszcza dalej kodu, który ma za dużo słabych miejsc.

Analizatory kodu — jak działają?

Nie martw się, nie zejdziemy tutaj do poziomu pojedynczych bitów w sposobie działania analizatorów. Nie ma absolutnie takiej potrzeby, ani nawet możliwości. Bo działają one raczej dość prosto. Szczególnie jeśli chodzi o statyczne analizatory kodu, o których tutaj jest mowa.

Analizator taki bierze kod i przechodzi po nim linijka po linijce. Nie wykonuje go, ale zapisuje sobie w pamięci, co widzi. Jeśli na przykład natrafi na tworzenie zmiennej, będzie pamiętał ją i sprawdzał, czy w ogóle została użyta. Jeśli nie, wtedy powie nam o tym. Dla komputera jest to w praktyce obojętne czy zmienna jest w ogóle użyta. Dla człowieka może to być niezły cyrk — czy ta zmienna już jest niepotrzebna i nie powinno jej w ogóle być? A może jest kluczowa w tej funkcji i po prostu ktoś o niej zapomniał?

W każdym razie jest to analiza statyczna, bo obliczenia nie są wykonywane, obserwowany jest tylko kod w dokładnie takim stanie, w jakim jest zapisany na dysku.

Przykład — Pylint

https://www.pylint.org/
https://www.pylint.org/

Sprawdźmy na przykładzie jak się za to zabrać. Do przykładu wybrałem analizator Pylint, których chyba jest najbardziej rozbudowany i popularny. Jako przykład kodu posłuży nam moduł TPOT, o którym pisałem w zupełnie innym kontekście wcześniej.

Zacznijmy od stworzenia środowiska do pracy z modułem pylint i git-em, którego użyjemy do pobrania wspomnianego kodu:

conda create -y -n analizator pylint git

Aktywujmy środowisko:

conda activate analizator

Sklonujmy konkretną wersję TPOT z GitHub:

git clone [email protected]:EpistasisLab/tpot.git
cd tpot/
git checkout 1387839b55cfee2e5f31465841582973f30b5ea1

Skoro mamy już konkretną wersję kodu, możemy ją przeanalizować:

pylint tpot

Dostaniemy informację, co jest nie tak, oraz ogólną ocenę kodu.

Przykładowy wyniki Pylint
Przykładowy wyniki Pylint

Przykładowa interpretacja

Ogólna ocena kodu, którą mamy na końcu mówi mniej więcej coś takiego:

Your code has been rated at 4.79/10

Czyli dostajemy ocenę 4.79/10. Jak to zwykle bywa, mamy tutaj kilka „ALE” które należy uwzględnić. Dostajemy na przykład takie komunikaty:

tpot/config/regressor.py:26:0: E0401: Unable to import ‚numpy’ (import-error)

Mówią nam one o tym, że pylint nie wie co to numpy i gdybyśmy chcieli w tym momencie uruchomić TPOT, to on też by nie wiedział, o co chodzi. Wynika to z tego, że nie mamy numpy w naszym środowisku pracy. Warto by więc o to zadbać prędzej lub później. Jeżeli jesteśmy świadomi tego typu problemu, to możemy go sobie zignorować:

pylint tpot --disable "E0401"

Uzyskamy wtedy coś takiego:

Your code has been rated at 6.64/10 (previous run: 4.79/10, +1.85)

W ten sposób możemy dogadać się z naszym analizatorem, żeby sprawdzał to, co nas interesuje. Jeśli przyjrzymy się raportowi pylint, to znajdziemy tam trochę ciekawostek, które by nam nie przyszły od razu do głowy:

tpot/builtins/one_hot_encoder.py:269:4: C0103: Argument name „X” doesn’t conform to snake_case naming style (invalid-name)
tpot/builtins/one_hot_encoder.py:269:4: R0914: Too many local variables (25/15) (too-many-locals)
tpot/builtins/one_hot_encoder.py:269:4: R0912: Too many branches (21/12) (too-many-branches)

Mamy tutaj następujące uwagi: C0103, R0914 i R0912. Przyjrzyjmy się, o co tutaj chodzi:

  • C0103 – sposób nazwania zmiennej nie jest zgodny z przyjętym standardem
  • R0914 – zaszaleliśmy z ilością zmiennych w tym pliku
  • R0912 – zaszaleliśmy z ilością rozgałęzień w danej funkcji

Oczywiście, autor TPOT mógł z premedytacją złamać te reguły, żeby osiągnąć wyższą czytelność. A może się po prostu z nimi nie zgadzać. Dlatego prawie każdy projekt ma swoje reguły pylint.

Reguły

Przy decydowaniu jakie reguły nam pomogą, możemy wybierać z puli podzielonej na następujące zbiory:

  • C — konwencja. Na przykład: jak nazywamy zmienne i funkcje. Chyba najbardziej kosmetyczne reguły.
  • R — refaktoryzacja. Prawdopodobnie coś przekombinowaliśmy. Będzie to pewnie działać, ale skoro można to zrobić lepiej, to powinno to być zrobione lepiej.
  • W — ostrzeżenie. Reguły zajmujące się ryzykownymi kawałkami kodu. Możemy zrobić to, co robimy, ale niekoniecznie może to dać takie wyniki, na jakie wygląda. Warto więc się pochylić nad takimi ostrzeżeniami.
  • E — błąd. Pylint wskazuje nam tutaj potencjalny błąd. Zdecydowanie nie warto ich ignorować, jeśli się pojawiają.
  • F — błąd krytyczny. Pylint zgłupiał i nie jest w stanie dalej analizować kodu. Czy na pewno nasz kod działa?

Inne analizatory

Świat FLOSS ma to do siebie, że wszystko tam jest dostępne w kilku wersjach, standardach, podejściach i implementacjach. Nikt nie zmusza nikogo do jednego i słusznego rozwiązania. Wprowadza to trochę mętliku. W naszym przypadku jednak nic nie stoi na przeszkodzie, żeby sprawdzać nasz kod po kolei różnymi analizatorami.

Filozofia FLOSS
Filozofia FLOSS

Przejrzymy więc inne najpopularniejsze analizatory dla Pythona:

  • bandit — jest to narzędzie zaprojektowanie do wyszukiwania problemów z bezpieczeństwem naszej aplikacji. Nie będzie więc czepiał się nazw zmiennych, ale może namierzyć jakieś problemy np. z generatorami liczb losowych używanych w kryptografii.
  • flake8 – narzędzie bardzo podobne do pylint. Bazuje na trzech innych narzędziach: PyFlakes, pycodestyle, Ned Batchelder’s McCabe script.
  • mypy — analizator specjalizujący się w wyłapywaniu zagrożeń związanych z dynamicznym typowaniem. Python pozwala na bardzo wiele w tej kwestii i zbyt luźne podejście do tematu może skończyć się katastrofą. Mypy obiecuje wyłapywać takie zagrożenia, co daje nam wygodę dynamicznego i bezpieczeństwo statycznego typowania.
  • pep8 – mały i prosty analizator zajmujący się stylem i konwencjami zdefiniowanymi w dokumencie PEP 8.
  • prospector — analizator podobny do pylint, który dodatkowo wykrywa użycie Celery, Django i Flask i dopasowuje się do ichnich standardów.
  • pydocstyle — a może by tak dla odmiany sprawdzić dokumentację? Analizator pydocstyle sprawdza dokumentację, którą powinniśmy tworzyć wraz z kodem i informuje nas o lipie, którą odstawiamy. Bazuje na dokumencie PEP 257.
  • pylama – „Jeden, by wszystkie zgromadzić i w ciemności związać„. Ktoś chyba w pewnym momencie zirytował się mnogością narzędzi do analizy, które musi ogarnąć i postanowił stworzyć nowe własne narzędzie, które będzie zbierać jak najwięcej z nich. Mamy więc tutaj wspomniane pep8, pydocstyle, pyflakes, mccabe, pylint i mypy. A dodatkowo dostajemy jeszcze radon, gjslint i eradicate.

Konkluzja

Myślę, że po zapoznaniu się z tematem, ciężko jest znaleźć wymówkę, żeby ignorować tego typu narzędzie. Oczywiście, uważny czytelnik tego bloga (a Ty na pewno takim jesteś) zauważy, że kod, który prezentuję, nie spełnia kryteriów zdefiniowanych w regułach powyższych analizatorów. Wynika to z tego, że uważam, że przykłady dydaktyczne rządzą się swoimi prawami, które zdecydowanie różnią się od implementacji produkcyjnych. No bo w tym wypadku najważniejsze jest przekazanie idei, a nie dostarczenie kodu. Kajam się uniżenie za tą nieścisłość :-).

W mojej skromnej opinii, jeśli sądzisz, że ktoś poza Tobą będzie rozwijał Twoją aplikację, warto jak najszybciej wymusić korzystanie z takich analizatorów. Oczywiście, będziesz pewnie definiował wyjątki i reguły zgodnie z kierunkiem rozwoju aplikacji. Jednocześnie w ten sposób będziesz też definiował obiektywny sposób na sprawdzenie, czy kod spełnia te reguły. W automatyczny i niewidzialny sposób. No, chyba że wszystko u Ciebie dzieje się na ASAP-ie i nie ma czasu na dostarczanie dobrej jakości kodu. Cóż, Python i inne języki programowania zawsze dadzą Ci możliwość solidnego strzału w kolano.

Jeśli interesuje Cię jakiś temat – nie musi być związany z tym artykułem – to zostaw mi sygnał tutaj. Dzięki!

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *