Protokoly předních vývojářů Habr: refaktorování a reflektování

Protokoly předních vývojářů Habr: refaktorování a reflektování

Vždy mě zajímalo, jak je Habr strukturovaný zevnitř, jak je strukturovaný workflow, jak je strukturovaná komunikace, jaké se používají standardy a jak se zde obecně píše kód. Naštěstí jsem takovou příležitost dostal, protože jsem se nedávno stal součástí týmu habra. Na příkladu malého refaktoringu mobilní verze se pokusím odpovědět na otázku: jaké to je pracovat tady vepředu. V programu: Node, Vue, Vuex a SSR s omáčkou z poznámek o osobní zkušenosti v Habr.

První věc, kterou potřebujete vědět o vývojovém týmu, je, že je nás málo. Nestačí - to jsou tři fronty, dva zadní a technický náskok všech Habr - Baxley. Nechybí samozřejmě ani tester, designér, tři Vadimové, zázračné koště, marketingový specialista a další Bumburumové. Ale do Habrových zdrojů je pouze šest přímých přispěvatelů. To je poměrně vzácné – projekt s mnohamilionovým publikem, který zvenčí vypadá jako obří podnik, ve skutečnosti vypadá spíše jako útulný startup s co nejplošší organizační strukturou.

Stejně jako mnoho jiných IT společností i Habr vyznává agilní nápady, praktiky CI, a to je vše. Ale podle mých pocitů se Habr jako produkt vyvíjí spíše ve vlnách než kontinuálně. Několik sprintů za sebou tedy pilně něco kódujeme, navrhujeme a předěláváme, něco rozbíjíme a opravujeme, řešíme tikety a vytváříme nové, šlápneme na hrábě a střelíme se do nohou, abychom funkci konečně uvolnili Výroba. A pak přijde určitý klid, období přestavby, čas udělat to, co je v kvadrantu „důležité – ne naléhavé“.

Přesně o tomto „mimosezónním“ sprintu bude řeč níže. Tentokrát zahrnoval refaktoring mobilní verze Habr. Obecně do něj společnost vkládá velké naděje a v budoucnu by měla nahradit celou zoo Habrových inkarnací a stát se univerzálním multiplatformním řešením. Jednou tu bude adaptivní rozvržení, PWA, offline režim, uživatelské přizpůsobení a mnoho dalších zajímavých věcí.

Pojďme nastavit úkol

Jednou na obyčejném stand-upu jeden z předních mluvil o problémech v architektuře komponenty komentářů mobilní verze. S tímto vědomím jsme zorganizovali mikrosetkání ve formátu skupinové psychoterapie. Všichni se střídali v tom, kde to bolelo, všechno si zaznamenávali na papír, sympatizovali, rozuměli, až na to, že nikdo netleskal. Výsledkem byl seznam 20 problémů, z nichž bylo jasné, že mobilního Habra čeká ještě dlouhá a trnitá cesta k úspěchu.

Primárně jsem se staral o efektivitu zdrojů a to, čemu se říká hladké rozhraní. Každý den jsem cestou domů-práce-domů viděl svůj starý telefon, jak se zoufale snaží zobrazit ve zdroji 20 titulků. Vypadalo to nějak takto:

Protokoly předních vývojářů Habr: refaktorování a reflektováníRozhraní Mobile Habr před refaktorizací

Co se tam děje? Stručně řečeno, server naservíroval HTML stránku všem stejně, bez ohledu na to, zda byl uživatel přihlášen nebo ne. Poté se načte klientský JS a znovu požaduje potřebná data, ale upravená pro autorizaci. To znamená, že jsme vlastně dělali stejnou práci dvakrát. Rozhraní zablikalo a uživatel si stáhl dobrých sto kilobajtů navíc. V detailu vše vypadalo ještě strašidelněji.

Protokoly předních vývojářů Habr: refaktorování a reflektováníStaré schéma SSR-CSR. Autorizace je možná pouze ve fázích C3 a C4, kdy Node JS není zaneprázdněn generováním HTML a může proxy požadavky na API.

Naši tehdejší architekturu velmi přesně popsal jeden z uživatelů Habr:

Mobilní verze je blbost. Říkám to tak, jak to je. Příšerná kombinace SSR a CSR.

Museli jsme si to přiznat, bez ohledu na to, jak smutné to bylo.

Posoudil jsem možnosti, vytvořil tiket v Jira s popisem na úrovni „teď je to špatné, udělej to správně“ a rozložil úkol širokými tahy:

  • znovu použít data,
  • minimalizovat počet překreslení,
  • eliminovat duplicitní požadavky,
  • zviditelnit proces načítání.

Pojďme znovu použít data

Teoreticky je vykreslování na straně serveru navrženo tak, aby vyřešilo dva problémy: netrpět omezeními vyhledávačů SPA indexace a zlepšit metriku FMP (nevyhnutelně se zhoršuje TTI). V klasickém scénáři, že konečně formulované na Airbnb v roce 2013 rok (stále na Backbone.js), SSR je stejná izomorfní JS aplikace běžící v prostředí Node. Server jednoduše odešle vygenerované rozložení jako odpověď na požadavek. Poté dojde k rehydrataci na straně klienta a poté vše funguje bez opětovného načtení stránky. Pro Habra, stejně jako pro mnoho dalších zdrojů s textovým obsahem, je serverové vykreslování zásadním prvkem při budování přátelských vztahů s vyhledávači.

Navzdory tomu, že od nástupu technologie uplynulo více než šest let a za tuto dobu už pod mostem ve front-end světě proteklo opravdu hodně vody, pro mnohé vývojáře je tato myšlenka stále zahalena rouškou tajemství. Nestáli jsme stranou a spustili do výroby aplikaci Vue s podporou SSR, chyběl nám jeden malý detail: neposlali jsme klientovi počáteční stav.

Proč? Na tuto otázku neexistuje přesná odpověď. Buď nechtěli zvětšit velikost odpovědi ze serveru, nebo kvůli spoustě dalších architektonických problémů, nebo to prostě nevzlétlo. Tak či onak, vyhodit stav a znovu použít vše, co server udělal, se zdá být docela vhodné a užitečné. Úkol je vlastně triviální - stavu se jednoduše vstříkne do kontextu provádění a Vue jej automaticky přidá do generovaného rozvržení jako globální proměnnou: window.__INITIAL_STATE__.

Jedním z problémů, které se objevily, je neschopnost převést cyklické struktury na JSON (kruhový odkaz); byl vyřešen prostým nahrazením takových konstrukcí jejich plochými protějšky.

Kromě toho byste při práci s obsahem UGC měli pamatovat na to, že data by měla být převedena na entity HTML, aby nedošlo k porušení kódu HTML. Pro tyto účely používáme he.

Minimalizace překreslování

Jak můžete vidět z výše uvedeného diagramu, v našem případě jedna instance Node JS vykonává dvě funkce: SSR a „proxy“ v API, kde dochází k autorizaci uživatele. Tato okolnost znemožňuje autorizaci, když je na serveru spuštěn kód JS, protože uzel je jednovláknový a funkce SSR je synchronní. To znamená, že server jednoduše nemůže posílat požadavky sám sobě, když je zásobník volání něčím zaneprázdněn. Ukázalo se, že jsme aktualizovali stav, ale rozhraní nepřestalo škubat, protože data na klientovi musela být aktualizována s ohledem na uživatelskou relaci. Potřebovali jsme naši aplikaci naučit uvádět správná data do výchozího stavu s ohledem na přihlášení uživatele.

Existovaly pouze dvě řešení problému:

  • připojit autorizační data k požadavkům mezi servery;
  • rozdělit vrstvy Node JS na dvě samostatné instance.

První řešení vyžadovalo použití globálních proměnných na serveru a druhé prodloužilo termín dokončení úkolu minimálně o měsíc.

Jak si vybrat? Habr se často pohybuje po cestě nejmenšího odporu. Neformálně existuje všeobecná touha snížit cyklus od nápadu k prototypu na minimum. Model postoje k produktu trochu připomíná postuláty booking.com, jen s tím rozdílem, že Habr bere zpětnou vazbu od uživatelů mnohem vážněji a důvěřuje vám, jako vývojáři, že taková rozhodnutí uděláte.

