Какие существуют варианты запуска линтеров
Перейти к содержимому

Какие существуют варианты запуска линтеров

  • автор:

Линтеры

В сообществе Python, как и в любой другой группе людей, существует некое коллективное знание. Множество людей прошлось по всем возможным граблям и получило опыт через набитые шишки. Затем через какое-то время, благодаря выступлениям на конференциях, официальным заявлениям, документам, статьям в блогах, код-ревью и личному общению, это знание стало коллективным. Теперь мы просто называем его “хорошими практиками”.

К таким хорошим практикам можно отнести, например, следующие.

  • Форматировать код по PEP8 — если этого не делать, то другим людям будет намного сложнее понимать ваш код; в плохо оформленном коде сложнее увидеть суть, потому что мозг постоянно отвлекается на не несущие смысловой нагрузки особенности оформления.
  • Не допускать объявленных, но неиспользуемых переменных/функций/импортов — опять же, это усложняет восприятие кода; читателю потребуется потратить время на то, чтобы осознать, что вот на эту сущность обращать внимания не нужно.
  • Писать короткие функции — слишком сложные функции с большим количеством ветвлений и циклов тяжело понимать.
  • Не использовать изменяемый объект в качестве значения аргумента функции по умолчанию — иначе в результате можно получить очень неожиданные эффекты.

Соблюдать (и даже просто помнить) все хорошие практики — не самая простая задача. Зачастую люди плохо справляются с тем, чтобы отсчитывать пробелы и контролировать переменные, и вообще склонны допускать ошибки по невнимательности. Таковы люди, ничего не поделаешь. Машины, наоборот, прекрасно справляются с такими хорошо определёнными задачами, поэтому появились инструменты, которые контролируют следование хорошим практикам.

В компилируемых языках ещё на этапе компиляции программист может получить по щщам первый полезный фидбэк о написанном коде. Компилятор проверит, что код валиден и может быть скомпилирован, а также может выдать предупреждения и рекомендации, как сделать код лучше или читаемее. Т.к. Python является интерпретируемым языком, где этап компиляции как таковой отсутствует, линтеры особенно полезны. На самом деле, это очень важно и круто — узнать, что твой код как минимум является валидным Python-кодом, даже не запуская его.

В этом посте я рассмотрю два самых популярных линтера для Python:

Термин “lint” впервые начал использоваться в таком значении в 1979 году. Так называлась программа для статического анализа кода на C, которая предупреждала об использовании непортабельных на другие архитектуры языковых конструкций. С тех пор “линтерами” называют любые статические анализаторы кода, которые помогают находить распространённые ошибки, делать его однообразным и более читаемым. А названо оно «lint» в честь вот такой штуки:

lint roller

flake8

flake8 — это утилита-комбайн, которая органично объединяет в себе несколько других анализаторов кода ( pycodestyle , pyflakes и mccabe ), а также имеет огромную экосистему плагинов, которые могут добавить к стандартной поставке ещё кучу различных проверок. На данный момент, это самый популярный линтер для Python-кода. Кроме того, он предельно прост в настройке и использовании.

Установка

flake8 устанавливается, как и любой другой Python-пакет, через pip . Внутри виртуального окружения проекта выполните:

$ pip install flake8 

Если вы пользуетесь pipenv , то flake8 нужно устанавливать как dev-зависимость (ведь для работы программы линтер не нужен, он нужен только для разработчика):

$ pipenv install --dev flake8 

Аналогично с poetry :

$ poetry add --dev flake8 
$ flake8 --version 3.8.1 (mccabe: 0.6.1, pycodestyle: 2.6.0, pyflakes: 2.2.0) CPython 3.8.2 on Linux 

Использование

Для работы flake8 нужно просто указать файл или директорию, которые нужно проверять, например:

# проверить один файл $ flake8 file.py # проверить директорию рекурсивно $ flake8 src/ # проверить текущую директорию рекурсивно $ flake8 . 

Давайте для демонстрации попытаемся написать программу с как можно большим количеством “плохих практик”:

Возможно, вам не видно всего, но в этом коде точно есть следующие «запахи кода»:

  • import * — импортирование всех имен из модуля, хотя используется из них только одно;
  • import itertools — ненужный импорт;
  • во множестве мест стоят лишние или отсутствующие пробелы;
  • название функции написано в стиле PascalCase;
  • в некоторых местах используются табы для отступов;
  • используется список (изменяемый объект) в качестве значения аргумента функции по умолчанию;
  • используется слишком “широкое” выражение except: без указания конкретного исключения.

Давайте посмотрим, что flake8 скажет по поводу этого файла:

$ flake8 bad_code.py bad_code.py:1:1: F403 'from math import *' used; unable to detect undefined names bad_code.py:2:1: F401 'itertools' imported but unused bad_code.py:4:1: E302 expected 2 blank lines, found 1 bad_code.py:4:4: E271 multiple spaces after keyword bad_code.py:4:25: E211 whitespace before '(' bad_code.py:4:33: E202 whitespace before ')' bad_code.py:5:1: W191 indentation contains tabs bad_code.py:5:8: E271 multiple spaces after keyword bad_code.py:5:10: F405 'sqrt' may be undefined, or defined from star imports: math bad_code.py:5:21: E202 whitespace before ')' bad_code.py:7:1: E302 expected 2 blank lines, found 1 bad_code.py:7:23: E741 ambiguous variable name 'l' bad_code.py:8:1: E101 indentation contains mixed spaces and tabs bad_code.py:9:1: E101 indentation contains mixed spaces and tabs bad_code.py:11:1: E305 expected 2 blank lines after class or function definition, found 1 bad_code.py:12:1: E101 indentation contains mixed spaces and tabs bad_code.py:13:1: E101 indentation contains mixed spaces and tabs bad_code.py:13:20: E225 missing whitespace around operator bad_code.py:14:1: E101 indentation contains mixed spaces and tabs bad_code.py:14:67: W291 trailing whitespace bad_code.py:15:1: E101 indentation contains mixed spaces and tabs bad_code.py:15:14: W291 trailing whitespace bad_code.py:16:1: E101 indentation contains mixed spaces and tabs bad_code.py:16:5: E722 do not use bare 'except' bad_code.py:17:1: E101 indentation contains mixed spaces and tabs 

