forskningsingeniøren bloghoved

Den noble kunst at debugge

I arbejdet i mit ph.d.-projekt skriver jeg med jævne mellemrum Matlab-kode for at simulere optiske og fotoniske effekter i strukturerede materialer. Udgangspunktet er i de fleste tilfælde noter og udledninger, som beskriver den grundlæggende teori og de centrale ligninger, som Matlab-koden skal baseres på og udregne løsninger til.

Når koden er skrevet, og de mest trivielle programmeringsfejl er udryddet, så den kan køre, kommer det spændende øjeblik: Er resultaterne meningsfulde? Opfylder resultaterne de mest oplagte "sanity checks"? Opfyldes de elektromagnetiske grænsebetingelser ved overgangen mellem forskellige materialer eksempelvis? Ofte, desværre, er svaret nej. Noget er åbenlyst forkert, og eftersom koden kan køres, stikker fejlen dybere end én af de omtalte trivielle programmeringsfejl.

Så starter debuggingprocessen. Når man er gået i gang med at løse ligningerne i Matlab, er de (forhåbentlig!) så komplicerede, at de i et generelt tilfælde ikke kan løses analytisk. Men som min ph.d.-vejleder har lært mig, mens jeg var studerende, handler debugging om noget meget basalt: at reducere det problem, man er ved at løse, til det simplest mulige tilfælde, hvor man med sikkerhed kan forudsige nogle af de beregnede størrelser. Når man så i det simple tilfælde, finder at de udregnes til noget andet end det forventede, indikerer dette, hvor man kan finde fejl. Eller som Richard P. Feynman sagde:

“We are trying to prove ourselves wrong as quickly as possible, because only in that way can we find progress”

Sommetider trækker debugging imidlertid ud; jeg finder nogle fejl, men resultaterne bryder stadig nogle af de opstillede "sanity checks". Hvad gør man så? Så begynder man at kigge i sine noter, som det hele er baseret på. Er der nogen trivielle fortegns- eller lignende småfejl? Nå, det var der ikke. Har jeg på et mere grundlæggende plan misforstået noget? Det er svært umiddelbart at afgøre, men tanken er nærliggende, når nu alle småfejlene er udryddet.

Nogle gange har jeg misforstået noget grundlæggende, men i langt de fleste tilfælde er det én eller flere trivielle fejl, som ikke er blevet udryddet i den første fase, som viser sig som årsagen til de efterfølgende problemer. Ofte trivielle fejl af den subtile slags; et forkert element i en liste hentes, et forkert indeks benyttes, en variabel staves med forkert syntaks eller lignende. I retrospekt altid trivielle fejl, men som man ikke ser ved første øjekast.

Når dette er tilfældet, er jeg på den ene side, glad for at indse, at jeg ikke har misforstået noget fundamentalt. Men på den anden side ærgerlig over tiden, jeg har brugt på at rette noget, som jeg grundlæggende ikke har lært noget af. Så hvad kan man lære af den noble kunst at debugge? Grundlæggende handler det bare om ikke at lave fejl, men min erfaring siger mig, at det ikke kan udryddes. Måske handler det om at bruge mere tid, mens koden skrives, i bytte for at bruge mindre tid på at fejlsøge bagefter.

Hvad er jeres erfaringer med effektivt at skrive kode? Hvordan minimerer man den samlede tid, man bruger på at skrive, fejlsøge og optimere sin kode?

PS: Når de sidste fejl i en længerevarende debuggingproces endelig er udryddet, føler jeg mig altid som denne lille fyr.

Emner : Software
Jakob Rosenkrantz de Lasson er civilingeniør og ph.d. i nanofotonik fra DTU. Jakob bloggede fra 2012-2022 om forskning, fotonik og rumteknologi.
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først

Jeg har en klar fornemmelse af, at hvis jeg brugte mere tid på at læse min kode igennem, inden jeg kører det første gang (eller efter en rettelse), så ville jeg samlet set bruge mindre tid på udviklingen. Årsagen er, at selvom en kørsel tit kan fange de overfladiske trivielle fejl, så er der ikke voldsomt meget tid sparet ved denne metode fremfor at lave noget (meget) mere grundig kode inspektion til at starte med. Nogle gange må jeg erkende, at jeg ikke havde forstået de grundlæggende problematikker inden, jeg satte mig til at kode - endnu et udtryk for utålmodighed. Det vil en testkørsel aldrig finde ud af alligevel, mens det omvendte tit er tilfældet: kodeinspektion finder også de trivielle fejl. På den anden side må man heller ikke forklejne det feedback, man får, ved at forsøge at udtrykke en problemstilling i kode. Det sker ofte, at jeg først får klarhed over kompleksiteten, når jeg forsøger at beskrive den programmæssigt. Så mit råd er at forsøge sig frem til den rette balance, mens man er bevidst om de valg, man tager undervejs, samt de konsekvenser de får for udviklingsprocessen.

  • 0
  • 0

