Hvordan jeg stoppede med at shippe alt på én gang og begyndte at bruge feature flags

Hvordan jeg stoppede med at shippe alt på én gang og begyndte at bruge feature flags

Jeg har deployet en hel ny booking-funktion fredag eftermiddag uden nogen form for sikkerhedsnet. Det virkede fint på min maskine, og jeg tænkte: “Hvor slemt kan det gå?” Svaret var: ret slemt. Timeout-fejl, vrede beskeder og en meget lang aften.

Det var først, da jeg for tredje gang sad og rullede kode tilbage manuelt, at jeg faktisk satte mig ned og tænkte: jeg har brug for en måde at tænde og slukke for ting uden at lave et nyt deploy hver gang. Altså, feature flags.

Hvad feature flags egentlig løser (og hvornår de bliver giftige)

Feature flags (også kaldet feature toggles) er i bund og grund bare betingelser i din kode, som du kan styre fra konfiguration i stedet for at skulle ændre og deploye kode hver gang.

Meget simpelt:

if (flags.newCheckout) {
  renderNewCheckout()
} else {
  renderOldCheckout()
}

Pointen er: du kan ændre flags.newCheckout uden at røre resten af koden. Det giver dig nogle ret gode superkræfter:

  • Du kan deploye kode, før du viser funktionen til brugerne (dark launch).
  • Du kan tænde for noget for 5 % af brugerne og se, hvad der sker.
  • Du kan have en “kill switch”, der slukker en tung eller ustabil del af systemet hurtigt.

Det fjerner meget af “alt eller intet” følelsen, når du trykker deploy.

Hvornår feature flags skaber flere problemer end de løser

Du kan dog også skyde dig selv i foden med feature flags.

Typiske måder at ødelægge det på:

  • Flag bliver aldrig fjernet, og koden fyldes med if (flags.x) overalt.
  • Ingen ved, hvad flaggene betyder, eller hvem der må ændre dem.
  • Flags ligger hårdkodet i kodebasen og kræver nyt deploy for at ændre dem.
  • Flags styrer ting, der burde være ren konfiguration (fx URL til database).

Min erfaring: feature flags er gode, hvis de bruges aktivt til at styre en overgang eller et eksperiment. De er dårlige, hvis de bliver til permanent infrastruktur uden struktur.

Fire typer feature flags der faktisk giver mening i små projekter

Du kan lave 20 kategorier, hvis du vil, men til en lille webapp eller et hobbyprojekt ender jeg næsten altid med de her fire.

1. Release flag: “er den nye feature tændt?”

Bruges til at rulle en konkret feature ud kontrolleret.

if (flags.enableProfilePage) {
  showProfilePage()
} else {
  showComingSoon()
}

Typisk livscyklus for et release flag:

  1. Flag oprettes, koden bagved bygges.
  2. Feature deployes til produktion med flag = false.
  3. Feature testes af internt team eller få brugere.
  4. Flag sættes til true for alle.
  5. Flag og gammel kode slettes, når alt er stabilt.

2. Ops flag (kill switch): “sluk den tunge ting nu”

Et ops-flag er din nødstop. Brug det til ting, der:

  • er tunge (fx en dyr rapport eller eksport),
  • rammer en skrøbelig integration (ekstern API),
  • kan tåle at være midlertidigt slået fra.
if (!flags.enableReports) {
  return res.status(503).json({
    message: "Rapporter er midlertidigt slået fra. Prøv igen senere."
  })
}

return generateAndSendReport()

Her er det vigtigt, at flagget kan ændres hurtigt. Det skal ikke være noget, der kræver, at du først kører CI-pipeline færdig.

3. Experiment flag: A/B-test uden enterprise-setup

Et experiment flag bruges, når du vil teste to versioner af noget: måske en ny forside eller en anden tekst på en knap.

function chooseVariant(user, flags) {
  if (!flags.searchExperiment) return "control"

  // simpel 50/50 split baseret på user.id
  const hash = hashString(user.id)
  return hash % 2 === 0 ? "control" : "variantA"
}

Du behøver ikke et kæmpe system. Det vigtige er, at:

  • brugere får den samme variant hver gang (stabil fordeling),
  • du logger, hvilken variant de fik, så du kan måle på det.

4. Permission flag: feature kun for bestemte brugere

Permission flags styrer adgang på et groft niveau. Fx:

  • admin-only features,
  • beta-funktion til udvalgte kunder,
  • intern test for teamet.
if (!user.isAdmin || !flags.enableUserImpersonation) {
  return res.status(403).json({ message: "Ikke tilladt" })
}