Как видите, flake8 нашёл кучу ошибок. Для каждой ошибки указана строка и номер символа в строке (не всегда точный), где произошла ошибка. Также у каждой категории ошибок есть свой код: E101 , W291 и т.д. Эти коды ошибок могут использоваться для включения/отключения правил. Тем не менее, не все ошибки были найдены. Давайте установим пару плагинов, чтобы добавить ещё правил!

Плагины

Как я уже говорил, для flake8 написано множество плагинов. Обычно плагины легко гуглятся или находятся в списках плагинов. Есть плагины для всех популярных фреймворков и библиотек — пользуйтесь ими! Давайте для нашего простого примера установим flake8-bugbear (находит распространённые логические ошибки) и pep8-naming (проверяет имена на соответствие PEP8).

Плагины устанавливаются так же, как и сам flake8 (для краткости я не буду писать примеры для pipenv и poetry — сами сможете обобщить):

$ pip install flake8-bugbear pep8-naming 

Давайте убедимся, что плагины действительно установились и flake8 может их найти:

$ flake8 --version 3.8.1 (flake8-bugbear: 20.1.4, mccabe: 0.6.1, naming: 0.10.0, pycodestyle: 2.6.0, pyflakes: 2.2.0) CPython 3.8.2 on Linux 

Если вы видите в списке в скобках названия ваших плагинов, то всё хорошо.

Теперь снова проверим наш файл:

$ flake8 bad_code.py bad_code.py:1:1: F403 'from math import *' used; unable to detect undefined names bad_code.py:2:1: F401 'itertools' imported but unused bad_code.py:4:1: E302 expected 2 blank lines, found 1 bad_code.py:4:4: E271 multiple spaces after keyword bad_code.py:4:6: N802 function name 'CalculateSquareRoot' should be lowercase bad_code.py:4:25: E211 whitespace before '(' bad_code.py:4:28: N803 argument name 'Number' should be lowercase bad_code.py:4:33: E202 whitespace before ')' bad_code.py:5:1: W191 indentation contains tabs bad_code.py:5:8: E271 multiple spaces after keyword bad_code.py:5:10: F405 'sqrt' may be undefined, or defined from star imports: math bad_code.py:5:21: E202 whitespace before ')' bad_code.py:7:1: E302 expected 2 blank lines, found 1 bad_code.py:7:23: E741 ambiguous variable name 'l' bad_code.py:7:25: B006 Do not use mutable data structures for argument defaults. They are created during function definition time. All calls to the function reuse this one instance of that data structure, persisting changes between them. bad_code.py:8:1: E101 indentation contains mixed spaces and tabs bad_code.py:9:1: E101 indentation contains mixed spaces and tabs bad_code.py:11:1: E305 expected 2 blank lines after class or function definition, found 1 bad_code.py:12:1: E101 indentation contains mixed spaces and tabs bad_code.py:13:1: E101 indentation contains mixed spaces and tabs bad_code.py:13:20: E225 missing whitespace around operator bad_code.py:14:1: E101 indentation contains mixed spaces and tabs bad_code.py:14:67: W291 trailing whitespace bad_code.py:15:1: E101 indentation contains mixed spaces and tabs bad_code.py:15:14: W291 trailing whitespace bad_code.py:16:1: E101 indentation contains mixed spaces and tabs bad_code.py:16:5: E722 do not use bare 'except' bad_code.py:16:5: B001 Do not use bare `except:`, it also catches unexpected events like memory errors, interrupts, system exit, and so on. Prefer `except Exception:`. If you're sure what you're doing, be explicit and write `except BaseException:`. bad_code.py:17:1: E101 indentation contains mixed spaces and tabs 

В выводе появились новые категории ошибок ( N802 , B006 ) — они как раз добавлены плагинами. На этот раз, как мне кажется, найдены все ошибки. К сожалению, flake8 не умеет сам чинить найденные ошибки, поэтому давайте сделаем это вручную:

Обратите внимание на строки 8 и 10, там содержится комментарии # noqa . При помощи этих комментариев можно заставить flake8 игнорировать ошибки. Это бывает полезно, когда по какой-то причине код должен остаться именно таким, например:

  • он автоматически сгенерирован и исправление в нём ошибок не имеет смысла;
  • исправление этой ошибки породит куда более уродливый код, чем комментарий # noqa ;
  • у вас просто сейчас нет времени, чтобы исправлять эту ошибку (плохая отмазка, серьёзно).

Если не указать код ошибки, то будут проигнорированы все ошибки в строке — я не рекомендую так делать, потому что так можно пропустить и на самом деле плохие ошибки. Если указать номер правила, то flake8 будет игнорировать только указанную категорию, а о других ошибках в этой же строке доложит. Вообще, комментариями # noqa нужно пользоваться с большой осторожностью. Считайте, что каждый раз, когда вы это делаете, вы берёте на себя ответственность за эту строку кода. Если программа сломается в этом месте, то пеняйте на себя — минздрав линтер вас предупреждал.