Peter Toft har for nyligt diskuteret problemet på Version2. Der er en masse gode råd både i selve blogindlæget og i kommentarene: http://www.version2.dk/blog/debugging-en-f...

Sørg for at få dokumenteret dine antagleser (dine sanity checks er er antagelser om resultatet) hele vejen igennem koden og følg antagelserne gennem hele koden. Lidt forsimplet, hvis problemet er multiplikation og du forventer et ikke-negativt resultat, så dokumenter at operanderne forventes at have samme fortegn.

Og med 'dokumenter' mener jeg selvfølgelig: Test for det i koden!

Hvis køretiden er kritisk for det færdige system, så bør de fleste udviklingssystemer tillade at springe denne slags antagelser over i produktionsmiljøet.

  • 0
  • 0

Tja min erfaring er at det går nemmest, hvis man går langsomt til værks. Dvs. Starte med at definere alle parametre man skal bruge øverst og i et afsnit for sig, og give dem meningsfulde navne, og herefter tage en ligning af gangen og se om den giver det rigtige resultat.

Skal man i gang med løkker og if sætninger så start med få iterationer, og se om det giver mening. indsæt evt. hjælpe tælle variable for at se om løkken kører det korrekte antal gange, eller om en if sætning overhovedet bliver aktiveret. Tjek desuden at en værdi der skal opdateres ved hver iteration ikke bliver nulstillet hver gang løkken startes forfra.

Skal man gange nogle store matricer sammen, så start med at tjekke hver matrice, med kommandoen "whos" og se om den har den rette størrelse, om værdierne i den giver mening osv.

Kig på matlab fejlmeddelelserne og lær dem at kende evt. vha. google. De giver ofte mere mening ved andet øjekast.

  • 1
  • 0

Når du laver dine sanity checks, så ikke kun lav dem på den endelige parameter. Sørg for at gemme alle parametre som du har et forhold til qua din viden.

Så plot dem alle sammen i et multiplot men som funktion af det samme. Stir lidt på det noget tid og se om det giver mening.

Jeg er ikke den store helt til at kode systematisk - Jeg prøver virkelig hårdt på at organisere min kode men gang på gang har jeg behov for at have unikke scenarier som kræver jeg sætter variabler som køres mod if sætninger eller case sætninger (Er der nogen som har navnet på "bogen" for folk som ikke studere datalogi?)...

Så for mig er det stort set altid i "if" og "case" sætningerne det går galt. I en god stor kode med mange af dem er det bare tæt på umuligt at teste om enhver mulig kombination nu også giver mening.

Og altså, også i indekser/variabler hvor det gang på gang lykkedes mig at overskrive variabler 10 linier længere nede - Den fejl opdager jeg dog altid når jeg plotter alle mine variabler og ser en pæn lige linie for pågældende...

  • 0
  • 0

"Afprøvning kan ikke vise, at et program er fejlfrit, kun at det har fejl".

Selv om dette ikke er 100% korrekt (hvis der kun er 17 forskellige mulige inputs og din test påviser, at programmet giver det rigtige resultat for disse 17 inputs, er det fejlfrit), viser det, at en afprøvning ikke kan stå alene som garanti for fejlfrihed.

Modsat er Knuths udsagn "Jeg garanterer ikke for programmets korrekthed, for jeg har ikke afprøvet det, kun bevist det korrekt". Selv om et korrekthedsbevis kunne synes som en garanti for korrekthed, så kan man lave fejl i beviset, og hvis den bevismetode, man bruger, ikke tager højde for for eksempel nontermination, så er beviset ikke en komplet garanti, selv om det er korrekt.

Så den bedste løsning er at kombinere grundig afprøvning og logiske ræsonnementer. Og måske smide noget statisk analyse (typecheck, analyse af pegere osv.) efter programmet.

  • 0
  • 0

Jeg tilslutter mig Kim Hyldgaards indlæg: en iterativ tilgang vil gøre det nemmere at fange fejl tidligt. Og som du har konstateret, så laver vi altid fejl i udviklingen - kunsten er at fange dem tidligt så de koster mindst muligt (tid, ressourcer, ...) Og det uanset om det er udviklingsprojekter med mange udviklere på eller mindre opgaver. Et problem man kan støde ind i mht. iterativ opbygning af koden er 'Jamen, det kan ikke rigtig testes før hele programmet er færdig'. I så tilfælde er problemet (typisk) at der mangler design på koden, altså opdeling i mindre objekter som i sig selv løser en overskuelig opgave.

