”Fungerar på min maskin” (engelska: ”Works on my machine”) — den klassiska frasen från en utvecklare som inte kan återskapa en bugg i sin lokala miljö, även om buggen stabilt uppträder hos andra teammedlemmar eller i produktion. Situationen uppstår på grund av skillnader i konfiguration, versionsberoenden, operativsystem eller data mellan utvecklarens maskin och miljön där buggen återskapas. Enligt Stack Overflow Survey 2023 säger 58 % av utvecklarna denna fras minst en gång i månaden och 31 % — varje vecka. Vi undersöker varför kod inte fungerar likadant överallt och hur man standardiserar miljön.
Huvudpunkter
”Fungerar på min maskin” — frasen som en utvecklare säger när en kollega eller testare rapporterar en bugg, men på utvecklarens maskin kan buggen inte återskapas. Utåt sett ser det ut som förnekelse av problemet, men tekniskt sett är situationen verklig: koden kan verkligen fungera i en miljö och misslyckas i en annan. Skillnaden på en bit i konfigurationen — och applikationens beteende förändras dramatiskt.
Frasen har blivit en meme i IT-communityt eftersom den är samtidigt sann och värdelös. Ur utvecklarens perspektiv — koden fungerar verkligen på hans maskin. Ur teamets perspektiv — problemet finns och måste lösas, inte rättfärdigas. Humor i situationen är att utvecklaren talar sanning, men denna sanning hjälper inte att fixa buggen. Memen är så populär att tusentals inlägg på Reddit, XKCD och DevOps-konferenser har tillägnats den.
Ur processperspektiv är frasen ”fungerar på min maskin” en indikator på problem med miljöns reproducerbarhet. Om två utvecklare inte kan få samma resultat på samma kod — betyder det att processen för att konfigurera miljön inte är standardiserad. DevOps-praxis säger: miljön måste vara reproducerbar med ett enda kommando från repositoryt utan manuella åtgärder.
Den lokala miljön för utvecklaren skiljer sig nästan alltid från produktion. Utvecklaren använder macOS eller Windows, medan servern kör på Linux. Olika operativsystem har olika filsystem, kodningar, tråd-timings och systemanrop. Även om båda miljöerna är Linux — kernelversionen, glibc, OpenSSL kan skilja sig.
Den andra orsaken — uppsättningen installerad programvara. På utvecklarens maskin kan en global version av Node.js 20 vara installerad, medan i CI/CD-konfigurationen anges version 18. Eller utvecklaren använder PostgreSQL 16 lokalt och i produktion — PostgreSQL 14. Skillnader i mindre versioner är ofta inte märkbara, men större uppdateringar kan förändra beteendet hos SQL-frågor. Enligt npm Inc. orsakas 67 % av buggarna relaterade till beroenden av skillnader i patch-versioner.
Den tredje orsaken — nätverksförhållanden. På den lokala maskinen finns inga fördröjningar, bandbreddsbegränsningar eller DNS-problem. I produktion kan varje begäran till ett externt API ta 500 ms istället för 5 ms. Timeouter, retry-logik, race conditions — alla dessa problem manifesterar sig endast under verklig belastning och under verkliga nätverksförhållanden. Nätverksemulering genom verktyg som Toxiproxy hjälper att identifiera dessa problem före driftsättning.
Första orsaken — brist på data. Utvecklaren arbetar med testfixtures, medan det i produktion finns miljontals poster med oväntade värden. NULL i ett fält som utvecklaren ansåg vara obligatoriskt, ett Unicode-tecken i ett namn, en alltför lång sträng — allt detta kan orsaka buggar som inte är reproducerbara på en lokal databas med syntetisk data.
Andra orsaken — olika kompilerings- och byggflaggor. Release-build (Release/Distribution) kan skilja sig från debug-build (Debug). Kompilatoroptimeringar, borttagning av debug-loggar, inlinering av funktioner — allt detta kan dölja eller tvärtom visa buggar. Typiskt exempel: i debug-build fungerar en assert som misslyckas i release-build på grund av en annan ordningsföljd för initiering av variabler.
Tredje orsaken — lokal cache och temporära filer. Utvecklaren kanske inte märker buggen för att i webbläsaren cachas gamla skript, i Redis sparas föråldrade data och i filsystemet hänger temporära filer från tidigare körningar. En ren start (inkognitoläge, cachetömning, färsk installation) återskapar ofta buggen som inte visade sig ”av sig själv”.
Fjärde orsaken — konflikter mellan globala och lokala beroenden. Verktyg som Ruby gems, Python pip, Node.js npm kan ha globalt installerade paket som ”hjälper” koden att fungera lokalt men saknas i produktion. Användning av virtuella miljöer (virtualenv, venv, nvm) isolerar projektet från globala installationer och gör miljön reproducerbar.
Frasen ”fungerar på min maskin” förstör förtroendet i teamet. Om en utvecklare regelbundet inte kan återskapa buggar börjar kollegor tvivla på hans kompetens eller noggrannhet i testning. Med tiden leder detta till mikrostyrning: varje ändring måste verifieras av en andra utvecklare, vilket saktar ner utvecklingen. Enligt Google Project Aristotle påverkar psykologisk trygghet i teamet direkt produktiviteten och ständiga tvister om miljön är en av faktorerna som minskar den.
Andra problemet — försening av code review. Om en utvecklare inte kan återskapa buggen lokalt kan han avvisa en kollegas pull request med orden ”hos mig fungerar det — så problemet är hos dig”. Detta orsakar konflikter och försenar leverans av funktioner. Standardisering av miljön eliminerar denna konflikt: om båda utvecklarna arbetar i samma Docker-container förlorar frågan ”hos vem fungerar det” sin mening.
Tredje problemet — förlust av buggar i spåraren. Buggar som ”inte återskapas hos utvecklaren” stängs ofta med anteckningen ”kan inte återskapas” (Cannot Reproduce). En månad senare dyker buggen upp i produktion och dess fix kostar 10 gånger mer. Regel: om en bugg återskapas hos åtminstone en person — finns den, oavsett om den fungerar hos utvecklaren eller inte.
Första och mest effektiva sättet — Docker. Hela projektet bör startas med docker-compose up utan ytterligare åtgärder. Databas, cache, meddelandekö, webbserver — allt körs i containrar. Utvecklaren installerar endast Docker och Git. Resten — inuti containrarna. Detta garanterar att alla teammedlemmar har samma miljö oavsett operativsystem.
Andra sättet — versionshanterare. Om Docker inte är möjligt (licensbegränsningar, äldre infrastruktur), använd nvm (Node.js), rbenv (Ruby), pyenv (Python), sdkman (Java). Versionshanterare tillåter växling mellan versioner av språk och verktyg inom projektet. Filer som .nvmrc, .ruby-version, .python-version bör finnas i repositoryt och kontrolleras av CI/CD.
Tredje sättet — Vagrant för virtuella maskiner. Vagrant startar en virtuell maskin med specificerat operativsystem och konfiguration ovanpå VirtualBox eller VMware. Inuti VM installeras alla beroenden via provisioning-skript (shell, Ansible, Puppet). Vagrant är tyngre än Docker men ger fullständig isolering på OS-nivå — användbart för projekt som är beroende av en specifik Linux-kernelversion.
Fjärde sättet — makefile och bootstrap-skript. Även en enkel Makefile med målen install, test, build, clean kan standardisera rutinmässiga åtgärder. Kommandot make install bör installera alla beroenden, konfigurera databasen och skapa testdata. En enda ingångspunkt för alla utvecklare utesluter manuella fel vid konfiguration av miljön.
Huvudverktyget — låsningsfiler för beroenden. package-lock.json (npm), yarn.lock (Yarn), Podfile.lock (CocoaPods), pubspec.lock (Flutter) fixar exakta versioner av varje paket. Utan låsningsfil kan två utvecklare som installerar beroenden vid olika tidpunkter få olika mindre versioner. Låsningsfilen bör finnas i repositoryt och inte redigeras manuellt.
Andra verktyget — .env.example i repositoryt. En mallfil för miljövariabler med kommentarer. Utvecklaren kopierar den till .env och fyller i sina egna värden. CI/CD-pipelinen kontrollerar att alla obligatoriska variabler är inställda. Enligt GitLab 2023 minskar team som använder .env.example antalet incidenter relaterade till miljövariabler med 40 %.
Tredje verktyget — pre-commit hooks. Automatisk kontroll som körs före varje commit: linter, formatter, typkontroll, tester. Om hooks är konfigurerade likadant hos alla utvecklare kommer formaterings- eller typfel som ”gick igenom på den lokala maskinen” inte att nå produktion. Husky för JavaScript och pre-commit för Python är populära lösningar.
Fjärde verktyget — CI/CD-pipeline som kör tester i en ren miljö. Om tester passerar i CI men inte lokalt — är problemet i konfigurationen av den lokala miljön. Om tester inte passerar i CI — slås pull request inte samman. Denna strikta regel förhindrar att buggar som ”fungerar lokalt” kommer in i huvudgrenen.
Vanliga frågor
Det är en försvarsreaktion: utvecklaren lägger mycket tid på felsökning och att höra att koden inte fungerar är psykologiskt smärtsamt. Frasen ger tid att ”växla om” och börja söka orsaken utan skuldkänslor.
Be honom att återskapa buggen i en ren miljö (ren installation, inkognitoläge). Om den inte återskapas — jämför versionsberoenden och miljövariabler. Om det inte hjälper — starta en Docker-miljö som är identisk med produktion.
Docker tillhandahåller en isolerad container med fast konfiguration som fungerar likadant på alla operativsystem. Alla utvecklare använder samma Dockerfile, så miljön är identisk. Om buggen inte återskapas i containern — är problemet verkligen i koden, inte i systemet.
Låsningsfilen fixar exakta hashar och versioner av alla transitiva beroenden. Även om en ny version av ett beroende dyker upp i paketregistret garanterar installation enligt låsningsfilen att varje utvecklare får samma uppsättning paket som de andra.
Vagrant med VirtualBox är motiverat om projektet är beroende av specifika OS-kernelmoduler eller kräver fullständig isolering på kernel-nivå. För 90 % av projekten är Docker lättare, snabbare och bekvämare. Valet beror på hur djupt projektet interagerar med operativsystemet.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också