Hvordan Google bygger webrammer

Det er offentlig viden, at Google bruger et enkelt arkiv til at dele kode - alle 2 milliarder linjer af det - og at det bruger det trunkbaserede udviklingsparadigme.

For mange udviklere uden for virksomheden er dette overraskende og kontraintuitivt, men det fungerer rigtig godt. (Artiklen linket ovenfor giver gode eksempler, så jeg gentager dem ikke her.)

Googles codebase deles af mere end 25.000 Google-softwareudviklere fra snesevis af kontorer i lande rundt om i verden. På en typisk arbejdsdag forpligter de 16.000 ændringer til kodebasen. (kilde)

Denne artikel handler om detaljerne i at opbygge en open source web framework (AngularDart) i denne sammenhæng.

Kun én version

Når du anvender trunkbaseret udvikling i en enkelt stor repo, har du kun en version af alt. Det er lidt indlysende. Det er dog stadig godt at påpege det her, for det betyder, at - på Google - kan du ikke have app FooBar, der bruger AngularDart 2.2.1 og en anden app BarFoo, der er på 2.3.0. Begge apps skal være i samme version - den seneste.

Derfor siger Googlers undertiden, at al software hos Google lever på den blødende kant.

Hvis hele din sjæl skriger 'farligt!' lige nu, det er forståeligt. Afhængigt af bagagerummet ('master' i git-terminologi) i et bibliotek med din produktionskode lyder det sikkert farligt. Men der er et plot twist foran.

74 tusind test pr. Forpligtelse

AngularDart definerer 1601 test (her). Men når du foretager en ændring af AngularDart-koden i Google-arkivet, kører den også tests for alle hos Google, der er afhængige af rammen . I øjeblikket er det cirka 74 tusind tests (afhængigt af hvor stor din ændring er - en heuristisk springer over tests, som systemet ved, at du ikke påvirker).

Det er godt at have flere tests.

Jeg har lige foretaget en ændring, der kun manifesterer sig 5% af tiden og simulerer noget i retning af en race-tilstand i algoritmen til verifikation af genindsættelse af ændringsdetektering (jeg tilføjede && random.nextDouble() >.05 til denne if-sætning). Det manifesterede sig ikke i nogen af ​​1601-testene, da jeg kørte dem (en gang). Men det brød en masse klienttest.

Den virkelige værdi her er dog, at det er test af faktiske apps . Ikke kun er de mange, de afspejler også, hvordan rammen bruges af udviklere (ikke kun rammeforfatterne). Dette er vigtigt: rammeejere estimerer ikke altid korrekt, hvordan deres ramme bruges.

Det hjælper også, at disse apps er i produktion, og milliarder af dollars strømmer gennem dem hver måned. Der er en stor forskel mellem demo-apps, som en rammeforfatter sammensætter i sin fritid, og ægte produktionsapps med flere eller hundreder årsværk investeret i dem. Hvis internettet skal være relevant i fremtiden, er vi nødt til bedre at støtte udviklingen af ​​sidstnævnte.

Så hvad sker der, hvis rammen bryder nogle af de apps, der er bygget på den?

Du bryder det, du ordner det

Når AngularDart-forfattere ønsker at introducere en brudende ændring, er de nødt til at rette op på det for deres brugere . Da alt hos Google lever i en enkelt repo, er det trivielt at finde ud af, hvem de bryder, og de kan begynde at rette med det samme.

Enhver ændring i AngularDart, der går i stykker, inkluderer også alle rettelserne til den ændring i alle Google-apps, der er afhængige af den. Så bruddet og rettelsen går ind i repoen samtidigt og - selvfølgelig - efter korrekt kodevaluering af alle berørte parter.

Lad os give et konkret eksempel. Når nogen fra AngularDart-teamet foretager en ændring, der påvirker koden i AdWords-appen, går de til appens kildekode og retter den. De kan køre AdWords 'eksisterende test i processen, og de kan tilføje nye. Derefter sætter de alt dette på deres ændringsliste og beder om gennemgang. Da deres ændringsliste berører kode i både AngularDart-repoen og AdWords-repoen, kræver systemet automatisk godkendelse af kodegennemgang fra begge disse teams. Først da kan ændringen indsendes.

Dette har den åbenlyse virkning af at forhindre rammeudvikling i et vakuum. AngularDart framework-udviklere har adgang til millioner af kodelinjer, der er bygget med deres platform, og de rører regelmæssigt den kode selv. De behøver ikke antage, hvordan deres rammer bruges. (Den åbenlyse advarsel er, at de kun ser Google-koden og ikke koden for alle Workivas, Wrikes og StableKernels i verden, der også bruger AngularDart.)

At skulle opgradere dine brugeres kode bremser også udviklingen. Ikke så meget som du måske tror (se på AngularDarts fremskridt siden oktober), men det bremser stadig tingene ned. Det er både godt og dårligt, afhængigt af hvad du vil have fra en ramme. Vi vender tilbage til det.

Alligevel. Næste gang nogen hos Google siger, at en alfa-version af et bibliotek er stabil og i produktion, ved du nu hvorfor.