Конфигурация

flake8 для работы не требует никакой конфигурации. Он имеет достаточно (но не слишком) строгие настройки по умолчанию, которые подойдут большинству пользователей, но иногда бывает нужно отключить (или наоборот включить) определённые правила на уровне всего проекта. Сделать это можно через файлы .flake8 или setup.cfg в корне проекта. Если у вас в проекте уже есть файл setup.cfg , то можно добавить конфигурацию flake8 в него. Если вы предпочитаете для каждой утилиты держать отдельный файл конфигурации, то используйте .flake8 . В любом случае, формат для обоих этих файлов совпадает:

[flake8] ignore = D203,E741 exclude = # No need to traverse our git directory .git, # There's no value in checking cache directories __pycache__, # The conf file is mostly autogenerated, ignore it docs/source/conf.py, # The old directory contains Flake8 2.0 old, # This contains our built documentation build, # This contains builds of flake8 that we don't want to check dist max-complexity = 10 

В конфигурации можно перечислить игнорируемые правила и директории, в которые flake8 заглядывать не будет, а также максимальную цикломатическую сложность для функций. Все эти настройки будут автоматически применяться к запускам flake8 во всех поддиректориях проекта.

Если же вам не хватает какого-нибудь правила, и его нет даже в уже готовых плагинах, то написание собственного плагина — не такая уж и сложная задача. Я попробовал, у меня на это ушло 2-3 часа.

pylint

pylint — это ещё один популярный линтер для Python. Этот линтер значительно умнее и продвинутее flake8 . В pylint из коробки заложено очень много правил и рекомендаций, и по умолчанию они все включены, так что он достаточно строгий и придирчивый. Чтобы интегрировать его в существующий большой проект придётся потратить некоторое время, чтобы выбрать те правила, которые для вас важны. Так же как и flake8 , pylint поддерживает плагины для расширения базовой функциональности, но насколько я вижу, экосистема плагинов у pylint значительно беднее.

Также при каждом запуске pylint выводит оценку качества кода по десятибалльной шкале, а также следит, как эта оценка меняется с течением времени. Достичь десятки очень сложно, но это благородная цель, к которой нужно стремиться.

Установка

Установка pylint принципиально ничем не отличается от установки flake8 . Выполнить внутри виртуального окружения проекта:

$ pip install pylint 
$ pipenv install --dev pylint 
$ poetry add --dev pylint 

Использование

pylint можно натравить на определённый файл:

$ pylint file.py 

С директориями у pylint дела обстоят чуть сложнее. Все директории он обрабатывает как питоновские модули, поэтому если в директории нет хотя бы пустого файла __init__.py , то работать с ней pylint не сможет. Имейте это ввиду.

Давайте попросим pylint прокомментировать файл с плохими практиками из предыдущего примера:

$ pylint bad_code.py ************* Module bad_code bad_code.py:4:25: C0326: No space allowed before bracket def CalculateSquareRoot (Number ): ^ (bad-whitespace) bad_code.py:4:33: C0326: No space allowed before bracket def CalculateSquareRoot (Number ): ^ (bad-whitespace) bad_code.py:5:0: W0312: Found indentation with tabs instead of spaces (mixed-indentation) bad_code.py:5:21: C0326: No space allowed before bracket return sqrt(Number ) ^ (bad-whitespace) bad_code.py:13:19: C0326: Exactly one space required around assignment your_number=float(input('Enter your number: ')) ^ (bad-whitespace) bad_code.py:14:66: C0303: Trailing whitespace (trailing-whitespace) bad_code.py:15:13: C0303: Trailing whitespace (trailing-whitespace) bad_code.py:1:0: W0622: Redefining built-in 'pow' (redefined-builtin) bad_code.py:1:0: C0114: Missing module docstring (missing-module-docstring) bad_code.py:1:0: W0401: Wildcard import math (wildcard-import) bad_code.py:4:0: C0103: Function name "CalculateSquareRoot" doesn't conform to snake_case naming style (invalid-name) bad_code.py:4:0: C0103: Argument name "Number" doesn't conform to snake_case naming style (invalid-name) bad_code.py:4:0: C0116: Missing function or method docstring (missing-function-docstring) bad_code.py:7:0: W0102: Dangerous default value [] as argument (dangerous-default-value) bad_code.py:7:0: C0103: Argument name "l" doesn't conform to snake_case naming style (invalid-name) bad_code.py:7:0: C0116: Missing function or method docstring (missing-function-docstring) bad_code.py:16:4: W0702: No exception type(s) specified (bare-except) bad_code.py:1:0: W0614: Unused import acos from wildcard import (unused-wildcard-import) bad_code.py:1:0: W0614: Unused import acosh from wildcard import (unused-wildcard-import) bad_code.py:1:0: W0614: Unused import asin from wildcard import (unused-wildcard-import) bad_code.py:1:0: W0614: Unused import asinh from wildcard import (unused-wildcard-import) . bad_code.py:2:0: W0611: Unused import itertools (unused-import) ------------------------------------- Your code has been rated at -41.43/10 

Я немного сократил вывод. Как видите, даже без плагинов pylint нашёл все ожидаемые ошибки, и даже больше — например, он даже предлагает написать документацию.

По каждой ошибке можно запросить более подробную справку, используя название правила из конца строки с ошибкой или код:

$ pylint --help-msg=missing-docstring $ pylint --help-msg=R0902 

