Как Google изгражда уеб рамки

Публично известно е, че Google използва едно хранилище за споделяне на код - всичките 2 милиарда реда от него - и че използва парадигмата за развитие, базирана на магистрала.

За много разработчици извън компанията това е изненадващо и неинтуитивно, но работи наистина добре. (Статията, свързана по-горе, дава добри примери, така че няма да ги повтарям тук.)

Кодовата база на Google се споделя от над 25 000 разработчици на софтуер на Google от десетки офиси в страни по света. В типичен работен ден те извършват 16 000 промени в кодовата база. (източник)

Тази статия разказва за особеностите на изграждането на уеб рамка с отворен код (AngularDart) в този контекст.

Само една версия

Когато използвате базирано на транк развитие в едно огромно репо, имате само една версия на всичко. Това е очевидно. Все пак е добре да го посочим тук, защото това означава, че - в Google - не можете да имате приложение FooBar, което използва AngularDart 2.2.1 и друго приложение BarFoo, което е на 2.3.0. И двете приложения трябва да са с една и съща версия - най-новата.

Ето защо служителите на Google понякога казват, че целият софтуер в Google живее на кървящия ръб.

Ако цялата ти душа крещи „опасно!“ точно сега, това е разбираемо. В зависимост от багажника („master“ в git терминологията) на библиотека с вашия производствен код със сигурност звучи опасно. Но предстои сюжет.

74 хиляди теста на ангажимент

AngularDart определя 1601 теста (тук). Но когато извършвате промяна в кода на AngularDart в хранилището на Google, той също така провежда тестове за всички в Google, които зависят от рамката . В момента това са около 74 хиляди теста (в зависимост от това колко голяма е вашата промяна - евристиката прескача тестове, за които системата знае, че не засягате).

Добре е да имате повече тестове.

Току-що направих промяна, която се проявява само 5% от времето, симулирайки нещо като състояние на състезание в алгоритъма за повторно въвеждане на откриването на промяната (добавих && random.nextDouble() >.05 към това if изявление). Не се прояви в нито един от тестовете от 1601 г., когато ги проведох (веднъж). Но той разби много клиентски тестове.

Истинската стойност тук обаче е, че това са тестове на действителни приложения . Те не само са многобройни, но и отразяват как рамката се използва от разработчиците (не само авторите на рамката). Това е важно: Собствениците на рамки не винаги правилно оценяват как се използва тяхната рамка.

Също така помага, че тези приложения се произвеждат и милиарди долари текат през тях всеки месец. Има голяма разлика между демонстрационните приложения, които рамков автор събира в свободното си време, и реалните производствени приложения с десетки или стотици човеко-години, инвестирани в тях. Ако мрежата ще бъде актуална в бъдеще, трябва да подкрепим по-добре развитието на последната.

И така, какво се случва, ако рамката счупи някои от приложенията, които са изградени върху нея?

Счупиш го, оправиш го

Когато авторите на AngularDart искат да въведат нова промяна, те трябва да отидат и да я поправят за своите потребители . Тъй като всичко в Google живее в един репо, е тривиално да се разбере кого разбиват и те могат да започнат да поправят веднага.

Всяка пробивна промяна на AngularDart включва и всички корекции на тази промяна във всички приложения на Google, които зависят от нея. Така че счупването и поправката влизат в репото едновременно и - разбира се - след правилен преглед на кода от всички засегнати страни.

Нека дадем конкретен пример. Когато някой от екипа на AngularDart направи промяна, която засяга кода в приложението AdWords, той отива в изходния код на това приложение и го поправя. Те могат да изпълняват съществуващите тестове на AdWords в процеса и могат да добавят нови. След това те поставят всичко това в списъка си за промяна и искат преглед. Тъй като техният списък с промени засяга кода както в репозиторията AngularDart, така и в репото на AdWords, системата автоматично изисква одобрение за преглед на кода и от двата екипа. Само тогава промяната може да бъде подадена.

Това има очевидния ефект за предотвратяване на развитието на рамки във вакуум. Разработчиците на AngularDart имат достъп до милиони редове код, които са изградени с тяхната платформа, и редовно докосват този код сами. Не е нужно да приемат как се използва тяхната рамка. (Очевидната забележка е, че те виждат само кода на Google, а не кода на всички Workivas, Wrikes и StableKernels в света, които също използват AngularDart.)

Налагането на ъпгрейд на потребителския код също забавя развитието. Не толкова, колкото си мислите (погледнете напредъка на AngularDart от октомври), но все пак забавя нещата. Това е и добро, и лошо, в зависимост от това, което искате от рамката. Ще се върнем към това.

Така или иначе. Следващият път, когато някой в ​​Google каже, че алфа версията на някаква библиотека е стабилна и се произвежда, сега знаете защо.

Мащабни промени

