Calea către verificarea tipului a 4 milioane de linii de cod Python. Partea 1

Astăzi vă aducem în atenție prima parte a traducerii materialului despre modul în care Dropbox se ocupă de controlul tipurilor codului Python.

Calea către verificarea tipului a 4 milioane de linii de cod Python. Partea 1

Dropbox scrie mult în Python. Este un limbaj pe care îl folosim extrem de pe scară largă, atât pentru serviciile de back-end, cât și pentru aplicațiile client desktop. De asemenea, folosim foarte mult Go, TypeScript și Rust, dar Python este limbajul nostru principal. Având în vedere amploarea noastră și vorbim despre milioane de linii de cod Python, s-a dovedit că scrierea dinamică a unui astfel de cod a complicat în mod inutil înțelegerea acestuia și a început să afecteze serios productivitatea muncii. Pentru a atenua această problemă, am început să trecem treptat codul nostru la verificarea statică a tipului folosind mypy. Acesta este probabil cel mai popular sistem de verificare a tipului independent pentru Python. Mypy este un proiect open source, dezvoltatorii săi principali lucrează în Dropbox.

Dropbox a fost una dintre primele companii care a implementat verificarea statică a tipului în codul Python la această scară. Mypy este folosit în mii de proiecte în zilele noastre. Acest instrument de nenumărate ori, după cum se spune, „testat în luptă”. Am parcurs un drum lung pentru a ajunge unde suntem acum. Pe parcurs, au existat multe întreprinderi nereușite și experimente eșuate. Această postare acoperă istoria verificării statice a tipurilor în Python, de la începuturile sale stâncoase ca parte a proiectului meu de cercetare, până în prezent, când verificarea și indicarea tipului au devenit obișnuite pentru nenumărații dezvoltatori care scriu în Python. Aceste mecanisme sunt acum susținute de multe instrumente, cum ar fi IDE-uri și analizoare de cod.

Citiți a doua parte

De ce este necesară verificarea tipului?

Dacă ați folosit vreodată Python cu tastare dinamică, este posibil să aveți o confuzie cu privire la motivul pentru care a fost atât de tam-tam în jurul tastării statice și mypy în ultima vreme. Sau poate vă place Python tocmai din cauza tastării sale dinamice, iar ceea ce se întâmplă pur și simplu vă supără. Cheia valorii tastării statice este dimensiunea soluțiilor: cu cât proiectul dvs. este mai mare, cu atât vă înclinați mai mult spre tastarea statică și, în cele din urmă, cu atât aveți mai multă nevoie de el.

Să presupunem că un anumit proiect a atins dimensiunea de zeci de mii de linii și s-a dovedit că mai mulți programatori lucrează la el. Privind un proiect similar, pe baza experienței noastre, putem spune că înțelegerea codului acestuia va fi cheia pentru menținerea productivității dezvoltatorilor. Fără adnotări de tip, poate fi dificil să ne dăm seama, de exemplu, ce argumente trebuie transmise unei funcții sau ce tipuri poate returna o funcție. Iată întrebări tipice la care sunt adesea dificil de răspuns fără a utiliza adnotări de tip:

  • Poate reveni această funcție None?
  • Care ar trebui să fie acest argument? items?
  • Care este tipul de atribut id: int este, str, sau poate un tip personalizat?
  • Ar trebui să fie acest argument o listă? Este posibil să-i treci un tuplu?

Dacă te uiți la următorul fragment de cod adnotat și încerci să răspunzi la întrebări similare, se dovedește că aceasta este cea mai simplă sarcină:

class Resource:
    id: bytes
    ...
    def read_metadata(self, 
                      items: Sequence[str]) -> Dict[str, MetadataItem]:
        ...

  • read_metadata nu se întoarce None, deoarece tipul de returnare nu este Optional[…].
  • Argument items este o succesiune de linii. Nu poate fi repetat la întâmplare.
  • Atribut id este un șir de octeți.

Într-o lume ideală, ne-am aștepta ca toate aceste subtilități să fie descrise în documentația încorporată (docstring). Dar experiența oferă o mulțime de exemple ale faptului că o astfel de documentare nu este adesea respectată în codul cu care trebuie să lucrați. Chiar dacă o astfel de documentație este prezentă în cod, nu se poate conta pe corectitudinea sa absolută. Această documentație poate fi vagă, inexactă și deschisă la neînțelegeri. În echipe mari sau proiecte mari, această problemă poate deveni extrem de acută.

