Wyślij zapytanie Dołącz do Sii

Współcześnie profesjonalne zespoły programistów przykładają ogromną wagę do jakości wytwarzanego oprogramowania, jak również optymalizacji czasu potrzebnego do jego sprawdzenia. Wysoki standard kodu bezpośrednio koreluje z bezpieczeństwem, stabilnością i niezawodnością produktów. Aby zachować tę jakość, wiele zespołów stosuje techniki, takie jak manualne przeglądanie kodu (ang. code review), testowanie automatyczne i ręczne.

Chociaż przegląd kodu i testy automatyczne są ważne dla tworzenia kodu wysokiej jakości, nie odkryją one wszystkich problemów w oprogramowaniu. Ponieważ recenzenci kodu i autorzy testów automatycznych to ludzie, błędy i luki w zabezpieczeniach często przedostają się do środowiska produkcyjnego. Analiza kodu źródłowego może zapobiec połowie problemów, które często prześlizgują się przez pęknięcia w produkcji.

Zamiast gasić pożary spowodowane złym kodem, lepszym podejściem byłoby włączenie zapewniania jakości i egzekwowanie standardów kodowania na wczesnym etapie cyklu życia oprogramowania za pomocą statycznej analizy kodu.

Czym jest statyczna analiza kodu?

Statyczna analiza kodu to proces, który bez uruchamiania analizuje strukturę kodu źródłowego lub kodu skompilowanego. Służy do wykrywania potencjalnych luk w zabezpieczeniach, ale również uwspólnia i poprawia jakość poprzez porównanie z ogólnie przyjętymi standardami.

Każdy język programowania ma swój indywidualny zestaw reguł/sugestii/wytycznych dotyczących jakości kodu. W Pythonie jest to dokument Python Enhancement Proposals (w skrócie PEP).

Szczegółowość PEP potrafi przytłaczać, ale też nie ma potrzeby zapamiętania wszystkich wytycznych. Współczesne IDE (na przykład Pycharm, VS Code) potrafią analizować kod „w locie”.

kod

Niestety, nadal nie jest to rozwiązanie idealne. Nie wszyscy programiści korzystają z tego samego IDE, a pomiędzy poszczególnymi programami potrafią pojawiać się różnice w interpretacji PEP. Aby ujednolicić sposób analizy, warto zastosować dedykowane narzędzia, które jednocześnie są proste w automatyzacji CI (ang. continous integration). Takimi narzędziami są:

  • Pylint,
  • Black,
  • Isort,

potocznie nazywanymi „linterami”.

Pylint

Pylint jest jednym z najbardziej rozbudowanych i zaawansowanych linterów. Jednocześnie próg wejścia i początkowa konfiguracja są bardzo proste i intuicyjne.

Bardzo ważne pytanie brzmi: co wyróżnia Pylint od reszty programów do statycznej analizy kodu?

Pylint nie ufa typowaniu zapisanemu w kodzie, zawsze sprawdza typ w każdym węźle z osobna. Niestety, powoduje to znaczne wydłużenie czasu skanowania. Dodatkowo posiada mechanizm pluginów (na przykład od sprawdzania pisowni nazw, docstringów itp.).

Aby go zainstalować, należy wykonać w terminalu komendę:

pip install pylint

Istnieje również możliwość sprawdzania gramatyki. Wymaga to instalacji dodatkowych pluginów:

pip install pylint[spelling]

Działanie Pylint zaprezentować można na przykładzie pliku simplecaesar.py. Jego zawartość:

#!/usr/bin/env python3

import string

shift = 3
choice = input("would you like to encode or decode?")
word = input("Please enter text")
letters = string.ascii_letters + string.punctuation + string.digits
encoded = ""
if choice == "encode":
    for letter in word:
        if letter == " ":
            encoded = encoded + " "
        else:
            x = letters.index(letter) + shift
            encoded = encoded + letters[x]
if choice == "decode":
    for letter in word:
        if letter == " ":
            encoded = encoded + " "
        else:
            x = letters.index(letter) - shift
            encoded = encoded + letters[x]

print(encoded)

Przy użyciu najprostszej dostępnej komendy:

pylint simplecaesar.py

Wynikiem będzie „sprawozdanie” z jakości kodu:

************* Module simplecaesar
simplecaesar.py:1:0: C0114: Missing module docstring (missing-module-docstring)
simplecaesar.py:5:0: C0103: Constant name "shift" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:8:0: C0103: Constant name "letters" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:9:0: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:13:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:15:12: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:16:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:20:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:22:12: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:23:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)