impersonateUser(targetUserId)

Her bruger du både brugerens rolle og flagget. Det gør det nemt at slå funktionen helt fra uden at ændre roller.

Sådan bygger du feature flags uden tredjepartstjenester

De fleste artikler om feature flags hopper direkte til eksterne services. Det kan være fint, men til små apps kan du komme langt med en simpel konfig og et lille adgangslag.

1. Start med konfiguration, ikke if-sætninger

Det vigtigste er, at dine flags ikke er spredt ud i koden som magiske booleans.

Lad os sige, du har en Node backend. Du kan starte med en simpel JSON-fil:

// feature-flags.json
{
  "enableProfilePage": false,
  "enableReports": true,
  "searchExperiment": true,
  "enableUserImpersonation": false
}

Og så et lille modul til at læse dem:

// featureFlags.ts
import fs from "node:fs"

export type FeatureFlags = {
  enableProfilePage: boolean
  enableReports: boolean
  searchExperiment: boolean
  enableUserImpersonation: boolean
}

let flags: FeatureFlags

export function loadFlags() {
  const raw = fs.readFileSync("./feature-flags.json", "utf8")
  flags = JSON.parse(raw)
}

export function getFlags(): FeatureFlags {
  return flags
}

Du kalder loadFlags() ved startup, og så bruger du getFlags() i resten af din kode.

En simpel forbedring er at genindlæse filen, når den ændrer sig, men det kan du gemme til senere.

2. Brug typer til at holde styr på dine flags

Hvis du bruger TypeScript, kan du lade kompileren hjælpe dig med at undgå stavefejl og glemte flags. Ovenstående FeatureFlags-type tvinger dig til at have alle flags samlet ét sted.

Det gør også oprydning lettere: når du fjerner et flag fra typen, vil TypeScript brokke sig alle de steder, det stadig bruges.

3. Miljøvariabler som flags i små projekter

Nogle flags giver mere mening som miljøvariabler, især ops-flags og ting, du vil ændre pr. environment (dev, staging, prod).

Et eksempel i Node:

export const flags = {
  enableProfilePage: process.env.FLAG_ENABLE_PROFILE_PAGE === "true",
  enableReports: process.env.FLAG_ENABLE_REPORTS !== "false", // default on
}

Sammenhængen med miljøvariabler forklarer jeg mere i artiklen om miljøvariabler, men ideen er den samme: konfiguration adskilt fra kode.

Frontend vs backend: hvor skal dine feature flags bo?

Frontenden og backenden har forskellige behov. Du behøver ikke samme setup begge steder.

Flags i backend

Backend er typisk stedet for:

  • ops-flags (kill switches),
  • permission-flags (adgang til API endpoints),
  • release-flags der styrer tunge operationer.

Her er det backend, der bestemmer, om en route er tilgængelig eller ej:

app.get("/reports", (req, res) => {
  if (!flags.enableReports) {
    return res.status(503).json({ message: "Rapporter er midlertidigt slået fra" })
  }

  return handleReports(req, res)
})

Flags i frontend

Frontend-flags bruger jeg mest til:

  • at gemme eller vise UI-elementer,
  • klient-side eksperimenter (tekst, layout),
  • feature previews til bestemte brugere.

I en React-app kunne det se sådan her ud:

type FeatureFlags = {
  enableProfilePage: boolean
}

const FlagsContext = createContext<FeatureFlags>({ enableProfilePage: false })

export function App() {
  const [flags, setFlags] = useState<FeatureFlags>({ enableProfilePage: false })

  useEffect(() => {
    fetch("/api/feature-flags")
      .then(res => res.json())
      .then(setFlags)
  }, [])

  return (
    <FlagsContext.Provider value={flags}>
      <Routes />
    </FlagsContext.Provider>
  )
}

function ProfileLink() {
  const flags = useContext(FlagsContext)
  if (!flags.enableProfilePage) return null

  return <Link to="/profile">Din profil</Link>
}

Tricket her er, at backenden stadig er den autoritative kilde. Frontenden spørger bare: “Hvilke flags gælder for den her bruger lige nu?”

Tre simple måder at lave gradual rollout

Gradual rollout betyder, at du ikke tænder for en feature for alle på én gang. I stedet gør du det i mindre bidder, så du kan reagere, hvis noget går galt.

1. Procent-baseret på bruger-id

Den klassiske: giv f.eks. 10 % af brugerne den nye funktion. Du kan lave en simpel hash på brugerens id.

function isInRollout(userId: string, percentage: number): boolean {
  const hash = hashString(userId) // returnerer et positivt heltal
  return hash % 100 < percentage
}