Podle této logiky a mé vlastní touhy problém rychle vyřešit jsem zvolil globální proměnné. A jak se často stává, musíte za ně dříve nebo později zaplatit. Zaplatili jsme téměř okamžitě: pracovali jsme o víkendu, uklízeli následky, psali pitva a začal rozdělovat server na dvě části. Chyba byla velmi hloupá a chybu, která ji zahrnovala, nebylo snadné reprodukovat. A ano, je to škoda, ale tak či onak, klopýtání a sténání, můj PoC s globálními proměnnými se přesto dostal do výroby a docela úspěšně funguje, zatímco čeká na přechod na novou „dvouuzlovou“ architekturu. To byl důležitý krok, protože formálně bylo cíle dosaženo – SSR se naučilo dodávat stránku zcela připravenou k použití a uživatelské rozhraní se stalo mnohem klidnějším.

Protokoly předních vývojářů Habr: refaktorování a reflektováníRozhraní Mobile Habr po první fázi refaktoringu

Nakonec architektura SSR-CSR mobilní verze vede k tomuto obrázku:

Protokoly předních vývojářů Habr: refaktorování a reflektování„Dvouuzlový“ obvod SSR-CSR. Node JS API je vždy připraveno pro asynchronní I/O a není blokováno funkcí SSR, protože ta je umístěna v samostatné instanci. Řetězec dotazů č. 3 není potřeba.

Eliminace duplicitních požadavků

Po provedení manipulací již počáteční vykreslení stránky nevyvolávalo epilepsii. Ale další použití Habra v režimu SPA stále způsobilo zmatek.

Protože základem uživatelského toku jsou přechody formuláře seznam článků → článek → komentáře a naopak bylo důležité v první řadě optimalizovat spotřebu zdrojů tohoto řetězce.

Protokoly předních vývojářů Habr: refaktorování a reflektováníNávrat do kanálu příspěvků vyvolá nový požadavek na data

Nebylo třeba kopat hluboko. Na screencastu výše můžete vidět, že aplikace při přejetí zpět požaduje znovu seznam článků a během požadavku se nám články nezobrazují, což znamená, že předchozí údaje někde zmizí. Vypadá to, že komponenta seznamu článků používá místní stav a při zničení jej ztratí. Ve skutečnosti aplikace používala globální stav, ale architektura Vuex byla postavena přímo: moduly jsou svázány se stránkami, které jsou zase svázány s trasami. Všechny moduly jsou navíc „na jedno použití“ – každá další návštěva stránky přepsala celý modul:

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

Celkem jsme měli modul Seznam článků, který obsahuje objekty typu Článek a modul Článek na stránce, což byla rozšířená verze objektu Článek, druh ČlánekPlný. Celkově tato implementace v sobě nenese nic hrozného - je velmi jednoduchá, dalo by se říci naivní, ale nesmírně srozumitelná. Pokud modul resetujete pokaždé, když změníte trasu, můžete s ním dokonce žít. Například pohyb mezi články /krmit → /vše, zaručeně vyhodí vše, co souvisí s osobním krmivem, jelikož máme jen jeden Seznam článků, do kterého je potřeba vložit nová data. To nás opět vede k duplicitě požadavků.

Shromáždil jsem vše, co se mi k tématu podařilo vyhrabat, zformuloval jsem novou státní strukturu a představil ji svým kolegům. Diskuse byly zdlouhavé, ale nakonec argumenty ve prospěch převážily nad pochybnostmi a já začal s realizací.

Logiku řešení nejlépe odhalíte ve dvou krocích. Nejprve se pokusíme oddělit modul Vuex od stránek a navázat přímo na trasy. Ano, v obchodě bude trochu více dat, gettery budou trochu složitější, ale nebudeme načítat články dvakrát. U mobilní verze je to snad nejsilnější argument. Bude to vypadat nějak takto:

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

Ale co když se seznamy článků mohou překrývat mezi více cestami a co když chceme znovu použít objektová data Článek vykreslit stránku příspěvku a přeměnit ji na ČlánekPlný? V tomto případě by bylo logičtější použít takovou strukturu:

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

Seznam článků tady je to jen jakési úložiště článků. Všechny články, které byly staženy během uživatelské relace. Zacházíme s nimi s maximální opatrností, protože se jedná o provoz, který se možná stáhl bolestí někde v metru mezi stanicemi, a rozhodně nechceme tuto bolest způsobit uživateli znovu tím, že jej budeme nutit načítat data, která již má staženo. Objekt ArticlesIds je jednoduše pole ID (jakoby „odkazů“) na objekty Článek. Tato struktura vám umožňuje vyhnout se duplikaci dat společných pro trasy a opětovnému použití objektu Článek při vykreslování stránky příspěvku sloučením rozšířených dat do ní.

Výstup seznamu článků se také stal transparentnějším: komponenta iterátoru prochází polem s ID článků a vykresluje komponentu upoutávky na článek, předá ID jako podpěru a podřízená komponenta zase získá potřebná data z Seznam článků. Když přejdete na stránku publikace, získáme již existující datum od Seznam článků, provedeme požadavek na získání chybějících dat a jednoduše je doplníme do stávajícího objektu.

Proč je tento přístup lepší? Jak jsem psal výše, tento přístup je šetrnější vůči staženým datům a umožňuje je znovu použít. Ale kromě toho to otevírá cestu k některým novým možnostem, které do takové architektury dokonale zapadají. Například dotazování a načítání článků do zdroje tak, jak se objevují. Můžeme jednoduše uložit nejnovější příspěvky do „úložiště“ Seznam článků, uložte samostatný seznam nových ID do ArticlesIds a upozornit na to uživatele. Když klikneme na tlačítko „Zobrazit nové publikace“, jednoduše vložíme nová ID na začátek pole aktuálního seznamu článků a vše bude fungovat téměř magicky.

Zpříjemnění stahování

Třešničkou na refaktorovacím dortu je koncept kostlivců, díky kterému je proces stahování obsahu na pomalém internetu o něco méně nechutný. O této věci se nediskutovalo, cesta od nápadu k prototypu trvala doslova dvě hodiny. Návrh se prakticky nakreslil sám a naučili jsme naše komponenty vykreslovat jednoduché, sotva blikající bloky div při čekání na data. Subjektivně tento přístup k zátěži ve skutečnosti snižuje množství stresových hormonů v těle uživatele. Kostra vypadá takto:

Protokoly předních vývojářů Habr: refaktorování a reflektování
Habraloading

Odrážející

V Habré pracuji šest měsíců a moji přátelé se stále ptají: no, jak se vám tam líbí? Dobře, pohodlné - ano. Ale je tu něco, čím se tato práce liší od ostatních. Pracoval jsem v týmech, kterým byl jejich produkt naprosto lhostejný, nevěděli a nechápali, kdo jsou jejich uživatelé. Tady je ale všechno jinak. Tady cítíte zodpovědnost za to, co děláte. V procesu vývoje funkce se částečně stáváte jejím vlastníkem, účastníte se všech produktových schůzek souvisejících s vaší funkcionalitou, podáváte návrhy a sami se rozhodujete. Vyrobit produkt, který sami používáte každý den, je velmi cool, ale psát kód pro lidi, kteří jsou v tom pravděpodobně lepší než vy, je prostě neuvěřitelný pocit (žádný sarkasmus).

Po vydání všech těchto změn jsme obdrželi pozitivní zpětnou vazbu a bylo to velmi, velmi příjemné. Je to inspirativní. Děkuji! Napište více.

Dovolte mi připomenout, že po globálních proměnných jsme se rozhodli změnit architekturu a alokovat proxy vrstvu do samostatné instance. „Dvouuzlová“ architektura již dosáhla vydání ve formě veřejného beta testování. Nyní na něj může přejít kdokoli a pomoci nám vylepšit mobilního Habra. To je pro dnešek vše. Všechny vaše dotazy rád zodpovím v komentářích.

Zdroj: www.habr.com

Přidat komentář