Edument

Enkla felsökningsidéer för vinst och nöjes skull

Det finns massor av information om programmeringsspråk, koncept och existerande kod. Däremot finns det inte lika mycket om personlig programmeringspraxis. Och ännu mindre om felsökningsprocessen. Ändå är felsökning av både egen och andras kod någonting som många programmerare gör varje dag.

Det finns en rad olika typer av buggar: avgörande, skrämmande, förväntade och de som inte alls är något av det. Det kan till och med uppstå situationer som kan påverka ens liv. Men oroa dig inte! I den här kampen finns det både vänner och allierade, och här kommer vi att presentera dem. Nu kör vi. 


Innan vi börjar

När du upptäcker ett fel i programmets beteende bör du kontrollera om det är uppenbart. Är det? Är du säker? Eller är felet inte alls uppenbart? I så fall måste vi vanligtvis vidta en rad åtgärder. Dessa åtgärder kan till exempel vara:

  • Hitta exakta reproduktionssteg. Hur ska vi annars kunna förbereda tester? Och ännu intressantare; hur ska vi kunna veta att vi har fixat problemet (åtminstone på vår egen maskin)?
  • Reproducera buggen. Det kanske försvinner av sig själv? Antagligen inte, men du kommer åtminstone att få mer information. Även små saker, som "det funkar på GNU/Linux men misslyckas på Windows" kan hjälpa.
  • Minimera eventuell input. Skär bort allt som inte stoppar felet från att uppstå, ju mindre kod desto bättre.
  • Beroende på gällande policys, kontrollera om buggen redan har rapporterats, fyll i felrapporter, etc. Gör de saker som passar just ditt arbetsflöde.


Hitta källor

F: När?

S: När du ser ett fel, ett undantag eller felaktigt beteende. Ibland kan du få en stacktrace (spår från alla anrop som orsakade felet) och ibland inte. För att kunna inleda en faktisk felsökning måste du hitta koden som ligger bakom.

F: Jag har en stacktrace! Räcker det?

S: Förhoppningsvis. Stacktraces kan vara en rejält lång lista, men oftast finns informationen i de första 30 raderna eller så. Ett undantag som bär en stacktrace innehåller vanligtvis någon typ av meddelande om felstatusen, vilket kan göra saken enklare.

F: Jag har ingen stacktrace. Det skapas bara ett felmeddelande eller en etikett som jag måste jobba med. Och den här kodbasen har miljoner rader kod!

S: Använd textsök för att hitta var raden definieras. Det kan låta uppenbart, men många glömmer bort det här steget. Det tar bara några sekunder att använda verktyg som ripgrep eller en välkonfigurerad grep i och hitta textmönster, även i stora kodbaser. Tänk även på att felmeddelanden ofta skapas från delar, så om sökningen inte ger några resultat, prova söka på mindre text och eventuellt byta ut delar av den. För att begränsa resultaten från en sökning på några vanliga ord, prova att sätta dem inom citationstecken.

Låt oss titta på ett par exempel från IntelliJ Communitys kodbas (dess fler än 9 miljoner rader kod gör den rejält stor och intressant, men råden som ges är dock allmänna).

Case 1: exekvering av rg '"Extract"' ger 2 resultat, medan rg Extract ger 2751.

Case 2: exekvering av  rg 'If you already have a 64-bit JDK installed' ör samma projekt ger inga resultat alls, medan rg 'If you already have a' ger källan till runtime-felmeddelandet: antalet bits är inbakat i felmeddelandet, vilket gör att det inte är direkt sökbart. 

Case 3: säg att du inte vet vad som går fel med 'Recompile ...' i IDEAs kontextmeny när du försöker kompilera en viss fil. Att utföra en  rg '"Recompile ' ger ingenting relevant. Däremot har det ett snabbkommando kopplat till sig: Ctrl+Alt+F9. Med en sökning på rg '"control shift F9"' får vi fram en rad keymap-filer. Med dessa får vi:


<action id="Compile">
    <keyboard-shortcut first-keystroke="control shift F9">
  </action>