const enableNewSearch = flags.newSearch && isInRollout(user.id, 10)

Fordele:

  • Samme bruger får altid samme oplevelse.
  • Du kan øge fra 10 % til 30 % til 100 % uden kaos.

2. Bruger-liste: whiteliste tidlige testere

Nogle gange vil du selv styre præcis, hvem der ser hvad. Så kan du gemme en liste over “beta-brugere” i databasen.

// pseudo-query
const betaUsers = await db.betaUsers.find({ feature: "new-search" })
const isBetaUser = betaUsers.some(u => u.userId === user.id)

const enableNewSearch = flags.newSearch && isBetaUser

Det er især nyttigt i små B2B-agtige systemer, hvor du vil rulle noget ud til 2-3 kunder først.

3. Environment-baseret rollout

Noget af det mest oversete: brug environments fornuftigt. Du kan lade et flag være:

  • altid true i dev,
  • true i staging for at teste real-world,
  • false som default i prod.
const isProd = process.env.NODE_ENV === "production"

export const flags = {
  enableProfilePage: !isProd || process.env.FLAG_ENABLE_PROFILE_PAGE === "true",
}

Så har du featuret tændt i dine ikke-produktionsmiljøer, uden at du behøver at huske at ændre noget manuelt hver gang.

Hvad du skal logge for at kunne slukke hurtigt igen

Feature flags er kun halve løsningen, hvis du ikke kan se, hvad der sker, når du tænder for noget.

Jeg plejer at logge tre ting, når et flag påvirker adfærden:

1. Hvilke flags var aktiveret for den request?

Når du håndterer en request, kan du logge et lille snapshot af de vigtigste flags.

logger.info("handleSearch", {
  userId: user.id,
  flags: {
    newSearch: flags.newSearch,
    searchExperiment: flags.searchExperiment,
  }
})

Det gør det meget lettere at se, om fejl kun sker, når et bestemt flag er tændt.

2. Variant i eksperimenter

Hvis du bruger experiment flags, så log, hvilken variant brugeren så.

const variant = chooseVariant(user, flags)

logger.info("searchVariantChosen", {
  userId: user.id,
  variant,
})

Så kan du senere tælle, om “variantA” faktisk gør tingene bedre eller værre.

3. Brug flagget i dine errors

Når du logger fejl, så tilføj relevante flags til konteksten. Det kan være med et felt som featureFlags eller specifikke navne.

try {
  return handleSearch(req, res)
} catch (err) {
  logger.error("searchError", {
    error: err,
    flags: {
      newSearch: flags.newSearch,
    },
  })
  throw err
}

Det gør “skal vi bare slukke flagget?” til et kvalificeret gæt i stedet for ren mavefornemmelse.

Oprydning: sådan undgår du at drukne i gamle flags

Det her er den del, der ofte mangler i fortællingen. Hvis du aldrig rydder op i dine feature flags, får du:

  • if-else labyrinter med gammel kode, ingen tør røre ved,
  • flags som ingen tør slukke, for “hvad nu hvis nogen bruger det?”,
  • forvirring ved onboarding af nye udviklere.

Du har brug for en fast rutine. Ikke et stort system, bare nogle faste vaner.

1. Giv hvert flag et formål og en slutdato

Når du opretter et flag, så skriv i koden, hvorfor det findes, og hvornår det kan fjernes.

// enableProfilePage
// Formål: rulle ny profilside ud gradvist
// Kan slettes når: 100 % af brugerne er på ny profil i 2 uger uden kritiske fejl

Det lyder banalt, men det gør en stor forskel, når du sidder der et halvt år senere.

2. Definition of done: feature ikke færdig før flag er væk

En ting jeg har god erfaring med: en feature er først “færdig”, når:

  • flagget er sat til true for alle,
  • gammel kode (else-grenen) er slettet,
  • selve flagget er fjernet fra konfiguration og kode.

Det kan stå i din pull request-template eller i din issueskabelon. Nærmest som en mini-tjekliste.

3. Månedligt flag-review

I små teams eller solo-projekter kan du sætte en tilbagevendende opgave: kig alle flags igennem én gang om måneden.

For hvert flag, spørg:

  • Er formålet stadig relevant?
  • Er jeg klar til at fjerne den gamle kode?
  • Hvis ikke: hvorfor ikke?

Det kan være så simpelt som et issue i dit repo. Pointen er bare, at det sker jævnligt.

En lille release-tjekliste med feature flags

