Habr логови на програмери од предниот дел: рефакторирање и рефлектирање

Habr логови на програмери од предниот дел: рефакторирање и рефлектирање

Отсекогаш ме интересирало како Habr е структуриран одвнатре, како е структуриран работниот тек, како се структурирани комуникациите, кои стандарди се користат и како генерално се пишува кодот овде. За среќа, добив таква можност, бидејќи неодамна станав дел од тимот на хабра. Користејќи го примерот на мало рефакторирање на мобилната верзија, ќе се обидам да одговорам на прашањето: како е да се работи овде напред. Во програмата: Node, Vue, Vuex и SSR со сос од белешки за лично искуство во Хабр.

Првото нешто што треба да го знаете за тимот за развој е дека нè има малку. Не е доволно - тоа се три фронтови, два бека и техничкото водство на сите Хабр - Баксли. Има, се разбира, и тестер, дизајнер, тројца Вадими, чудотворна метла, специјалист за маркетинг и други Бумбуруми. Но, има само шест директни соработници на изворите на Хабр. Ова е прилично ретко - проект со повеќемилионска публика, кој однадвор изгледа како џиновско претпријатие, во реалноста повеќе личи на пријатен стартап со најрамна можна организациона структура.

Како и многу други ИТ компании, Хабр исповеда агилни идеи, CI практики и тоа е сè. Но, според моите чувства, Хабр како производ се развива повеќе во бранови отколку континуирано. Така, неколку спринтови по ред, вредно кодираме нешто, дизајнираме и редизајнираме, кршиме нешто и поправаме, решаваме билети и создаваме нови, газиме на гребло и си пукаме во нозете, за конечно да ја пуштиме функцијата во производство. И тогаш доаѓа одредено затишје, период на повторен развој, време да се направи она што е во квадрантот „важно-не итно“.

Токму овој „вонсезонски“ спринт ќе се дискутира подолу. Овој пат вклучуваше рефакторирање на мобилната верзија на Habr. Генерално, компанијата има големи надежи за тоа, а во иднина треба да ја замени целата зоолошка градина од инкарнациите на Хабр и да стане универзално решение за повеќе платформи. Еден ден ќе има адаптивен распоред, PWA, офлајн режим, прилагодување на корисникот и многу други интересни работи.

Ајде да ја поставиме задачата

Еднаш, на обичен стенд-ап, еден од предните зборуваше за проблеми во архитектурата на компонентата за коментари на мобилната верзија. Имајќи го ова на ум, организиравме микро-средба во формат на групна психотерапија. Сите наизменично кажуваа каде боли, снимаа се на хартија, сочувствуваа, разбраа, освен што никој не плескаше. Резултатот беше листа од 20 проблеми, кои јасно покажаа дека мобилниот Хабр сè уште има долг и трнлив пат до успехот.

Бев првенствено загрижен за ефикасноста на користењето на ресурсите и она што се нарекува мазен интерфејс. Секој ден, на релација дома-работа-дома, го гледав мојот стар телефон како очајно се обидува да прикаже 20 наслови во доводот. Изгледаше нешто вака:

Habr логови на програмери од предниот дел: рефакторирање и рефлектирањеMobile Habr интерфејс пред рефакторирање

Што се случува овде? Накратко, серверот ја сервира HTML страницата на сите на ист начин, без разлика дали корисникот бил најавен или не. Потоа клиентот JS се вчитува и повторно ги бара потребните податоци, но прилагоден за авторизација. Односно, ние всушност ја завршивме истата работа двапати. Интерфејсот трепереше, а корисникот презеде стотина дополнителни килобајти. Во детали, сè изгледаше уште поморничаво.

Habr логови на програмери од предниот дел: рефакторирање и рефлектирањеСтара шема SSR-CSR. Овластувањето е можно само во фазите C3 и C4, кога Node JS не е зафатен со генерирање на HTML и може да ги пренасочува барањата до API.

Нашата архитектура од тоа време беше многу прецизно опишана од еден од корисниците на Хабр:

Мобилната верзија е глупост. Го кажувам како што е. Страшна комбинација на РБС и ООП.

Моравме да признаеме, колку и да беше тажно.

