Il percorso per il typechecking di 4 milioni di righe di codice Python. Parte 3

Presentiamo alla vostra attenzione la terza parte della traduzione del materiale sul percorso intrapreso da Dropbox nell'implementazione di un sistema di controllo del tipo per il codice Python.

Il percorso per il typechecking di 4 milioni di righe di codice Python. Parte 3

→ Parti precedenti: prima и secondo

Raggiungere 4 milioni di righe di codice digitato

Un'altra sfida importante (e la seconda preoccupazione più comune tra gli intervistati internamente) è stata l'aumento della quantità di codice coperto dai controlli di tipo in Dropbox. Abbiamo provato diversi approcci per risolvere questo problema, dall'aumento naturale delle dimensioni della base di codice digitata al concentrare gli sforzi del team mypy sull'inferenza di tipo automatizzato statico e dinamico. Alla fine, sembrava che non esistesse una strategia vincente semplice, ma siamo riusciti a ottenere una rapida crescita del volume del codice annotato combinando molti approcci.

Di conseguenza, il nostro più grande repository Python (con codice backend) contiene quasi 4 milioni di righe di codice annotato. Il lavoro sulla tipizzazione statica del codice è stato completato in circa tre anni. Mypy ora supporta vari tipi di report sulla copertura del codice che semplificano il monitoraggio dell'avanzamento della digitazione. In particolare, possiamo generare report su codice con ambiguità nei tipi, come, ad esempio, l'uso esplicito di un tipo Any in annotazioni che non possono essere verificate o con cose come l'importazione di librerie di terze parti che non dispongono di annotazioni di tipo. Nell'ambito di un progetto volto a migliorare la precisione del controllo dei tipi in Dropbox, abbiamo contribuito a migliorare le definizioni dei tipi (i cosiddetti file stub) per alcune popolari librerie open source in un repository Python centralizzato dattiloscritto.

Abbiamo implementato (e standardizzato nei PEP successivi) nuove funzionalità del sistema di tipi che consentono tipi più precisi per alcuni modelli Python specifici. Un notevole esempio di ciò è TypeDict, che fornisce tipi per dizionari simili a JSON che hanno un set fisso di chiavi stringa, ciascuna con un valore del proprio tipo. Continueremo ad espandere il sistema di tipi. Il nostro prossimo passo sarà probabilmente quello di migliorare il supporto per le capacità numeriche di Python.

Il percorso per il typechecking di 4 milioni di righe di codice Python. Parte 3
Numero di righe di codice annotato: server

Il percorso per il typechecking di 4 milioni di righe di codice Python. Parte 3
Numero di righe di codice annotato: client

Il percorso per il typechecking di 4 milioni di righe di codice Python. Parte 3
Numero totale di righe di codice annotato

Ecco una panoramica delle principali caratteristiche delle cose che abbiamo fatto per aumentare la quantità di codice annotato in Dropbox:

Rigore dell'annotazione. Abbiamo gradualmente aumentato i requisiti per il rigore nell'annotazione del nuovo codice. Abbiamo iniziato con suggerimenti linter che suggerivano di aggiungere annotazioni ai file che ne avevano già alcune. Ora richiediamo annotazioni di tipo nei nuovi file Python e nella maggior parte dei file esistenti.

Digitazione di rapporti. Inviamo ai team rapporti settimanali sul livello di digitazione del loro codice e diamo consigli su cosa dovrebbe essere annotato per primo.

Divulgazione di mypy. Parliamo di mypy durante gli eventi e parliamo con i team per aiutarli a iniziare con le annotazioni dei tipi.

Sondaggi. Conduciamo sondaggi periodici tra gli utenti per identificare i problemi principali. Siamo pronti ad andare molto lontano nella risoluzione di questi problemi (anche creando un nuovo linguaggio per velocizzare mypy!).

Prestazione. Abbiamo notevolmente migliorato le prestazioni di mypy utilizzando il demone e mypyc. Ciò è stato fatto per eliminare gli inconvenienti che si verificano durante il processo di annotazione e per poter lavorare con grandi quantità di codice.

Integrazione con gli editori. Abbiamo creato strumenti per supportare l'esecuzione di mypy negli editor più popolari su Dropbox. Ciò include PyCharm, Vim e VS Code. Ciò ha notevolmente semplificato il processo di annotazione del codice e di verifica della sua funzionalità. Questi tipi di azioni sono comuni quando si annota il codice esistente.

Analisi statica. Abbiamo creato uno strumento per dedurre le firme delle funzioni utilizzando strumenti di analisi statica. Questo strumento può funzionare solo in situazioni relativamente semplici, ma ci ha aiutato ad aumentare la copertura dei tipi di codice senza troppi sforzi.

Supporto per librerie di terze parti. Molti dei nostri progetti utilizzano il toolkit SQLAlchemy. Sfrutta le capacità dinamiche di Python che i tipi PEP 484 non sono in grado di modellare direttamente. Noi, in conformità con PEP 561, abbiamo creato il file stub corrispondente e scritto un plugin per mypy (fonte aperta), che migliora il supporto SQLAlchemy.

Difficoltà che abbiamo incontrato

Il percorso verso 4 milioni di righe di codice digitato non è sempre stato facile per noi. Su questo percorso abbiamo incontrato molte buche e commesso diversi errori. Questi sono alcuni dei problemi che abbiamo riscontrato. Ci auguriamo che raccontarli aiuti altri a evitare problemi simili.