Lad os samle det i en workflow-agtig form, du kan bruge næste gang, du har en større ændring på vej. Ikke som “enterprise proces”, men som en stille hjælper.

Før du begynder at kode

  • Beslut: Har den her feature brug for et release flag?
  • Hvis den er tung/risikabel: har du brug for et ops-flag (kill switch)?
  • Skriv flag-navn, formål og hvornår det kan fjernes.

Mens du bygger

  • Brug flagget alle de steder, hvor den nye adfærd adskiller sig fra den gamle.
  • Hold flag-adgangen samlet et sted (fx et featureFlags-modul).
  • Tilføj logging, der viser, hvad flagget står på, når koden kører.

Ved første deploy

  • Deploy til prod med flag = false.
  • Tjek logs: ser alt normalt ud?
  • Aktiver flagget for dig selv eller et lille sæt testbrugere først.

Gradual rollout

  • Vælg strategi: procent, whiteliste eller environment.
  • Øg langsomt: fx 5 % → 20 % → 50 % → 100 %.
  • Hold øje med error rate, svartider og evt. brugerfeedback.

Oprydning

  • Når feature kører stabilt for alle: fjern gammel kode.
  • Fjern selve flagget fra konfiguration og kode.
  • Luk evt. tilhørende issues og skriv kort ned, hvad du lærte.

Hvis du vil kombinere det her med bedre deploy-vaner, så giver det mening at kigge på artiklen om databaseændringer uden svedige håndflader eller den om GitHub Actions. De spiller ret godt sammen med feature flags.

Personligt synes jeg, at feature flags føles som at få lysdæmper i stedet for bare en kontakt med tænd/sluk. Du kan stadig lave fejl, men overgangen bliver blødere, og det gør det meget mere overskueligt at eksperimentere, især i små projekter, hvor du både er udvikler og support på én gang.

Start simpelt: brug en konfigurationsfil eller miljøvariable for få flags, eller en lille tabel i databasen med caching og et enkelt admin-endpoint til at ændre værdier. Hvis du vil undgå driftshovedpine, sørg for at flag-check er billig (cache resultatet) og at ændringer kan rulles tilbage hurtigt.
Giv hvert flag metadata - ejer, oprettelsesdato og en plan eller udløbsdato - og kræv denne info i PR-skabelonen når et flag oprettes. Automatisk linting eller CI-warnings for gamle flags samt faste rengøringsopgaver i sprintet hjælper med at fjerne dem rettidigt.
Del ansvaret: product-ejeren eller feature-ansvarlig styrer release-flags, mens SRE/ops eller support bør have adgang til ops-flags/killswitches. Brug rollebaseret adgang, audit-logs og en kort runbook, så ændringer følger processen og kan rulles tilbage sikkert.
Definér nøglemetrikker før rollout (fejlrate, latency, konvertering) og mål dem per brugersegment eller procentandel. Sæt dashboards og alerts op, så du kan sammenligne grupper i realtid og hurtigt slå flagget fra hvis negative tegn opstår.

Sara Vestergaard er selvlært kode-nørd, der stille og roligt er gået fra at rode med en enkelt HTML-side til at bygge små værktøjer, scripts og hjemmesider til sig selv og vennerne. Hun startede med at lave en simpel band-hjemmeside som teenager og opdagede, hvor tilfredsstillende det er, når noget, du har skrevet, pludselig lever på skærmen.

For Sara handler kodning ikke om store ord eller imponerende titler, men om meget konkrete problemer: den kedelige opgave, der tager for lang tid, den ven der mangler en lille porteføljeside, eller den liste, der burde sortere sig selv. Hun elsker at pille ting fra hinanden – også kode – for at se, hvad der egentlig foregår, og hun har brugt utallige aftener på at google fejlbeskeder, teste små eksempler og langsomt bygge sin forståelse op.

På Coding Class deler hun den tilgang videre. Hun skriver til dig, der gerne vil lære at kode ved at gøre det i praksis: små projekter, korte kodebidder og forklaringer, der hænger sammen med det, du faktisk sidder med på skærmen. Hun skærer ind til benet, viser typiske fejl og deres løsninger og giver altid et forslag til, hvordan du kan bygge en tand videre, når grundideen først virker.

Når hun ikke skriver til Coding Class eller nørkler med nye små projekter, hænger Sara på klatrevæggen, vander sine altanplanter eller spiller gamle Nintendo-spil. Men hun ender næsten altid tilbage ved tasterne – for der er altid endnu en lille ting, der kunne være smartere, hurtigere eller bare lidt sjovere at bruge.

Send kommentar

You May Have Missed