Ги проценив опциите, создадов билет во Џира со опис на ниво „сега е лошо, направете го правилно“ и ја разложив задачата во широки потези:

  • повторна употреба на податоци,
  • минимизирајте го бројот на прецртувања,
  • елиминирање на дупликат барања,
  • направете го процесот на вчитување поочигледен.

Ајде повторно да ги искористиме податоците

Во теорија, рендерирањето од страна на серверот е дизајнирано да реши два проблеми: да не страда од ограничувања на пребарувачот во смисла на СПА индексирање и подобрување на метриката ФМП (неизбежно се влошува ТТИ). Во класично сценарио кое конечно формулиран во Airbnb во 2013 година година (сè уште на Backbone.js), SSR е истата изоморфна JS апликација која работи во околината Node. Серверот едноставно го испраќа генерираниот распоред како одговор на барањето. Потоа се случува рехидратација на страната на клиентот, а потоа сè работи без повторно вчитување на страницата. За Habr, како и за многу други ресурси со текстуална содржина, прикажувањето на серверот е критичен елемент во градењето пријателски односи со пребарувачите.

И покрај фактот дека поминаа повеќе од шест години од појавата на технологијата и за тоа време навистина многу вода леташе под мостот во предниот свет, за многу програмери оваа идеја сè уште е обвиткана во тајност. Не застанавме настрана и пуштивме апликација Vue со поддршка за SSR за производство, пропуштајќи еден мал детал: не ја испративме почетната состојба до клиентот.

Зошто? Нема точен одговор на ова прашање. Или не сакаа да ја зголемат големината на одговорот од серверот, или поради еден куп други архитектонски проблеми, или едноставно не тргна. На еден или друг начин, исфрлањето на состојбата и повторното користење на сè што направи серверот изгледа сосема соодветно и корисно. Задачата е всушност тривијална - состојба едноставно се инјектира во контекстот на извршување, а Vue автоматски го додава во генерираниот распоред како глобална променлива: window.__INITIAL_STATE__.

Еден од проблемите што се појави е неможноста да се конвертираат цикличните структури во JSON (кружна референца); беше решено со едноставно замена на таквите структури со нивните рамни колеги.

Дополнително, кога се занимавате со UGC содржина, треба да запомните дека податоците треба да се претворат во HTML ентитети за да не се скрши HTML. За овие цели користиме he.

Минимизирање на прецртувањата

Како што можете да видите од дијаграмот погоре, во нашиот случај, еден примерок на Node JS извршува две функции: SSR и „прокси“ во API, каде што се јавува авторизација на корисникот. Оваа околност го оневозможува овластувањето додека JS кодот работи на серверот, бидејќи јазолот е со една нишка, а функцијата SSR е синхрона. Односно, серверот едноставно не може да испраќа барања до себе додека стакот на повици е зафатен со нешто. Се испостави дека ја ажуриравме состојбата, но интерфејсот не престана да се грче, бидејќи податоците за клиентот требаше да се ажурираат земајќи ја предвид сесијата на корисникот. Требаше да ја научиме нашата апликација да ги става точните податоци во почетна состојба, земајќи го предвид најавувањето на корисникот.

Имаше само две решенија за проблемот:

  • прикачете податоци за овластување на барањата за вкрстени сервери;
  • подели Node JS слоеви на два посебни примероци.

Првото решение бараше употреба на глобални променливи на серверот, а второто го продолжи рокот за завршување на задачата за најмалку еден месец.

Како да се направи избор? Хабр често се движи по патеката со најмал отпор. Неформално, постои општа желба циклусот од идеја до прототип да се сведе на минимум. Моделот на однос кон производот донекаде потсетува на постулатите на booking.com, со единствената разлика што Habr многу посериозно ги сфаќа повратните информации од корисниците и ви верува, како развивач, да носите такви одлуки.