Nu när vi har åtgärdens ID, kan vi kolla upp det med rg 'id="Compile"'. Det ger oss antalet lookups, där ett av dessa är resources/src/idea/JavaActions.xml file containing <action id="Compile" class="com.intellij.compiler.actions.CompileAction"/>. Nu vet vi exakt klass och kan börja felsökningen därifrån.

Sökprocessen kan alltså kräva flera steg och vara beroende av projektspecifika mönster där ytterligare data kan hjälpa dig hitta källor, även där det inte finns några uppenbara genvägar till en specifik fil bland tusentals.

F: Det går fortfarande inte!

S: Det kan vara någonting från ett bibliotek, så prova att googla på texten (det kan ge bättre resultat om man tar bort delar som ser ut som identifierare). Ett annat alternativ är att någon verkligen inte ville att du skulle kunna kolla upp meddelandet i källorna.


Utforskning av källor

F: När?

S: Du vet var felet uppstår. Kanske för att du skrev det själv. Eller så hittade du det kanske genom att använda en stacktrace eller hade tur/en känsla under sökningen. Nu bör du ha viss koll på läget.

F: Jag har en debugger, räcker det?

S: Förhoppningsvis, men tyvärr inte så ofta som man hade hoppats på. En interaktiv debugger är dock ett väldigt bra sätt att utforska den specifika situationen. Lär dig använda den med massor av resurser om stepping, brytpunkter och genom att leka med värden och stack frames.

Låt oss titta på ett par exempel från IntelliJ Communitys kodbas.

F: Inputen för delen av kod jag felsöker är stor och felet triggas när koden exekverats hundratals gånger!

S: Du kan: 1) reducera mängden input så mycket som möjligt (här är förberedelser en bra hjälp); eller 2) lägga till en brytpunkt. Det förutsätter dock att du vet tillräckligt mycket om felet.

F: Inputen är fortfarande enorm, trots reduceringar. Det funkar inte med brytpunkter.

S: Då gäller det att dra fram en av de populäraste universella metoderna - en gammal hederlig loggdriven felsökning. Lägg helt enkelt till lite textoutput och kontrollera outputen vid nästa körning! Se vilka data och resultat du får runt den inkorrekta delen av koden. Det här steget är vanligtvis ett krav för att kunna gissa rätt villkor för en brytpunkt, och om du har ett, sluta omedelbart! Fortsätt istället med brytpunkten. Eftersom den här metoden är otroligt vanlig och enkel (och därmed lockande) att använda, är det en bra idé att skriva bra loggmeddelanden. Ta dig tid till att beskriva situationen eller skriv något neutralt, som exempelvis siffror. Skriv aldrig någonting som kan vara stötande mot dina kollegor i en gitlogg! Det finns alltid någon som kommer att se dina ändringar.


Obligatoriska edge cases

F: När?

S: När inget annat funkar men vissa villkor uppfylls.

F: Buggen uppstår inte under testning utan bara på produktionsbygget.

S: Det säkraste kortet är en extern debugger för det specifika språket. Då produktions- och testmiljöer kan ha små skillnader, kan det underlätta att ansluta med en extern debugger.

F: Jag kan reproducera buggen, men ingen annan i mitt team kan det.

S: Även om det kan kännas som om hela världen är ute efter att göra livet surt för dig, är det inte fallet här. Koden du skriver står antagligen på jättars axlar: ett stort antal bibliotek, applikationer, självaste OS, enorma mängder lager och inte minst en rad specifika villkor som är unika för just din arbetsplats. Man kan lugnt säga att just din miljö skiljer sig från andras, frågan är bara var. Tidszon? Lokala inställningar? LibreSSL-version? Oavsett vad skillnaden kan vara, kan det vara värt att leta. Om felet verkar vara ovanligt kan det vara en bra idé att googla på det.

F: Koden skapar input till ett annat program, t.ex. en binär kod till ett OS eller ett textdokument i ett specifik format till vår bankserver som inte godkänner det.

S: Om du har ett fungerande exempel och ett trasigt, tveka inte att ignorera källorna en stund och inspektera dem ordentligt. Använd alla verktyg du kan hitta, som exempelvis specifika redigerare och jämförelseprogram. Om skillnaderna är stora kan du försöka porta dem i sektioner tills det upptäckta beteendet inte ändras. Om du kan hitta vilka knappar som behöver tryckas på, är själva tryckandet oftast inte så svårt. 