Derudover har jeg oplevet at pair programming er fantastisk til at finde fejl meget tidligt, både i design og kode. For nogle kan det godt være en barriere at skulle over før man indlader sig på pair programming, men resultatet er bedre design, bedre kode, vidensdeling. Hvis jeg mener at kunne løse en opgave på x timer, så er jeg næppe færdig efter x timer. Der mangler nemlig 0.2x med efterfølgende review, og måske 0.5x med debugging. Altså er den samlede tid brugt på opgave 1.9x (med 1 reviewer). Hvis nu revieweren havde været med fra starten og vi kan gøre opgaven færdig på samme tid som jeg estimerede har vi altså bedre design, bedre kode og vidensdeling uden det har kostet ekstra!! (ja, jeg ved det afhænger af mine 0.2x og 0.5x men de er vist ikke helt skæve)

  • 0
  • 0

Må jeg have lov at slå endnu et slag for testdrevet udvikling. Jeg bruger det ikke altid, men jeg kan uden tøven sige, at af de kodestumper og programmer jeg har produceret hen over årene, er jeg allermest tilfreds med (og stolt af) dem, hvor jeg har skrevet testkode først. Vel at mærke testkode, som fejler, indtil den kode jeg arbejder på (lad os kalde den produktionskoden), gør det den skal. Dette forudsætter, at jeg først skriver en grundig testkode, som så vidt muligt aftester alle hjørner og afkroge af min produktionskode. Nogen gange synes jeg, at jeg bruger lang tid på at skrive testkoden. Til gengæld oplever jeg ofte, at det går hurtigt med at få lavet produktionskoden i en tilfredsstillende kvalitet.

Derudover kan jeg anbefale: - Mantraet: "Kopieret kode er for meget kode." Når jeg fanger mig selv i at klippe og kopiere en kodedel, laver jeg en funktion med denne del. Dette forhindrer, at jeg glemmer at rette variablenavne og lignende i den kopierede kode. - At skrive funktioner så små som muligt og så sammensætte sit program af disse funtioner. For mig har det fordelene, at jeg kan fokusere på en lille del af det samlede program, at det er nemmere at teste, verificere og debugge en enkel, lille funktion frem for en stor, kompliceret en. - At anvende debuggere, code-analysere, profilere og hvad der ellers er af værktøjer.

Specifikt for Matlab kan jeg helt klart tilråde brugen af den medfølgende debugger og af MLint (også en del af Matlabdistributionen).

  • 0
  • 0

Jeg er enig med mange af dine synspunkter, men jeg synes ik, at debugging altid er spildt. Selvom det viser sig at være en tanketorsk, så har man (ofte!) undervejs i sin debugging været nødt til at overveje, om man har misforstået noget fundamentalt. Dermed ik sagt at, at det ik er irriterende, men jeg vælger alligevel altid at tro, at jeg har lært noget ved at debugge..

  • 0
  • 0

Jeg er enig med mange af dine synspunkter, men jeg synes ik, at debugging altid er spildt. Selvom det viser sig at være en tanketorsk, så har man (ofte!) undervejs i sin debugging været nødt til at overveje, om man har misforstået noget fundamentalt. Dermed ik sagt at, at det ik er irriterende, men jeg vælger alligevel altid at tro, at jeg har lært noget ved at debugge..

Jeg kan et stykke hen ad vejen godt følge dig - og så alligevel ikke helt. For hvis alting var velforstået fra begyndelsen, og problemet alene skyldtes en triviel programmeringsfejl, så har fejlsøgningsprocessen ofte mere bidraget til forvirring end klarhed.

Sommetider indser jeg dog også nogle ting undervejs i debuggingprocessen - hvilket kan være småting eller mere dybdegående aspekter - og så er det naturligvis en lærerig proces. Der er bare flere af den første slags debuggingprocesser, er min oplevelse.

  • 0
  • 0

Jeg synes aldrig, at jeg er blevet forvirret af at debugge, for det vil betyde, at jeg ikke rigtigt har forstået fejlen ordentligt. Men vil nok godt give dig ret i, at udbyttet i forhold til antal timer brugt på debugging måske ikke altid falder ud til den gode side..

  • 0
  • 0

Jeg har observeret, at jeg ofte finder bugs, jeg aftenen forinden har brugt timer på at prøve at lokalisere, hurtigt morgenen efter: https://twitter.com/Jakobrdl/status/501640...

For at undgå at skulle starte helt forfra morgenen efter, skriver jeg, før jeg går hjem om aftenen, så mange observationer og undersøgelser, jeg kan komme i tanke om, ned; det hjælper til hurtigt at komme i gang igen om morgenen.

Dvs. et råd er: Skriv ned, og gå så hjem og slap af, få noget mad og en god nats søvn - og så løser du sikkert hurtigt problemet, når du næste dag kommer med et friskt hoved og syn på tingene.

  • 0
  • 0
Bidrag med din viden – log ind og deltag i debatten