Следејќи ја оваа логика и сопствената желба за брзо решавање на проблемот, избрав глобални променливи. И, како што често се случува, треба да платите за нив порано или подоцна. Плативме речиси веднаш: работевме за викенд, ги расчистивме последиците, напиша постмортална и почна да го дели серверот на два дела. Грешката беше многу глупава, а грешката што ја вклучуваше не беше лесно да се репродуцира. И да, срамота е за ова, но вака или онака, сопнување и стенкање, мојот PoC со глобални променливи сепак влезе во производство и работи доста успешно додека чека да се пресели во нова архитектура „два јазли“. Ова беше важен чекор, бидејќи формално целта беше постигната - SSR научи да испорачува страница целосно подготвена за употреба, а интерфејсот стана многу помирен.

Habr логови на програмери од предниот дел: рефакторирање и рефлектирањеMobile Habr интерфејс по првата фаза на рефакторирање

На крајот на краиштата, архитектурата SSR-CSR на мобилната верзија води до оваа слика:

Habr логови на програмери од предниот дел: рефакторирање и рефлектирањеКоло SSR-CSR со „двојазли“. Јазол JS API е секогаш подготвен за асинхрони I/O и не е блокиран од функцијата SSR, бидејќи втората се наоѓа во посебен пример. Не е потребен синџир за прашања #3.

Елиминирање на дупликат барања

Откако беа извршени манипулациите, првичното прикажување на страницата повеќе не предизвикуваше епилепсија. Но, понатамошната употреба на Habr во режим SPA сепак предизвика конфузија.

Бидејќи основата на корисничкиот проток е транзициите на формата список на статии → статија → коментари и обратно, на прво место беше важно да се оптимизира потрошувачката на ресурси на овој синџир.

Habr логови на програмери од предниот дел: рефакторирање и рефлектирањеВраќањето во доводот на објавата предизвикува ново барање за податоци

Немаше потреба да се копа длабоко. Во екранот погоре можете да видите дека апликацијата повторно ја бара листата на написи при лизгање назад, а при барањето не ги гледаме статиите, што значи дека претходните податоци некаде исчезнуваат. Изгледа дека компонентата на списокот на статии користи локална состојба и ја губи при уништување. Всушност, апликацијата користеше глобална состојба, но архитектурата Vuex беше изградена директно: модулите се врзани за страници, кои пак се врзани за рути. Покрај тоа, сите модули се „за еднократна употреба“ - секоја наредна посета на страницата го препишуваше целиот модул:

ArticlesList: [
  { Article1 },
  ...
],
PageArticle: { ArticleFull1 },

Севкупно, имавме модул Список на написи, кој содржи објекти од типот Член и модул PageArticle, што беше проширена верзија на објектот Член, вид на Член Целосна. Во голема мера, оваа имплементација не носи ништо страшно сама по себе - таа е многу едноставна, може да се каже дури и наивна, но крајно разбирлива. Ако го ресетирате модулот секогаш кога ја менувате рутата, тогаш можете дури и да живеете со него. Меѓутоа, на пример, преместувањето помеѓу доводите на статиите /feed → /all, гарантирано ќе фрли се што е поврзано со личната храна, бидејќи имаме само една Список на написи, во кој треба да ставите нови податоци. Ова повторно не води до дуплирање на барањата.

Откако собрав сè што можев да откопам на темата, формулирав нова државна структура и им ја презентирав на моите колеги. Дискусиите беа долги, но на крајот аргументите во корист ги надминаа сомнежите и почнав со имплементација.

Логиката на решението најдобро се открива во два чекора. Прво се обидуваме да го одвоиме модулот Vuex од страниците и директно да се поврземе со маршрутите. Да, ќе има малку повеќе податоци во продавницата, добивачите ќе станат малку посложени, но нема да вчитуваме статии двапати. За мобилната верзија ова е можеби најсилниот аргумент. Ќе изгледа отприлика вака:

ArticlesList: {
  ROUTE_FEED: [ 
    { Article1 },
    ...
  ],
  ROUTE_ALL: [ 
    { Article2 },
    ...
  ],
}

Но, што ако списоците на статии може да се преклопуваат помеѓу повеќе правци и што ако сакаме повторно да ги користиме податоците за објектот Член да ја рендерирате страницата за објавување, претворајќи ја во Член Целосна? Во овој случај, би било пологично да се користи таква структура:

ArticlesIds: {
  ROUTE_FEED: [ '1', ... ],
  ROUTE_ALL: [ '1', '2', ... ],
},
ArticlesList: {
  '1': { Article1 }, 
  '2': { Article2 },
  ...
}

Список на написи овде тоа е само еден вид складиште на статии. Сите написи што беа преземени за време на сесијата на корисникот. Ние ги третираме со најголема грижа, бидејќи ова е сообраќај кој можеби е преземен преку болка некаде во метрото помеѓу станиците и дефинитивно не сакаме повторно да му ја предизвикаме оваа болка на корисникот со тоа што ќе го принудиме да вчита податоци што веќе ги има преземени. Предмет ИД на написи е едноставно низа од ИД (како „врски“) до објекти Член. Оваа структура ви овозможува да избегнете дуплирање на податоци вообичаени за рутите и повторно користење на објектот Член при рендерирање на страница со објава со спојување на проширени податоци во неа.

Излезот од списокот на статии, исто така, стана потранспарентен: компонентата итератор повторува низ низата со ID на написи и ја црта компонентата закачка на написи, пренесувајќи го ID како потпора, а детската компонента, пак, ги враќа потребните податоци од Список на написи. Кога ќе отидете на страницата за објавување, го добиваме веќе постоечкиот датум од Список на написи, правиме барање да ги добиеме податоците што недостасуваат и едноставно ги додаваме на постоечкиот објект.

Зошто овој пристап е подобар? Како што напишав погоре, овој пристап е понежен во однос на преземените податоци и ви овозможува повторно да ги користите. Но, покрај ова, го отвора патот кон некои нови можности кои совршено се вклопуваат во таква архитектура. На пример, анкетирање и вчитување статии во доводот како што се појавуваат. Ние едноставно можеме да ги ставиме најновите објави во „складирање“ Список на написи, зачувајте посебна листа на нови лични карти во ИД на написи и известете го корисникот за тоа. Кога ќе кликнете на копчето „Прикажи нови публикации“, едноставно ќе вметнеме нови ИД во почетокот на низата од тековната листа на статии и сè ќе функционира речиси магично.

Преземањето е попријатно

Шлагот на тортата за рефакторирање е концептот на скелети, што го прави процесот на преземање содржини на бавен интернет малку помалку одвратен. Немаше дискусии за ова прашање; патот од идеја до прототип траеше буквално два часа. Дизајнот практично се нацрта сам по себе, а ние ги научивме нашите компоненти да прикажуваат едноставни, едвај треперливи див блокови додека чекаат податоци. Субјективно, овој пристап кон вчитување всушност го намалува количеството на стрес хормони во телото на корисникот. Скелетот изгледа вака:

Habr логови на програмери од предниот дел: рефакторирање и рефлектирање
Habraloading

Рефлектирајќи

Работам во Хабре шест месеци и моите пријатели сè уште прашуваат: добро, како ви се допаѓа таму? Во ред, удобно - да. Но, има нешто што го прави ова дело поразлично од другите. Работев во тимови кои беа целосно рамнодушни кон нивниот производ, не знаеја или разбираа кои се нивните корисници. Но, тука сè е поинаку. Овде чувствувате одговорност за она што го правите. Во процесот на развивање на функцијата, делумно станувате нејзин сопственик, учествувате на сите состаноци на производи поврзани со вашата функционалност, давате предлози и сами донесувате одлуки. Да направите производ што го користите секој ден сами е многу кул, но пишувањето код за луѓе кои веројатно се подобри во тоа од вас е само неверојатно чувство (без сарказам).

По објавувањето на сите овие промени, добивме позитивни повратни информации, и тоа беше многу, многу убаво. Тоа е инспиративно. Ви благодарам! Напиши повеќе.

Дозволете ми да ве потсетам дека по глобалните променливи решивме да ја смениме архитектурата и да го распределиме прокси слојот во посебен пример. Архитектурата со „два јазли“ веќе е објавена во форма на јавно бета тестирање. Сега секој може да се префрли на него и да ни помогне да го подобриме мобилниот Habr. Тоа е се за денес. Со задоволство ќе одговорам на сите ваши прашања во коментарите.

Извор: www.habr.com

Додадете коментар