-----------------------------------
Your code has been rated at 4.74/10

Rozbijmy na czynniki powyższy raport na podstawie pierwszego znalezionego problemu:

"simplecaesar.py:1:0: C0114: Missing module docstring (missing-module-docstring)"

Oznacza to, że w linii pierwszej, w kolumnie zerowej (sama góra pliku) naruszana jest konwencja C0114. Istotna jest również czytelna nazwa: missing-module-docstring. Jeżeli chcemy uzyskać dodatkową informację na temat konkretnego błędu, niepotrzebne jest przeszukiwanie internetu. Wystarczy komenda:

pylint --help-msg=missing-module-docstring

która zwróci wynik:

:missing-module-docstring (C0114): *Missing module docstring*
  Used when a module has no docstring.Empty modules do not require a docstring.
  This message belongs to the basic checker.

Dzięki temu zdobędziemy wiedzę, co należy poprawić.

Wracając do raportu wygenerowanego przez Pylint. Na dole można zauważyć:

Your code has been rated at 4.74/10

Jest to globalna punktacja, a maksymalna wartość to 10. Każdy kod powinien być oceniany na właśnie taką liczbę punktów. Z każdym poprawionym błędem punktacja wzrasta.

Pylint w praktyce

Dobrze, w takim razie spróbujmy naprawić pierwszy problem z brakującym docstringiem na początku pliku pythonowego. Obecnie plik simplecaesar.py został zaktualizowany w ten sposób na samej górze:

#!/usr/bin/env python3

"""This script prompts a user to enter a message to encode or decode
using a classic Caesar shift substitution (3 letter shift)"""

import string

Gdy ponownie wygenerujemy raport, otrzymamy zmniejszoną liczbę błędów i podniesioną punktację:

************* Module simplecaesar
simplecaesar.py:8:0: C0103: Constant name "shift" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:11:0: C0103: Constant name "letters" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:12:0: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:16:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:18:12: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:19:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:23:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:25:12: C0103: Constant name "x" doesn't conform to UPPER_CASE naming style (invalid-name)
simplecaesar.py:26:12: C0103: Constant name "encoded" doesn't conform to UPPER_CASE naming style (invalid-name)

------------------------------------------------------------------
Your code has been rated at 5.26/10 (previous run: 4.74/10, +0.53)

W ostatniej linii dowiedzieliśmy się, że punktacja wzrosła, co jest zgodne z planem. Pozostał tylko błąd invalid-name (C0103). Istnieją dość dobrze zdefiniowane konwencje dotyczące nazewnictwa takich rzeczy, jak zmienne instancji, funkcje, klasy itp. Konwencje skupiają się na użyciu WIELKICH i małych liter, a także znaków oddzielających wiele słów w nazwie. Nadaje się to dobrze do sprawdzania za pomocą wyrażenia regularnego, dlatego powinno pasować do (([A-Z_][A-Z1-9_]*)|(__.*__))$.

W tym przypadku Pylint mówi nam, że te zmienne wydają się być stałymi i wszystkie powinny być pisane WIELKIMI LITERAMI. Można tworzyć własne, wewnętrzne konwencje nazewnictwa, ale na potrzeby tego samouczka chcemy trzymać się standardu PEP 8. W tym przypadku zadeklarowane przez nas zmienne powinny być zgodne z konwencją małych liter. Odpowiednia reguła wyglądałaby mniej więcej tak: „powinno pasować do [a-z_][a-z0-9_]{2,30}$”. Zwróć uwagę na małe litery w wyrażeniu regularnym (a-z kontra A-Z).

Jeżeli wygenerujemy raport przy użyciu tej komendy (z nową regułą nazewnictwa):

pylint simplecaesar.py --const-rgx='[a-z\_][a-z0-9\_]{2,30}$'

To otrzymamy zaktualizowane sprawozdanie:

************* Module simplecaesar
simplecaesar.py:18:12: C0103: Constant name "x" doesn't conform to '[a-z\\_][a-z0-9\\_]{2,30}$' pattern (invalid-name)
simplecaesar.py:25:12: C0103: Constant name "x" doesn't conform to '[a-z\\_][a-z0-9\\_]{2,30}$' pattern (invalid-name)

------------------------------------------------------------------
Your code has been rated at 8.95/10 (previous run: 5.26/10, +3.68)

Ciągłe określanie tego wyrażenia regularnego w wierszu poleceń byłoby naprawdę uciążliwe, szczególnie jeśli używamy wielu innych opcji. Do tego właśnie służy plik konfiguracyjny. Można zorganizować Pylint tak, aby przechowywał opcje, by nie trzeba było deklarować ich w wierszu poleceń.