Вот какие ошибки pylint находит для файла, который с точки зрения flake8 не содержит никаких ошибок:

$ pylint not_so_bad_code.py ************* Module not_so_bad_code not_so_bad_code.py:1:0: C0114: Missing module docstring (missing-module-docstring) not_so_bad_code.py:4:0: C0116: Missing function or method docstring (missing-function-docstring) not_so_bad_code.py:8:0: C0103: Argument name "l" doesn't conform to snake_case naming style (invalid-name) not_so_bad_code.py:8:0: C0116: Missing function or method docstring (missing-function-docstring) not_so_bad_code.py:20:11: W0703: Catching too general exception Exception (broad-except) ----------------------------------- Your code has been rated at 6.67/10 

А вот так в pylint можно игнорировать отдельную ошибку на строке прямо в файлах с кодом:

def append_item(item, l=None): # pylint: disable=C0103 . 

Ещё pylint умеет игнорировать ошибки в блоках кода:

def test(): # Disable all the no-member violations in this function # pylint: disable=no-member . 

И для файлов целиком. Вот так можно отключить все ошибки из категорий Warning, Convention и Refactor:

# pylint: disable=W,C,R 

А можно не проверять файл вообще:

# pylint: skip-file 

Подробнее о правилах управления сообщениями смотрите в документации. Для более сложной настройки правил, придётся по-настоящему сконфигурировать pylint .

Конфигурация

pylint настраивается через файл .pylintrc в корне проекта. Чтобы создать дефолтный файл конфигурации, нужно выполнить следующую команду:

$ pylint --generate-rcfile > .pylintrc 

Созданный файл содержит все поддерживаемые pylint опции с довольно подробными комментариями, так что углубляться я не буду.

Плагины

Давайте установим какой-нибудь популярный плагин, например, pylint-django :

$ pip install pylint-django 

Теперь запускать pylint нужно вот так:

$ pylint --load-plugins pylint_django [..other options..] 

либо в .pylintrc нужно исправить директиву load-plugins :

load-plugins=pylint_django 

Интеграция линтера в проект

Интегрировать линтер в проект можно на трёх уровнях. Я рекомендую по возможности использовать все три, но обязательным является как минимум один (лучше всего, чтобы это была CI система).

Редактор кода или IDE

Популярные IDE для Python умеют легко интегрировать с линтерами и подсвечивать ошибки линтера прямо в редактируемом файле. Это удобно, потому что позволяет не выходя из редактора получить полезную обратную связь.

PyCharm автоматически находить установленные flake8 и pylint внутри интерпретатора проекта и подключается к ним.

VS Code требует небольшой настройки, которая описана здесь.

Git-хуки

В git есть возможность запрограммировать определенные скрипты (хуки) в ответ на действия пользователя. Например, можно запускать какие-нибудь проверки перед коммитом, заново скачивать зависимости проекта при переключении веток, высылать сообщение в рабочий чат после пуша в удалённый репозиторий и вообще что угодно.

я запушель

Нас интересует возможность запускать линтер перед коммитом так, чтобы если линтер найдёт какие-нибудь проблемы, операция коммита прерывалась. Git-хуки можно настроить, написав несложный shell-скрипт, но я рекомендую использовать для этого специальные утилиты, такие как pre-commit. Вот здесь можно найти описание процесса настройки запуска flake8 через pre-commit .

Обратите внимание, что Git-хуки нужно будет настроить на машине каждого разработчика в проекте.

Continuous Integration (CI)

Последний эшелоном защиты от попадания “сломанного” кода в основную ветку репозитория является система непрерывной интеграции (CI) — такая, как:

  • GitHub Actions;
  • GitLab CI (а ещё читайте пост в блоге моего хорошего товарища про основы GitLab CI);
  • Travis CI;
  • или другая.

На каждый пуш в репозиторий система непрерывной интеграции должна запускать проверки (включая все линтеры и тесты), и если что-то идёт не так, рядом с коммитом должен появиться красный крестик. Ветку с таким коммитом на конце нельзя будет слить с основной веткой проекта через пулл-реквест на GitHub (или мёрдж-реквест на GitLab). Пример того, как настроить GitHub Actions для запуска flake8 и других питоновских проверок.

CI — это единственный надёжный способ обеспечить качество кода. Предыдущие способы нужны скорее для удобства разработчика, чтобы он как можно скорее получал обратную связь, но разработчик вправе проигнорировать или отключить эти предупреждения.

Заключение

В подзаголовке этой статьи я написал фразу, что линтер способен сэкономить разработчику один день жизни в месяц. Фраза может показаться кликбейтной, но, поверьте мне, это так это и работает. Возможно, я даже преуменьшил. Чем раньше найдена ошибка, тем быстрее идёт разработка. Иногда линтер предотвращает баги, иногда спасает от мучительного траблшутинга. Линтеры абсолютно точно значительно сокращают время, потраченное коллегами на код-ревью, потому что все тривиальные ошибки будут отловлены автоматикой.

Не стоит недооценивать линтеры. Это те инструменты, которые делают из “кодера” настоящего “software engineer”, из мальчика — мужчину. Если вы до сих пор не пользуетесь каким-нибудь линтером, то рекомендую всерьез задуматься над внедрением!

Я предпочитаю использовать flake8 , потому что он простой и понятный, как топор. С ним легко работать, его легко настроить под свои нужды, а почти любые недостающие правила можно получить через уже готовые плагины.

У pylint тоже есть свои последователи. Его ценят за подробный вывод и большое количество правил в стандартной поставке. Мне же pylint всегда казался слишком сложным в эксплуатации.

