Это расшифровка одной из тем пилотного выпуска Python Junior Podcast от команд сообщества MoscowPython и курсов LearnPython. Вы можете прослушать аудиоверсию статьи:
Григорий Петров, MoscowPython, VoxImplant: У любого разработчика есть немного идеального кода. Обычно это несколько экранов, классов, методов, помещенных в один файл (реже — два или три). Разработчик его постоянно пишет, улучшает, дописывает. И долгими зимними вечерами он открывает этот единственный файл, смотрит на эти несколько строк идеального кода, плачет, а потом закрывает его и делает то, за что ему платят деньги.
Поэтому статический анализ кода — это скорее история боли, костылей и ловушек. Это как писать код через замочную скважину при ограничении нашего мозга в пять элементов. Что делать, чтобы писать код через замочную скважину?
Илья Лебедев, LearnPython, Devman: Расскажи.
Григорий: Тут на помощь нам приходят линтеры и типизаторы. Есть два варианта: «лайтовый» и серьезный. Лайтовый — использовать линтер, например pylint.
Если разработчик использует, к примеру, Visual Studio Code (а я думаю, многие разработчики так делают), делаем следующее: ставим pytools, нажимаем ctrl + shift + p, выбираем линтер. Visual Studio Code предложит список, в котором нужно выбрать pylint. И дальше наш редактор подсветит красным тот код, который (по его мнению) мы написали не очень хорошо.
Небольшой совет: не стоит запускать линтер на существующий код.
Если в нем хотя бы несколько тысяч строк, то линтер покажет миллион всяких «косяков». И вы замучаетесь их править. В идеале включать pylint на новые исходные файлы, новые проекты…
Илья: ...но такое бывает редко.
Григорий: Мы редко создаем новые файлы с исходным кодом? Да ладно!
Илья: Приходишь ты на работу, тебе говорят: «Вот твой сайт на Django. До тебя над ним работал человек, который ещё и дизайнер». Ты открываешь код и понимаешь, что там всё плохо. Ты хочешь что-то сделать, чтобы было менее больно каждый день. Чтобы кровь шла хотя бы из одного глаза. Тут хочется использовать линтер. Как в таком случае быть?
Григорий: Отдельно работать с каждым файлом. Идеально было бы создать новый файл, включить для него линтер и начать писать код. Линтер подсвечивает нам (как ему кажется) ошибки. В процессе написания мы сразу же их правим и в результате экономим себе огромное количество времени на отладку.
Мы можем этого не замечать, но в нашем коде будет гораздо меньше ошибок.
Если вдруг у нас появилось свободное время (та самая сказка, которую ты только что рассказал), то мы можем открыть существующий файл, «натравить» на него линтер и поправить те косяки (потенциальные косяки), о которых линтер расскажет.
Если мы хотим что-то совсем серьезное, мы можем использовать type checker, например mypy. Это тяжелая артиллерия. Для его запуска в Visual Studio Code также нажимаем ctrl + shift + p, выбираем линтер и кликаем mypy.
Слона нужно есть по частям. В идеальном мире линтер лучше использовать, когда вы пишете новый код. Открываешь новый файл, включаешь для него линтер, начинаешь писать. Он тебе красным подсвечивает ошибки, ты их сразу же исправляешь, и у тебя всё хорошо. Ты практически не тратишь времени, а ошибок в коде становится меньше. Так на отладку уйдёт гораздо меньше времени.
Если у тебя существующий проект, и твой внутренний перфекционист хочет принести время в жертву «богу рефакторинга», то можно включать линтер на каждый файл.
Он покажет не 10 тыс. ошибок, а пару сотен. При этом большая часть из этого ошибками не будет — это будет код, который можно по-разному трактовать. Ты приводишь его в соответствии с тем, что нравится pylint’у, попутно исправляя некоторое количество ошибок. Хотя их там может и не быть.
Линтеры больше смотрят на внешний вид кода. Они совсем не понимают, о чем он.
Type checker’ы же назначают каждому идентификатору тип и пытаются понять, не хочешь ли ты, например, прибавить к строке число или обратиться к полю объекта, которого там не может быть.
Если просто «натравить» type checker на код, по сравнению с линтером, он покажет меньше ошибок. Но если писать код и указывать типы (а начиная с версии Python 3.5 мы можем это делать), то мы даем подсказки type checker’у, что мы имели в виду.
Вооружаясь этими подсказками, он начнет подчеркивать красным те вещи, которые не подчеркнет никакой линтер. Это может сэкономить дни, а иногда и недели отладки.
Огромный плюс Python заключается в том, что если мы хотим использовать типы, то в отличие, например, от С++, нам не надо расставлять их везде. Мы можем расставлять типы только в тех местах, где мы хотим углубленно рассказать о том, что делает наш код.
Пишем мы, например, метод, который принимает name, и думаем: «Наверняка я потом сюда какой-нибудь идентификатор вставлю». Дописываем: двоеточие str. Так мы даем подсказку: на момент написания мы были уверены, что имена будут строковые.
В таком случае, каким бы огромным ни был наш проект, type checker будет по всему коду отслеживать, что эта функция вызывается только со строками. Стоит тебе туда вставить другой объект, всё сразу же будет подчеркнуто красным. Круто?
Илья: Звучит неплохо. По шкале от 0 до 10, насколько автотесты помогают избавлять код от багов?
Григорий: Шесть.
Илья: Так. Линтеры?
Григорий: Семь.
Илья: Линтер круче, чем тесты?
Григорий: Да.
Илья: Окей. И наконец, типы?
Григорий: Типы это 9 из 10. Практически ultimate weapon, которое можно использовать для написания кода без ошибок.
Илья: Когда ты приходишь на проект, ты первым делом заставляешь всех подставлять типы, а не писать тесты?
Григорий: Есть такое. Понимаешь, когда пишешь «на плюсах», там есть такой стиль программирования: defensive development. Это когда ты «обкладываешь» код проверками. Если написанный в таком стиле код запустился и не упал, то он работает.
Илья: А как быть с этим в Питоне?
Григорий: Использовать версию 3.5 или версию 2.7 и проставлять типы в комментариях. Типы способны найти большинство наших ошибок лучше, чем тесты. Потому что тесты надо писать руками и поддерживать.
Честно скажу, unit-тесты хороши там, где есть unit’ы. Например, в библиотеках: если вы пишете библиотеку, то unit-тесты у вас зайдут лучше, чем типы. К сожалению, сайт на Django содержит мало unit’ов. Из него очень…
Илья: Подожди. Во-первых, автотесты бывают не только unit-тестами. Во-вторых, с этими типами такая же проблема, как и с тестами: их надо писать и поддерживать. Кроме того, они могут быть огромными. Ты видел, как выглядит typedef для списка словарей со строками?
Григорий: Нормально выглядит.
Илья: Но как минимум pep8 придется нарушить, чтоб такое написать.
[Григорий отрицательно качает головой]