Mundarija (39)
- Bu darsda
- 1. Nega bu kerak?
- 2. Eng oddiy flag: kodda va build'da
- 2.1 Flaglar ro'yxati
- 2.2 Build paytidagi flag: VITE_FLAGLAR
- 3. Ish paytidagi flag: brauzerda
- 3.1 Uch manba, bitta qiymat
- 3.2 Jonli: useFlag
- 3.3 Sinovchi havola: ?flag=
- 4. Masofaviy flag va miltillash
- 4.1 Nega server kerak
- 4.2 Jonli: miltillash
- 4.3 Uch yechim
- 5. Foizli rollout: "10% ga yoqish" qanday ishlaydi
- 5.1 Tasodif emas — hash
- 5.2 Kim "foydalanuvchi"?
- 6. A/B test: qaysi variant yaxshi?
- 6.1 G'oya
- 6.2 Farq bor — lekin haqiqiymi?
- 6.3 Uch tuzoq
- 7. Tayyor xizmatlar va OpenFeature
- 7.1 Kim nima beradi
- 7.2 Biz sinab ko'rdik: GrowthBook va OpenFeature
- 8. Vazifalar qadami: flaglar (holat 20)
- 8.1 Branch va fayllar
- 8.2 flaglar.ts
- 8.3 Flag ortidagi grafik va Sozlamalar
- 8.4 Natija
- 8.5 Commit
- 9. Hujumchi nigohi
- 10. Ko'p uchraydigan xatolar
- 11. Mashqlar
- 1-mashq (oson): bo'sh joylarni to'ldiring
- 2-mashq (o'rta): variant tanlash
- 3-mashq (qiyin): miltillashsiz masofaviy flag
- 4-mashq: Vazifalar qadami
- 12. Real ishda
- Xulosa
- Manbalar
Feature flag React'da: deploysiz yoqish, foizli rollout va A/B testlar
Qisqacha: Feature flag — kodda allaqachon bor funksiyani yoqib-o'chiradigan kalit:
if (useFlag("yangi-banner")) …. Kod deploy bilan keladi, funksiya esa flag bilan yoqiladi — buzilsa, qayta deploysiz o'chiriladi. Flag qiymati build'da (VITE_FLAGLAR), brauzerda (localStorage,?flag=) yoki serverda (GrowthBook, PostHog, Unleash) turadi. Masofaviy flagning tuzog'i — miltillash: javob kelguncha eski variant ko'rinadi. A/B test — foydalanuvchilarni barqaror guruhlarga bo'lish va natijani statistika bilan solishtirish.vazifalarda: o'z flaglari (+0.5 KB), A/B — yo'q.
Bu darsda
- Build paytidagi va ish paytidagi (runtime) flagni farqlaysiz;
useFlaghook'iniuseSyncExternalStorebilan yozasiz. - Masofaviy flagda miltillash qayerdan kelishini ko'rasiz va uch yechimini bilasiz.
- Foydalanuvchini hash bilan barqaror guruhga bo'lasiz: "10% ga yoqish" aynan qanday ishlaydi.
- A/B test natijasini o'qiysiz: konversiya, tasodif va "farq haqiqiymi?" savoli (z-test asoslari).
- GrowthBook, OpenFeature, PostHog, Unleash, LaunchDarkly orasida tanlaysiz;
vazifalardagi flaglarni o'qiysiz.
Oldin bilishingiz kerak: Xatolarni kuzatish, Analitika, useSyncExternalStore, Vite: env o'zgaruvchilar, Hash table ichidan.
1. Nega bu kerak?
O'tgan darsda xatolarni yig'ishni o'rgandik. Endi tasavvur qiling: «Bahor» ilovasiga yangi "tezkor to'lov" tugmasi qo'shildi. Juma kuni kechqurun deploy qilindi, shanba ertalab xato kuzatuvi to'lib ketdi — tugma ba'zi telefonlarda buyurtmani ikki marta yuboryapti.
Nima qilasiz? Eski yo'l: kodni qaytarish, build, test, deploy — yarim soat, dam olish kuni. Bu vaqt ichida mehmonlar ikki baravar pul to'layapti.
Feature flag bilan: tugma kodi if (flag("tezkor-tolov")) ichida. Sozlamada flag false qilinadi — bir daqiqada hamma joyda o'chdi. Kod joyida qoldi, dushanba kuni tinchgina tuzatiladi.
Hayotiy o'xshatish: uydagi elektr. Usta devorga sim tortadi va chiroqni o'rnatadi — bu deploy. Chiroqni esa siz kalit bilan yoqasiz va o'chirasiz. Chiroq miltillay boshlasa, ustani chaqirmaysiz — kalitni bosasiz. Flag — o'sha kalit.
Flag'ning yana uch ishi bor:
- Bosqichma-bosqich chiqarish (rollout): avval jamoaga, keyin 10% mehmonga, keyin hammaga.
- Sinovchilarga ochish: Sardor yangi menyuni production'da ko'radi, mehmonlar — hali yo'q.
- A/B test: yarim mehmonga yashil tugma, yarmiga sariq — qaysi biri ko'proq buyurtma oldi?
2. Eng oddiy flag: kodda va build'da
2.1 Flaglar ro'yxati
Flag — nom va standart qiymat. Ro'yxat bitta joyda bo'lsa, xato nomni TypeScript ushlaydi va eski flaglarni topish oson. vazifalar dagi flag-royxati.ts shunday boshlanadi:
export const FLAGLAR = {
/** 21/#04: statistikada teglar grafigi */
"teglar-grafigi": true,
} as const satisfies Record<string, boolean>;
export type FlagNomi = keyof typeof FLAGLAR;satisfies (TS: satisfies) — "hamma qiymat boolean bo'lsin" deb tekshiradi, lekin aniq kalitlarni yo'qotmaydi. FlagNomi — "teglar-grafigi". useFlag("teglar-grafik") (harf tushib qolgan) — tsc xatosi.
2.2 Build paytidagi flag: VITE_FLAGLAR
Eng oddiy "kalit" — env o'zgaruvchi: VITE_FLAGLAR="ai-yordamchi,-teglar-grafigi". Minus — o'chirish. Vite uni build paytida koddagi import.meta.env.VITE_FLAGLAR o'rniga satr qilib yozadi. Matnni o'qiydigan funksiya (kanondagi flagMatniniOqi, o'quv modeli):
const FLAGS = { "teglar-grafigi": true, "yangi-banner": false };
function isFlag(name) {
return Object.hasOwn(FLAGS, name); // faqat o'z kalitlari
}
function parseFlags(text) {
const result = {};
for (const part of text.split(",")) {
const clean = part.trim();
const name = clean.replace(/^-/, "");
if (isFlag(name)) {
result[name] = !clean.startsWith("-");
}
}
return result;
}
console.log(parseFlags("yangi-banner"));
console.log(parseFlags(" -teglar-grafigi , boshqa"));
console.log(parseFlags("__proto__,toString"));
console.log(isFlag("toString"), "toString" in FLAGS);Konsolda:
{ 'yangi-banner': true }
{ 'teglar-grafigi': false }
{}
false trueOxirgi qatorga qarang: "toString" in FLAGS — true! in prototip zanjiriga ham qaraydi, toString esa har obyektda bor (prototip zanjiri). Object.hasOwn faqat obyektning o'z kalitlarini ko'radi. Flag nomi tashqaridan (manzil, localStorage) kelganda bu farq muhim — «Hujumchi nigohi» da.
Build flagining afzalligi — tezlik va hajm: qiymat build'da ma'lum, flag o'chiq kod esa tree shaking bilan build'dan butunlay tushib qolishi mumkin. Kamchiligi — o'zgartirish uchun qayta build va deploy kerak. "Shanba ertalab" holatida bu yetmaydi.
3. Ish paytidagi flag: brauzerda
3.1 Uch manba, bitta qiymat
Kanon flag qiymatini uch joydan oladi, kuchlisi birinchi:
flowchart TD
A["useFlag('teglar-grafigi')"] --> B{"localStorage da?<br/>(Sozlamalar, ?flag=)"}
B -- ha --> R["shu qiymat"]
B -- yo'q --> C{"VITE_FLAGLAR da bormi?<br/>(build)"}
C -- ha --> R
C -- yo'q --> D["FLAGLAR dagi standart"]
D --> RNimaga qarang: eng "yaqin" sozlama ustun. Sardor o'z brauzerida flagni yoqsa — faqat unda yoqiladi. Build hamma uchun, standart — hech narsa berilmaganda.
3.2 Jonli: useFlag
Flag qiymati React state'i emas — u localStorage da, ya'ni React'dan tashqarida. Tashqi manbaga obuna bo'lish uchun React'da maxsus hook bor — useSyncExternalStore:
import { useSyncExternalStore } from "react";
const DEFAULTS = { "yangi-banner": false };
const KEY = "bahor:flaglar";
const listeners = new Set();
function saved() {
try {
return JSON.parse(localStorage.getItem(KEY) ?? "{}");
} catch {
return {}; // buzuq JSON — standartga
}
}
function flag(name) {
return saved()[name] ?? DEFAULTS[name];
}
function setFlag(name, value) {
const next = { ...saved(), [name]: value };
localStorage.setItem(KEY, JSON.stringify(next));
for (const l of listeners) l(); // obunachilarga xabar
}
function useFlag(name) {
return useSyncExternalStore(
(l) => {
listeners.add(l);
return () => listeners.delete(l);
},
() => flag(name),
);
}
function Banner() {
const on = useFlag("yangi-banner");
return on ? <p>Bugun: manti 10% arzon!</p> : null;
}
export default function App() {
return (
<>
<h3>«Bahor» menyusi</h3>
<Banner />
<button onClick={() => setFlag("yangi-banner", true)}>
Yangi bannerni yoqish
</button>
</>
);
}Ekranda:
«Bahor» menyusi
Bugun: manti 10% arzon!
Yangi bannerni yoqishsubscribe(birinchi argument) — React o'z tinglovchisini beradi, biz uni to'plamga qo'shamiz.setFlaghammasini chaqiradi — Reactflag(name)ni qayta o'qiydi va qiymat o'zgargan komponentlarni chizadi.getSnapshot(ikkinchisi) — joriy qiymat. U boolean qaytaradi — ReactObject.isbilan solishtirib, o'zgarmagan bo'lsa chizmaydi.- Qiymat sinxron o'qiladi: birinchi chizishdayoq to'g'ri. Bu muhim — keyingi bo'limda nega.
Natija oynasida localStorage — vaqtinchalik o'rinbosar, sahifa yangilansa tozalanadi. Haqiqiy brauzerda flag tab yopilsa ham qoladi. Boshqa tabda o'zgarsa — storage hodisasi keladi, kanon unga ham obuna bo'ladi («Vazifalar qadami»).
3.3 Sinovchi havola: ?flag=
Sardorga yangi funksiyani ko'rsatish uchun "DevTools'ni och, localStorage ga yoz" deyish noqulay. Kanon yo'li — havola: https://…/vazifalar/?flag=-teglar-grafigi. Ilova ochilganda flag parametrini o'qiydi, localStorage ga yozadi va manzildan olib tashlaydi (history.replaceState). Nega olib tashlaydi? Sardor havolani ulashsa yoki xatcho'pga qo'shsa, flag boshqalarga "yuqib" ketmasin.
Tekshirib ko'ring:
getSnapshothar chaqiruvda{ ...saved() }— yangi obyekt qaytarsa nima bo'ladi?
Javob
React har safar "qiymat o'zgardi" deb o'ylaydi (Object.is yangi obyektni eskisiga teng demaydi) va cheksiz qayta chizish boshlanadi. React buni sezib, ogohlantiradi: The result of getSnapshot should be cached to avoid an infinite loop. Shuning uchun getSnapshot oddiy qiymat (boolean, satr) yoki keshlangan obyekt qaytarishi kerak.
4. Masofaviy flag va miltillash
4.1 Nega server kerak
Brauzerdagi flagni faqat shu brauzer egasi o'zgartiradi. "Shanba ertalab hamma uchun o'chirish" uchun qiymat serverda bo'lishi kerak: ilova ochilganda /api/flaglar ni so'raydi. Bu — masofaviy flag (remote flag). Xizmatlar (GrowthBook, PostHog, LaunchDarkly) aynan shuni beradi: tugma bosilgan zahoti barcha foydalanuvchilarda flag o'zgaradi.
Lekin javob tarmoqdan keladi — vaqt oladi. Shu vaqt ichida ilova nimani ko'rsatadi?
4.2 Jonli: miltillash
Server javobini 200 ms kechikish bilan taqlid qilamiz (natija oynasida tarmoq yo'q):
import { useEffect, useState } from "react";
function fetchFlags() {
// o'quv modeli: haqiqiy fetch("/api/flaglar") o'rnida
return new Promise((resolve) =>
setTimeout(() => resolve({ "yangi-menyu": true }), 200),
);
}
export default function App() {
const [flags, setFlags] = useState({}); // javob kelguncha bo'sh
useEffect(() => {
fetchFlags().then(setFlags);
}, []);
const newMenu = flags["yangi-menyu"] ?? false;
useEffect(() => {
console.log("ekranda:", newMenu ? "yangi" : "eski");
}, [newMenu]);
return <h3>{newMenu ? "Yangi menyu" : "Eski menyu"}</h3>;
}Konsolda:
ekranda: eski
ekranda: yangiEkranda:
Yangi menyuMehmon avval eski menyuni ko'rdi, 200 ms dan keyin — yangisini. Bu — miltillash (flicker; 17-qismdagi vizual miltillashning "katta akasi"). Sekin internetda u bir-ikki soniya davom etadi. Yomoni faqat ko'rinishda emas: A/B testda mehmon ikkala variantni ham ko'rdi — natija buziladi.
4.3 Uch yechim
| Yechim | Qanday | Narxi |
|---|---|---|
| Kutish | flaglar kelguncha skeleton yoki «Yuklanmoqda…» | birinchi chizish kechikadi |
| Keshdan | oxirgi javob localStorage da; darhol shu, yangisi — keyingi tashrifda |
o'zgarish bir tashrif kech yetadi |
| Serverda | HTML bilan birga keladi (SSR yoki <script> ichida JSON) |
server render kerak (Next.js) |
Ko'p xizmatlarning SDK'larida bu sozlama bor: GrowthBook — loadFeatures({ timeout }) va kesh, LaunchDarkly — bootstrap (boshlang'ich qiymatlarni HTML bilan berish). Hammasi bir g'oya: birinchi kadr flag qiymatini allaqachon bilsin.
vazifalar dagi flaglar masofaviy emas — localStorage va build'dan, ya'ni sinxron. Kanon izohi: "Qiymat SINXRON o'qiladi (sahifa ochilganda) — birinchi kadrdayoq to'g'ri, «miltillash» yo'q". Masofaviy flag uchun server kerak — 25-qismdan (HTTP, Express va REST API) keyin.
Tekshirib ko'ring: «Keshdan» yo'lida Jasur aka flagni o'chirdi. Kecha kirgan mehmon bugun ilovani ochdi. U nimani ko'radi va qachon o'chgan variantga o'tadi?
Javob
Ochilganda — keshdagi eski qiymat (yoqilgan). Fonda yangi javob keladi va keshga yoziladi. Agar ilova uni darhol qo'llasa — miltillash qaytadi. Qo'llamasa — keyingi tashrifda o'chadi. Shuning uchun "kill switch" (avariya o'chirgichi) uchun bu yo'l sekin: bunday flaglarni o'zgarganda darhol qo'llash ma'qul — buzilgan tugmani miltillab yo'qotish, uni ishlatishdan yaxshiroq.
5. Foizli rollout: "10% ga yoqish" qanday ishlaydi
5.1 Tasodif emas — hash
"Yangi to'lov tugmasini mehmonlarning 10% iga ko'rsatamiz". Birinchi fikr — Math.random() < 0.1. Lekin har sahifa ochilganda boshqa son chiqadi: Aziz bir safar yangi tugmani ko'radi, keyin eskisini. Bu chalkash va A/B test uchun yaroqsiz.
Kerak bo'lgani — barqaror tanlov: bir foydalanuvchi har safar bir xil guruhda. Buning uchun foydalanuvchi id'sidan hash olinadi — matnni songa aylantiradigan funksiya: bir xil matn — doim bir xil son (hash table dagi g'oya). Sonni 100 ga bo'lgan qoldiq — "savat" (bucket) 0…99. 10% — savati 10 dan kichiklar.
// FNV-1a — oddiy va tez hash (kriptografik emas)
function hash(text) {
let h = 0x811c9dc5;
for (let i = 0; i < text.length; i++) {
h ^= text.charCodeAt(i);
h = Math.imul(h, 0x01000193);
}
return h >>> 0; // musbat 32 bitli son
}
const bucket = (flag, user) => hash(`${flag}:${user}`) % 100;
const inRollout = (flag, user, percent) =>
bucket(flag, user) < percent;
for (const user of ["aziz", "malika", "jasur", "sardor"]) {
console.log(user, bucket("yangi-banner", user));
}
let count = 0;
for (let i = 1; i <= 1000; i++) {
if (inRollout("yangi-banner", `u${i}`, 10)) count++;
}
console.log("1000 tadan 10% da:", count);
console.log("aziz 15% da:", inRollout("yangi-banner", "aziz", 15));Konsolda:
aziz 14
malika 27
jasur 23
sardor 49
1000 tadan 10% da: 105
aziz 15% da: trueUch narsa:
- Barqaror:
azizning savati — doim 14. Sahifani yangilasa ham, ertaga ham. - Taxminan 10%: 1000 dan 105 — aniq 100 emas, lekin yaqin. Ko'p foydalanuvchida foiz aniqlashadi.
- O'sish yo'qotmaydi: 10% → 15% ga ko'targaningizda savati 0–9 bo'lganlar qoladi, 10–14 qo'shiladi. Aziz (14) 10% da yo'q edi, 15% da kirdi. Hech kim yangi tugmani "yo'qotmaydi".
Hash'ga flag nomi ham qo'shilgan ("yangi-banner:aziz"). Nega? Faqat user bo'lsa, savati kichik foydalanuvchilar har yangi funksiyaning birinchi 10% ida bo'lib qolardi — doim "sinov quyoni".
5.2 Kim "foydalanuvchi"?
Hash uchun barqaror id kerak. Kirgan foydalanuvchida — uning id'si. Kirmagan mehmonda — birinchi tashrifda yaratilib localStorage yoki cookie'da saqlanadigan tasodifiy id (crypto.randomUUID()). Mehmon boshqa telefondan kirsa — boshqa id, boshqa guruh bo'lishi mumkin. Bu — anonim rollout'ning tabiiy chegarasi.
Xizmatlar bundan tashqari segmentlar beradi: "faqat Toshkent", "faqat ruscha interfeys", "faqat ichki jamoa". Bu qoidalar ham hash kabi serverda yoki SDK ichida hisoblanadi — backend tomoni — Feature flag'lar backend'da.
6. A/B test: qaysi variant yaxshi?
6.1 G'oya
A/B test — bitta savolga javob: "B variant A dan yaxshiroqmi, yoki bu tasodifmi?". Mehmonlar hash bilan ikki guruhga bo'linadi (50/50). Har guruh o'z variantini ko'radi. Bitta asosiy ko'rsatkich o'lchanadi — masalan, konversiya (conversion): ko'rganlardan necha foizi buyurtma berdi. O'lchash — analitika hodisalari bilan: "variant ko'rsatildi" va "buyurtma berildi".
6.2 Farq bor — lekin haqiqiymi?
«Bahor»da bir hafta test: A (eski tugma) va B (yangi tugma).
function compare(a, b) {
const pa = a.orders / a.visits;
const pb = b.orders / b.visits;
// umumiy konversiya (ikkala guruh birga)
const p = (a.orders + b.orders) / (a.visits + b.visits);
const se = Math.sqrt(p * (1 - p) * (1 / a.visits + 1 / b.visits));
const z = (pb - pa) / se;
const percent = (x) => (x * 100).toFixed(1) + "%";
const verdict =
Math.abs(z) > 1.96
? "— farq ishonchli"
: "— tasodif bo'lishi mumkin";
console.log(
`A ${percent(pa)}, B ${percent(pb)}, z = ${z.toFixed(2)}`,
verdict,
);
}
compare({ visits: 1000, orders: 50 }, { visits: 1000, orders: 62 });
compare(
{ visits: 10000, orders: 500 },
{ visits: 10000, orders: 620 },
);Konsolda:
A 5.0%, B 6.2%, z = 1.17 — tasodif bo'lishi mumkin
A 5.0%, B 6.2%, z = 3.69 — farq ishonchliIkkala testda foizlar bir xil: 5.0% va 6.2%. Lekin birinchisida bu tasodif bo'lishi mumkin, ikkinchisida — yo'q. Farq — mehmonlar sonida.
Tanga tashlashni eslang. 10 marta tashlasangiz, 6 marta gerb chiqishi g'alati emas. 10 000 marta tashlab, 6 000 marta gerb — tanga qiyshiq. Kichik guruhda tasodifiy tebranish katta, katta guruhda kichik.
z (z-ko'rsatkich) — farq "tasodifiy tebranish" dan necha baravar katta. se — o'sha tebranishning taxminiy kattaligi (standart xato). Birinchi testni qo'lda hisoblaymiz:
| Qadam | Hisob | Natija |
|---|---|---|
umumiy konversiya p |
(50 + 62) / (1000 + 1000) | 0.056 |
standart xato se |
√(0.056 × 0.944 × (1/1000 + 1/1000)) | ≈ 0.0103 |
| farq | 0.062 − 0.050 | 0.012 |
z |
0.012 / 0.0103 | ≈ 1.17 |
Farq (1.2 foiz punkti) tebranishdan atigi 1.17 baravar katta — ko'p emas. Ikkinchi testda mehmon 10 baravar ko'p, shuning uchun se √10 ≈ 3.2 baravar kichik va o'sha farq z = 3.69 beradi.
Odatiy chegara — 1.96. Uning ma'nosi aniq aytilishi kerak: agar A va B aslida bir xil bo'lsa, |z| > 1.96 ni tasodifan ko'rish ehtimoli 5% atrofida. Shuning uchun undan oshsa, "farq ishonchli (95% darajada)" deymiz. Bu "B yaxshiroq bo'lishi ehtimoli 95%" degani emas — keng tarqalgan xato talqin. Bu — ikki ulushni solishtirish uchun z-test, statistikaning eng sodda vositasi.
Nechta mehmon kerak? Testdan oldin hisoblanadi. Taxminiy formula (95% ishonch va 80% "sezish kuchi" uchun): har guruhga n ≈ 7.85 × (pA(1−pA) + pB(1−pB)) / (pB − pA)². 5% → 6.2% farqni sezish uchun: 7.85 × (0.0475 + 0.0582) / 0.000144 ≈ 5 760 mehmon har guruhga. Birinchi testdagi 1000 kishi bunga juda oz edi — natija "tasodif bo'lishi mumkin" bo'lgani shundan.
6.3 Uch tuzoq
- Erta qarash (peeking): har kuni natijaga qarab, birinchi marta
z > 1.96bo'lganda to'xtatish. Tasodif kunlar bo'yi tebranadi — bir kun albatta chegaradan oshadi. Davosi: test hajmini (necha mehmon, necha kun) oldindan belgilash va oxirigacha kutish. - To'liq hafta: dushanba va shanba mehmonlari boshqacha buyurtma qiladi. Test kamida bitta to'liq haftani qamrasin.
- Ko'p ko'rsatkich: 20 ta ko'rsatkichga qarasangiz, ulardan bittasi tasodifan "ishonchli" chiqadi (har biri 5% xato ehtimoli bilan). Asosiy ko'rsatkich bitta — testdan oldin tanlanadi.
Xizmatlar (GrowthBook, PostHog, Optimizely) bu hisoblarni o'zi qiladi — ba'zilari Bayes usulida, "B ning yaxshiroq bo'lish ehtimoli 94%" kabi. Frontend dasturchining vazifasi: guruhni barqaror bo'lish, variantni miltillamasdan ko'rsatish va "ko'rsatildi" hodisasini to'g'ri yuborish.
Tekshirib ko'ring: Test 3 kun davom etdi, B 20% yaxshiroq, z = 2.1. Mahsulot menejeri "g'alaba, hammaga yoqamiz" demoqda. Nima deysiz?
Javob
Uchta savol: test hajmi oldindan belgilanganmidi (yoki bu "erta qarash")? 3 kun — to'liq hafta emas, dam olish kunlari bormidi? Bu asosiy ko'rsatkichmi, yoki ko'p ko'rsatkichdan biri? Agar hajm belgilanmagan bo'lsa — rejadagi muddatgacha davom ettirish kerak. z = 2.1 chegaraga juda yaqin: erta qarashda bunday "g'alaba" ko'pincha keyin yo'qoladi.
7. Tayyor xizmatlar va OpenFeature
7.1 Kim nima beradi
| Xizmat | Nima | Eslatma |
|---|---|---|
| GrowthBook | flag + A/B, ochiq kod, o'z serveringizda ham | statistika ichida |
| PostHog | analitika + flag + A/B + replay bitta joyda | posthog-js og'ir (pastda) |
| Unleash | flag, ochiq kod, o'z serveringizda | A/B — oddiyroq |
| LaunchDarkly | korporativ, eng to'liq | pullik |
| OpenFeature | xizmat emas — standart | provayderni almashtirish oson |
OpenFeature — CNCF loyihasi: flag o'qish uchun yagona API. Kodda doim useBooleanFlagValue("yangi-grafik", false) yoziladi, orqasida esa provayder turadi — GrowthBook, LaunchDarkly, Unleash yoki o'zingizniki. Xizmatni almashtirsangiz — komponentlar o'zgarmaydi, faqat provayder. Bu vendor lock-in (bitta sotuvchiga bog'lanib qolish) dan himoya.
7.2 Biz sinab ko'rdik: GrowthBook va OpenFeature
Kanon ikkalasini eng kichik misolda, server va kalitsiz — flaglar kod ichida — o'lchadi. GrowthBook 1.8.0 (tajriba/kutubxonalar/src/k/flag-growthbook.tsx, qatorlarga bo'lindi):
import {
GrowthBook,
GrowthBookProvider,
useFeatureIsOn,
} from "@growthbook/growthbook-react";
const gb = new GrowthBook({
features: { "yangi-grafik": { defaultValue: true } },
attributes: { id: "aziz" }, // hash uchun foydalanuvchi
});
function A() {
const on = useFeatureIsOn("yangi-grafik");
return <p>{on ? "yoqilgan" : "o'chiq"}</p>;
}
// <GrowthBookProvider growthbook={gb}><A /></GrowthBookProvider>Haqiqiy loyihada features kodda emas — apiHost va clientKey bilan GrowthBook serveridan keladi (hisob yoki o'z serveringiz kerak, biz yaratmadik). attributes.id — «Foizli rollout» dagi hash shu id'dan olinadi.
OpenFeature (web-sdk 1.10.0 + react-sdk 1.4.1), xotiradagi provayder bilan:
import {
InMemoryProvider,
OpenFeature,
OpenFeatureProvider,
useBooleanFlagValue,
} from "@openfeature/react-sdk";
void OpenFeature.setProviderAndWait(
new InMemoryProvider({
"yangi-grafik": {
variants: { on: true, off: false },
defaultVariant: "on",
disabled: false,
},
}),
);
function A() {
const on = useBooleanFlagValue("yangi-grafik", false);
return <p>{on ? "yoqilgan" : "o'chiq"}</p>;
}
// <OpenFeatureProvider><A /></OpenFeatureProvider>InMemoryProvider o'rniga GrowthBookProvider yoki UnleashProvider qo'yiladi — A komponenti o'zgarmaydi. useBooleanFlagValue ning ikkinchi argumenti (false) — provayder javob bermasa ishlatiladigan zaxira.
Natija (kutubxonalar-21.txt):
flag-growthbook JS 82144 (+ 14.1 KB) CSS 0 | CSP 0 | xato: yo'q | ochiq: tashqi yo'q
flag-openfeature JS 76518 (+ 8.6 KB) CSS 0 | CSP 0 | xato: yo'q | ochiq: tashqi yo'qIkkalasi vazifalar siyosati va Trusted Types ostida muammosiz ochildi. Masofaviy rejimda esa ular serverga so'rov yuboradi — connect-src ga xizmat hosti qo'shiladi. PostHog flaglari posthog-js ichida keladi — kanon analitika darsida uni o'lchadi: +98.3 KB va Trusted Types ostida ochilmadi.
- vazifalar: o'z flaglaribirinchi yuklash farqi, holat 19 → 200,5 KB
- OpenFeature (web + react)8,6 KB
- GrowthBook 1.8.014,1 KB
- posthog-js 1.438.7analitika + flag + replay; TT'da ochilmadi98,3 KB
Manba: O'lchandi: kanon, Vite 8.3.3, React 19.3.0, gzip -9, natijalar/tekshiruv-19.txt va -20.txt, natijalar/tajriba/kutubxonalar-21.txt, 2026-10-17
O'z flaglarimiz arzon, lekin ularda server, segment va statistika yo'q. Jamoa kattalashib, haftasiga o'nlab flag paydo bo'lganda xizmat o'zini oqlaydi.
8. Vazifalar qadami: flaglar (holat 20)
8.1 Branch va fayllar
- Branch:
feat/flaglar
| Fayl | O'zgarish |
|---|---|
src/flag-royxati.ts |
yangi: ro'yxat, flagmi, flagMatniniOqi (sof — node:test) |
src/flaglar.ts |
yangi: uch manba, useFlag, flagniQoy, ?flag= |
komponentlar/TajribaviySozlamalar.tsx |
yangi: Sozlamalar'da katakchalar |
BatafsilStatistika.tsx |
teglar grafigi — flag ortida |
main.tsx, SozlamalarSahifasi.tsx, vite-env.d.ts |
ulash |
tekshiruv/flaglar.test.js |
yangi: 3 test |
8.2 flaglar.ts
Fayl boshidagi izoh kanon qarorini aytadi (izoh qatorlari telefonga sig'ishi uchun qayta bo'lingan):
// Qiymat SINXRON o'qiladi (sahifa ochilganda) — birinchi
// kadrdayoq to'g'ri, "miltillash" yo'q. Masofaviy flag
// (GrowthBook +14.1 KB, OpenFeature +8.6; tajriba) — server kerak
// va javob kelguncha qaysi variantni ko'rsatish muammosi bor:
// 25-qismdan keyin. A/B test (foydalanuvchilarni guruhlash,
// statistika) — bu yerda YO'Q (TEXNIK-QARZ)Saqlangan qiymatlar — tashqi ma'lumot, shuning uchun filtr bilan o'qiladi:
function saqlangan(): Partial<Record<FlagNomi, boolean>> {
try {
const j: unknown = JSON.parse(localStorage.getItem(KALIT) ?? "{}");
if (j === null || typeof j !== "object") {
return {};
}
return Object.fromEntries(
Object.entries(j).filter(
([k, v]) => flagmi(k) && typeof v === "boolean",
),
);
} catch {
return {};
}
}
const muhitdan = flagMatniniOqi(import.meta.env.VITE_FLAGLAR ?? "");
/** Joriy qiymat: localStorage > VITE_FLAGLAR > standart */
function flag(nom: FlagNomi): boolean {
return saqlangan()[nom] ?? muhitdan[nom] ?? FLAGLAR[nom];
}j: unknown— JSON.parse natijasiga ishonilmaydi:null, massiv, satr bo'lishi mumkin.flagmi(k) && typeof v === "boolean"— noma'lum nom va"true"satri kabi noto'g'ri qiymatlar tashlanadi.??zanjiri — «Uch manba, bitta qiymat» dagi diagramma bitta qatorda.
useFlag — «Jonli: useFlag» dagi bilan bir xil, useSyncExternalStore. Qo'shimchasi — boshqa tablar:
addEventListener("storage", (event) => {
if (event.key === KALIT) {
for (const t of tinglovchilar) {
t();
}
}
});Sinovchi havola — router manzilni o'qishidan oldin (main.tsx):
export function manzildagiFlaglar(): void {
const url = new URL(location.href);
const matn = url.searchParams.get("flag");
if (matn === null) {
return;
}
for (const [nom, qiymat] of Object.entries(flagMatniniOqi(matn))) {
if (flagmi(nom)) {
flagniQoy(nom, qiymat);
}
}
url.searchParams.delete("flag");
history.replaceState(history.state, "", url);
}history.replaceState — 19-qismda ko'rgan: tarixga yangi yozuv qo'shmaydi, joriysini almashtiradi. Boshqa parametrlar (?filtr=faol) qoladi.
8.3 Flag ortidagi grafik va Sozlamalar
+ // 21/#20: grafik — flag ortida
+ // (o'chirish — deploysiz, Sozlamalar'da)
+ const grafik = useFlag("teglar-grafigi");
...
- {s.teglar.length > 0 && <TeglarGrafigi teglar={s.teglar} />}
+ {grafik && s.teglar.length > 0 && (
+ <TeglarGrafigi teglar={s.teglar} />
+ )}Grafiklar I da yozilgan o'z SVG grafigi endi flag ortida. Sozlamalarda — details tegi ichida «Tajribaviy imkoniyatlar» (oddiy foydalanuvchiga xalaqit bermasin):
function FlagKatagi({ nom }: { nom: FlagNomi }) {
const yoqilgan = useFlag(nom);
return (
<p>
<input
type="checkbox"
id={`flag-${nom}`}
checked={yoqilgan}
onChange={(event) => flagniQoy(nom, event.target.checked)}
/>{" "}
<label htmlFor={`flag-${nom}`}>{NOMLAR[nom]}</label>
</p>
);
}8.4 Natija
Kanon tekshiruvi (tekshiruv-20.txt): node:test 241 (+3), Vitest 65, lint, format, tip, knip — toza. Birinchi yuklash JS: 223 376 → 223 883 bayt — +0.5 KB. axe — 0, ko'rinish solishtiruvi: 240/240 element, farq 0. Brauzer (natija-21-20.txt, Chrome 155):
--- 21/#20 feature flag
✅ standart: grafik yoqilgan: true
✅ ?flag=-…: manzildan olindi (filtr qoldi), localStorage: ["/?filtr=faol","{\"teglar-grafigi\":false}"]
✅ grafik o'chdi (birinchi kadrdayoq — sinxron): false
✅ tajribaviy: katak holati: false
✅ qayta yoqildi — grafik bor: true
✅ flag: CSP buzilishi yo'q: []
✅ noma'lum flag: saqlanmadi, manzildan olindi: ["/",null]Uchinchi qator — darsning asosiy g'oyasi: ?flag=-teglar-grafigi bilan ochilgan sahifada grafik birinchi kadrdayoq yo'q, miltillamadi. Oxirgi qator — ?flag=boshqa (ro'yxatda yo'q) saqlanmadi.
Halol chegara (TEXNIK-QARZ 65): masofaviy flag, foizli rollout va A/B test — yo'q. Ular server talab qiladi.
8.5 Commit
git add -A
git commit -m "feat: feature flag'lar — standart, VITE_FLAGLAR, ?flag= sinovchi havola, Sozlamalar"9. Hujumchi nigohi
1. Flag — xavfsizlik emas. Brauzerdagi har flagni foydalanuvchi o'zgartira oladi: DevTools'da localStorage, ?flag= havola, hatto build ichidagi FLAGLAR obyekti. Shuning uchun:
// ❌ "admin panel faqat flag yoqilganlarga"
if (useFlag("admin-panel")) return <AdminPanel />;Hujumchi ?flag=admin-panel bilan panelni ochadi. Panel ko'rinishi — kichik muammo, agar server har so'rovda huquqni tekshirsa. Tekshirmasa — katta. Qoida: flag ko'rinishni boshqaradi, ruxsatni — faqat server (28-qism).
2. Sinovchi havola — begona qo'lda. ?flag= ni hujumchi ham yuborishi mumkin: "Bu havolani oching, yangi chegirma bor!". Sizning ilovangizda chala, buzuq funksiya yoqiladi. Kanon himoyasi: faqat ro'yxatdagi flaglar (flagmi), qiymat faqat boolean. Xavfli yoki chala funksiyani ?flag= bilan ochiladigan qilmang.
3. Prototip kalitlari. ?flag=__proto__ yoki toString — in yoki obj[nom] bilan tekshirilsa, "bor" deb topiladi. Object.hasOwn — kanon testi aynan shuni tekshiradi (flagmi("__proto__") === false).
4. Sir sizib chiqishi. Flag nomlari va standart qiymatlari build'da — har kim o'qiydi. "yangi-narx-siyosati-2027" kabi nom raqobatchiga reja haqida aytadi. Masofaviy xizmatlarda esa client key brauzerda (bu normal), server/admin key — hech qachon.
10. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Eski variant bir lahza ko'rinadi | masofaviy flag, javob kelguncha standart | kutish, kesh yoki server render |
| Aziz bir safar yangi, bir safar eski variantni ko'radi | Math.random() |
id'dan hash, flag nomi bilan |
The result of getSnapshot should be cached… |
getSnapshot har safar yangi obyekt |
oddiy qiymat qaytaring |
?flag=toString "yoqildi" |
in yoki obj[nom] |
Object.hasOwn |
| Flag o'zgardi, boshqa tab eski | storage hodisasi tinglanmagan |
addEventListener("storage", …) |
| Kodda 40 ta flag, yarmi kimga kerakligi noma'lum | eski flaglar tozalanmagan | flag'ga egasi va muddati; 100% bo'lgach — kod va flag o'chiriladi |
| A/B "g'alaba" keyin yo'qoldi | erta qarash, kam mehmon | hajmni oldindan belgilang |
11. Mashqlar
Mashqlar kurs/mashqlar/21/20-feature-flag/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.
1-mashq (oson): bo'sh joylarni to'ldiring
- Tashqi manbaga (localStorage) obuna bo'ladigan React hook'i:
- Masofaviy flag javobi kelguncha eski variant ko'rinishi:
- Obyektning faqat o'z kalitlarini tekshiradigan metod:
Object. - Foydalanuvchini barqaror guruhga bo'lish uchun
Math.random()o'rniga: - Ikki ulushni solishtirishda odatiy z chegarasi (95%):
Yechim
useSyncExternalStore — React'dan tashqaridagi qiymatga obuna. Miltillash (flicker) — birinchi kadrda flag qiymati noma'lum. Object.hasOwn — prototip kalitlarini (toString, __proto__) ko'rmaydi. Hash — bir xil id doim bir xil savat. 1.96 — 95% ishonch chegarasi: farq bo'lmasa, undan oshish ehtimoli ≈ 5%.
2-mashq (o'rta): variant tanlash
«Foizli rollout» dagi hash dan foydalanib variant(test, user, weights) funksiyasini yozing. weights — variant nomlari va foizlari: { A: 50, B: 30, C: 20 }. Savat 0–49 → A, 50–79 → B, 80–99 → C. Keyin "tugma-rangi" testi uchun 1000 foydalanuvchini (u1 … u1000) taqsimlang va har variant sonini chiqaring. Oxirida aziz uchun variantni ikki marta so'rab, bir xil ekanini ko'rsating.
Ishora: Object.entries(weights) tartib bilan yuradi; har variantda chegarani (limit) foizga oshiring va bucket < limit bo'lgan birinchisini qaytaring (Object.entries).
Yechim
function hash(text) {
let h = 0x811c9dc5;
for (let i = 0; i < text.length; i++) {
h ^= text.charCodeAt(i);
h = Math.imul(h, 0x01000193);
}
return h >>> 0;
}
function variant(test, user, weights) {
const bucket = hash(`${test}:${user}`) % 100;
let limit = 0;
for (const [name, percent] of Object.entries(weights)) {
limit += percent;
if (bucket < limit) return name;
}
return null; // foizlar yig'indisi 100 dan kam bo'lsa
}
const weights = { A: 50, B: 30, C: 20 };
const counts = { A: 0, B: 0, C: 0 };
for (let i = 1; i <= 1000; i++) {
counts[variant("tugma-rangi", `u${i}`, weights)]++;
}
console.log(counts);
console.log(
variant("tugma-rangi", "aziz", weights),
variant("tugma-rangi", "aziz", weights),
);Konsolda:
{ A: 500, B: 307, C: 193 }
C C50/30/20 ga yaqin taqsimot. aziz doim bir xil variantda. return null — foizlar yig'indisi 100 dan kam bo'lsa (masalan { A: 10, B: 10 } — 20% testda, qolganlari testdan tashqarida): bu holat ham haqiqiy testlarda ko'p uchraydi.
3-mashq (qiyin): miltillashsiz masofaviy flag
«Jonli: miltillash» dagi misolni tuzating — «Keshdan» yo'li bilan:
- Ilova ochilganda flag
localStoragedagi oxirgi javobdan ("bahor:flaglar-kesh") sinxron olinsin. Kesh bo'lmasa — standart (false). - Server javobi (200 ms) kelganda keshga yozilsin, lekin ekranda joriy tashrifda o'zgarmasin.
- «Qayta ochish» tugmasi yangi tashrifni taqlid qilsin: komponentni
keybilan qayta yaratadi (key bilan reset).
Sinov: ilk ochilishda «Eski menyu», server javobidan keyin ham «Eski menyu» (konsolda kesh yangilandi), «Qayta ochish» dan keyin — «Yangi menyu».
Yechim
import { useEffect, useState } from "react";
const CACHE = "bahor:flaglar-kesh";
function fetchFlags() {
return new Promise((resolve) =>
setTimeout(() => resolve({ "yangi-menyu": true }), 200),
);
}
function cachedFlags() {
try {
return JSON.parse(localStorage.getItem(CACHE) ?? "{}");
} catch {
return {};
}
}
function Menu() {
// useState(funksiya) — faqat birinchi chizishda o'qiladi
const [flags] = useState(cachedFlags);
useEffect(() => {
fetchFlags().then((fresh) => {
localStorage.setItem(CACHE, JSON.stringify(fresh));
console.log("kesh yangilandi");
});
}, []);
const newMenu = flags["yangi-menyu"] ?? false;
return <h3>{newMenu ? "Yangi menyu" : "Eski menyu"}</h3>;
}
export default function App() {
const [visit, setVisit] = useState(1);
return (
<>
<Menu key={visit} />
<button onClick={() => setVisit((v) => v + 1)}>
Qayta ochish
</button>
</>
);
}Konsolda:
kesh yangilandi
kesh yangilandiEkranda:
Yangi menyu
Qayta ochishBirinchi tashrifda ekran bir marta chizildi va o'zgarmadi — miltillash yo'q. Server javobi faqat keshga tushdi. «Qayta ochish» Menu ni yangi key bilan yaratdi — useState(cachedFlags) yana o'qidi va endi keshda true bor. Ikkinchi kesh yangilandi — yangi tashrifdagi fon so'rovi. Narxi: o'zgarish bir tashrif kech yetadi.
4-mashq: Vazifalar qadami
feat/flaglar branch'ida «Vazifalar qadami» ni bajaring: flag-royxati.ts, flaglar.ts, TajribaviySozlamalar.tsx, statistika grafigi flag ortida, main.tsx da manzildagiFlaglar(), vite-env.d.ts da VITE_FLAGLAR, flaglar.test.js. npm run check toza bo'lsin. Keyin:
npm run devda/?flag=-teglar-grafigi&filtr=faolni oching. Manzil satrida nima qoldi? Statistikada grafik bormi?- Ikkinchi tabda Sozlamalar → «Tajribaviy imkoniyatlar» → katakchani yoqing. Birinchi tabdagi statistika nima qildi?
VITE_FLAGLAR=-teglar-grafigi npm run build, keyinnpm run preview. Grafik bormi? Sozlamalarda katak yoqilsa-chi?
Yechim
/?filtr=faol—flagolib tashlandi, filtr qoldi. Grafik yo'q va u birinchi kadrda ham ko'rinmadi.- Grafik qaytdi, sahifa yangilanmasdan:
storagehodisasi → tinglovchilar →useSyncExternalStoreqayta o'qidi. - Build'da o'chiq — grafik yo'q. Katak yoqilsa — paydo bo'ladi:
localStoragebuild qiymatidan kuchli («Uch manba, bitta qiymat»).
12. Real ishda
- Trunk-based development: ko'p jamoalar chala funksiyani ham
mainga qo'shadi — flag o'chiq holda. Uzun yashaydigan branch'lar va katta merge'lar o'rniga — flag (jamoa workflowlari). - Kill switch: to'lov, xarita, AI kabi tashqi xizmatga bog'liq har funksiyada avariya o'chirgichi. Xizmat yiqilsa — funksiya yashiriladi, ilova ishlayveradi.
- Flag qarzi: har flag'ning egasi va muddati bo'lsin. 100% ga chiqqan flag — 2–4 haftada koddan o'chiriladi. Aks holda kod
iflar o'rmoniga aylanadi. - Intervyu savollari: "Feature flag va branch farqi?", "Miltillashni qanday oldini olasiz?", "Foizli rollout'da foydalanuvchi qanday barqaror guruhda qoladi?", "A/B testda erta qarash nega xavfli?", "Flag bilan admin panelni yashirish yetarlimi?".
Xulosa
- Feature flag — deploy va yoqishni ajratadi: kod keladi, funksiya kalit bilan yoqiladi; buzilsa — deploysiz o'chiriladi.
- Manbalar: build (
VITE_FLAGLAR, arzon, lekin deploy kerak), brauzer (localStorage,?flag=), server (masofaviy, hamma uchun darhol);useFlag—useSyncExternalStorebilan. - Masofaviy flag tuzog'i — miltillash: kutish, keshdan yoki server render; birinchi kadr qiymatni bilsin.
- Foizli rollout va A/B —
hash(flag:user) % 100: barqaror va o'sganda hech kim chiqib ketmaydi; natija — z-test, hajm oldindan, to'liq hafta, bitta asosiy ko'rsatkich. - Xizmatlar: GrowthBook (+14.1 KB), OpenFeature (+8.6, standart API), PostHog (+98.3, TT'da ochilmadi);
vazifalar— o'z flaglari (+0.5 KB), A/B yo'q. - Flag ko'rinishni boshqaradi, ruxsatni — server.
Keyingi dars: To'lov UI: Click, Payme va Uzum — o'zbek to'lov tizimlarining checkout sahifasiga yo'naltirish, qaytish sahifasi va holatni so'rab turish; nega summa faqat serverda hisoblanadi.
Manbalar
- OpenFeature: "Specification", "React SDK" (
useBooleanFlagValue,InMemoryProvider) — openfeature.dev - GrowthBook: "React SDK" (
useFeatureIsOn,attributes,loadFeatures) — docs.growthbook.io - Unleash, LaunchDarkly ("bootstrapping"), PostHog ("Feature flags", "Experiments") — rasmiy hujjatlar
- React:
useSyncExternalStore— react.dev/reference/react/useSyncExternalStore - FNV hash — "FNV-1a" (IETF draft-eastlake-fnv)
- Kanon:
vazifalarholat 20 (src/flaglar.ts,src/flag-royxati.ts,TajribaviySozlamalar.tsx),natijalar/tekshiruv-20.txt,brauzer-tekshiruv/natija-21-20.txt,natijalar/tajriba/kutubxonalar-21.txt, TEXNIK-QARZ 65 (2026-10-17)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!