Какво ще стане, ако AngularDart трябва да направи голяма пробивна промяна (да речем, преминавайки от 2.x на 3.0) и тази промяна прекъсне 74 000 теста? Ще отиде ли екипът и ще ги оправи? Ще направят ли промени в хиляди изходни файлове, повечето от които не са автори?

Да.

Едно от страхотните неща при наличието на звукова система е, че инструментите ви могат да бъдат много по-полезни. В Sound Dart инструментите могат да бъдат сигурни, че променливата е от определен тип, например. За рефакторинг това означава, че много промени могат да бъдат напълно автоматични, без да е необходимо потвърждение от разработчика.

Когато един метод на класа Foo променя от bar()до baz(), можете да създадете един инструмент, който минава през целостта на единния Google хранилището, намира всички случаи на това Foo клас и неговите подкласове, както и промени на всички споменавания на bar()с baz(). Със звуковата система на Dart можете да сте сигурни, че това няма да повреди нищо. Без типове звук дори такава проста промяна може да ви затрудни.

Друго нещо, което помага при големи мащабни промени, е dart_style, форматиращият инструмент на Dart по подразбиране. Всички кодове на Dart в Google се форматират с помощта на този инструмент. Докато вашият код достигне рецензенти, той е автоматично форматиран с помощта на dart_style, така че няма аргументи дали да поставите новия ред тук или там. И това важи и за мащабните рефактори.

Показатели за ефективност

Както казах по-горе, AngularDart се възползва от тестовете на своите зависими. Но това не са само тестове. Google е много строг по отношение на измерването на производителността на своите приложения и затова повечето (всички?) Производствени приложения имат референтни пакети.

Така че, когато екипът на AngularDart въведе промяна, която прави AdWords с 1% по-бавно да се зарежда, те знаят, преди да качат промяната. Когато през октомври екипът заяви, че приложенията на AngularDart стават с 40% по-малки и с 10% по-бързи от август, те не говорят за някои синтетични малки TodoMVC примерни приложения. Говореха за реални, критични за мисията производствени приложения с милиони потребители и мегабайта код на бизнес логиката.

Странична бележка: Херметичен инструмент за изграждане

Може би се чудите: откъде този човек разбра кои тестове в огромното вътрешно хранилище да стартира, след като въведе нестабилната грешка в AngularDart? Със сигурност той не избираше ръчно 74-те хиляди теста и също толкова сигурно не провеждаше всички тестове в Google. Отговорът се крие в нещо, наречено Bazel.

В този мащаб не можете да имате поредица от скриптове за черупки за изграждане на неща. Нещата биха били нестабилни и твърде бавни. Това, от което се нуждаете, е херметичен инструмент за изграждане.

„Херметичен“ в този контекст е много подобен на „чист“ в контекста на функциите. Вашите стъпки за изграждане не могат да имат странични ефекти (като временни файлове, промени в PATH и т.н.) и те трябва да бъдат детерминирани (един и същи вход винаги води до същия изход). Когато случаят е такъв, можете да стартирате компилациите и тестовете на всяка машина по всяко време и ще получите последователен изход. Не е нужно make clean. Следователно можете да изпратите своите компилации / тестове, за да изградите сървъри и да ги паралелизирате.

Google прекара години в разработването на такъв инструмент за изграждане. Миналата година беше с отворен източник като Bazel.

И благодарение на тази част от инфраструктурата, инструментите за вътрешно тестване могат да определят кои компилации / тестове засягат всяка промяна и да ги изпълняват, когато е подходящо.

Какво означава всичко това?

Изричната цел на AngularDart е да бъде най-добрият в класа си по производителност, производителност и надеждност за изграждане на големи уеб приложения. Надяваме се, че тази публикация обхваща последната част - надеждността - и защо е важно критичните приложения на Google като AdWords и AdSense да използват рамката. Не само екипът се хвали с техните потребители - както беше обяснено по-горе, наличието на големи вътрешни потребители прави AngularDart по-малко вероятно да въведе повърхностни промени. Това прави рамката по-надеждна.

Ако търсите рамка, която прави основни ремонти и въвежда основни функции на всеки няколко месеца, AngularDart определено не е за вас. Дори ако екипът искаше да изгради рамката по такъв начин, мисля, че от тази статия става ясно, че не са могли. Искрено вярваме обаче, че има място за рамка, която е по-малко модерна, но надеждна.

Според мен най-добрата прогноза за дългосрочна поддръжка на технологичен стек с отворен код е, че това е голяма част от бизнеса на основния поддръжник. Вземете Android, кинжал, MySQL или git като примери. Ето защо се радвам, че Dart най-накрая има една предпочитана уеб рамка (AngularDart), една предпочитана библиотека на компоненти (AngularDart Components) и една предпочитана мобилна рамка (Flutter) - всички те се използват за изграждане на критични за бизнеса приложения на Google.

[Матан Лури и Кати Уолрат са допринесли за тази статия.]

[Дискутирайте в Reddit, HN, Twitter.]