Жизнь до A/B тестов: почему интуиция подводит
Вы когда-нибудь спорили с дизайнером, какая кнопка лучше — красная или синяя? Или с менеджером, какой заголовок привлечет больше кликов? Раньше такие споры решались громким голосом или положением в иерархии. Сегодня у нас есть A/B тестирование в TypeScript — научный метод, который превращает догадки в данные.
Представьте: вы запускаете интернет-магазин. Кажется, что если добавить отзывы на карточку товара, продажи вырастут. А если убрать карусель изображений и оставить одно большое фото? Без сплит-тестирования вы никогда не узнаете правду. Именно здесь на помощь приходит TypeScript — строгая типизация и современные инструменты делают A/B тесты надежными и удобными.
Что такое A/B тестирование простыми словами
A/B тест (он же сплит-тест) — это эксперимент, в котором двум группам пользователей показывают разные версии одного элемента. Контрольная группа (A) видит текущую версию, экспериментальная (B) — измененную. Затем мы сравниваем метрики: конверсию, время на сайте, клики.
Почему это важно? Потому что даже опытные продакты ошибаются в 70% случаев. Тестирование гипотез с помощью A/B тестов позволяет принимать решения на основе данных, а не интуиции. TypeScript добавляет к этому безопасность типов и удобство разработки.
Как работает A/B тестирование в TypeScript приложениях
В TypeScript приложениях A/B тестирование обычно реализуется через:
- Распределение пользователей — какой вариант показать конкретному пользователю
- Трекинг событий — сбор данных о действиях пользователя
- Анализ результатов — расчет статистической значимости
Рассмотрим каждый этап на примере. Допустим, мы хотим протестировать новый заголовок на странице входа. Вот как это выглядит в коде:
// Определяем типы для эксперимента
type ExperimentVariant = 'control' | 'variant';
type ExperimentConfig = {
id: string;
variants: ExperimentVariant[];
weights: number[]; // сумма должна быть 100
};
// Конфигурация A/B теста
const headlineExperiment: ExperimentConfig = {
id: 'headline_test_2024',
variants: ['control', 'variant'],
weights: [50, 50] // 50% видят старый заголовок, 50% - новый
};
// Функция рандомного распределения
function assignVariant(config: ExperimentConfig): ExperimentVariant {
const random = Math.random() * 100;
let cumulativeWeight = 0;
for (let i = 0; i < config.variants.length; i++) {
cumulativeWeight += config.weights[i];
if (random < cumulativeWeight) {
return config.variants[i];
}
}
return config.variants[0]; // fallback
}
// Использование
const userVariant = assignVariant(headlineExperiment);
console.log(`Пользователь получит вариант: ${userVariant}`);
Сегментация пользователей: чтобы не было путаницы
Важно, чтобы один пользователь всегда видел один и тот же вариант. Иначе данные будут испорчены. Для этого используем идентификатор пользователя или куки:
function getStableVariant(
userId: string,
config: ExperimentConfig
): ExperimentVariant {
// Используем хеш от userId для детерминированного распределения
const hash = simpleHash(userId + config.id);
const normalizedHash = hash % 100;
let cumulativeWeight = 0;
for (let i = 0; i < config.variants.length; i++) {
cumulativeWeight += config.weights[i];
if (normalizedHash < cumulativeWeight) {
return config.variants[i];
}
}
return config.variants[0];
}
function simpleHash(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash |= 0; // Convert to 32bit integer
}
return Math.abs(hash);
}
Трекинг событий: собираем данные
Без данных A/B тест — просто игра. Нужно отправлять события в аналитику. TypeScript помогает строго типизировать эти события:
// Типизируем события для трекинга
type ExperimentEvent = {
experimentId: string;
variant: ExperimentVariant;
eventType: 'impression' | 'conversion' | 'click';
userId: string;
timestamp: number;
metadata?: Record<string, string | number>;
};
// Функция отправки события
async function trackExperimentEvent(
event: ExperimentEvent
): Promise<void> {
try {
await fetch('/api/track', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(event)
});
} catch (error) {
console.error('Ошибка трекинга:', error);
// Никогда не ломаем пользовательский опыт
}
}
// Пример использования
async function onPageLoad(userId: string, variant: ExperimentVariant) {
await trackExperimentEvent({
experimentId: 'headline_test_2024',
variant,
eventType: 'impression',
userId,
timestamp: Date.now()
});
}
Библиотеки для A/B тестирования в TypeScript
Писать всё с нуля не обязательно. Вот несколько библиотек для A/B тестов, которые отлично работают с TypeScript:
- GrowthBook — опенсорс платформа с хорошей типизацией
- Flagsmith — feature flags + A/B тесты
- Split — мощная платформа для экспериментов
- LaunchDarkly — enterprise решение с SDK для TS
Пример интеграции с GrowthBook:
import { GrowthBook } from '@growthbook/growthbook';
const gb = new GrowthBook({
apiHost: 'https://cdn.growthbook.io',
clientKey: 'sdk-abc123',
// Передаем атрибуты пользователя
attributes: {
id: userId,
country: 'RU',
beta: true
}
});
// Получаем значение для эксперимента
const headline = gb.getFeatureValue('new-headline', 'Старый заголовок');
console.log('Заголовок:', headline);
Расчет статистической значимости: не делайте выводов слишком рано
Самая частая ошибка — остановить A/B тест через час, потому что вариант B показывает +10%. Это может быть просто случайность. Нужно дождаться статистической значимости.
Вот простая функция для расчета p-value (уровня значимости) с использованием z-теста:
function calculatePValue(
controlConversions: number,
controlTotal: number,
variantConversions: number,
variantTotal: number
): number {
const p1 = controlConversions / controlTotal;
const p2 = variantConversions / variantTotal;
const pPooled = (controlConversions + variantConversions) /
(controlTotal + variantTotal);
const se = Math.sqrt(
pPooled * (1 - pPooled) * (