Шлях до перевірки типів 4 мільйонів рядків Python-коду. Частина 3

Представляємо вашій увазі третину перекладу матеріалу про шлях, який пройшла компанія Dropbox, впроваджуючи систему перевірки типів Python-коду.

Шлях до перевірки типів 4 мільйонів рядків Python-коду. Частина 3

→ Попередні частини: перша и друга

Досягнення 4 мільйонів рядків типізованого коду

Ще одним важливим завданням (це була друга за популярністю проблема, яка хвилювала тих, хто брав участь у внутрішніх опитуваннях) було збільшення обсягу коду в Dropbox, покритого перевірками типів. Ми випробували кілька підходів для вирішення цього завдання – від природного зростання обсягу типизованої кодової бази до зосередження зусиль членів команди mypy на статичному та динамічному автоматизованому виведенні типів. У результаті склалося таке враження, що тут немає простої виграшної стратегії, але ми змогли досягти швидкого зростання обсягу анотованого коду, скомбінувавши безліч підходів.

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

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

Шлях до перевірки типів 4 мільйонів рядків Python-коду. Частина 3
Кількість рядків анотованого коду: сервер

Шлях до перевірки типів 4 мільйонів рядків Python-коду. Частина 3
Кількість рядків анотованого коду: клієнт

Шлях до перевірки типів 4 мільйонів рядків Python-коду. Частина 3
Загальна кількість рядків анотованого коду

Ось огляд основних особливостей тих дій, які ми виконали задля підвищення обсягу анотованого коду Dropbox:

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

Звіти про типізацію. Ми щотижня розсилаємо командам звіти про рівень типізації їх коду і даємо поради щодо того, що саме варто анотувати в першу чергу.

Популяризація mypy. Ми розповідаємо про mypy на різних заходах та спілкуємося з командами, допомагаючи їм почати користуватися анотаціями типів.

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

Продуктивність. Ми значно покращили продуктивність mypy завдяки використанню демона та mypyc. Зроблено це заради згладжування незручностей, що виникають у процесі анотування, і для того, щоб отримати можливість працювати з великими обсягами коду.

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

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

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

Складнощі, з якими ми зустрілися

Дорога до 4 мільйонів рядків типізованого коду не завжди давалася нам легко. На цьому шляху ми зустріли чимало ям та зробили кілька помилок. Ось деякі проблеми, з якими ми зіткнулися. Сподіваємося, розповідь про них допоможе іншим подібних проблем уникнути.

Пропущені файли. Ми розпочинали роботу з перевірки лише невеликого обсягу файлів. Все, що не входить до цих файлів, не перевірялося. Файли до списку перевірки додавалися тоді, як у них з'являлися перші інструкції. Якщо щось імпортувалося з модуля, розташованого за межами області перевірки, то йшлося про роботу зі значеннями типу Any, які взагалі перевірялися. Це призвело до значної втрати точності типізації, особливо на ранніх стадіях міграції. Такий підхід досі працював напрочуд добре, хоча типовою була ситуація, коли додавання файлів до області перевірки виявляло проблеми в інших частинах кодової бази. У найгіршому випадку, коли об'єднувалися дві ізольовані області коду, в яких, незалежно один від одного, типи були вже перевірені, виявлялося, що типи цих областей несумісні один з одним. Це призводило до необхідності внесення в інструкції безлічі змін. Тепер, озираючись назад, ми розуміємо, що нам слід якомога раніше додати в область перевірки типів mypy базові бібліотечні модулі. Це зробило б нашу роботу набагато більш передбачуваною.

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

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

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

Підсумки: шлях до 5 мільйонів рядків коду та нових горизонтів

Проект mypy пройшов довгий шлях - від ранніх прототипів, до системи, засобами якої контролюються типи продакшн-коду обсягом 4 мільйони рядків. Під час розвитку mypy було здійснено стандартизацію підказок за типами в Python. В наші дні навколо типізації Python-коду розвинулася потужна екосистема. У ній знайшлося місце підтримки бібліотек, в ній присутні допоміжні засоби для IDE та редакторів, в ній є кілька систем контролю типів, кожна з яких має свої плюси та мінуси.

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

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

Шановні читачі! Чи керуєтеся типами у своїх Python-проектах?

Шлях до перевірки типів 4 мільйонів рядків Python-коду. Частина 3
Шлях до перевірки типів 4 мільйонів рядків Python-коду. Частина 3

Джерело: habr.com

Додати коментар або відгук