А кто-то вообще рекомендует устанавливать flake8 и pylint параллельно.

Если понравилась статья, то подпишитесь на уведомления о новых постах в блоге, чтобы ничего не пропустить!

Дополнительное чтение

  • документация flake8 ;
  • исходный код flake8 ;
  • список плагинов flake8 ;
  • сайт, где можно посмотреть правила flake8 ;
  • документация pylint ;
  • исходный код pylint ;
  • обсуждение “flake8 vs pylint” на Reddit;
  • пост на RealPython про качество кода;
  • статья на Хабре про линтеры.

golangci-lint и внедрение его в большой проект

Хороший разработчик обычно стремится написать код, который не просто работает, но работает стабильно. Код, который легко читать и сопровождать. Достичь такого высокого качества кода кроме тестов также помогают единый стиль кода, хорошая документация, а также простота и изящность, как на уровне всей системы, так и на уровне отдельных модулей и функций.

Считается, что соответствие кода принятым в команде стандартам качества проверяет как сам разработчик, так и его коллеги на код-ревью. Проблема только в том, что все мы люди, и всегда можем что-то упустить. А иногда (на самом деле очень часто), разработчики закрывают глаза на те моменты, которые кажутся им незначительными. В связи с этим придумали статический анализ кода. Если говорить просто, это анализ исходного кода без непосредственного запуска этого кода. Когда вы пробегаете глазами по коду, чтобы убедиться, что с ним всё ок — это тоже статический анализ ��. Противоположный подход — динамический анализ — предполагает, что код будет запускаться и анализироваться во время исполнения. Примером динамического анализа кода являются тесты, но о них поговорим в другой раз.

Обычно статический анализ строится на основе набора правил, например:

  • Длина строки не должна превышать 120 символов;
  • Нельзя вызывать defer внутри for ;
  • и т.д.

Программы, которые выполняют статический анализ, называют линтерами. Для Go написано уже очень много линтеров, каждый из которых специализируется на своём наборе проверок. Например, bodyclose проверяет, что разработчик не забыл закрыть тело ответа при отправке HTTP-запросов:

internal/httpclient/httpclient.go:13:13: response body must be closed 

А wsl следит за тем, чтобы в коде были правильно расставлены пустые строки для повышения читаемости.

Линтеров для Go настолько много, что управлять всеми ими самостоятельно стало в один момент сложнее, чем проверять программы вручную. Поэтому следующим этапом стало появление металинтеров — программ, которые позволяют настроить и запустить большое количество линтеров из единого места. Одним из таких является golangci-lint .

Установка#

golangci-lint можно установить как с помощью привычных пакетных менеджеров, так и с помощью go install . Однако авторы рекомендуют всё-таки ставить с помощью пакетного менеджера.

Быстрый старт#

После установки можно сразу пользоваться программой, используя настройки по умолчанию:

golangci-lint run 
golangci-lint run ./. 

./. означает, что линтер отработает на файлах в текущей директории, а также рекурсивно на всех файлах во всех подпапках текущей директории.

Настройка#

У golangci-lint хорошая конфигурация по умолчанию, однако она может подойти не для всех проектов. Поэтому можно положить кастомный конфиг-файл .golangci.yml в папку, откуда вы запускаете golangci-lint или явно передать путь до файла:

golangci-lint run ./. --config=./.golangci.yml 

Сам конфигурационный файл может выглядеть примерно вот так:

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 
# Options for analysis running.  run:  # The default concurrency value is the number of available CPU.  concurrency: 4  # Timeout for analysis, e.g. 30s, 5m.  # Default: 1m  timeout: 5m  # Exit code when at least one issue was found.  # Default: 1  issues-exit-code: 2  # Include test files or not.  # Default: true  tests: false  # List of build tags, all linters use it.  # Default: [].  build-tags:  - mytag  # Which dirs to skip: issues from them won't be reported.  # Can use regexp here: `generated.*`, regexp is applied on full path.  # Default value is empty list,  # but default dirs are skipped independently of this option's value (see skip-dirs-use-default).  # "/" will be replaced by current OS file path separator to properly work on Windows.  skip-dirs:  - src/external_libs  - autogenerated_by_my_lib  # Enables skipping of directories:  # - vendor$, third_party$, testdata$, examples$, Godeps$, builtin$  # Default: true  skip-dirs-use-default: false  # Which files to skip: they will be analyzed, but issues from them won't be reported.  # Default value is empty list,  # but there is no need to include all autogenerated files,  # we confidently recognize autogenerated files.  # If it's not please let us know.  # "/" will be replaced by current OS file path separator to properly work on Windows.  skip-files:  - ".*\\.my\\.go$"  - lib/bad.go  # If set we pass it to "go list -mod=". From "go help modules":  # If invoked with -mod=readonly, the go command is disallowed from the implicit  # automatic updating of go.mod described above. Instead, it fails when any changes  # to go.mod are needed. This setting is most useful to check that go.mod does  # not need updates, such as in a continuous integration and testing system.  # If invoked with -mod=vendor, the go command assumes that the vendor  # directory holds the correct copies of dependencies and ignores  # the dependency descriptions in go.mod.  #  # Allowed values: readonly|vendor|mod  # By default, it isn't set.  modules-download-mode: readonly  # Allow multiple parallel golangci-lint instances running.  # If false (default) - golangci-lint acquires file lock on start.  allow-parallel-runners: false  # Define the Go version limit.  # Mainly related to generics support in go1.18.  # Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.17  go: '1.18' 

