Habr front-end udviklerlogfiler: refaktorering og reflektering

Habr front-end udviklerlogfiler: refaktorering og reflektering

Jeg har altid interesseret mig for, hvordan Habr er opbygget indefra, hvordan arbejdsgangen er opbygget, hvordan kommunikation er opbygget, hvilke standarder der bruges og hvordan kode generelt skrives her. Heldigvis fik jeg sådan en mulighed, for jeg er for nylig blevet en del af habra-teamet. Ved at bruge eksemplet med en lille refaktorering af mobilversionen vil jeg forsøge at besvare spørgsmålet: hvordan er det at arbejde her foran. I programmet: Node, Vue, Vuex og SSR med sauce fra noter om personlig erfaring i Habr.

Det første du skal vide om udviklingsteamet er, at vi er få. Ikke nok - det er tre fronter, to backs og den tekniske føring af alle Habr - Baxley. Der er selvfølgelig også en tester, en designer, tre Vadim, en mirakelkost, en marketingspecialist og andre Bumburums. Men der er kun seks direkte bidragydere til Habrs kilder. Dette er ret sjældent – ​​et projekt med et multimillion-dollar publikum, som udefra ligner en kæmpe virksomhed, i virkeligheden ligner mere en hyggelig startup med den fladste organisationsstruktur som muligt.

Som mange andre it-virksomheder bekender Habr sig til agile ideer, CI-praksis, og det er alt. Men ifølge mine følelser udvikler Habr som produkt sig mere i bølger end kontinuerligt. Så i flere spurter i træk koder vi flittigt noget, designer og redesigner, knækker noget og fikser det, løser billetter og skaber nye, træder på en rive og skyder os selv i fødderne, for endelig at frigive funktionen i produktion. Og så kommer der en vis pause, en periode med ombygning, tid til at gøre det, der står i "vigtigt-ikke presserende" kvadrant.

Det er netop denne "off-season" sprint, der vil blive diskuteret nedenfor. Denne gang omfattede det en refaktorering af mobilversionen af ​​Habr. Generelt har virksomheden store forhåbninger til det, og i fremtiden bør det erstatte hele den zoologiske have af Habrs inkarnationer og blive en universel cross-platform løsning. En dag vil der være adaptivt layout, PWA, offline-tilstand, brugertilpasning og mange andre interessante ting.

Lad os sætte opgaven

Engang, ved en almindelig stand-up, talte en af ​​fronterne om problemer i arkitekturen af ​​kommentarkomponenten i mobilversionen. Med dette i tankerne arrangerede vi et mikromøde i form af gruppepsykoterapi. Alle skiftedes til at sige, hvor det gjorde ondt, de noterede alt på papir, de sympatiserede, de forstod, bortset fra at ingen klappede. Resultatet blev en liste med 20 problemer, som gjorde det klart, at mobile Habr stadig havde en lang og vanskelig vej til succes.

Jeg var primært optaget af ressourceeffektivitet og det, der kaldes en glat grænseflade. Hver dag, på ruten hjem-arbejde-hjem, så jeg min gamle telefon desperat forsøge at vise 20 overskrifter i feedet. Det så nogenlunde sådan her ud:

Habr front-end udviklerlogfiler: refaktorering og reflekteringMobil Habr-grænseflade før refaktorering

Hvad sker der her? Kort sagt serverede serveren HTML-siden til alle på samme måde, uanset om brugeren var logget ind eller ej. Derefter indlæses klient-JS og anmoder om de nødvendige data igen, men justeres for autorisation. Det vil sige, at vi faktisk udførte det samme arbejde to gange. Interfacet flimrede, og brugeren downloadede godt hundrede ekstra kilobyte. I detaljer så alt endnu mere uhyggeligt ud.

Habr front-end udviklerlogfiler: refaktorering og reflekteringGammel SSR-CSR-ordning. Autorisation er kun mulig i trin C3 og C4, når Node JS ikke har travlt med at generere HTML og kan proxy-anmodninger til API'et.

Vores arkitektur på den tid blev meget præcist beskrevet af en af ​​Habr-brugerne:

Mobilversionen er lort. Jeg fortæller det, som det er. En frygtelig kombination af SSR og CSR.

Vi måtte indrømme det, uanset hvor trist det var.

Jeg vurderede mulighederne, oprettede en billet i Jira med en beskrivelse på niveauet "det er dårligt nu, gør det rigtigt" og opløste opgaven i store træk:

  • genbruge data,
  • minimere antallet af gentegninger,
  • eliminere dobbelte anmodninger,
  • gøre indlæsningsprocessen mere indlysende.

Lad os genbruge dataene

I teorien er server-side rendering designet til at løse to problemer: ikke at lide af søgemaskine begrænsninger mht. SPA-indeksering og forbedre metrikken FMP (uundgåeligt forværres TTI). I et klassisk scenarie, der endelig formuleret hos Airbnb i 2013 år (stadig på Backbone.js), er SSR den samme isomorfe JS-applikation, der kører i Node-miljøet. Serveren sender simpelthen det genererede layout som et svar på anmodningen. Så sker der rehydrering på klientsiden, og så fungerer alt uden sidegenindlæsninger. For Habr, som for mange andre ressourcer med tekstindhold, er servergengivelse et kritisk element i opbygningen af ​​venskabelige relationer med søgemaskiner.

På trods af at der er gået mere end seks år siden fremkomsten af ​​teknologien, og i løbet af denne tid er der virkelig fløjet meget vand under broen i front-end-verdenen, er denne idé for mange udviklere stadig indhyllet i hemmeligholdelse. Vi stod ikke til side og udrullede en Vue-applikation med SSR-understøttelse til produktionen, der manglede en lille detalje: Vi sendte ikke den oprindelige tilstand til klienten.

Hvorfor? Der er ikke noget præcist svar på dette spørgsmål. Enten ønskede de ikke at øge størrelsen af ​​svaret fra serveren, eller på grund af en masse andre arkitektoniske problemer, eller også tog det simpelthen ikke fart. På en eller anden måde virker det ret passende og nyttigt at smide tilstand og genbruge alt, hvad serveren gjorde. Opgaven er faktisk triviel - tilstand er simpelthen injiceret ind i udførelseskonteksten, og Vue tilføjer det automatisk til det genererede layout som en global variabel: window.__INITIAL_STATE__.

Et af de problemer, der er opstået, er manglende evne til at konvertere cykliske strukturer til JSON (cirkulær reference); blev løst ved blot at erstatte sådanne strukturer med deres flade modstykker.

Derudover, når du beskæftiger dig med UGC-indhold, skal du huske, at dataene skal konverteres til HTML-enheder for ikke at bryde HTML. Til disse formål bruger vi he.

Minimering af gentegninger

Som du kan se fra diagrammet ovenfor, udfører en Node JS-instans i vores tilfælde to funktioner: SSR og "proxy" i API'en, hvor brugerautorisation finder sted. Denne omstændighed gør det umuligt at autorisere, mens JS-koden kører på serveren, da noden er single-threaded, og SSR-funktionen er synkron. Det vil sige, at serveren simpelthen ikke kan sende forespørgsler til sig selv, mens callstakken er optaget af noget. Det viste sig, at vi opdaterede tilstanden, men grænsefladen stoppede ikke med at rykke, da dataene på klienten skulle opdateres under hensyntagen til brugersessionen. Vi var nødt til at lære vores applikation at sætte de korrekte data i den oprindelige tilstand under hensyntagen til brugerens login.

Der var kun to løsninger på problemet:

  • vedhæfte autorisationsdata til anmodninger på tværs af servere;
  • opdele Node JS-lag i to separate instanser.

Den første løsning krævede brug af globale variabler på serveren, og den anden forlængede fristen for at udføre opgaven med mindst en måned.

Hvordan træffer man et valg? Habr bevæger sig ofte langs den mindste modstands vej. Uformelt er der et generelt ønske om at reducere cyklussen fra idé til prototype til et minimum. Modellen for holdning til produktet minder en del om postulaterne fra booking.com, med den eneste forskel, at Habr tager brugerfeedback meget mere seriøst og stoler på, at du som udvikler træffer sådanne beslutninger.

Efter denne logik og mit eget ønske om hurtigt at løse problemet, valgte jeg globale variabler. Og som det ofte sker, skal du betale for dem før eller siden. Vi betalte næsten med det samme: vi arbejdede i weekenden, fik ryddet op i konsekvenserne, skrev efter døden og begyndte at opdele serveren i to dele. Fejlen var meget dum, og fejlen, der involverede den, var ikke let at genskabe. Og ja, det er en skam for dette, men på en eller anden måde, snublende og stønnende, gik min PoC med globale variabler ikke desto mindre i produktion og fungerer ganske succesfuldt, mens jeg venter på flytningen til en ny "to-node"-arkitektur. Dette var et vigtigt skridt, for formelt var målet nået – SSR lærte at levere en helt klar-til-brug side, og brugerfladen blev meget mere rolig.

Habr front-end udviklerlogfiler: refaktorering og reflekteringMobil Habr-grænseflade efter den første fase af refactoring

I sidste ende fører SSR-CSR-arkitekturen i mobilversionen til dette billede:

Habr front-end udviklerlogfiler: refaktorering og reflektering"To-node" SSR-CSR kredsløb. Node JS API er altid klar til asynkron I/O og er ikke blokeret af SSR-funktionen, da sidstnævnte er placeret i en separat instans. Forespørgselskæde #3 er ikke nødvendig.

Eliminering af duplikerede anmodninger

Efter at manipulationerne blev udført, fremkaldte den indledende gengivelse af siden ikke længere epilepsi. Men den videre brug af Habr i SPA-tilstand forårsagede stadig forvirring.

Da grundlaget for brugerflow er overgange af formularen liste over artikler → artikel → kommentarer og omvendt var det vigtigt at optimere ressourceforbruget i denne kæde i første omgang.

Habr front-end udviklerlogfiler: refaktorering og reflekteringAt vende tilbage til postfeedet fremkalder en ny dataanmodning

Der var ingen grund til at grave dybt. I screencastet ovenfor kan du se, at applikationen beder om listen over artikler igen, når du swiper tilbage, og under anmodningen ser vi ikke artiklerne, hvilket betyder, at de tidligere data forsvinder et sted. Det ser ud til, at artikellistekomponenten bruger en lokal stat og mister den ved ødelæggelse. Faktisk brugte applikationen en global tilstand, men Vuex-arkitekturen blev bygget frontalt: moduler er bundet til sider, som igen er bundet til ruter. Desuden er alle moduler "engangs" - hvert efterfølgende besøg på siden omskrev hele modulet:

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

I alt havde vi et modul Artikelliste, som indeholder objekter af typen Artikel og modul SideArtikel, som var en udvidet version af objektet Artikel, en slags Fuld artikel. I det store og hele bærer denne implementering ikke noget forfærdeligt i sig selv - den er meget enkel, man kan endda sige naiv, men yderst forståelig. Hvis du nulstiller modulet, hver gang du ændrer ruten, så kan du endda leve med det. Men flytning mellem artikelfeeds f.eks /feed → /alle, smider med garanti alt relateret til det personlige feed væk, da vi kun har et Artikelliste, hvor du skal lægge nye data ind. Dette fører igen os til duplikering af anmodninger.

Efter at have samlet alt, hvad jeg var i stand til at grave frem om emnet, formulerede jeg en ny statsstruktur og præsenterede den for mine kolleger. Diskussionerne var lange, men til sidst opvejede argumenterne for tvivlen, og jeg begyndte at implementere.

Logikken i en løsning afsløres bedst i to trin. Først forsøger vi at afkoble Vuex-modulet fra sider og binde direkte til ruter. Ja, der vil være lidt mere data i butikken, getters bliver lidt mere komplekse, men vi indlæser ikke artikler to gange. For mobilversionen er det måske det stærkeste argument. Det kommer til at se sådan ud:

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

Men hvad nu hvis artikellister kan overlappe mellem flere ruter, og hvad hvis vi vil genbruge objektdata Artikel at gengive indlægssiden, forvandle den til Fuld artikel? I dette tilfælde ville det være mere logisk at bruge en sådan struktur:

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

Artikelliste her er det bare en slags lager af artikler. Alle artikler, der blev downloadet under brugersessionen. Vi behandler dem med den største omhu, for det er trafik, der kan være downloadet gennem smerte et sted i metroen mellem stationerne, og vi ønsker bestemt ikke at påføre brugeren denne smerte igen ved at tvinge ham til at indlæse data, som han allerede har. downloadet. Et objekt ArticlesIds er simpelthen en række id'er (som om "linker") til objekter Artikel. Denne struktur giver dig mulighed for at undgå at duplikere data, der er fælles for ruter, og genbruge objektet Artikel når du renderer en postside ved at flette udvidede data ind i den.

Outputtet af listen over artikler er også blevet mere gennemskueligt: ​​Iterator-komponenten itererer gennem arrayet med artikel-id'er og tegner artiklens teaser-komponent, sender id'et som en rekvisit, og den underordnede komponent henter til gengæld de nødvendige data fra Artikelliste. Når du går til udgivelsessiden, får vi den allerede eksisterende dato fra Artikelliste, laver vi en anmodning om at få de manglende data og tilføjer dem blot til det eksisterende objekt.

Hvorfor er denne tilgang bedre? Som jeg skrev ovenfor, er denne tilgang mere skånsom med hensyn til de downloadede data og giver dig mulighed for at genbruge dem. Men udover dette åbner det for nogle nye muligheder, der passer perfekt ind i sådan en arkitektur. For eksempel polling og indlæsning af artikler i feedet, som de vises. Vi kan simpelthen lægge de seneste indlæg i et "lager" Artikelliste, gem en separat liste over nye id'er i ArticlesIds og underrette brugeren om det. Når vi klikker på knappen "Vis nye publikationer", vil vi simpelthen indsætte nye Id'er i begyndelsen af ​​rækken af ​​den aktuelle liste over artikler, og alt vil fungere næsten magisk.

Gør det sjovere at downloade

Prikken over i'et på refactoring-kagen er begrebet skeletter, som gør processen med at downloade indhold på et langsomt internet lidt mindre ulækkert. Der var ingen diskussioner om denne sag; vejen fra idé til prototype tog bogstaveligt talt to timer. Designet tegnede næsten sig selv, og vi lærte vores komponenter at gengive enkle, knapt flimrende div-blokke, mens vi ventede på data. Subjektivt reducerer denne tilgang til belastning faktisk mængden af ​​stresshormoner i brugerens krop. Skelettet ser således ud:

Habr front-end udviklerlogfiler: refaktorering og reflektering
Habraloading

Reflekterende

Jeg har arbejdet i Habré i seks måneder, og mine venner spørger stadig: Nå, hvordan kan du lide det der? Okay, behageligt - ja. Men der er noget, der gør dette arbejde anderledes end andre. Jeg arbejdede i teams, der var fuldstændig ligeglade med deres produkt, ikke vidste eller forstod, hvem deres brugere var. Men her er alt anderledes. Her føler du dig ansvarlig for det, du gør. I processen med at udvikle en funktion bliver du delvist dens ejer, deltager i alle produktmøder relateret til din funktionalitet, kommer med forslag og træffer selv beslutninger. At lave et produkt, som du selv bruger hver dag, er meget fedt, men at skrive kode til folk, der sikkert er bedre til det end dig, er bare en utrolig følelse (ingen sarkasme).

Efter udgivelsen af ​​alle disse ændringer fik vi positiv feedback, og det var meget, meget rart. Det er inspirerende. Tak skal du have! Skriv mere.

Lad mig minde dig om, at vi efter globale variabler besluttede at ændre arkitekturen og allokere proxylaget til en separat instans. "To-node"-arkitekturen er allerede nået udgivet i form af offentlig beta-test. Nu kan alle skifte til det og hjælpe os med at gøre mobile Habr bedre. Det var alt for i dag. Jeg vil med glæde besvare alle dine spørgsmål i kommentarerne.

Kilde: www.habr.com

Tilføj en kommentar