De ce am trecut de la Python la Go

Actualizat la 14 mai 2019 pentru a reflecta mai bine îmbunătățirile aduse lui Go în ultimii 2 ani (gestionarea pachetelor, performanță mai bună, timpi de compilare mai rapizi și un ecosistem mai matur) Trecerea la un nou limbaj este întotdeauna un pas mare, mai ales atunci când doar unul dintre membrii echipei dvs. are experiență anterioară cu acel limbaj. La începutul acestui an, am schimbat principalul limbaj de programare al Stream de la Python la Go. Această postare va explica câteva dintre motivele pentru care am decis să lăsăm Python în urmă și să facem trecerea la Go. Îi mulțumim lui Ren Sakamoto pentru că a tradus în japoneză Why we switched from Python to Go, なぜ私達は Python から Go に移行したのか.

Razonul 1 – Performanța

Go este rapid! Go este extrem de rapid. Performanța este similară cu cea a Java sau C++. Pentru cazul nostru de utilizare, Go este de obicei de 40 de ori mai rapid decât Python. Iată un mic joc de referință care compară Go cu Python.

Razonul 2 – Performanța limbajului contează

Pentru multe aplicații, limbajul de programare este pur și simplu lipiciul dintre aplicație și baza de date. De obicei, performanța limbajului în sine nu contează prea mult. Stream, însă, este un furnizor de API care alimentează o platformă de feed-uri și chat pentru 700 de companii și peste 500 de milioane de utilizatori finali. Optimizăm Cassandra, PostgreSQL, Redis etc. de ani de zile, dar, în cele din urmă, ajungi la limitele limbajului pe care îl folosești. Python este un limbaj excelent, dar performanța sa este destul de lentă pentru cazuri de utilizare precum serializarea/deserializarea, clasificarea și agregarea. Ne-am confruntat frecvent cu probleme de performanță în care Cassandra avea nevoie de 1ms pentru a prelua datele, iar Python petrecea următorii 10ms transformându-le în obiecte.

Razonul 3 – Productivitatea dezvoltatorului & Nu deveniți prea creativi

Aruncă o privire la acest mic fragment de cod Go din tutorialul How I Start Go. (Acesta este un tutorial grozav și un bun punct de plecare pentru a deprinde un pic de Go.)

Dacă sunteți nou în Go, nu sunt multe lucruri care vă vor surprinde la citirea acestui mic fragment de cod. Acesta prezintă atribuții multiple, structuri de date, pointeri, formatare și o bibliotecă HTTP încorporată. Când am început să programez mi-a plăcut întotdeauna să folosesc funcțiile mai avansate ale lui Python. Python îți permite să devii destul de creativ cu codul pe care îl scrii. De exemplu, puteți:

  • Utilizați MetaClasses pentru a auto-înregistra clase la inițializarea codului
  • Schimbați True și False
  • Adaugați funcții la lista de funcții încorporate
  • Supraîncărcați operatorii prin metode magice
  • Utilizați funcții ca proprietăți prin intermediul decoratorului @property

Este distractiv să vă jucați cu aceste caracteristici, dar, așa cum majoritatea programatorilor vor fi de acord, ele fac adesea codul mai greu de înțeles atunci când se citește munca altcuiva. Go vă obligă să rămâneți la elementele de bază. Acest lucru face foarte ușor să citești codul oricui și să înțelegi imediat ce se întâmplă. Notă: Cât de „ușor” este într-adevăr depinde de cazul dumneavoastră de utilizare, desigur. Dacă doriți să creați un API CRUD de bază, v-aș recomanda în continuare Django + DRF, sau Rails.

Razonul 4 – Concurrency & Channels

Ca limbaj, Go încearcă să păstreze lucrurile simple. Nu introduce multe concepte noi. Se concentrează pe crearea unui limbaj simplu care este incredibil de rapid și ușor de lucrat. Singurul domeniu în care devine inovator sunt gorutinele și canalele. (Pentru a fi 100% corect, conceptul de CSP a început în 1977, așa că această inovație este mai degrabă o nouă abordare a unei idei vechi). Goroutinele sunt abordarea ușoară a lui Go pentru threading, iar canalele sunt modalitatea preferată de a comunica între goroutine. Goroutinele sunt foarte ieftine de creat și ocupă doar câțiva KB de memorie suplimentară. Deoarece goroutinele sunt atât de ușoare, este posibil să aveți sute sau chiar mii dintre ele rulând în același timp. Puteți comunica între goroutine folosind canale. Timpul de execuție Go se ocupă de toată complexitatea. Abordarea bazată pe goroutine și pe canale în ceea ce privește concurența face foarte ușoară utilizarea tuturor nucleelor CPU disponibile și gestionarea IO concurentă – toate acestea fără a complica dezvoltarea. În comparație cu Python/Java, rularea unei funcții pe o goroutine necesită un cod boilerplate minim. Pur și simplu preapreciați apelul funcției cu cuvântul cheie „go”:

https://tour.golang.org/concurrency/1 Abordarea Go a simultaneității este foarte ușor de utilizat. Este o abordare interesantă în comparație cu Node, unde dezvoltatorul trebuie să acorde o atenție deosebită modului în care este gestionat codul asincron. Un alt aspect grozav al simultaneității în Go este detectorul de curse. Acesta face ușor să vă dați seama dacă există condiții de cursă în codul vostru asincron.

Knock knock Race condition Who’s there?

– I Am Devloper (@iamdevloper) November 11, 2013

Iată câteva resurse bune pentru a începe cu Go și canalele:

  • https://gobyexample.com/channels
  • https://tour.golang.org/concurrency/2
  • http://guzalexander.com/2013/12/06/golang-channels-tutorial.html
  • https://www.golang-book.com/books/intro/10
  • https://www.goinggo.net/2014/02/the-nature-of-channels-in-go.html
  • Goroutines vs Green threads

Razonul 5 – Timp de compilare rapid

Cel mai mare micro-serviciu al nostru scris în Go are nevoie în prezent de 4 secunde pentru a fi compilat. Timpii de compilare rapizi ai Go reprezintă un câștig major de productivitate în comparație cu limbaje precum Java și C++, care sunt renumite pentru viteza de compilare lentă. Îmi plac luptele cu săbii, dar este și mai plăcut să fac lucrurile cât încă îmi amintesc ce ar trebui să facă codul:

Razonul 6 – Capacitatea de a construi o echipă

În primul rând, să începem cu ceea ce este evident: nu există atât de mulți dezvoltatori Go în comparație cu limbaje mai vechi precum C++ și Java. Conform StackOverflow, 38% dintre dezvoltatori cunosc Java, 19,3% cunosc C++ și doar 4,6% cunosc Go. Datele GitHub arată o tendință similară: Go este mai utilizat decât limbaje precum Erlang, Scala și Elixir, dar este mai puțin popular decât Java și C++. Din fericire, Go este un limbaj foarte simplu și ușor de învățat. Acesta oferă caracteristicile de bază de care aveți nevoie și nimic altceva. Noile concepte noi pe care le introduce sunt instrucțiunea „defer” și gestionarea integrată a simultaneității cu „rutine go” și canale. (Pentru puriști: Go nu este primul limbaj care implementează aceste concepte, ci doar primul care le face populare). Orice dezvoltator Python, Elixir, C++, Scala sau Java care se alătură unei echipe poate fi eficient în Go în decurs de o lună, datorită simplității sale. Am constatat că este mai ușor să construim o echipă de dezvoltatori Go în comparație cu multe alte limbaje. Dacă angajați oameni în ecosisteme competitive precum Boulder și Amsterdam, acesta este un beneficiu important.

Razonul 7 – Ecosistem puternic

Pentru o echipă de mărimea noastră (~20 de persoane), ecosistemul contează. Pur și simplu nu poți crea valoare pentru clienții tăi dacă trebuie să reinventezi fiecare bucățică de funcționalitate. Go are un suport excelent pentru instrumentele pe care le folosim. Biblioteci solide erau deja disponibile pentru Redis, RabbitMQ, PostgreSQL, Template parsing, Task scheduling, Expression parsing și RocksDB. Ecosistemul Go este un câștig major în comparație cu alte limbaje mai noi, cum ar fi Rust sau Elixir. Desigur, nu este la fel de bun ca limbaje precum Java, Python sau Node, dar este solid și pentru multe nevoi de bază veți găsi pachete de înaltă calitate deja disponibile.

Razonul 8 – Gofmt, Enforced Code Formatting

Să începem cu ce este Gofmt? Și nu, nu este o înjurătură. Gofmt este un minunat utilitar de linie de comandă, încorporat în compilatorul Go pentru formatarea codului dumneavoastră. Din punct de vedere al funcționalității, este foarte asemănător cu autopep8 din Python. Deși serialul Silicon Valley prezintă altfel, celor mai mulți dintre noi nu ne place cu adevărat să ne certăm despre tabulații vs. spații. Este important ca formatarea să fie consecventă, dar standardul real de formatare nu contează atât de mult. Gofmt evită toate aceste discuții, având un singur mod oficial de formatare a codului dumneavoastră.

Razonul 9 – gRPC și tampoanele de protocol

Go are un suport de primă clasă pentru tampoanele de protocol și gRPC. Aceste două instrumente funcționează foarte bine împreună pentru construirea de microservicii care trebuie să comunice prin RPC. Trebuie doar să scrieți un manifest în care să definiți apelurile RPC care pot fi efectuate și ce argumente acceptă. Atât codul serverului, cât și cel al clientului sunt apoi generate automat din acest manifest. Acest cod rezultat este rapid, are o amprentă de rețea foarte mică și este ușor de utilizat. Pornind de la același manifest, puteți genera cod client chiar și pentru mai multe limbaje diferite, cum ar fi C++, Java, Python și Ruby. Astfel, nu mai există puncte finale REST ambigue pentru traficul intern, pentru care trebuie să scrieți de fiecare dată aproape același cod client și server. .

Dezavantajul 1 – Lipsa cadrelor de lucru

Go nu are un singur cadru de lucru dominant, cum ar fi Rails pentru Ruby, Django pentru Python sau Laravel pentru PHP. Acesta este un subiect de dezbateri aprinse în cadrul comunității Go, deoarece multe persoane susțin că nu ar trebui să folosiți un framework pentru început. Sunt total de acord că acest lucru este adevărat pentru anumite cazuri de utilizare. Cu toate acestea, dacă cineva dorește să construiască un API CRUD simplu, îi va fi mult mai ușor cu Django/DJRF, Rails Laravel sau Phoenix. Actualizare: așa cum au subliniat comentariile, există mai multe proiecte care oferă un cadru pentru Go. Revel, Iris, Echo, Echo, Macaron și Buffalo par a fi principalii competitori. Pentru cazul de utilizare al Stream, preferăm să nu folosim un framework. Cu toate acestea, pentru multe proiecte noi care doresc să furnizeze un API CRUD simplu, lipsa unui cadru dominant va fi un dezavantaj serios.

Dezavantajul 2 – Gestionarea erorilor

Go gestionează erorile prin simpla returnare a unei erori de la o funcție și așteptând ca codul de apelare să gestioneze eroarea (sau să o returneze în stiva de apelare). Deși această abordare funcționează, este ușor să pierdeți domeniul de aplicare a ceea ce a mers prost pentru a vă asigura că puteți oferi o eroare semnificativă utilizatorilor dvs. Pachetul errors rezolvă această problemă, permițându-vă să adăugați un context și o urmă de stivă la erorile dumneavoastră. O altă problemă este că este ușor să uitați să gestionați o eroare din greșeală. Instrumentele de analiză statică precum errcheck și megacheck sunt la îndemână pentru a evita să faceți aceste greșeli. Deși aceste soluții de rezolvare funcționează bine, nu se simte destul de bine. Te-ai aștepta ca gestionarea corectă a erorilor să fie suportată de limbaj.

Dezavantajul 3 – Gestionarea pachetelor

Actualizare: Gestionarea pachetelor Go a parcurs un drum lung de când a fost scrisă această postare. Modulele Go sunt o soluție eficientă, singura problemă pe care am văzut-o cu ele este că strică unele instrumente de analiză statică, cum ar fi errcheck. Iată un tutorial pentru a învăța să folosiți Go folosind modulele Go. Gestionarea pachetelor Go nu este în niciun caz perfectă. În mod implicit, nu are o modalitate de a specifica o versiune specifică a unei dependențe și nu există o modalitate de a crea compilări reproductibile. Python, Node și Ruby au sisteme mai bune de gestionare a pachetelor. Cu toate acestea, cu instrumentele potrivite, gestionarea pachetelor Go funcționează destul de bine. Puteți utiliza Dep pentru a vă gestiona dependențele pentru a permite specificarea și fixarea versiunilor. În afară de asta, am contribuit cu un instrument open-source numit VirtualGo care facilitează lucrul la mai multe proiecte scrise în Go.

Python vs Go

Actualizare: Diferența de performanță între Python și Go a crescut de când a fost scrisă această postare. (Go a devenit mai rapid, iar Python nu) Un experiment interesant pe care l-am efectuat a fost să luăm funcționalitatea noastră de feed clasificat în Python și să o rescriem în Go. Aruncați o privire la acest exemplu de metodă de clasificare:

Atât codul Python, cât și codul Go trebuie să facă următoarele pentru a susține această metodă de clasificare:

  1. Aproașează expresia pentru scor. În acest caz, dorim să transformăm acest șir de caractere „simple_gauss(time)*popularity” într-o funcție care ia o activitate ca intrare și returnează un scor ca ieșire.
  2. Crearea de funcții parțiale pe baza configurației JSON. De exemplu, dorim ca „simple_gauss” să apeleze „decay_gauss” cu o scală de 5 zile, un decalaj de 1 zi și un factor de decădere de 0,3.
  3. Primăriți configurația „defaults” astfel încât să aveți o soluție de rezervă în cazul în care un anumit câmp nu este definit pe o activitate.
  4. Utilizați funcția de la pasul 1 pentru a evalua toate activitățile din feed.

Dezvoltarea versiunii Python a codului de clasificare a durat aproximativ 3 zile. Aceasta include scrierea codului, testele unitare și documentația. Apoi, am petrecut aproximativ 2 săptămâni optimizând codul. Una dintre optimizări a fost traducerea expresiei de scor (simple_gauss(time)*popularitate) într-un arbore sintactic abstract. De asemenea, am implementat o logică de caching care a precalculat scorul pentru anumite momente din viitor. În schimb, dezvoltarea versiunii Go a acestui cod a durat aproximativ 4 zile. Performanța nu a necesitat nicio optimizare suplimentară. Așadar, deși partea inițială de dezvoltare a fost mai rapidă în Python, versiunea bazată pe Go a necesitat în cele din urmă mult mai puțină muncă din partea echipei noastre. Ca un beneficiu suplimentar, codul Go a funcționat de aproximativ 40 de ori mai rapid decât codul nostru Python foarte optimizat. Acum, acesta este doar un singur exemplu de creștere a performanțelor pe care le-am înregistrat prin trecerea la Go. Este, desigur, o comparație între mere și portocale:

  • Codul de clasificare a fost primul meu proiect în Go
  • Codul Go a fost construit după codul Python, astfel încât cazul de utilizare a fost mai bine înțeles
  • Librăria Go pentru analiza expresiilor a fost de o calitate excepțională

Kilometrajul dvs. va varia. Unele alte componente ale sistemului nostru au necesitat substanțial mai mult timp pentru a fi construite în Go comparativ cu Python. Ca o tendință generală, observăm că dezvoltarea codului Go necesită un efort ușor mai mare. Cu toate acestea, petrecem mult mai puțin timp optimizând codul pentru performanță.

Elixir vs Go – The Runner Up

Un alt limbaj pe care l-am evaluat este Elixir. Elixir este construit pe baza mașinii virtuale Erlang. Este un limbaj fascinant și l-am luat în considerare deoarece unul dintre membrii echipei noastre are o tonă de experiență cu Erlang. Pentru cazurile noastre de utilizare, am observat că performanța brută a lui Go este mult mai bună. Atât Go, cât și Elixir vor face o treabă excelentă la deservirea a mii de cereri concurente. Cu toate acestea, dacă ne uităm la performanța cererilor individuale, Go este substanțial mai rapid pentru cazul nostru de utilizare. Un alt motiv pentru care am ales Go în locul lui Elixir a fost ecosistemul. Pentru componentele de care aveam nevoie, Go avea biblioteci mai mature, în timp ce, în multe cazuri, bibliotecile Elixir nu erau pregătite pentru utilizarea în producție. De asemenea, este mai greu de instruit/găsit dezvoltatori care să lucreze cu Elixir. Aceste motive au înclinat balanța în favoarea lui Go. Totuși, cadrul Phoenix pentru Elixir arată minunat și merită cu siguranță o privire.

Concluzie

Go este un limbaj foarte performant, cu un suport excelent pentru concurență. Este aproape la fel de rapid ca și limbaje precum C++ și Java. Deși este nevoie de ceva mai mult timp pentru a construi lucruri folosind Go în comparație cu Python sau Ruby, veți economisi o tonă de timp petrecut pe optimizarea codului. Avem o echipă mică de dezvoltare la Stream, care alimentează feed-uri și chat pentru peste 500 de milioane de utilizatori finali. Combinația dintre un ecosistem grozav, integrarea ușoară pentru noii dezvoltatori, performanța rapidă, suportul solid pentru concurență și un mediu de programare productiv fac din Go o alegere excelentă. Stream folosește în continuare Python pentru tabloul nostru de bord, pentru site și pentru învățarea automată pentru fluxurile personalizate. Nu ne vom lua la revedere de la Python prea curând, dar, în viitor, tot codul de performanță intensivă va fi scris în Go. Noul nostru API de chat este, de asemenea, scris în întregime în Go. Dacă doriți să aflați mai multe despre Go, consultați articolele de blog enumerate mai jos. Pentru a afla mai multe despre Stream, acest tutorial interactiv este un punct de plecare excelent.

Mai multe lecturi despre trecerea la Golang

  • https://movio.co/en/blog/migrate-Scala-to-Go/
  • https://hackernoon.com/why-i-love-golang-90085898b4f7
  • https://sendgrid.com/blog/convince-company-go-golang/
  • https://dave.cheney.net/2017/03/20/why-go

.

Învățarea Go

  • https://learnxinyminutes.com/docs/go/
  • https://tour.golang.org/
  • http://howistart.org/posts/go/1/
  • https://getstream.io/blog/building-a-performant-api-using-go-and-cassandra/
  • https://www.amazon.com/gp/product/0134190440
  • Go Rocket Tutorial

.

Lasă un răspuns

Adresa ta de email nu va fi publicată.