Чаще всего будете редактировать конфиг в следующих ситуациях:

  • Нужно выключить определённую проверку;
  • Нужно добавить или убрать линтер;
  • Нужно задать настройки для конкретного линтера;
  • Нужно исключить файл или папку из анализа.

Давайте разберёмся с данными кейсами по порядку.

Выключение проверки#

golangci-lint позволяет исключить правила из вывода по шаблону. Например, многие команды отказываются от обязательного комментирования экспортируемых (т.е. публичных) типов, методов, полей, переменных и т.д. Отключить проверку на наличие комментария можно, добавив в конфиге в секцию issues.exclude регулярное выражение, которому соответствует сообщению от линтера:

1 2 3 4 5 
# .  issues:  exclude:  - "(comment on exported (method|function|type|const)|should have( a package)? comment|comment should be of the form)"  # . 

Либо включить исключения по умолчанию, если данная опция была принудительно выключена, т.к. данное исключение уже есть в наборе из коробки:

1 2 3 4 
# .  issues:  exclude-use-default: true  # . 

Все исключения по умолчанию можно посмотреть, вызвав команду golangci-lint run —help :

# EXC0001 errcheck: Almost all programs ignore errors on these functions and in most cases it's ok - Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv). is not checked # EXC0002 golint: Annoying issue about not having a comment. The rare codebase has such comments - (comment on exported (method|function|type|const)|should have( a package)? comment|comment should be of the form) # EXC0003 golint: False positive when tests are defined in package 'test' - func name will be used as test\.Test.* by other packages, and that stutters; consider calling this # EXC0004 govet: Common false positives - (possible misuse of unsafe.Pointer|should have signature) # EXC0005 staticcheck: Developers tend to write in C-style with an explicit 'break' in a 'switch', so it's ok to ignore - ineffective break statement. Did you mean to break out of the outer loop # EXC0006 gosec: Too many false-positives on 'unsafe' usage - Use of unsafe calls should be audited # EXC0007 gosec: Too many false-positives for parametrized shell calls - Subprocess launch(ed with variable|ing should be audited) # EXC0008 gosec: Duplicated errcheck checks - (G104|G307) # EXC0009 gosec: Too many issues in popular repos - (Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less) # EXC0010 gosec: False positive is triggered by 'src, err := ioutil.ReadFile(filename)' - Potential file inclusion via variable # EXC0011 stylecheck: Annoying issue about not having a comment. The rare codebase has such comments - (comment on exported (method|function|type|const)|should have( a package)? comment|comment should be of the form) # EXC0012 revive: Annoying issue about not having a comment. The rare codebase has such comments - exported (.+) should have comment( \(or a comment on this block\))? or be unexported # EXC0013 revive: Annoying issue about not having a comment. The rare codebase has such comments - package comment should be of the form "(.+). # EXC0014 revive: Annoying issue about not having a comment. The rare codebase has such comments - comment on exported (.+) should be of the form "(.+). " # EXC0015 revive: Annoying issue about not having a comment. The rare codebase has such comments - should have a package comment, unless it's in another file for this package 

Если вам необходимо выключить правило не глобально, а в определённом файле, или даже на определённой строке, можно оставить в соответствующем месте директиву nolint :

1 2 3 
func Sum(a, b int) int < //nolint:revive,golint  return a + b > 

Конкретные линтеры хоть и не обязательно, но всё же желательно указать, чтобы случайно не пропустить другие ошибки.

Изменение списка линтеров#

По умолчанию golangci-lint использует небольшой набор из поддерживаемых линтеров. Чтобы включить какой-то из линтеров, например, вышеупомянутый bodyclose , нужно добавить его в секцию linters.enable :

1 2 3 4 5 6 7 
# .  linters:  enable:  # .  - bodyclose  # .  # . 

Отдельно устанавливать при этом линтеры не нужно, они все зашиты внутрь golangci-lint .

Настройка конкретного линтера#

Кроме того, каждый линтер можно потюнить в том же файле. Например, чтобы поменять максимальную допустимую длину строки, которую проверяет lll , в секции linter-settings.lll нужно добавить следующее:

1 2 3 4 5 6 7 
# .  linter-settings:  # .  lll:  line-length: 70 # default is 120  # .  # . 

Т.е. нет необходимости хранить кучу конфигов для каждого линтера — всё можно настроить в одном единственном файле в едином формате!

Исключение файлов и папок#

Чтобы исключить определённый файл или папку из проверки, нужно добавить его в секцию exclude_paths :

1 2 3 4 5 6 7 8 9 
# .  linters:  # .  exclude_paths:  # .  - "vendor"  - "*.gen.go"  # .  # . 

Настройка golangci-lint в Visual Studio Code#

Официальное расширение Go для Visual Studio Code умеет запускать golangci-lint из коробки. Достаточно в settings.json прописать следующие параметры:

1 2 3 4 5 6 
  "go.lintTool": "golangci-lint",  "go.lintFlags": [  "--fast"  ] > 

При включенном флаге —fast golangci-lint будет исключать некоторые линтеры, работающие долго, чтобы повысить отзывчивость редактора.

Советы по внедрению линтеров в команде#

