Testerze, popraw swój kod!
Wątpliwości
Jeżeli zaczynasz swoją drogę z programowaniem w Python’ie, z pewnością cieszy Cię każdy kawałek napisanego kodu, który po prostu działa. Z czasem jednak możesz zacząć się zastanawiać:
Czy to co tworzę, spełnia standardy programowania?
Czy mógłbym napisać to lepiej/wydajniej?
Jeśli zaczynają nachodzić Cię takie refleksje, to znaczy, że nadszedł moment, w którym powinieneś się zaznajomić ze statyczną analizą kodu oraz narzędziami ją wspomagającymi, takimi jak pylint.
Statyczna analiza kodu ma na celu sprawdzenie struktury kodu bez jego wykonywania. Często przeprowadza się ją przy użyciu specjalistycznych bibliotek i narzędzi. Jednym z nich jest pylint, będący dedykowanym narzędziem dla języka Python. Zawiera on zestaw predefiniowanych reguł, pozwalających w sposób automatyczny sprawdzić jak bardzo Twój kod odbiega od zdefiniowanych standardów (i jak się polepsza, im więcej programujesz 😉).
Jeżeli np. definicja funkcji którą piszesz zawiera zbyt dużą liczbę argumentów lub moduł, który stworzyłeś, jest zbyt długi (zawiera zbyt dużo linii kodu), to pylint Cię o tym poinformuje. Po więcej informacji na temat standardów kodowania w Python’ie odsyłam do https://www.python.org/dev/peps/pep-0008/. W tym wpisie opiszemy narzędzie, które sprawdzi w jakim stopniu Twój program spełnia te standardy😉
W dalszej części dowiesz się jak zainstalować pylint oraz zintegrować go z PyCharm’em w celu łatwiejszej i szybszej analizy swojego kodu.
Środowisko
Użycie pylint zaprezentujemy przy użyciu zestawiania następującego oprogramowania:
- System: Windows 10
- Język: Python w wersji 3.8.1
- Środowisko programistyczne: PyCharm Community Edition 2019.3.1
- pylint w wersji 2.5.3
Skrypt do testów
W pierwszej kolejności wygenerujemy nowy skrypt python’owy, na którym będziemy testowali możliwości narzędzia pylint. W tym celu otwórzmy IDE PyCharm’a i utwórzmy nowy projekt. Nazwijmy go cars. Przy tworzeniu projektu wykorzystamy wirtualne środowisko Python’a (venv), które może być automatycznie utworzone przez PyCharm’a (opcja New environment using Virtualenv). Więcej o venv możecie poczytać w naszych darmowych materiałach – Czym jest Python Virtual Environment (venv)?.
Następnie dodajmy do projektu nowy skrypt python’owy o nazwie car_brands.py. Wewnątrz skryptu wklejmy następujący kod:
class Car(object): def __init__(self, brand): self.brand = brand def main(): car1 = Car("Seat") car2 = Car("Volvo") print("Car 1 brand: ",car1.brand) print("Car 2 brand: ",car2.brand) if __name__ == "__main__": main()
Aby uruchomić skrypt, wystarczy przycisnąć prawy przycisk myszy na nazwie skryptu i po wybraniu opcji Run ‘car_brands’, powinniśmy uzyskać następujący wynik:
Car 1 brand: Seat Car 2 brand: Volvo
Car
, posiadająca jeden atrybut brand (marka). W funkcji main
tworzone są dwa obiekty: car1
oraz car2
. Tworząc obiekty, ustawiamy od razu atrybut brand, wpisując nazwy marek: Seat oraz Volvo. Następnie wypisujemy na ekran kolejno marki obiektu car1
oraz car2
.
Może Cię zastanawiać, po co na końcu skryptu dodaliśmy:
if __name__ == "__main__": main()
Aby to zrozumieć, należy najpierw wyjaśnić, że kiedy uruchamiany jest skrypt python’owy inicjalizowane są pewne zmienne m.in. zmienna __name__
. Jeśli uruchamiamy skrypt bezpośrednio (a nie np. z importu), zmienna __name__
przyjmuje wartość __main__
.
W naszym przypadku mówimy więc po prostu: jeśli skrypt uruchamiany jest bezpośrednio, wywołaj funkcję main()
.
Pylint – instalacja
Skoro już mamy działający skrypt, który możemy poddać analizie statycznej, przejdźmy do instalacji pylint.
Jak przystało na narzędzie python’owe, pylint jest całkowicie darmowy, a jego instalacja banalnie prosta. W PyCharm otwórz File -> Settings -> Project: cars -> Project Interpreter.
Aby dodać pylint’a, wystarczy wybrać + obecny z prawej strony listy zainstalowanych modułów. Po otwarciu się okna Available Packages, wpiszmy w pole wyszukiwania pylint. Następnie wybierzmy pylint z listy i kliknijmy Install Package. Zwróć uwagę, że na dole okna Available Packages znajduje się checkbox Specify version, pozwalający na określenie którą dokładnie wersje pylint’a chcemy zainstalować. Zalecana wersja w naszym przypadku to pylint 2.5.3.
Po zakończonej instalacji na dole okna powinna pojawić się informacja:
Pylint – jak używać
Kiedy pylint jest już zainstalowany, możemy używać go na kilka sposobów:
- poprzez wywołanie pylint.exe w linii komend,
- poprzez interfejs graficzny pylint-gui (środowisko graficzne dostarczane wraz z instalacją pylint’a), lub
- zintegrować go z PyCharm’em.
W dalszej części przedstawimy ostatni sposób, pozwalający na szybkie sprawdzanie kodu podczas jego tworzenia, przy pomocy jednego kliknięcia.
Integracja z PyCharm
Ponieważ zainstalowaliśmy już pylint’a, teraz musimy poinformować w jakiś sposób PyCharm’a, że chcemy go używać.
Konfiguracja integracji
W tym celu należy wybrać wybrać File -> Settings -> Tools -> External Tools i przycisnąć +. Powinno wyświetlić się poniższe okno:
Należy w nim wpisać:
- nazwę narzędzia (proponujemy pylint) w polu Name
$PyInterpreterDirectory$\pylint
w polu Program. Jest to ścieżka, w której znajduje się interpreter Pythona dla bieżącego projektu,$FileName$
w polu Arguments. Jest to nazwa bieżącego pliku, oraz$ProjectFileDir$
w Working directory. Jest to ścieżka do aktualnie analizowanego pliku.
Trzy ostatnie parametry, oznaczone przy pomocy $<nazwa>$, to makra zdefiniowane w Pycharm. Naciskając którykolwiek z przycisków Insert Macro… (znajdujący się po prawej stronie okna Create Tool), możemy podejrzeć listę dostępnych makr oraz ich aktualne wartości. I tak w naszym przypadku naciśnięcie Insert Macro… i wybranie FileName spowoduje, że w Macro preview zobaczymy nazwę pliku: car_brands.py. Na liście dostępnych makr odnajdź dwa pozostałe: PyInterpreterDirectory oraz ProjectFileDir i zobacz jakie przyjmują wartości 😉
Definicje zaczerpnięte zostały z https://pl.wikipedia.org/wiki/Makro.
Po takiej konfiguracji pylint będzie wiedzieć, że kiedy go uruchamiamy ma analizować bieżący plik. Następnie zaakceptujmy ustawienia klikając OK. Spowoduje to zamknięcie okna Create Tool.
W oknie Settings klikamy Apply i OK, aby zamknąć okno.
Skrót klawiszowy
Kolejnym krokiem, ułatwiającym korzystanie z pylint’a w PyCharm’ie, jest stworzenie skrótu klawiaturowego, powodującego uruchomienie pylint’a na aktualnie edytowanym pliku.
Aby to uczynić, wybierzmy File -> Settings -> Keymap -> External Tools, odnajdźmy pylint’a, naciśnijmy prawy przycisk myszy i wybierzmy Add keyboard shortcut.
Następnie musimy wybrać najbardziej odpowiadający nam skrót. Proponujemy CTRL+SHIFT+S.
Po zatwierdzeniu zmian, pylint może zostać uruchomiony dla dowolnego skryptu należącego do projektu.
Aby przetestować działanie pylint’a na naszym skrypcie, otwórzmy w PyCharm’ie plik car_brands.py i wciśnijmy CTRL+SHIFT+S. W rezultacie otrzymamy…
Wyniki analizy
Po uruchomieniu statycznej analizy kody w pylint, wygenerowane zostanie podsumowanie dla użytkownika w panelu uruchamiania plików Run:
C:\PycharmProjects\cars\venv\Scripts\pylint car_brands.py ************* Module car_brands car_brands.py:10:25: C0326: Exactly one space required after comma print("Car 1 brand: ",car1.brand) ^ (bad-whitespace) car_brands.py:11:25: C0326: Exactly one space required after comma print("Car 2 brand: ",car2.brand) ^ (bad-whitespace) car_brands.py:15:0: C0304: Final newline missing (missing-final-newline) car_brands.py:1:0: C0114: Missing module docstring (missing-module-docstring) car_brands.py:1:0: C0115: Missing class docstring (missing-class-docstring) car_brands.py:1:0: R0205: Class 'Car' inherits from object, can be safely removed from bases in python3 (useless-object-inheritance) car_brands.py:1:0: R0903: Too few public methods (0/2) (too-few-public-methods) car_brands.py:6:0: C0116: Missing function or method docstring (missing-function-docstring) ------------------------------------------------------------------ Your code has been rated at 2.00/10 Process finished with exit code 24
Z pierwszej linii możemy wyczytać, że program pylint.exe, znajdujący się w C:\PycharmProjects\cars\venv\Scripts, został uruchomiony z argumentem car_brands.py. Następnie wypisane są naruszenia reguł zdefiniowanych w pylint w następującym formacie:
{nazwa_skryptu}:{linia_kodu}:{kolumna}: {id_wiadomości}: {wiadomość} ({symbol})
Czytając wygenerowany dla nas raport pylint’a, możemy się dowiedzieć na przykład, że:
- w linii 10 i 11 (
car_brands.py:10:25
orazcar_brands.py:11:25
) brakuje spacji (bad-whitespace
), - w linii 15 (
car_brands.py:15:0
) brakuje pustej linii końcowej, - jeden z błędów w linii 1 (
car_brands.py:1:0
) o nazwie useless-object-inheritance wskazuje, że w Python 3 nie trzeba deklarować, że klasa dziedziczy po obiekcie.
Ponieważ nasz kod narusza zasady zdefiniowane w pylint, został oceniony na 2/10. 10 to w przypadku pylint wartość najlepsza, czyli najbardziej zgodna z regułami.
Poprawki po analizie
Teraz poprawmy trzy wymienione powyżej naruszenia zasad.
Pierwsza poprawka
Błąd wykazany przez pylint:
car_brands.py:15:0: C0304: Final newline missing (missing-final-newline)
Informacja ta wskazuje linię 15 i znak numer 0, czyli początek linii.
Dodajmy więc do naszego skryptu nową pustą linię na końcu skryptu. Zobaczmy wynik działania naszego makro pylint kryjącego się pod skrótem CTRL+SHIFT+S:
************* Module car_brands car_brands.py:10:25: C0326: Exactly one space required after comma print("Car 1 brand: ",car1.brand) ^ (bad-whitespace) car_brands.py:11:25: C0326: Exactly one space required after comma print("Car 2 brand: ",car2.brand) ^ (bad-whitespace) car_brands.py:1:0: C0114: Missing module docstring (missing-module-docstring) car_brands.py:1:0: C0115: Missing class docstring (missing-class-docstring) car_brands.py:1:0: R0205: Class 'Car' inherits from object, can be safely removed from bases in python3 (useless-object-inheritance) car_brands.py:1:0: R0903: Too few public methods (0/2) (too-few-public-methods) car_brands.py:6:0: C0116: Missing function or method docstring (missing-function-docstring) ------------------------------------------------------------------ Your code has been rated at 3.00/10 (previous run: 2.00/10, +1.00)
Jak widzimy błąd już nie jest zgłaszany, gdyż został poprawiony😉 Podana jest także informacja, że poprzednio nasz kod został oceniony na 2, a więc ulepszyliśmy go o 1 punkt.
Druga poprawka
Błąd wykazany przez pylint:
car_brands.py:1:0: R0205: Class 'Car' inherits from object, can be safely removed from bases in python3 (useless-object-inheritance)
Aby go naprawić, usuńmy słowo ‘object’ z klasy Car
. Z informacji o błędzie car_brands.py:1:0
możemy wyczytać, że jest to pierwsza linia skryptu car_brands.py
.
Zobaczmy wynik działania naszego makra pylint kryjącego się pod skrótem CTRL+SHIFT+S:
************* Module car_brands car_brands.py:10:25: C0326: Exactly one space required after comma print("Car 1 brand: ",car1.brand) ^ (bad-whitespace) car_brands.py:11:25: C0326: Exactly one space required after comma print("Car 2 brand: ",car2.brand) ^ (bad-whitespace) car_brands.py:1:0: C0114: Missing module docstring (missing-module-docstring) car_brands.py:1:0: C0115: Missing class docstring (missing-class-docstring) car_brands.py:1:0: R0903: Too few public methods (0/2) (too-few-public-methods) car_brands.py:6:0: C0116: Missing function or method docstring (missing-function-docstring) ------------------------------------------------------------------ Your code has been rated at 4.00/10 (previous run: 3.00/10, +1.00)
Dobrze idzie 😀 Kod został oceniony na 4.0, czyli znowu poprawiliśmy się o 1.0 😉 Czas na kolejne poprawki…
Trzecia poprawka
Błąd wykazany przez pylint:
car_brands.py:10:25: C0326: Exactly one space required after comma print("Car 1 brand: ",car1.brand) ^ (bad-whitespace) car_brands.py:11:25: C0326: Exactly one space required after comma print("Car 2 brand: ",car2.brand) ^ (bad-whitespace)
Aby je naprawić, musimy dodać w odpowiednich miejscach spacje w liniach 10 i 11, tak aby uzyskać:
print("Car 1 brand: ", car1.brand) print("Car 2 brand: ", car2.brand)
Kod po wstępnych poprawkach
Nasz kod powinien wyglądać teraz tak:
class Car(): def __init__(self, brand): self.brand = brand def main(): car1 = Car("Seat") car2 = Car("Volvo") print("Car 1 brand: ", car1.brand) print("Car 2 brand: ", car2.brand) if __name__ == "__main__": main()
Wynik statycznej analizy przedstawia się następująco:
C:\PycharmProjects\cars\venv\Scripts\pylint car_brands.py ************* Module car_brands car_brands.py:1:0: C0114: Missing module docstring (missing-module-docstring) car_brands.py:1:0: C0115: Missing class docstring (missing-class-docstring) car_brands.py:1:0: R0903: Too few public methods (0/2) (too-few-public-methods) car_brands.py:6:0: C0116: Missing function or method docstring (missing-function-docstring) ------------------------------------------------------------------ Your code has been rated at 6.00/10 (previous run: 4.00/10, +2.00) Process finished with exit code 24
Tym razem nasz skrypt został oceniony lepiej, na 6 punktów. Podana jest także informacja, że poprzednio został on oceniony na 4, a więc polepszyliśmy nasz kod o 2 punkty. W sumie po wszystkich poprawkach udało nam się polepszyć go aż o 4 punkty 😀
Gdy nie zgadzamy się z pylint’em
Eliminowanie niezgodności z regułami pylint’a jest oczywiście pożądane, jednak czasami zdarzają się sytuacje, kiedy z różnych powodów chcemy ignorować niektóre ostrzeżenia. Możemy nie chcieć np., żeby sprawdzane było czy funkcje posiadają dokumentacje. Aby zmusić pylint do nieprzestrzegania danej reguły, na początku skryptu musimy zadeklarować:
# pylint: disable={nazwa reguły}
W naszym przypadku, jeśli chcemy zignorować ostrzeżenia o braku dokumentacji, wystarczy, że rzucimy okiem na wcześniejszy wynik analizy. Przy każdym ostrzeżeniu z prawej strony w nawiasie znajduje się nazwa reguły, np missing-module-docstring. Z tą wiedzą możemy zignorować wszystkie ostrzeżenia związane z docstring. Wystarczy, że na samej górze naszego skryptu wpiszemy:
# pylint: disable=missing-module-docstring # pylint: disable=missing-class-docstring # pylint: disable=missing-function-docstring
Nasz kod będzie więc wyglądał następująco:
# pylint: disable=missing-module-docstring # pylint: disable=missing-class-docstring # pylint: disable=missing-function-docstring class Car(): def __init__(self, brand): self.brand = brand def main(): car1 = Car("Seat") car2 = Car("Volvo") print("Car 1 brand: ", car1.brand) print("Car 2 brand: ", car2.brand) if __name__ == "__main__": main()
Teraz po uruchomieniu pylint otrzymamy:
C:\PycharmProjects\cars\venv\Scripts\pylint car_brands.py ************* Module car_brands car_brands.py:5:0: R0903: Too few public methods (0/2) (too-few-public-methods) ------------------------------------------------------------------ Your code has been rated at 9.00/10 (previous run: 6.00/10, +3.00) Process finished with exit code 8
Tym razem kod został oceniony na 9/10.
Widzimy, że nasz kod został oceniony na 9 punktów, a wszystkie ostrzeżenia związane z dokumentacją metod i klas (czyli docstring) zniknęły 😀
Pozostało jeszcze jedno spostrzeżenie too-few-public-methods. Oznacza ono, że klasa Car
zawiera zbyt mało metod. W naszym przykładzie jest to w pełni akceptowalne, dlatego zignorujemy tę wiadomość.
Teraz Ty – odrobina praktyki z pylint
We wcześniejszej części pokazaliśmy czym jest pylint, jak go skonfigurować oraz jak z niego korzystać. Teraz przyszedł czas na proste zadanie praktyczne, aby utrwalić wiedzę i poćwiczyć korzystanie z narzędzia. Twoim zadaniem będzie użycie pylint’a w warunkach bojowych – a dokładniej statyczna analiza i poprawki w kodzie.
W ramach ćwiczenia utwórz nowy skrypt python’owy o nazwie calculator.py. Wklej do niego następujący kod:
def input_data(): a = int(input("Insert first number:")) b = int(input("Insert second number:")) operator = input("Which operator you want to use(+,-,*,/)") if operator in ['+','-','*',"/"]: return a, b, operator else: raise ValueError("Wrong operator4!") def multiply(a, b): return a*b def divide(a, b): return a/b def add(a, b): return a+b def subtract(a, b): return a-b def main(): print("SIMPLE CALCULATOR") a, b, operator = input_data() if operator=="+": result = add(a,b) elif operator=="-": result = subtract(a, b) elif operator =="/": result = divide(a,b) elif operator=="*": result = multiply(a,b) print("Result is {}".format(result)) if __name__ == "__main__": main()
Działanie skryptu jest następujące: po jego uruchomieniu użytkownik jest proszony o podanie dwóch liczb oraz wybranie działania jakie ma na nich zostać wykonane. Jeżeli wybrana zostanie inna operacja niż dodawanie, odejmowanie, mnożenie lub dzielenie (kolejno +
, -
, *
, /
) zostanie zgłoszony wyjątek. Wynik operacji jest wypisywany na ekranie.
Po utworzeniu skryptu wciśnij CTRL+SHIFT+S. Jak widzisz kod został oceniony na -0.69/10.
Spróbuj samodzielnie poprawić kod, tak aby otrzymać wynik 10/10.
Rozwiązanie
Przykładowe rozwiązanie znajdziesz poniżej:
Inne sposoby dbania o kod
Jeżeli używasz PyCharm’a, być może znasz skrót CTRL+ALT+L, pozwalający na automatyczne formatowanie kodu. Użycie tego skrótu powoduje sformatowanie kodu zgodnie z regułami zdefiniowanymi w ustawieniach PyCharm’a. Aby dowiedzieć się jakie reguły będą zastosowane, należy otworzyć File -> Settings -> Editor -> Code Style -> Python lub wcisnąć skrót CTRL+ALT+S (otwiera okno ustawień) i przenawigować do Editor -> Code Style -> Python. PyCharm umożliwia określenie między innymi rozmiaru odstępów, wcięć, spacji wokół operatorów, ustawień zawijania tekstu, odstępów między klasami, metodami etc.
Jeśli chodzi o pylint, to nie formatuje on automatycznie kodu. Wyświetla tylko informację co należy poprawić, aby kod był zgodny ze zdefiniowanymi regułami. Mogą one jednakże dotyczyć tak skomplikowanych kwestii jak maksymalna dopuszczalna ilość instrukcji return w metodzie, maksymalna/minimalna liczba publicznych metod w klasie, czy maksymalna liczba argumentów w metodzie.
Przy pracy nad kodem dobrym pomysłem może okazać się użycie automatycznego formatowania PyCharm’a (skrót CTRL+ALT+L), aby sformatować kod, a następnie wywołanie pylint’a w celu dalszej oceny jakości kodu.
Podsumowanie
Na koniec pamiętaj, że pylint jest tylko narzędziem. Efektywnie wspiera on pisanie dobrego kodu, ale ostateczna decyzja zawsze należy do Ciebie.
Czasem kod, który pylint uznaje za niespełniający standardów, jest bardziej czytelny dla ludzi, niż kod który akceptuje pylint. Weźmy np. sytuację, w której chcesz napisać funkcję matematyczną. Prawdopodobnie posłużysz się wtedy symbolami jak x, y
etc. W takim przypadku pylint zgłosi naruszenie reguły:
Variable name "x" doesn't conform to snake_case naming style (invalid-name).
Jednak każdy człowiek znający podstawy matematyki łatwiej zrozumie równanie w postaci: y=a*x+b
niż prosta = wspolczynnik_nachylenia * wartosc_x + stala
.
Rozsądek, praktyka a przede wszystkim standardy wypracowane w zespole wyznaczają finalnie reguły jakich będziemy się trzymać😉
Mimo wszystko stosowanie się do standardów pylint’a znacząco poprawia czytelność i utrzymywalność kodu. Zwłaszcza jeśli jesteś początkującym programistą/testerem.
Zachęcamy Cię, abyś przed udostępnieniem swojego kodu najpierw przeanalizował go przy pomocy pylint. Pozwoli to uniknąć wielu błędów i stworzyć lepszy kod. No i jest to rodzaj testowania czyli aktywności, którą kochamy najbardziej na jaktestowac.pl.
Polecane linki
- https://www.pylint.org/ – oficjalna strona projektu pylint
- https://pypi.org/project/pylint/ – pylint w serwisie pypi
- http://pylint.pycqa.org/en/latest/ – dokumentacja pylint
- https://www.python.org/dev/peps/pep-0008/ – standardy pisania kodu w Pythonie
Suplement – statyczna analiza kodu w innych językach programowania
Obecnie we wszystkich popularniejszych językach programowania istnieją narzędzia do statycznej analizy kodu. Część z tych narzędzi jest dedykowana tylko dla jednego języka, ale niektóre są dostępne dla szerszej gamy. Poniżej wylistowaliśmy kilka z najpopularniejszych narzędzi. Praktycznie wszystkie z nich posiadają pluginy dla popularnych IDE, takich jak VS Code, Visual Studio, Eclipse, IntelliJ, PyCharm i innych.
.NET
Java
- https://sourceforge.net/projects/jlint/ – JLint
- https://checkstyle.sourceforge.io/ – CheckStyle
JavaScript
- https://www.jslint.com/ – JSLint
TypeScript
- https://github.com/typescript-eslint/typescript-eslint – typescript-eslint
Wielojęzykowe
- https://www.sonarqube.org/downloads/ – SonarQube – jest dostępny dla 27 języków, takich jak C#, Java, Python, JavaScript, PHP, C++ i wielu innych.
- https://www.sonarlint.org/ – SonarLint – jest dostępny dla 12 języków, takich jak C#, Java, Python, JavaScript, PHP, C++ i wielu innych.
- https://pmd.github.io/ – PMD.
- https://prettier.io/ – Prettier, który jest dostępny dla języków JavaScript, TypeScript, Java, PHP, Ruby i wielu innych.
- https://sonarcloud.io/ – SonarCloud, który jest dostępny dla języków JavaScript, TypeScript, Python, C#, Java, PHP, Ruby i wielu innych.