Store ændringer

Hvad hvis AngularDart har brug for at foretage en større brydningsændring (f.eks. Gå fra 2.x til 3.0), og den ændring bryder 74 tusind test? Vil holdet gå og ordne dem alle? Vil de foretage ændringer i tusinder af kildefiler, hvoraf de fleste ikke har skrevet?

Ja.

En af de seje ting ved at have et lydtypesystem er, at dit værktøj kan være meget mere nyttigt. I sound Dart kan værktøjer f.eks. Være sikre på, at en variabel er af en bestemt type. For refactoring betyder det, at mange ændringer kan være helt automatiske uden behov for bekræftelse fra udvikleren.

Når en metode på klasse Foo skifter fra bar()til baz(), kan du oprette et værktøj, der gennemgår det samlede Google-arkiv, finder alle forekomster af den Foo-klasse og dens underklasser og ændrer alle omtaler bar()til baz(). Med Darts lydtypesystem kan du være sikker på, at dette ikke bryder noget. Uden lydtyper kan selv en så simpel ændring få dig i problemer.

En anden ting, der hjælper med store ændringer, er dart_style, Darts standardformatering. Al Dart-kode hos Google er formateret ved hjælp af dette værktøj. Når din kode når korrekturlæsere, er den blevet automatisk formateret ved hjælp af dart_style, så der er ingen argumenter for, om den nye linje skal placeres her eller der. Og det gælder også storskalerefaktorer.

Ydelsesmålinger

Som jeg sagde ovenfor, drager AngularDart fordel af de afhængige tests. Men det er ikke kun tests. Google er meget streng med hensyn til at måle ydeevnen for sine apps, og så har de fleste (alle?) Produktionsapps benchmark-suiter.

Så når AngularDart-teamet introducerer en ændring, der gør AdWords 1% langsommere at indlæse, ved de, inden de lander ændringen. Da holdet i oktober sagde, at AngularDart-apps blev 40% mindre og 10% hurtigere siden august, talte de ikke om nogle syntetiske små TodoMVC-eksempler på apps. De talte om virkelige, missionskritiske produktionsapps med millioner af brugere og megabyte forretningslogikode.

Sidebemærkning: Hermetisk byggeværktøj

Du undrer dig måske: hvordan vidste denne fyr, hvilke tests i det enorme interne arkiv, der skulle køres efter introduktion af den flaky bug i AngularDart? Han valgte bestemt ikke de 74 tusinde tests, og lige så sikkert kørte han ikke alle testene hos Google. Svaret ligger i noget, der hedder Bazel.

På denne skala kan du ikke have en række shell-scripts til at bygge ting. Ting ville være flaky og uoverkommeligt langsomt. Hvad du har brug for er et hermetisk opbygningsværktøj.

"Hermetisk" i denne sammenhæng ligner meget "ren" i sammenhæng med funktioner. Dine byggetrin kan ikke have bivirkninger (som midlertidige filer, ændringer i PATH osv.), Og de skal være deterministiske (samme input fører altid til den samme output). Når det er tilfældet, kan du køre builds og testene på enhver maskine til enhver tid, og du får ensartet output. Det behøver du ikke make clean. Du kan derfor sende dine builds / tests til at bygge servere og parallelisere dem.

Google har brugt år på at udvikle et sådant byggeværktøj. Det blev åbent fra sidste år som Bazel.

Og takket være dette stykke infrastruktur kan interne testværktøjer bestemme, hvilke builds / tests hver ændring påvirker, og køre dem, når det er relevant.

Hvad betyder det hele?

AngularDarts eksplicitte mål er at være klassens bedste inden for produktivitet, ydeevne og pålidelighed til opbygning af store webapplikationer. Dette indlæg dækker forhåbentlig den sidste del - pålidelighed - og hvorfor det er vigtigt, at missionskritiske Google-apps som AdWords og AdSense bruger rammen. Det er ikke kun holdet, der praler med deres brugere - som forklaret ovenfor gør AngularDart mindre tilbøjelige til at introducere overfladiske ændringer med store interne brugere. Det gør rammen mere pålidelig.

Hvis du leder efter en ramme, der foretager større eftersyn og introducerer vigtige funktioner hvert par måneder, er AngularDart bestemt ikke noget for dig. Selvom holdet ønskede at opbygge rammen på en sådan måde, synes jeg det fremgår af denne artikel, at de ikke kunne. Vi tror dog oprigtigt, at der er plads til en ramme, der er mindre trendy, men pålidelig.

Efter min mening er den bedste forudsigelse af langsigtet support af en open source tech stack, at det er en stor del af den primære vedligeholders forretning. Tag Android, dolk, MySQL eller git som eksempler. Derfor er jeg glad for, at Dart endelig har en foretrukken webramme (AngularDart), et foretrukket komponentbibliotek (AngularDart Components) og en foretrukken mobil ramme (Flutter) - som alle bruges til at opbygge forretningskritiske Google-apps.

[Matan Lurey og Kathy Walrath bidrog til denne artikel.]

[Diskuter på Reddit, HN, Twitter.]