Если вы собираетесь внедрить линтеры в проект, который только начался, скорее всего это будет просто. Но часто проблемы с качеством кода, и как следствие, необходимость внедрения анализа кода возникает в проектах с длинной и не всегда красивой историей. Для таких проектов есть несколько вариантов:

  1. Включить golangci-lint с максимальным количеством линтеров, причём так, чтобы при наличии ошибок, сборка проекта не прошла. На самом деле, именно так часто команды с большой нажитой кодовой базой и поступают, стреляя себе в ногу. Ведь в итоге всем разработчикам со своих задач приходится переключаться на исправление ошибок, либо на линтер в конце концов забивают, делая его запуск опциональным. В общем, такой экстремальный вариант подходит, только если проект только начался, и кода написано не так много.
  2. Внедрять постепенно. Любое значительное изменение в процессе разработки почти всегда негативно влияет как на мотивацию и производительность команды, так и качество и правильность использования внедряемого инструмента или процесса. Поэтому такие изменения не должны быть резкими. Линтеры — не исключение. И тут есть несколько подходов:
    1. Не включать все проверки сразу. Настройте базовый набор проверок, и после того, как все ошибки будут устранены, вводите новые.
    2. Не включайте всю кодовую базу в скоуп проверок сразу. В первую очередь исправьте ошибки в самых критичных местах проекта, добавив остальные в исключения. По мере исправления ошибок сужайте «зону исключений».
    3. Включите проверки только для нового кода. Если запустить golangci-lint с флагом —new-from-rev=HEAD~1 или просто —new , проверки будут срабатывать только на последних изменениях в коде. Постепенно можно менять указатель на всё более старые коммиты.

    Скорее всего вам стоит применять комбинацию из данных трёх способов, чтобы достичь наиболее плавного внедрения линтеров в проект.

    Окружение для разработки#

    Хорошим тоном является прогон линтера разработчиком перед коммитом или созданием пулл-реквеста — это экономит время и коллегам, и вам. Чтобы начать это делать было комфортнее, необходимо избавить разработчика от мук по установке и настройке линтера на рабочей машине.

    Наверное, самым популярным в сообществе Go инструментом по автоматизации различных разработческих задач является GNU Make. Базовый Makefile с тасками по запуску линтера может выглядеть так:

     1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 
    PROJECT_DIR = $(shell pwd) PROJECT_BIN = $(PROJECT_DIR)/bin $(shell [ -f bin ] || mkdir -p $(PROJECT_BIN)) PATH := $(PROJECT_BIN):$(PATH)  GOLANGCI_LINT = $(PROJECT_BIN)/golangci-lint  .PHONY: .install-linter .install-linter:  ### INSTALL GOLANGCI-LINT ###  [ -f $(PROJECT_BIN)/golangci-lint ] || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(PROJECT_BIN) v1.46.2  .PHONY: lint lint: .install-linter  ### RUN GOLANGCI-LINT ###  $(GOLANGCI_LINT) run ./. --config=./.golangci.yml  .PHONY: lint-fast lint-fast: .install-linter  $(GOLANGCI_LINT) run ./. --fast --config=./.golangci.yml 

    Скопируйте его в ваш проект (или возьмите из репозитория), затем запустите в терминале команду:

    make lint 

    Обратите внимание, что после выполнения команды сначала создалась папка bin , а в ней — исполняемый файл golangci-lint . Только после этого была запущена сама программа. Т.е. данная команда автоматически устанавливает локальную копию golangci-lint в проект, и используется именно она. Во-первых, это избавляет разработчика от необходимости устанавливать golangci-lint вручную. Во-вторых, так вы можете использовать разные версии данной программы в разных проектах.

    Полезные ссылки#

    • Официальный сайт golangci-lint
    • Пример Makefile с тасками для запуска линтеров
    • Илья Исаев, автор golangci-lint «Линтеры в Go: как их готовить» @ GopherCon Russia 2019

    Линтинг — Python: Настройка окружения

    У кода есть множество разных характеристик, по которым можно судить, насколько хорошо он написан. Среди них есть одна базовая, с которой начинают все разработчики — это стиль написания.

    Сравните два варианта оформления кода:

    ## Без форматирования def find_sum(a,b): c = a+b; return c ## С форматированием def find_sum(a, b): sum = a + b return sum 

    Второй вариант читается значительно проще. Чем больше будет кода, тем больше будет различий. Хороший стиль кодирования — базовое требование к коду в коммерческой разработке, потому что он упрощает командную разработку. В одном проекте может работать несколько десятков программистов. Важно, чтобы им было легко читать код друг друга, не спотыкаясь о неправильное форматирование.

    Для отслеживания подобных ситуаций существуют линтеры — особый класс программ. Линтеры содержат большое количество правил, по которым они могут выдать рекомендации по коду. Другими словами, они подсказывают, как стоит писать код, а как — не стоит. Более того, часто линтеры расширяются плагинами под конкретные фреймворки, что позволяет отслеживать специфичные ошибки и давать рекомендации по кодированию в этих фреймворках.

    В Python особой популярностью пользуется линтер flake8 . Количество правил, по которым он проверяет код, исчисляется десятками . Посмотрите на этот небольшой участок кода:

    from math import sqrt def sum(a, b): c = 5 return a + b 

    С точки зрения форматирования здесь все хорошо, а что скажет линтер flake8?

    Он выдаст два предупреждения:

    • F401 ‘math.sqrt’ imported but unused . Модуль импортируется, но не используется — либо он не нужен, либо в коде есть ошибка
    • F841 local variable ‘c’ is assigned to but never used . Переменная не используется — либо она не нужна, либо в коде ошибка

    Ссылки выше ведут на страницы конкретных правил. Там подробно объясняется, почему так код писать не нужно. Изучать правила flake8 очень полезно, они прививают хорошие практики написания кода.

    Установка и настройка flake8

    Линтер flake8 устанавливается как dev зависимость прямо в проект:

    --group=dev flake8 

    После установки его можно настроить под набор ваших правил и ограничений. Для этого нужно создать файл конфигурации setup.cfg, который нужно добавить в репозиторий. Ниже пример такого файла, который используется для настройки линтера в наших практиках:

    Выше настраивается максимальная сложность функций, максимальная длина строчки кода и другие параметры. Так же мы можем указать, какие файлы не нужно проверять линтером или какие правила можно игнорировать. Список всех опций вы можете изучить в документации .

    И последний шаг — запуск flake8:

    # Обратите внимание на точку # Это указание текущей директории и всех поддиректорий poetry run flake8 . 

    Открыть доступ

    Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

    • 130 курсов, 2000+ часов теории
    • 1000 практических заданий в браузере
    • 360 000 студентов

    Наши выпускники работают в компаниях:

    Линтер ES Lint

    ESLint — это инструмент, помогающий анализировать написанный на JavaScript код, находить синтаксические ошибки и автоматически их исправлять, писать аккуратный код в едином стиле по определённым правилам.

    ESLint в терминале

    Если у вас пока нет ESLint, его нужно установить из npm.

    Давайте испытаем ESLint в действии. Попробуем написать простую функцию для вывода суммы двух чисел и с помощью ESLint проверить правильность написанного кода. Для это в терминале выполним команду

    npm run lint 

    ESLint показывает, что нашёл 6 ошибок в файле main.js . Цифры слева говорят на какой строке, на каком символе, была найдена ошибка. Дальше в строке идёт описание ошибки. Например:

    Текст 5 errors and 0 warnings potentially fixable with the —fix option после списка ошибок говорит о том, что пять из шести найденных ошибок ESLint сможет исправить автоматически.

    Обратите внимание, что требования, по которым ESLint проверяет код, на каждом проекте могут быть свои, всё зависит от правил, принятых в команде. Например, в команде может быть принято использование двойных кавычек, в таком случае ESLint не будет ругаться на двойные кавычки, а вот при использовании одинарных возникнет ошибка. Такие правила описываются в специальном файле .eslintrc .

    Исправление ошибок

    Для исправления ошибок у ESLint есть опция fix . Чтобы воспользоваться этой опцией, выполним в терминале команду

    npm run lint -- --fix 

    Ключ —fix говорит о том, что мы хотим исправить ошибки автоматически, а два подчёркивания — перед ключом помогают понять терминалу, что ключ относится не к команде npm run lint , а к тому, что за ней скрывается — к eslint .

    ESLint исправил 5 ошибок: поправил пробелы, заменил кавычки на одинарные, удалил ненужную точку с запятой — теперь код выглядит чище. Осталось вызвать функцию, чтобы исправить последнюю ошибку. Здесь ESLint нам не поможет.

    ESLint в редакторе

    А что, если нам хочется сразу, в момент написания кода, знать, какие ошибки мы совершаем, и исправлять их на лету? Для этого в редактор можно установить расширение для ESLint, которое будет подсвечивать найденную ошибку прямо в файле, а при наведении подсказывать, в чём именно ошибка.

    Установка расширения для ESLint в VS Code

    Расширение для ESLint в VS Code может попросить подтвердить его запуск, если пакет eslint установлен локально (наш случай). Когда расширение спросит, откуда брать пакет eslint , нужно нажать «Allow», чтобы разрешить использовать eslint в текущем проекте.

    С помощью расширения для ESLint в редакторе можно автоматически исправить ошибки. Для этого нужно навести на подсвеченную ошибку, нажать кнопку Quick fix во всплывающем окошке и выбрать один из предложенных вариантов. Например, можно исправить только конкретную ошибку, а можно и все доступные разом. Если ошибка не может быть автоматически исправлена, вместо кнопки Quick fix появится текст No quick fixes available или будут предложены альтернативные варианты решения.

    Установка расширения для ESLint в Atom

    В Atom тоже требуется специальное расширение linter-eslint для работы ESLint. Чтобы в Atom установить расширение, нужно перейти в раздел настроек «Install Packages». Открыть его можно из окна команд (сочетание клавиш Ctrl + Shift + P на Windows и Command + Shift + P на macOS), введя в поиске «Install Packages».

    Также нужный раздел настроек можно открыть через меню: Edit → Preferences → Install — на Windows, Atom → Preferences → Install — в macOS.

    Далее ищем нужное расширение и устанавливаем его:

    После установки расширения может появиться всплывающее окно с предложением установить нужные зависимости, то есть другие расширения, с помощью которых работает linter-eslint . Для правильной работы линтера нужно установить все предложенные зависимости.

    Теперь можно приступить к исправлению ошибок, исправить большинство ошибок можно автоматически, наведя на ошибку и нажав «Fix» или снова использовать окно команд, где выполнить Linter Eslint: Fix File .

    Включение поддержки ESLint в WebStorm

    В WebStorm не нужно устанавливать отдельное расширение, ESLint работает в этом редакторе «из коробки», достаточно только включить поддержку ESLint. Откройте окно Preferences с настройками, перейдите на вкладку ESLint (Languages and Frameworks → JavaScript → Code Quality Tools → ESLint) и выберете автоматическую конфигурацию ESLint — Automatic ESLint configuration. При автоматической конфигурации ESLint всегда будет искать в директории проекта файл .eslintrc с правилами оформления кода и ориентироваться на него.

    Исправляются ошибки так же просто, достаточно нажать правой кнопкой мыши в файле с ошибками и выбрать из списка «Fix ESLint problems».

    «Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *