Habr front-end ծրագրավորողների տեղեկամատյանները. վերամշակում և արտացոլում

Habr front-end ծրագրավորողների տեղեկամատյանները. վերամշակում և արտացոլում

Ինձ միշտ հետաքրքրել է, թե ինչպես է Habr-ը կառուցված ներսից, ինչպես է կառուցված աշխատանքային հոսքը, ինչպես են կառուցված հաղորդակցությունները, ինչ ստանդարտներ են օգտագործվում և ինչպես է սովորաբար գրվում այստեղ կոդը: Բարեբախտաբար, ես նման հնարավորություն ստացա, քանի որ վերջերս դարձա habra թիմի անդամ։ Օգտվելով բջջային տարբերակի փոքր վերամշակման օրինակից՝ ես կփորձեմ պատասխանել այն հարցին, թե ինչպիսին է այստեղ աշխատել ճակատում: Ծրագրում՝ Node, Vue, Vuex և SSR սոուսով Habr-ի անձնական փորձի մասին գրառումներից:

Առաջին բանը, որ դուք պետք է իմանաք մշակողների թիմի մասին, այն է, որ մենք քիչ ենք: Բավական չէ. սրանք երեք ճակատ են, երկու թիկունք և բոլոր Հաբրի տեխնիկական առաջատարը` Բաքսլին: Կա, իհարկե, նաև փորձարկող, դիզայներ, երեք Վադիմ, հրաշք ցախավել, մարքեթինգի մասնագետ և այլ բումբուրումներ։ Բայց Հաբրի աղբյուրներին միայն վեց անմիջական ներդրող կա: Սա բավականին հազվադեպ է՝ բազմամիլիոնանոց լսարան ունեցող նախագիծ, որը դրսից կարծես հսկա ձեռնարկություն լինի, իրականում ավելի շատ նման է հարմարավետ ստարտափի՝ հնարավորինս հարթ կազմակերպչական կառուցվածքով:

Ինչպես շատ այլ ՏՏ ընկերություններ, Habr-ը դավանում է Agile գաղափարներ, CI պրակտիկա, և դա բոլորն է: Բայց իմ զգացմունքների համաձայն, Հաբրը որպես արտադրանք ավելի շատ զարգանում է ալիքային, քան շարունակաբար: Այսպիսով, անընդմեջ մի քանի սպրինտ մենք ջանասիրաբար կոդավորում ենք ինչ-որ բան, նախագծում և վերափոխում, ինչ-որ բան կոտրում և շտկում ենք, լուծում ենք տոմսերը և ստեղծում նորերը, քայլում ենք փոցխի վրա և կրակում ենք մեր ոտքերին, որպեսզի վերջապես թողարկենք գործառույթը: արտադրություն։ Եվ հետո գալիս է որոշակի հանգստություն, վերազարգացման շրջան, ժամանակն անելու այն, ինչ գտնվում է «կարևոր-ոչ հրատապ» քառորդում:

Հենց այս «արտասեզոնային» սպրինտն է, որը կքննարկվի ստորև: Այս անգամ այն ​​ներառում էր Habr-ի բջջային տարբերակի վերամշակում: Ընդհանուր առմամբ, ընկերությունը մեծ հույսեր է կապում դրա հետ, և ապագայում այն ​​պետք է փոխարինի Habr-ի մարմնավորումների ողջ կենդանաբանական այգին և դառնա ունիվերսալ խաչմերուկային լուծում: Մի օր կլինեն հարմարվողական դասավորություն, PWA, օֆլայն ռեժիմ, օգտագործողի հարմարեցում և շատ այլ հետաքրքիր բաներ:

Եկեք առաջադրանք դնենք

Մի անգամ, սովորական ստենդ-ափի ժամանակ, ճակատներից մեկը խոսեց բջջային տարբերակի մեկնաբանությունների բաղադրիչի ճարտարապետության խնդիրների մասին։ Այս նկատառումով կազմակերպեցինք միկրոհանդիպում խմբային հոգեթերապիայի ձևաչափով։ Բոլորը հերթով ասում էին, թե որտեղ է ցավում, ամեն ինչ արձանագրում էին թղթի վրա, կարեկցում էին, հասկանում էին, միայն թե ոչ ոք ծափ չէր տալիս։ Արդյունքը եղավ 20 խնդիրների ցուցակը, որը պարզ դարձրեց, որ բջջային Habr-ը դեռ երկար ու փշոտ ճանապարհ ունի դեպի հաջողություն:

Ինձ առաջին հերթին մտահոգում էր ռեսուրսների արդյունավետությունը և այն, ինչ կոչվում է հարթ ինտերֆեյս: Ամեն օր տուն-գործ-տուն երթուղու վրա ես տեսնում էի իմ հին հեռախոսը, որը հուսահատ փորձում էր 20 վերնագրեր ցուցադրել լրահոսում: Դա նման բան էր թվում.

Habr front-end ծրագրավորողների տեղեկամատյանները. վերամշակում և արտացոլումMobile Habr ինտերֆեյսը վերամշակումից առաջ

Ինչ է այստեղ կատարվում? Մի խոսքով, սերվերը HTML էջը մատուցում էր բոլորին նույն կերպ՝ անկախ նրանից՝ օգտատերը մուտք է գործել, թե ոչ։ Այնուհետև հաճախորդը JS-ը բեռնվում է և նորից պահանջում է անհրաժեշտ տվյալները, բայց ճշգրտվում է թույլտվության համար: Այսինքն՝ մենք իրականում երկու անգամ նույն գործն ենք արել։ Ինտերֆեյսը թարթեց, և օգտատերը ներբեռնեց հարյուրավոր լրացուցիչ կիլոբայթ: Մանրամասն ամեն ինչ ավելի սահմռկեցուցիչ էր թվում։

Habr front-end ծրագրավորողների տեղեկամատյանները. վերամշակում և արտացոլումՀին ԽՍՀ-ԿՍՊ սխեման. Թույլտվությունը հնարավոր է միայն C3 և C4 փուլերում, երբ Node JS-ը զբաղված չէ HTML-ի ստեղծմամբ և կարող է միջնորդել API-ի հարցումները:

Մեր այն ժամանակվա ճարտարապետությունը շատ ճշգրիտ նկարագրել է Habr-ի օգտատերերից մեկը.

Բջջային տարբերակը հիմար է: Ասում եմ այնպես, ինչպես կա: ԽՍՀ-ի ու ԿՍՊ-ի սարսափելի համադրություն.

Պետք էր դա խոստովանել, որքան էլ տխուր լիներ։

Ես գնահատեցի տարբերակները, Ժիրայում ստեղծեցի տոմս՝ «հիմա վատ է, ճիշտ արա» մակարդակի նկարագրությամբ և առաջադրանքը քայքայեցի լայն հարվածներով.

  • վերօգտագործման տվյալները,
  • նվազագույնի հասցնել վերափոխումների քանակը,
  • վերացնել կրկնօրինակ հարցումները,
  • ավելի ակնհայտ դարձնել բեռնման գործընթացը:

Եկեք նորից օգտագործենք տվյալները

Տեսականորեն, սերվերի կողմից մատուցումը նախատեսված է երկու խնդիր լուծելու համար. չտուժել որոնման համակարգի սահմանափակումներից SPA ինդեքսավորում և բարելավել մետրիկը FMP- ն (անխուսափելիորեն վատթարանում է TTI) Դասական սցենարով, որը վերջապես ձեւակերպվել է Airbnb-ում 2013թ տարի (դեռևս Backbone.js-ում), SSR-ը նույն իզոմորֆ JS հավելվածն է, որն աշխատում է Node միջավայրում: Սերվերը պարզապես ուղարկում է ստեղծված դասավորությունը՝ որպես հարցման պատասխան: Այնուհետև ռեհիդրացիան տեղի է ունենում հաճախորդի կողմից, այնուհետև ամեն ինչ աշխատում է առանց էջի վերաբեռնման: Habr-ի համար, ինչպես տեքստային բովանդակությամբ շատ այլ ռեսուրսների համար, սերվերի մատուցումը կարևոր տարր է որոնման համակարգերի հետ բարեկամական հարաբերություններ կառուցելու համար:

Չնայած այն հանգամանքին, որ տեխնոլոգիայի ի հայտ գալուց անցել է ավելի քան վեց տարի, և այս ընթացքում շատ ջուր իսկապես թռչել է կամրջի տակով ճակատային աշխարհում, շատ մշակողների համար այս գաղափարը դեռևս ծածկված է գաղտնիությամբ: Մենք մի կողմ չմնացինք և SSR-ի աջակցությամբ Vue հավելվածը թողարկեցինք արտադրության մեջ՝ բաց թողնելով մի փոքր մանրուք. մենք սկզբնական վիճակը չուղարկեցինք հաճախորդին:

Ինչո՞ւ։ Այս հարցին ստույգ պատասխան չկա։ Կամ նրանք չէին ցանկանում մեծացնել սերվերի պատասխանի չափը, կամ մի շարք այլ ճարտարապետական ​​խնդիրների պատճառով, կամ այն ​​պարզապես չհեռացավ: Այսպես թե այնպես, վիճակից դուրս նետելը և այն ամենը, ինչ արել է սերվերը, նորից օգտագործելը միանգամայն տեղին և օգտակար է թվում: Խնդիրն իրականում չնչին է. պետությունը պարզապես ներարկվում է կատարման համատեքստում, և Vue-ն ավտոմատ կերպով ավելացնում է այն ստեղծված դասավորությանը որպես գլոբալ փոփոխական. window.__INITIAL_STATE__.

Առաջացած խնդիրներից մեկը ցիկլային կառուցվածքները JSON-ի փոխակերպելու անկարողությունն է (շրջանաձեւ հղում); լուծվում էր՝ պարզապես փոխարինելով նման կառույցներն իրենց հարթ նմանակներով։

Բացի այդ, UGC բովանդակության հետ գործ ունենալիս պետք է հիշել, որ տվյալները պետք է փոխարկվեն HTML սուբյեկտների՝ HTML-ը չխախտելու համար։ Այս նպատակների համար մենք օգտագործում ենք he.

Նվազագույնի հասցնել վերագծումները

Ինչպես տեսնում եք վերևի գծապատկերից, մեր դեպքում, մեկ Node JS օրինակը կատարում է երկու գործառույթ՝ SSR և «proxy» API-ում, որտեղ տեղի է ունենում օգտատիրոջ թույլտվությունը: Այս հանգամանքը անհնարին է դարձնում թույլտվությունը, երբ JS կոդը աշխատում է սերվերում, քանի որ հանգույցը միահյուսված է, իսկ SSR ֆունկցիան՝ համաժամանակյա։ Այսինքն՝ սերվերը պարզապես չի կարող իրեն հարցումներ ուղարկել, մինչ callstack-ը զբաղված է ինչ-որ բանով։ Պարզվեց, որ մենք թարմացրել ենք վիճակը, բայց ինտերֆեյսը չի դադարել ճոճվել, քանի որ հաճախորդի տվյալները պետք է թարմացվեն՝ հաշվի առնելով օգտատիրոջ սեսիան: Մենք պետք է սովորեցնեինք մեր հավելվածին սկզբնական վիճակում դնել ճիշտ տվյալները՝ հաշվի առնելով օգտատիրոջ մուտքը։

Խնդրի լուծումը միայն երկուսն էր.

  • կցել թույլտվության տվյալները խաչասերվերի հարցումներին.
  • բաժանել Node JS շերտերը երկու առանձին օրինակների:

Առաջին լուծումը պահանջում էր սերվերի վրա գլոբալ փոփոխականների օգտագործումը, իսկ երկրորդը՝ առաջադրանքը կատարելու վերջնաժամկետը երկարաձգեց առնվազն մեկ ամսով։

Ինչպե՞ս ընտրություն կատարել: Հաբրը հաճախ շարժվում է նվազագույն դիմադրության ճանապարհով: Ոչ ֆորմալ առումով ընդհանուր ցանկություն կա նվազեցնել գաղափարից նախատիպ ցիկլը նվազագույնի: Ապրանքի նկատմամբ վերաբերմունքի մոդելը ինչ-որ չափով հիշեցնում է booking.com-ի պոստուլատները, միայն այն տարբերությամբ, որ Habr-ը շատ ավելի լուրջ է վերաբերվում օգտատերերի արձագանքներին և վստահում է ձեզ՝ որպես մշակողի, նման որոշումներ կայացնելը:

Հետևելով այս տրամաբանությանը և խնդիրը արագ լուծելու իմ սեփական ցանկությանը, ես ընտրեցի գլոբալ փոփոխականները: Եվ, ինչպես հաճախ է պատահում, վաղ թե ուշ պետք է վճարել դրանց համար։ Գրեթե անմիջապես վճարեցինք՝ շաբաթ-կիրակի աշխատեցինք, հետևանքները պարզեցինք, գրեցինք հետմահու և սկսեց սերվերը բաժանել երկու մասի: Սխալը շատ հիմար էր, և դրա հետ կապված սխալը վերարտադրելը հեշտ չէր: Եվ այո, դա ամոթ է, բայց այսպես թե այնպես, սայթաքելով և հառաչելով, գլոբալ փոփոխականներով իմ PoC-ն, այնուամենայնիվ, մտավ արտադրություն և բավականին հաջողությամբ աշխատում է՝ սպասելով նոր «երկհանգույց» ճարտարապետության անցնելուն: Սա կարևոր քայլ էր, քանի որ ֆորմալ առումով նպատակը իրագործվեց. ԽՍՀՄ-ն սովորեց մատուցել լիովին պատրաստի էջ, և UI-ը դարձավ շատ ավելի հանգիստ:

Habr front-end ծրագրավորողների տեղեկամատյանները. վերամշակում և արտացոլումMobile Habr ինտերֆեյս վերամշակման առաջին փուլից հետո

Ի վերջո, բջջային տարբերակի SSR-CSR ճարտարապետությունը հանգեցնում է այս պատկերին.

Habr front-end ծրագրավորողների տեղեկամատյանները. վերամշակում և արտացոլում«Երկհանգույց» ԽՍՀ-ԿՍՀ միացում: Node JS API-ն միշտ պատրաստ է ասինխրոն I/O-ի համար և արգելափակված չէ SSR ֆունկցիայի կողմից, քանի որ վերջինս գտնվում է առանձին օրինակում։ Հարցումների թիվ 3 շղթան անհրաժեշտ չէ:

Կրկնօրինակ հարցումների վերացում

Մանիպուլյացիաները կատարելուց հետո էջի նախնական արտապատկերումն այլևս էպիլեպսիա չի առաջացրել։ Բայց Habr-ի հետագա օգտագործումը SPA ռեժիմում դեռ շփոթություն առաջացրեց։

Քանի որ օգտագործողի հոսքի հիմքը ձևի անցումն է հոդվածների ցանկ → հոդված → մեկնաբանություններ և հակառակը, առաջին հերթին կարևոր էր այս շղթայի ռեսուրսների սպառման օպտիմալացումը:

Habr front-end ծրագրավորողների տեղեկամատյանները. վերամշակում և արտացոլումԳրառման հոսքին վերադառնալը առաջացնում է տվյալների նոր հարցում

Խորը փորելու կարիք չկար։ Վերևի էկրանային հեռարձակման մեջ դուք կարող եք տեսնել, որ հավելվածը կրկին պահանջում է հոդվածների ցանկը, երբ հետ սահեցրեք, իսկ հարցման ժամանակ մենք չենք տեսնում հոդվածները, ինչը նշանակում է, որ նախորդ տվյալները ինչ-որ տեղ անհետանում են: Կարծես հոդվածների ցանկի բաղադրիչն օգտագործում է տեղական վիճակ և կորցնում է այն ոչնչացման ժամանակ: Փաստորեն, հավելվածն օգտագործում էր գլոբալ վիճակ, բայց Vuex ճարտարապետությունը կառուցվել է առջև. մոդուլները կապված են էջերի հետ, որոնք իրենց հերթին կապված են երթուղիների հետ: Ավելին, բոլոր մոդուլները «մեկանգամյա օգտագործման» են. էջ յուրաքանչյուր հաջորդ այցելությունը վերագրում է ամբողջ մոդուլը.

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

Ընդհանուր առմամբ, մենք ունեինք մոդուլ Հոդվածների ցանկ, որը պարունակում է տիպի օբյեկտներ Հոդված և մոդուլ ԷջՀոդված, որը օբյեկտի ընդլայնված տարբերակն էր Հոդված, տեսակ Հոդված Ամբողջական. Մեծ հաշվով, այս իրականացումն ինքնին ոչ մի սարսափելի բան չի պարունակում. այն շատ պարզ է, նույնիսկ կարելի է ասել՝ միամիտ, բայց չափազանց հասկանալի։ Եթե ​​դուք զրոյացնում եք մոդուլը ամեն անգամ, երբ փոխում եք երթուղին, ապա կարող եք նույնիսկ ապրել դրա հետ: Այնուամենայնիվ, շարժվելով հոդվածների հոսքերի միջև, օրինակ / 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-ների զանգված է (կարծես «հղումներ») դեպի օբյեկտներ Հոդված. Այս կառուցվածքը թույլ է տալիս խուսափել երթուղիների համար սովորական տվյալների կրկնօրինակումից և օբյեկտի վերօգտագործումից Հոդված փոստի էջը մատուցելիս՝ դրա մեջ ընդլայնված տվյալները միաձուլելով:

Հոդվածների ցանկի արդյունքը նույնպես դարձել է ավելի թափանցիկ. iterator բաղադրիչը կրկնում է հոդվածների ID-ներով զանգվածի միջով և նկարում է հոդվածի թիզերի բաղադրիչը՝ փոխանցելով Id-ը որպես հենարան, իսկ երեխա բաղադրիչն իր հերթին առբերում է անհրաժեշտ տվյալները։ Հոդվածների ցանկ. Երբ դուք գնում եք հրապարակման էջ, մենք ստանում ենք արդեն գոյություն ունեցող ամսաթիվը Հոդվածների ցանկ, մենք հարցում ենք անում՝ ձեռք բերել բացակայող տվյալները և պարզապես դրանք ավելացնել առկա օբյեկտին։

Ինչու է այս մոտեցումը ավելի լավ: Ինչպես վերևում գրեցի, այս մոտեցումն ավելի մեղմ է ներբեռնված տվյալների նկատմամբ և թույլ է տալիս նորից օգտագործել դրանք: Բայց բացի դրանից, այն ճանապարհ է բացում մի քանի նոր հնարավորությունների համար, որոնք հիանալի տեղավորվում են նման ճարտարապետության մեջ: Օրինակ՝ հարցումներ կատարելը և հոդվածների ներբեռնումը լրահոսում, ինչպես դրանք հայտնվում են: Մենք պարզապես կարող ենք վերջին գրառումները տեղադրել «պահեստում» Հոդվածների ցանկ, պահեք նոր ID-ների առանձին ցուցակը Հոդվածների ID-ներ և այդ մասին տեղեկացնել օգտատիրոջը: Երբ սեղմում ենք «Ցուցադրել նոր հրապարակումներ» կոճակը, մենք պարզապես նոր ID-ներ կտեղադրենք ընթացիկ հոդվածների ցանկի սկզբնամասում, և ամեն ինչ կաշխատի գրեթե կախարդական կերպով:

Ներբեռնումն ավելի հաճելի դարձնելով

Վերամշակման տորթի վրա խարույկը կմախքներ հասկացությունն է, որը դանդաղ ինտերնետով բովանդակություն ներբեռնելու գործընթացը մի փոքր ավելի քիչ զզվելի է դարձնում: Այս հարցի շուրջ քննարկումներ չեն եղել, գաղափարից մինչև նախատիպ ճանապարհը տևել է բառացիորեն երկու ժամ: Դիզայնը գործնականում ինքն իրեն նկարեց, և մենք սովորեցրինք մեր բաղադրիչներին մատուցել պարզ, հազիվ թարթող div բլոկներ՝ սպասելով տվյալներին: Սուբյեկտիվորեն, բեռնման այս մոտեցումը իրականում նվազեցնում է օգտագործողի մարմնում սթրեսի հորմոնների քանակը: Կմախքը այսպիսի տեսք ունի.

Habr front-end ծրագրավորողների տեղեկամատյանները. վերամշակում և արտացոլում
Habraloading

Անդրադառնալով

Ես աշխատում եմ Հաբրեում վեց ամիս, և իմ ընկերները դեռ հարցնում են. լավ, ինչպե՞ս է ձեզ դուր գալիս այնտեղ: Լավ, հարմարավետ - այո: Բայց կա մի բան, որով այս աշխատանքը տարբերվում է մյուսներից: Ես աշխատում էի թիմերում, որոնք լիովին անտարբեր էին իրենց արտադրանքի նկատմամբ, չգիտեին կամ հասկանում, թե ովքեր են իրենց օգտատերերը: Բայց այստեղ ամեն ինչ այլ է. Այստեղ դու պատասխանատվություն ես զգում քո արածի համար։ Հատկանիշի մշակման գործընթացում դուք մասամբ դառնում եք դրա սեփականատերը, մասնակցում եք ձեր ֆունկցիոնալությանը վերաբերող բոլոր ապրանքային հանդիպումներին, առաջարկում եք առաջարկներ և ինքներդ որոշումներ կայացնում։ Ինքներդ ամեն օր օգտագործվող ապրանք պատրաստելը շատ հետաքրքիր է, բայց կոդ գրելն այն մարդկանց համար, ովքեր հավանաբար ավելի լավն են դրանում, քան դուք պարզապես անհավանական զգացողություն է (առանց սարկազմի):

Այս բոլոր փոփոխությունների թողարկումից հետո մենք ստացանք դրական արձագանքներ, և դա շատ, շատ հաճելի էր: Ոգեշնչող է։ Շնորհակալություն! Գրեք ավելին:

Հիշեցնեմ, որ գլոբալ փոփոխականներից հետո մենք որոշեցինք փոխել ճարտարապետությունը և պրոքսի շերտը հատկացնել առանձին օրինակի։ «Երկու հանգույցի» ճարտարապետությունն արդեն թողարկվել է հանրային բետա թեստավորման տեսքով: Այժմ յուրաքանչյուրը կարող է անցնել դրան և օգնել մեզ բարելավել բջջային Habr-ը: Այսօրվա համար այսքանը: Ես սիրով կպատասխանեմ ձեր բոլոր հարցերին մեկնաբանություններում։

Source: www.habr.com

Добавить комментарий