În timp ce Python excelează în etapele incipiente sau intermediare ale proiectelor, la un moment dat proiectele de succes și companiile care folosesc Python se pot confrunta cu întrebarea vitală: „Ar trebui să rescriem totul într-un limbaj tipizat static?”.

Sistemele de verificare a tipurilor precum mypy rezolvă problema de mai sus, oferind dezvoltatorului un limbaj formal pentru descrierea tipurilor și verificând dacă declarațiile de tip se potrivesc cu implementarea programului (și, opțional, verificând existența lor). În general, putem spune că aceste sisteme ne pun la dispoziție ceva de genul documentației atent verificate.

Utilizarea unor astfel de sisteme are alte avantaje și sunt deja complet netriviale:

  • Sistemul de verificare a tipului poate detecta unele erori mici (și nu atât de mici). Un exemplu tipic este atunci când uită să proceseze o valoare None sau o altă condiție specială.
  • Refactorizarea codului este mult simplificată, deoarece sistemul de verificare a tipului este adesea foarte precis în ceea ce privește codul care trebuie schimbat. În același timp, nu trebuie să sperăm la o acoperire de cod 100% cu teste, ceea ce, în orice caz, de obicei nu este fezabil. Nu trebuie să ne adâncim în profunzimea urmei stivei pentru a afla cauza problemei.
  • Chiar și în cazul proiectelor mari, mypy poate face adesea verificarea completă a tipului într-o fracțiune de secundă. Iar executarea testelor durează de obicei zeci de secunde sau chiar minute. Sistemul de verificare a tipului oferă programatorului feedback instantaneu și îi permite să-și facă treaba mai rapid. Nu mai trebuie să scrie teste unitare fragile și greu de întreținut, care înlocuiesc entitățile reale cu false și patch-uri doar pentru a obține mai rapid rezultatele testelor de cod.

IDE-urile și editorii precum PyCharm sau Visual Studio Code folosesc puterea adnotărilor de tip pentru a oferi dezvoltatorilor completarea codului, evidențierea erorilor și suport pentru constructele de limbaj utilizate în mod obișnuit. Și acestea sunt doar câteva dintre beneficiile tastării. Pentru unii programatori, toate acestea sunt argumentul principal în favoarea tastării. Acesta este ceva care beneficiază imediat după implementare. Acest caz de utilizare pentru tipuri nu necesită un sistem separat de verificare a tipurilor, cum ar fi mypy, deși trebuie remarcat faptul că mypy ajută la menținerea adnotărilor de tip în concordanță cu codul.

Fundalul mypy

Istoria mypy a început în Marea Britanie, la Cambridge, cu câțiva ani înainte să mă alătur lui Dropbox. Am fost implicat, în cadrul cercetării mele de doctorat, în unificarea limbajelor tipizate static și dinamic. M-am inspirat de un articol despre tastarea incrementală de Jeremy Siek și Walid Taha și de proiectul Typed Racket. Am încercat să găsesc modalități de a folosi același limbaj de programare pentru diferite proiecte - de la scripturi mici până la baze de cod formate din multe milioane de linii. În același timp, am vrut să mă asigur că într-un proiect de orice amploare nu va trebui să facem compromisuri prea mari. O parte importantă din toate acestea a fost ideea de a trece treptat de la un proiect prototip netipizat la un produs finit testat cuprinzător tip static. În zilele noastre, aceste idei sunt în mare măsură considerate de la sine înțelese, dar în 2010 era o problemă care era încă explorată activ.

Lucrarea mea inițială în verificarea tipurilor nu a vizat Python. În schimb, am folosit un mic limbaj „de casă”. Bine. Iată un exemplu care vă va permite să înțelegeți despre ce vorbim (adnotările de tip sunt opționale aici):

def Fib(n as Int) as Int
  if n <= 1
    return n
  else
    return Fib(n - 1) + Fib(n - 2)
  end
end