F: Buggen är skrämmande och svår att hitta. Jag kan reproducera den, men kan inte riktigt se varför den uppstår.

S: Om det är en fråga om regression och du vet när saker och ting fungerade, använd git bisect. Om du vet var buggen kanske gömmer sig, men inte vet vilken ändring som orsakade den: gå tillbaka till en tidigare version. Här är "git blame" och "git history" en fantastisk uppfinning! Förhoppningsvis kan du få buggen att försvinna och komma vidare därifrån. Om du har 10 000 committer och du fortfarande inte vet vilken som är fel kangit bisect vara den bästa lösningen. Även när det ser mörkt ut går det att hitta boven bland tusentals rader kod med hjälp av halvering. Det är en snabb metod, beroende på hur snabbt du kan kontrollera om buggen finns i den aktuella committen. 

F: Jag ändrar kod på flera olika sätt, om och om igen, men ingenting ändras när applikationen körs!

S: Hur uppenbart det än verkar, så rekommenderar jag att du kontrollerar att det är rätt källor som ändras och exekveras!


Skyddande av källor

Tipsen ovan är bra och populära i de fall där man kan se buggen, men ibland är det bättre att försöka förutse problemen och hjälpa ditt framtida jag. På det här sättet kan vi även hitta osynliga buggar.

 Överväg följande. Säg att din applikation kräver data från en rad olika datakällor. Vid en sökning ber du datakällorna att leverera en ny datauppsättning, den läggs till i en sammanslagen uppsättning och levererar alltihop. Du och andra personer kan arbeta under en lång tid - månader eller år - och ändra här och var i koden. Till slut kan en del datakällor bli smått felaktiga (t.ex. på grund av en misslyckad refaktorering) och leverera felaktiga data eller lägga till samma data flera gånger. Trots att detta inte nödvändigtvis får vår applikation att krascha, läggs det fortfarande tid på onödiga beräkningar. Och ännu värre - det kommer att vara helt "osynligt" för alla ifall processen genomförs relativt snabbt.

Svaret är enkelt: använd påståenden! Skriv ett påstående som säger att ingenting i ett enskilt dataset får bidra till det kombinerade datasetet. Det innebär att användare inte kommer att uppleva mer än viss prestandaförlust på grund av reduntanta beräkningar vid eventuell regression. I en testmiljö kommer däremot en enkel körning med aktiverade påståenden att tillåta skapandet av stacktrace för problemet och tänka om den regresserade delen.

Detta tillvägagångssätt gör det inte bara möjligt för läsaren att ha en explicitare och striktare exekveringslogik, utan hjälper dessutom till när det uppstår buggar som inte är uppenbara och synliga. Dessutom blir koden robustare och kan ändras under många år framöver.


Nytt hopp

F: När?

S: När ingenting fungerar och problemet verkar väldigt allvarligt.

F: Så det finns verkligen ingen lösning om du har sagt "ingenting fungerar" två gånger i rad, eller hur?

S: Inte riktigt. När du stöter på stora problem; be någon om hjälp. Det kan finnas någon i din närhet som kan just den här biten kod bättre än du. Någon annan kanske har mer tur just idag. Tänk på att vara ödmjuk och omtänksam! Fråga om personen sitter med något ännu större problem först. Om personen har tid, tacka för hjälpen och se hur de löser problemet så att du kan lösa det själv i framtiden.


Avslutande tankar

Här har vi tagit en informell titt på enkla råd om hur man kan hantera fel i kod. Den här artikeln är inte avsedd att vara ett omfattande facit. Jag försöker undvika både klichéerna, som "Försök förklara problemet för en gummianka på bordet för att förstå det bättre" och språkspecifika lösningar. Men om artikeln hjälper dig hantera buggar ens lite grand bättre, så har den gjort minst en persons liv lite enklare. 



Av Oleksandr Kyriukhin, en del av vårt team i Prag

JavaScript seem to be disabled in your browser.

You must have JavaScript enabled in your browser to utilize the functionality of this website.