Хабрахабр

[Перевод] Путь к проверке типов 4 миллионов строк Python-кода. Часть 3

Представляем вашему вниманию третью часть перевода материала о пути, который прошла компания Dropbox, внедряя у себя систему проверки типов Python-кода.

→ Предыдущие части: первая и вторая

Достижение 4 миллионов строк типизированного кода

Ещё одной важной задачей (это был вторая по популярности проблема, волновавшая тех, кто участвовал во внутренних опросах) было увеличение объёма кода в Dropbox, покрытого проверками типов. Мы испробовали несколько подходов для решения этой задачи — от естественного роста объёма типизированной кодовой базы до сосредоточения усилий членов команды mypy на статическом и динамическом автоматизированном выводе типов. В итоге создалось такое впечатление, что тут нет простой выигрышной стратегии, но мы смогли достичь быстрого роста объёма аннотированного кода, скомбинировав множество подходов.

Работа по статической типизации кода была проведена примерно за три года. В результате в нашем самом большом Python-репозитории (с бэкенд-кодом) число строк аннотированного кода достигло почти 4 миллионов. В частности, мы можем формировать отчёты по коду с неопределённостями в типах, с такими, например, как явное использование типа Any в аннотациях, которые невозможно проверить, или с такими, как импорт сторонних библиотек, в которых нет аннотаций типов. Mypy теперь поддерживает различные виды отчётов о покрытии кода типами, которые упрощают наблюдение за ходом типизации. Мы, в рамках проекта по повышению точности проверки типов в Dropbox, внесли вклад в улучшении определений типов (так называемых stub-файлов) для некоторых популярных опенсорсных библиотек в централизованном Python-репозитории typeshed.

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

Количество строк аннотированного кода: сервер

Количество строк аннотированного кода: клиент

Общее количество строк аннотированного кода

Вот обзор основных особенностей тех действий, которые мы выполнили ради повышения объёма аннотированного кода в Dropbox:

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

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

Мы рассказываем о mypy на различных мероприятиях и общаемся с командами, помогая им начать пользоваться аннотациями типов. Популяризация mypy.

Мы проводим периодические опросы пользователей для выявления главных проблем. Опросы. Мы готовы зайти достаточно далеко в деле решения этих проблем (вплоть до создания нового языка ради ускорения mypy!).

Мы значительно улучшили производительность mypy благодаря использованию демона и mypyc. Производительность. Сделано это ради сглаживания неудобств, возникающих в процессе аннотирования, и ради того, чтобы получить возможность работать с большими объёмами кода.

Мы создали средства для поддержки запуска mypy в редакторах, которые пользуются популярностью в Dropbox. Интеграция с редакторами. Это значительно упростило процесс выполнения работ по аннотированию кода и по проверке его работоспособности. Сюда входят PyCharm, Vim и VS Code. Подобные действия обычно характерны при аннотировании существующего кода.

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

Во многих наших проектах используется набор инструментов SQLAlchemy. Поддержка сторонних библиотек. Мы, в соответствии с PEP 561, создали соответствующий stub-файл и написали плагин для mypy (опенсорсный), улучшающий поддержку SQLAlchemy. В нём применяются динамические возможности Python, которые типы PEP 484 неспособны смоделировать напрямую.

Сложности, с которыми мы встретились

Путь к 4 миллионам строк типизированного кода не всегда давался нам легко. На этом пути мы встретили немало ям и сделали несколько ошибок. Вот некоторые из проблем, с которыми мы столкнулись. Надеемся, рассказ о них поможет другим подобных проблем избежать.

Мы начинали работу с проверки лишь небольшого объёма файлов. Пропущенные файлы. Файлы в список проверки добавлялись тогда, когда в них появлялись первые аннотации. Всё, не входящее в число этих файлов, не проверялось. Это привело к значительной потере точности типизации, особенно — на ранних стадиях миграции. Если что-то импортировалось из модуля, расположенного за пределами области проверки, то речь шла о работе со значениями типа Any, которые вообще не проверялись. В самом худшем случае, когда объединялись две изолированных области кода, в которых, независимо друг от друга, типы были уже проверены, оказывалось, что типы этих областей несовместимы друг с другом. Такой подход до сих пор работал на удивление хорошо, хотя типичной была ситуация, когда добавление файлов в область проверки выявляло проблемы в других частях кодовой базы. Теперь, оглядываясь назад, мы понимаем, что нам следовало бы как можно раньше добавить в область проверки типов mypy базовые библиотечные модули. Это приводило к необходимости внесения в аннотации множества изменений. Это сделало бы нашу работу гораздо более предсказуемой.

Когда мы начинали работу, у нас было около 4 миллионов строк уже существующего Python-кода. Аннотирование старого кода. Мы создали инструмент, названный PyAnnotate, который может собирать сведения о типах во время выполнения тестов и умеет добавлять в код аннотации типов, основываясь на собранных сведениях. Было ясно, что аннотирование всего этого кода — задача не из лёгких. Сбор сведений о типах был медленным, автоматически сгенерированные аннотации часто требовали множества ручных правок. Однако особенно широкого внедрения этого инструмента мы не заметили. Мы думали об автоматическом запуске этого средства при каждой проверке кода, или о том, чтобы собирать сведения о типах, основываясь на анализе некоего небольшого объёма реальных сетевых запросов, но решили этого не делать, так как любой из этих подходов слишком рискован.

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

Выше я говорил о циклических импортах (о «клубках зависимостей»), существование которых усложнило ускорение mypy. Циклические импорты. Недавно мы завершили крупный проект по редизайну системы, который исправил большинство проблем mypy, касающихся циклических импортов. Нам, кроме того, пришлось серьёзно поработать над тем, чтобы снабдить mypy поддержкой всех видов идиом, причиной возникновения которых являются эти вот циклические импорты. Синтаксис Alore позволяет легко решать проблемы циклических команд импорта. Эти проблемы, на самом деле, произрастали из весьма ранних дней проекта, ещё из Alore, учебного языка, на который был изначально ориентирован проект mypy. Python усложняет работу с циклическими импортами в основном из-за неоднозначности выражений. Современный mypy унаследовал некоторые ограничения от своей ранней бесхитростной реализации (которая отлично подходила для Alore). Mypy не всегда способен выявлять подобные вещи до тех пор, пока большая часть цикла импорта не будет обработана. Например, в ходе операции присваивания значения может, на самом деле, определяться псевдоним типа. Неудачные решения, принятые на ранних этапах разработки системы, могут преподнести программисту неприятный сюрприз через много лет. В Alore таких неоднозначностей не было.

Итоги: путь к 5 миллионам строк кода и к новым горизонтам

Проект mypy прошёл долгий путь — от ранних прототипов, до системы, средствами которой контролируются типы продакшн-кода объёмом в 4 миллиона строк. По ходу развития mypy была осуществлена стандартизация подсказок по типам в Python. В наши дни вокруг типизации Python-кода развилась мощная экосистема. В ней нашлось место поддержке библиотек, в ней присутствуют вспомогательные средства для IDE и редакторов, в ней имеются несколько систем контроля типов, у каждой из которых есть свои плюсы и минусы.

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

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

Уважаемые читатели! Пользуетесь ли вы контролем типов в своих Python-проектах?

Показать больше

Похожие публикации

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

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

Кнопка «Наверх»