Tests A/B en C++ : comment ne pas casser la production et trouver la vérité

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Pourquoi les tests A/B ne concernent pas que le web et le marketing

\n

Quand on entend « test A/B », on pense immédiatement aux landing pages, aux boutons « Acheter » et aux conversions dans Google Analytics. Mais que faire si votre produit n'est pas un site web, mais un serveur à forte charge en C++, un système embarqué ou un moteur de jeu ? Peut-on appliquer les tests A/B ici ? Absolument ! Et ce n'est pas qu'un terme à la mode, mais un véritable outil pour prendre des décisions basées sur les données, et non sur l'intuition.

\n

En C++, les tests A/B aident à répondre à des questions comme : « Le nouvel algorithme de tri est-il vraiment plus rapide ? », « Devrais-je introduire un pool de threads ? », « Le nouveau format de sérialisation améliorera-t-il le temps de réponse de l'API ? ». Sans test A/B, vous risquez d'implémenter une « optimisation » qui se révèle en réalité être une régression. Dans cet article, nous allons voir comment intégrer correctement un mécanisme d'expérimentation dans le code C++, sans le transformer en un plat de spaghettis de if-else.

\n\n

Architecture d'un test A/B en C++ : sans douleur ni souffrance

\n

Le principal danger des tests A/B en C++ est de polluer le code de production avec des opérateurs conditionnels. Pour éviter cela, il faut séparer la logique de l'expérience de la logique métier. Nous utilisons le pattern Feature Flag (drapeau de fonctionnalité) et la stratégie. Examinons l'architecture de base.

\n\n

Étape 1 : Configuration centralisée

\n

Tous les drapeaux et paramètres des expériences sont stockés au même endroit. Cela peut être un fichier JSON, une base de données ou un serveur de configuration distant. Pour simplifier, prenons du JSON.

\n
// experiment_config.json\n{\n  "experiments": {\n    "sort_algorithm_v2": {\n      "enabled": true,\n      "variants": {\n        "control": 50,\n        "treatment": 50\n      }\n    },\n    "memory_pool_size": {\n      "enabled": true,\n      "variants": {\n        "default": 33,\n        "pool_256": 33,\n        "pool_512": 34\n      }\n    }\n  }\n}
\n

La répartition du trafic (traffic split) est définie en pourcentages. En C++, nous chargeons cette configuration une fois au démarrage de l'application.

\n\n

Étape 2 : Classe de gestion des expériences

\n

Créons une classe thread-safe ExperimentManager qui, à partir d'une clé (par exemple, un ID utilisateur ou de transaction), détermine dans quel groupe l'objet est tombé.

\n
#include <string>\n#include <unordered_map>\n#include <random>\n#include <shared_mutex>\n#include <nlohmann/json.hpp> // pour l'analyse JSON\n\nclass ExperimentManager {\npublic:\n    explicit ExperimentManager(const std::string& config_path);\n    \n    // Retourne le nom de la variante pour l'expérience et la clé données\n    std::string getVariant(const std::string& experiment_name, \n                           const std::string& user_id) const;\n\nprivate:\n    struct Experiment {\n        bool enabled;\n        std::vector<std::pair<std::string, int>> variants; // nom, poids\n        int total_weight;\n    };\n    \n    std::unordered_map<std::string, Experiment> experiments_;\n    mutable std::shared_mutex mutex_;\n    \n    // Hachage déterministe pour une distribution stable\n    size_t hashAndMod(const std::string& key, int mod) const;\n};\n\n// Implémentation (simplifiée)\nstd::string ExperimentManager::getVariant(const std::string& exp_name,\n                                          const std::string& user_id) const {\n    std::shared_lock lock(mutex_);\n    auto it = experiments_.find(exp_name);\n    if (it == experiments_.end() || !it->second.enabled) {\n        return "control"; // fallback\n    }\n    const auto& exp = it->second;\n    size_t bucket = hashAndMod(user_id, exp.total_weight);\n    int cumulative = 0;\n    for (const auto& [name, weight] : exp.variants) {\n        cumulative += weight;\n        if (bucket < cumulative) {\n            return name;\n        }\n    }\n    return "control";\n}
\n

Important : Nous utilisons un hachage déterministe — le même utilisateur tombe toujours dans le même groupe. C'est crucial pour la validité du test.

\n\n

Exemple pratique : Test A/B d'un nouvel algo

Blogs

Book Recommendations