Utilizarea unei limbi materne simplificate este o abordare comună folosită în cercetarea științifică. Așa este, nu în ultimul rând pentru că vă permite să efectuați rapid experimente și, de asemenea, datorită faptului că ceea ce nu are nimic de-a face cu cercetarea poate fi ușor ignorat. Limbajele de programare din lumea reală tind să fie fenomene la scară largă cu implementări complexe, iar acest lucru încetinește experimentarea. Cu toate acestea, orice rezultate bazate pe un limbaj simplificat arată puțin suspect, deoarece în obținerea acestor rezultate cercetătorul poate să fi sacrificat considerații importante pentru utilizarea practică a limbilor.

Verificatorul meu de tip pentru Alore arăta foarte promițător, dar am vrut să-l testez experimentând cu cod real, care, ați putea spune, nu a fost scris în Alore. Din fericire pentru mine, limbajul Alore s-a bazat în mare parte pe aceleași idei ca Python. A fost destul de ușor să schimbi verificatorul de tip, astfel încât să poată funcționa cu sintaxa și semantica lui Python. Acest lucru ne-a permis să încercăm să verificăm tipul în codul open source Python. În plus, am scris un transpiler pentru a converti codul scris în Alore în cod Python și l-am folosit pentru a traduce codul meu de verificare. Acum aveam un sistem de verificare a tipului scris în Python care suporta un subset de Python, un fel de limbajul respectiv! (Anumite decizii arhitecturale care au avut sens pentru Alore au fost prost potrivite pentru Python, iar acest lucru este încă vizibil în anumite părți ale bazei de cod mypy.)

De fapt, limbajul suportat de sistemul meu de tip nu putea fi numit Python în acest moment: era o variantă a lui Python din cauza unor limitări ale sintaxei de adnotare de tip Python 3.

Arăta ca un amestec de Java și Python:

int fib(int n):
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Una dintre ideile mele la acea vreme a fost să folosesc adnotări de tip pentru a îmbunătăți performanța prin compilarea acestui tip de Python în C, sau poate JVM bytecode. Am ajuns la etapa de scriere a unui prototip de compilator, dar am abandonat această idee, deoarece verificarea tipului în sine părea destul de utilă.

Am ajuns să-mi prezint proiectul la PyCon 2013 din Santa Clara. Am vorbit despre asta și cu Guido van Rossum, binevoitor dictator Python pe viață. M-a convins să renunț la propria mea sintaxă și să rămân cu sintaxa standard Python 3. Python 3 acceptă adnotări de funcție, așa că exemplul meu ar putea fi rescris așa cum se arată mai jos, rezultând un program Python normal:

def fib(n: int) -> int:
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

A trebuit să fac niște compromisuri (în primul rând, vreau să remarc că mi-am inventat propria sintaxă tocmai din acest motiv). În special, Python 3.3, cea mai recentă versiune a limbajului la acea vreme, nu suporta adnotări variabile. Am discutat cu Guido prin e-mail diverse posibilități de proiectare sintactică a unor astfel de adnotări. Am decis să folosim comentarii de tip pentru variabile. Acest lucru a servit scopului propus, dar a fost oarecum greoaie (Python 3.6 ne-a oferit o sintaxă mai frumoasă):

products = []  # type: List[str]  # Eww

Comentariile de tip au fost utile și pentru a suporta Python 2, care nu are suport încorporat pentru adnotări de tip:

f fib(n):
    # type: (int) -> int
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

S-a dovedit că aceste (și alte) compromisuri nu au contat cu adevărat - beneficiile tastării statice au însemnat că utilizatorii au uitat curând de sintaxa mai puțin decât perfectă. Deoarece nu au fost folosite constructe sintactice speciale în codul Python verificat de tip, instrumentele Python existente și procesele de procesare a codului au continuat să funcționeze normal, făcându-le mult mai ușor pentru dezvoltatori să învețe noul instrument.

Guido m-a convins, de asemenea, să mă alătur la Dropbox după ce mi-am terminat teza de absolvire. Aici începe cea mai interesantă parte a poveștii mypy.

Pentru a fi continuat ...

Dragi cititori! Dacă utilizați Python, vă rugăm să ne spuneți despre amploarea proiectelor pe care le dezvoltați în acest limbaj.

Calea către verificarea tipului a 4 milioane de linii de cod Python. Partea 1
Calea către verificarea tipului a 4 milioane de linii de cod Python. Partea 1

Sursa: www.habr.com

Adauga un comentariu