Ce que c'est et pourquoi ça compte
Un feature flag, en français un interrupteur, est un oui ou non que l'app lit pendant qu'elle tourne. S'il est allumé, l'utilisateur voit la nouvelle fonction. S'il est éteint, il voit l'ancienne. Le code des deux chemins est déjà dans l'app : l'interrupteur choisit seulement lequel utiliser.
Le problème qu'il résout est simple. Vous publiez une fonction, quelque chose casse, et pour la retirer il faut une autre modification, un autre déploiement, peut-être attendre la validation du store. Avec l'interrupteur, vous changez une valeur et la fonction disparaît. Ensuite, vous la corrigez au calme.
Quand on construit avec Claude Code, ça compte encore plus : vous publiez souvent, et vous n'avez pas toujours lu chaque ligne. Pouvoir éteindre ce que vous venez de publier, sans toucher au code, enlève beaucoup de stress le jour de la sortie.
1. Où je les garde
J'utilise une table « flags » dans la base de données que l'app utilise déjà : une ligne par interrupteur, avec nom, allumé oui ou non, pourcentage et liste d'utilisateurs. L'app la lit au démarrage et de temps en temps pendant qu'elle est ouverte. Je change une valeur et la fonction s'allume ou s'éteint sans nouvelle version. Ceux qui ont déjà l'app ouverte reçoivent le changement à la lecture suivante des flags.
La seule vraie condition est celle-ci : la valeur doit se trouver à un endroit que vous pouvez modifier sans déploiement. Il y a trois voies principales.
- Une table dans la base de données. C'est ce que je fais : rien de nouveau à apprendre, et vous pouvez la modifier depuis une page à vous.
- Un service dédié aux feature flags ou à la configuration à distance. Il vous donne un tableau de bord tout prêt, des pourcentages et des statistiques. Pour commencer, la table suffit.
- Une variable d'environnement. Ça va côté serveur, mais chez beaucoup d'hébergeurs la modifier demande un nouveau déploiement ou un redémarrage : vérifiez comment votre hébergeur l'applique. Dans une app mobile, elle reste en général figée dans le build.
D'abord, je demande à Claude Code la table et un seul endroit du code par lequel passent toutes les questions sur les interrupteurs, au lieu de vingt vérifications éparpillées. Avec Claude Code, tout ce qui suit se met en place en quelques minutes.
Crée une table flags avec nom, allumé oui ou non, pourcentage et liste d'utilisateurs, et une seule fonction que l'app utilise pour savoir si un interrupteur est allumé pour l'utilisateur actuel.
2. La fonction naît éteinte
Chaque nouvelle fonction entre dans le code déjà derrière son interrupteur, éteint.
Mets la fonction X derrière un interrupteur appelé nouvelle-x, éteint par défaut, lu depuis la table flags.
Ensuite je publie comme ça. Pour les utilisateurs, rien ne change : le nouveau code est là, mais il ne s'exécute pas. C'est la première des trois règles : aucune valeur dans la base de données ne peut faire apparaître une fonction qui n'est pas dans l'app installée.
Pour les apps iPhone, c'est la deuxième règle, et Apple l'écrit clairement : les nouvelles fonctions doivent être décrites dans les notes pour la validation et accessibles à la personne qui vérifie, et les fonctions cachées sont interdites. Si vous publiez une fonction éteinte, écrivez-le dans les notes et expliquez comment la voir.
3. Allumer par paliers
Je n'allume jamais pour tout le monde d'un coup. D'abord pour moi, puis pour une dizaine d'utilisateurs, puis pour tous.
Fais en sorte que nouvelle-x puisse s'allumer pour une liste d'utilisateurs et pour un pourcentage, toujours le même pour le même utilisateur.
La partie « toujours le même pour le même utilisateur » compte. Si l'app tire au sort à chaque ouverture, la même personne voit la fonction aujourd'hui et pas demain. En général, on calcule un nombre fixe à partir de l'id de l'utilisateur : si ce nombre tombe sous le pourcentage, la fonction est allumée. Ainsi, quand vous montez le pourcentage, ceux qui l'avaient déjà la gardent.
Entre deux paliers, je regarde les erreurs et les messages des utilisateurs. Si tout est calme, je monte.
4. Si la lecture échoue, tout reste éteint
L'interrupteur est déjà un kill switch : si la fonction pose problème, je l'éteins et l'ancienne version revient. Mais il y a un cas auquel personne ne pense : et si l'app n'arrive pas à lire les flags ? Base de données lente, pas de réseau, table renommée par erreur.
Si la lecture des flags échoue, considère tous les interrupteurs comme éteints et affiche l'ancienne version.
Ainsi, si la lecture échoue, l'app affiche d'elle-même la version qui fonctionnait déjà. Tout allumer quand elle ne sait pas est le moyen le plus rapide de montrer à tout le monde une fonction à moitié finie.
Ensuite, il y a le délai : ceux qui ont l'app ouverte voient la fonction jusqu'à ce qu'elle relise les flags, et les services dédiés gardent aussi en général les valeurs en cache. Combien de temps je dois attendre, je le demande.
Dis-moi au bout de combien de temps, dans le pire des cas, un utilisateur qui a déjà l'app ouverte voit un interrupteur que je viens d'éteindre, et où ce délai est défini.
5. La table, c'est moi seul qui la modifie, et depuis mon téléphone
Pour ne pas avoir à ouvrir la base de données depuis l'ordinateur, j'utilise une page admin sur mon téléphone. Mais la protection passe avant la page : cacher un bouton ne suffit pas. C'est le serveur qui doit décider qui peut modifier la table, sinon quelqu'un de malin allume et éteint vos fonctions sans passer par la page. C'est pour ça que le prompt fait les deux choses dans cet ordre.
Protège la table flags sur le serveur : l'app peut la lire, mais seul mon compte peut la modifier. Ensuite crée une page admin, rien que pour moi, avec un interrupteur pour chaque ligne.
6. Le ménage
Un interrupteur allumé pour tout le monde depuis des semaines, et qui marche bien, ne sert plus à rien. Laissé là, c'est du vieux code que personne ne teste, et une ligne que quelqu'un finira par éteindre par erreur. En plus, avec la règle du point 4, si la lecture échoue, l'app revient à l'ancienne version même pour une fonction que tout le monde utilise.
Liste les interrupteurs allumés pour tout le monde depuis plus d'un mois et retire-les du code, en laissant la fonction allumée.
Avant de publier, je vérifie que la fonction reste allumée et je retire aussi la ligne de la table.
Les trois règles
- L'interrupteur n'allume que du code déjà présent dans la version publiée : la fonction doit être publiée éteinte, d'abord.
- Éteindre du code déjà publié ne demande pas de validation ; mais les stores n'acceptent pas de fonctions cachées qu'on allume ensuite sans validation. Les flags servent à éteindre et à allumer par paliers, pas à faire passer en douce de nouvelles fonctions.
- Rien de secret dans les flags : c'est l'app qui les lit, donc qui veut peut les voir.
Le nom compte aussi : « remise-secrete-partenaire » en dit déjà trop. Les clés et les mots de passe restent sur le serveur.
Les erreurs les plus courantes
- Allumer le flag avant d'avoir publié la fonction : rien ne se passe, parce que le code n'est pas là. Publiez d'abord éteint, puis allumez.
- Considérer comme allumé un flag qu'on n'arrive pas à lire : si la lecture échoue, la bonne valeur est éteint.
- Tirer au sort à chaque ouverture : le pourcentage doit toujours donner le même résultat pour le même utilisateur.
- Protéger seulement la page admin : la table aussi ne doit accepter de modifications que depuis votre compte.
- Ne jamais retirer les vieux interrupteurs : ceux allumés pour tout le monde depuis plus d'un mois doivent sortir du code.
Check-list
- Table flags créée, une seule fonction pour la lire
- La nouvelle fonction est publiée éteinte
- Pourcentage stable pour le même utilisateur
- Si la lecture échoue, tout éteint
- Je sais combien de temps il faut pour que ça s'éteigne chez ceux qui ont l'app ouverte
- Seul mon compte peut modifier les flags
- De temps en temps, je retire les flags allumés depuis plus d'un mois
Questions fréquentes
Est-ce la même chose que revenir à la version précédente ?
Non. Revenir en arrière remet toute l'app comme avant, et pour une app mobile cela veut dire une autre version et une autre validation. L'interrupteur éteint seulement cette fonction, et le reste ne bouge pas.
Est-ce que ça marche aussi sur une app déjà dans le store ?
Oui, et c'est là que ça sert le plus, parce que chaque nouvelle version passe par la validation et que tout le monde ne met pas à jour tout de suite. Mais ça ne vaut que pour le code déjà présent dans la version installée : un flag ne peut pas ajouter une fonction que l'app n'a pas.