Korzystanie z pliku konfiguracyjnego to dobry sposób na sformalizowanie reguł i szybkie udostępnienie ich innym. Wywołanie pylint –generate-toml-config spowoduje utworzenie przykładowej sekcji .toml ze wszystkimi opcjami ustawionymi i wyjaśnionymi w komentarzach. Można to następnie dodać do pliku pyproject.toml lub dowolnego innego pliku .toml wskazanego opcją –rcfile.

Black

Black stanowi zupełnie inną koncepcję linterów. W porównaniu do Pylinta nie tylko wskazuje błędy, ale również potrafi je poprawiać. Należy jednak pamiętać, że jego możliwości są dużo mniejsze niż Pylinta (nie pokrywa tak wielu konwencji). Kolejną kwestią wartą przemyślenia jest styl, jaki wprowadza Black. Dla części osób potrafi być nieczytelny, co oczywiście nie jest niczym złym.

Wprowadzenie konkretnego stylu kodu dla całego projektu oznacza większą powtarzalność kodu, co w perspektywie czasu przekłada się na jego czytelność. Formatowanie wykonywane przez Black można dołączyć do automatyzacji procesów, np. pre-commit, bądź przy budowaniu kodu źródłowego. W ten sposób mamy pewność, że cały kod cechuje się podobnym stylem.

Black można zainstalować standardową komendą pip:

pip install black

Przykładowy kod:

pip install black

# 1.15

x = {  'a':37,'b':42,

'c':927}

class Foo  (     object  ):
  def f    (self   ):
    return       37*-2
  def g(self, x,y=42):
      return y

def very_important_function(template: str,*variables,file: os.PathLike,debug:bool=False,):
    """Applies `variables` to the `template` and writes to `file`."""
    with open(file, "w") as f:
     ...

Po sformatowaniu przez Black komendą:

black {source_file_or_directory}...

Otrzymamy uporządkowany skrypt:

x = {"a": 37, "b": 42, "c": 927}


class Foo(object):
    def f(self):
        return 37 * -2

    def g(self, x, y=42):
        return y


def very_important_function(
    template: str,
    *variables,
    file: os.PathLike,
    debug: bool = False,
):
    """Applies `variables` to the `template` and writes to `file`."""
    with open(file, "w") as f:
        ...

Black może również generować raport, bez jakichkolwiek zmian w kodzie. Do tego można dodać na końcu komendy uruchomieniowej parametr –check. W ten sposób otrzymamy krótki wynik:

$ black test.py --check
would reformat test.py
Oh no! 💥 💔 💥
1 file would be reformatted.

Podsumowanie

Na rynku dostępnych jest wiele narzędzi do statycznej analizy kodu. Optymalizacja czasu i zwiększona jakość kodu zdecydowanie zachęcają do wdrożenia tych rozwiązań nie tylko w obszarze profesjonalnym, ale również w prywatnym kodzie źródłowym.

Przedstawione w tym artykule programy są tylko jednymi z wielu dostępnych na rynku rozwiązań. Warto również wspomnieć o kilku innych rozwiązaniach takich jak:

  • Flake8,
  • Ruff,
  • Isort.

Działają na bardzo podobnej zasadzie, z różną listą zaimplementowanych reguł.

Niezależnie od tego, które narzędzie wybierzesz, zdecydowanie podniesie jakość dostarczanego kodu w bardzo krótkim czasie.

5/5 ( głosy: 3)
Ocena:
5/5 ( głosy: 3)
Autor
Avatar
Wojciech Richert

Z branżą IT związany od 2016 roku. W Sii na stanowisku Software Engineer zajmuje się głównie architekturą rozwiązań automatyzacyjnych w testach. Miłośnik triathlonu i biegów górskich

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Może Cię również zainteresować

Pokaż więcej artykułów

Bądź na bieżąco

Zasubskrybuj naszego bloga i otrzymuj informacje o najnowszych wpisach.

Otrzymaj ofertę

Jeśli chcesz dowiedzieć się więcej na temat oferty Sii, skontaktuj się z nami.

Wyślij zapytanie Wyślij zapytanie

Natalia Competency Center Director

Get an offer

Dołącz do Sii

Znajdź idealną pracę – zapoznaj się z naszą ofertą rekrutacyjną i aplikuj.

Aplikuj Aplikuj

Paweł Process Owner

Join Sii

ZATWIERDŹ

This content is available only in one language version.
You will be redirected to home page.

Are you sure you want to leave this page?