File mancanti. Abbiamo iniziato il nostro lavoro controllando solo una piccola quantità di file. Tutto ciò che non è incluso in questi file non è stato controllato. I file sono stati aggiunti all'elenco di scansione quando sono apparse le prime annotazioni. Se qualcosa veniva importato da un modulo situato al di fuori dell'ambito di verifica, stavamo parlando di lavorare con valori come Any, che non sono stati affatto testati. Ciò ha portato ad una significativa perdita di precisione della digitazione, soprattutto nelle prime fasi della migrazione. Questo approccio ha funzionato sorprendentemente bene finora, anche se una situazione tipica è che l'aggiunta di file all'ambito della revisione rivela problemi in altre parti della base di codice. Nel peggiore dei casi, quando sono state unite due aree isolate di codice, in cui i tipi erano già stati controllati indipendentemente l'uno dall'altro, si è scoperto che i tipi di queste aree erano incompatibili tra loro. Ciò ha portato alla necessità di apportare numerose modifiche alle annotazioni. Guardando indietro ora, ci rendiamo conto che avremmo dovuto aggiungere prima i moduli della libreria principale all'area di controllo del tipo di mypy. Ciò renderebbe il nostro lavoro molto più prevedibile.

Annotazione del vecchio codice. Quando abbiamo iniziato, avevamo circa 4 milioni di righe di codice Python esistente. Era chiaro che annotare tutto questo codice non era un compito facile. Abbiamo creato uno strumento chiamato PyAnnotate in grado di raccogliere informazioni sul tipo durante l'esecuzione dei test e aggiungere annotazioni sul tipo al codice in base alle informazioni raccolte. Non abbiamo però notato un’adozione particolarmente diffusa di questo strumento. La raccolta delle informazioni sul tipo era lenta e le annotazioni generate automaticamente spesso richiedevano molte modifiche manuali. Abbiamo pensato di eseguire questo strumento automaticamente ogni volta che esaminiamo il codice o di raccogliere informazioni sul tipo in base all'analisi di un piccolo volume di richieste di rete effettive, ma abbiamo deciso di non farlo perché entrambi gli approcci erano troppo rischiosi.

Di conseguenza, si può notare che la maggior parte del codice è stata annotata manualmente dai suoi proprietari. Per guidare questo processo nella giusta direzione, prepariamo report su moduli e funzioni particolarmente importanti che devono essere annotati. Ad esempio, è importante fornire annotazioni sul tipo per un modulo di libreria utilizzato in centinaia di posti. Ma un vecchio servizio che viene sostituito con uno nuovo non è più così importante da annotare. Stiamo anche sperimentando l'utilizzo dell'analisi statica per generare annotazioni di tipo per il codice legacy.

Importazioni cicliche. Sopra ho parlato delle importazioni cicliche (i “grovigli di dipendenza”), la cui esistenza rendeva difficile accelerare mypy. Abbiamo anche dovuto lavorare duro per fare in modo che mypy supporti tutti i tipi di idiomi causati da queste importazioni cicliche. Recentemente abbiamo completato un importante progetto di riprogettazione del sistema che ha risolto la maggior parte dei problemi di mypy relativi alle importazioni circolari. Questi problemi in realtà derivano dai primissimi giorni del progetto, di ritorno da Alore, il linguaggio educativo su cui era originariamente incentrato il progetto mypy. La sintassi di Alore semplifica la risoluzione dei problemi con i comandi di importazione ciclica. Mypy moderno ha ereditato alcune limitazioni dalla sua precedente implementazione semplice (che era perfetta per Alore). Python rende difficile lavorare con le importazioni circolari, principalmente perché le espressioni sono ambigue. Ad esempio, un'operazione di assegnazione può effettivamente definire un alias di tipo. Mypy non è sempre in grado di rilevare cose del genere finché la maggior parte del ciclo di importazione non è stata elaborata. Non c'erano tali ambiguità ad Alore. Le decisioni sbagliate prese nelle prime fasi dello sviluppo del sistema possono presentare una spiacevole sorpresa al programmatore molti anni dopo.

Risultati: il percorso verso 5 milioni di righe di codice e nuovi orizzonti

Il progetto mypy ha fatto molta strada: dai primi prototipi a un sistema che controlla 4 milioni di righe di tipi di codice di produzione. Con l'evoluzione di mypy, i suggerimenti sui tipi di Python sono stati standardizzati. Al giorno d'oggi, si è sviluppato un potente ecosistema attorno alla digitazione del codice Python. Ha uno spazio per il supporto della libreria, contiene strumenti ausiliari per IDE ed editor, ha diversi sistemi di controllo dei tipi, ognuno dei quali ha i suoi pro e contro.

Anche se il controllo del tipo è già un dato di fatto in Dropbox, credo che siamo ancora agli inizi della digitazione del codice Python. Penso che le tecnologie di controllo del tipo continueranno ad evolversi e migliorare.

Se non hai già utilizzato il controllo del tipo nel tuo progetto Python su larga scala, sappi che ora è un ottimo momento per iniziare a passare alla digitazione statica. Ho parlato con coloro che hanno compiuto una transizione simile. Nessuno di loro se ne è pentito. Il controllo del tipo rende Python un linguaggio molto più adatto allo sviluppo di progetti di grandi dimensioni rispetto al "Python normale".

Cari lettori! Utilizzi il controllo del tipo nei tuoi progetti Python?

Il percorso per il typechecking di 4 milioni di righe di codice Python. Parte 3
Il percorso per il typechecking di 4 milioni di righe di codice Python. Parte 3

Fonte: habr.com

Aggiungi un commento