Debuggen


Debuggen is het vinden en oplossen van problemen (bugs) in code. Het kunnen debuggen van code is een van de belangrijkste vaardigheden van een programmeur.

Veel voorkomende bugs

Er zijn heel veel soorten bugs. Om het jullie gemakkelijker te maken pakken we er een paar soorten uit.


Typo's

Typo's zijn bugs waarbij een typfout is gemaakt. Vaak komen deze bugs doordat je even snel iets typt en niet goed oplet wát je eigenlijk opschrijft. Het kan ook gebeuren dat je een functie of variabele een onhandige naam hebt gegeven. De oplossing voor deze bug is het maken van minder typo's. Let er dus altijd op dat je duidelijke namen gebruikt, consistent blijft en oplet terwijl je programmeert (als je moe bent maak je meer typo's). Een andere handige oplossing is het toevoegen van een spelling checker extensie aan je editor (Bijvoorbeeld Code spell checker voor VSCode).

// ------------ Bugs ------------ //
// Heel veel typo's
let ballSize = 50;
functoin draw() {
    elipse(witdh/2, hieght/2, ballsize, balSize);
}

// ---------- Oplossing ---------- //
let ballSize = 50;
function draw() {
    ellipse(width/2, height/2, ballSize, ballSize);
}

Datatypen

Binnen een programmeertaal werk je met verschillende datatypen. Jullie hebben al kennis gemaakt met Strings (tekst), Integers (nummers) en Booleans (true of false), later komen hier nog veel meer bij. Het is dan ook mogelijk dat je een ander datatype uit een functie of bewerking krijgt dan je eigenlijk had verwacht. Een bekend voorbeeld is het toevoegen van een integer getal aan een string van een getal. Je zou verwachten dat hij de twee getallen bij elkaar optelt, terwijl hij in werkelijkheid een nieuwe string bouwt van de twee getallen achter elkaar. Let er dus altijd goed op dat je begrijpt wat er uit een functie komt!

"8" + 1 == "81" // Maar je zou hier 9 hebben verwacht

Scope

Scope bugs zijn problemen waarin een functie niet bij een variabele kan. De scope van een functie is het gebied waarbinnen hij variabelen kan vinden en bewerken. Deze scope werkt als een hiërarchische structuur, variabelen die je in de hoofd scope aanmaakt kunnen gebruikt worden door functies in die scope. Andersom bestaan variabelen die in een bepaalde functie worden aangemaakt niet in de hoofdscope.

// ------------ Bugs ------------ //
// Als je een variabele maakt in de ene functie, kun je er met de andere niet bij.
function setup() {
    let variabele = 10;
}
function draw() {
    console.log(variabele); // ERROR: variabele is not defined
}

// ---------- Oplossing ---------- //
// De variabele is gedefinieerd in de hoofd scope. Hierdoor mogen zowel de functie
// setup, als de functie draw de variabele aanpassen en gebruiken.
let variabele = 10;
function setup() {
    variabele = 20;
}
function draw() {
    console.log(variabele); // -> 20
}

Volgorde

De volgorde van je code is natuurlijk erg belangrijk. Code wordt van boven naar beneden uitgevoerd. Je moet er daarom op letten dat alles op de juiste plek staat. Als je niet goed weet waar iets moet staan, teken dan een flow-chart. Daarmee maak je visueel duidelijk wat op welke plek moet gebeuren!

console.log(variabele) // ERROR: Variabele bestaan nog niet
let variabele;
function setup() {
    variabele = variabele * 10 // Error: Variabele is leeg (of undefined)
}
function draw() {
    variabele = 10;
    console.log(variabele) // Hier is hij pas '10'
}

Vinden van een bug

Er zijn een hoop strategieën voor het vinden van een bug. De voorbeelden die wij hier geven hebben niet allemaal officieel die naam, maar geven wel goed aan hoe het precies werkt.

Error tracer

Vrijwel iedere programmeertaal heeft een error tracer. Dit is een systeem wat bij een error aangeeft wat het probleem was. Sommige talen geven ook aan op welke plek het probleem (ongeveer!) staat. In het geval van JavaScript kun je deze vinden in de console of inspector van je browser. Deze kun je vaak openen met control + shift + i op Windows of cmd+ shift + i op Mac.

De error message

Als je de console eenmaal gevonden hebt staat er soms een enorme error melding. Het heeft vaak geen nut om de volledige melding in Google te zoeken, vaak is het eerste deel al genoeg. Als je een pad in de error hebt staan kun je deze ook vaak weglaten, deze is namelijk afhankelijk van jouw computer. Voor oplossingen kun je meestal zoeken op de volgende sites:

Blindstaren

Een andere oplossing voor het vinden van bugs is blindstaren. Dit betekend dat je alles gaat lezen van begin tot eind tot je eindelijk een keer de fout vindt. Voor kleine stukken code is dit een erg effectieve methode. Je analyseer wat de code doet en wat de werking is. Voor grotere projecten (eigenlijk zijn deze opdrachten al te groot) is deze vorm van debugging minder effectief.

Devide and Concuer

Een vierde methode is Devide en Concuer. Hierbij voer je steeds kleine stukjes code uit om te kijken of het probleem zich daar voordoet. Deze techniek is goed bruikbaar in systemen die geen error tracers hebben.

Print debugging

Als je niet zeker weet of de berekeningen kloppen kun je met print debugging gaan werken. Hierbij zet je op veel plaatsen een print functie (console.log) waarmee je kijkt of de berekeningen juist worden uitgevoerd. Haal achteraf deze print functies wel weer weg, anders wordt je code erg lang en rommelig.

Peer review

Een laatste strategie is voor sommigen nog wel de moeilijkste. Sommige bugs zijn zo geniepig dat je er constant overheen leest, of niet realiseert dat het überhaupt een fout is. In dit geval kun je je code laten checken door iemand anders. De ander heeft een frisse blik en vind de bug vaak binnen een minuut. Het is in dit geval wel handig om je code goed opgeruimd te houden, anders moet de ander eerst de rommelige code uitspitten!

Voorkomen

Het is onvermijdelijk dat je bugs krijgt, zelfs de programmeurs bij Apple en Microsoft maken geregeld typo's en denkfouten. Toch zijn er bepaalde manieren om de kans op bugs te verminderen.

Syntax

Veel programmeurs werken in meerdere programmeer talen. Het komt dan ook voor dat deze programmeer talen door elkaar worden gehaald. Een voorbeeld is het opvragen van de lengte van een lijst. In iedere taal is dit op een andere manier geïmplementeerd. De oplossing is in dit geval om simpelweg heel veel te programmeren. Hierdoor maak je de taal eigen en maak je minder fouten. Uiteindelijk hoef je niet een meer te Googlen hoe je de lengte opvraagt!

Variabel namen

Een manier om typo's te voorkomen is het gebruiken van goede variabel en functie namen. De naam van een functie heeft net zo veel betekenis als de waarde die in de variabele zit. Gebruik dan ook voor een x positie van een bal niet de variabele x, maar meer iets als ballPosX. Andersom moet je variabelen ook weer niet te lang maken, deHorizontalePositieVanDeEersteBal is ook niet echt handig!

Het is ook verstandig vast te houden aan bepaalde normen voor het geven van namen. In vrijwel alle programmeertalen kunnen geen spaties worden gebruikt in variabelnamen. Daarom zijn er verschillende standaarden bedacht om toch iets van woorden te kunnen aangeven in varabelnamen. Voorbeelden zijn camelCasing (eerste woord heeft een kleine letter, alle woorden daarna beginnen met een hofdletter), PascalCasing (ieder nieuw woord heeft hoofdletter), snake_casing (spaties worden vervangen door een underscore _ ). Binnen JavaScript is het gebruikelijk om te werken met camelCasing voor variabelnamen en functies. Classes (krijgen jullie later) worden in JavaScript geschreven met PascalCasing.

Opsplitten

Soms is het nodig om hele grote berekeningen te doen. Dit kan dan zo uitgebreid worden dat je niet meer weet wat de berekening nou eigenlijk doet. Het is dan ook handig om in deze gevallen de berekening op te splitsen in meerdere regels. Je kan hier ook haakjes () gebruiken om stukken berekening wiskundig van elkaar te scheiden. Hierdoor weet je beter wat de berekening precies doet.

// Hele lange berekening waarvan niet goed te volgen is wat het doet
if ( mouseX > this.posX && mouseX < this.posX + this.rectSize && mouseY > this.posY && mouseY < this.posY + this.rectSize) {
    console.log("De muis is ingedrukt op " + mouseX + ", " + mouseY);
}

// Overzichtelijke berekening
let insideXBounds = (mouseX > this.posX) && (mouseX < this.posX + this.rectSize);
let insideYBounds = (mouseY > this.posY) && (mouseY < this.posY + this.rectSize);
if (insideXBounds && insideYBounds) {
  console.log("De muis is ingedrukt op " + mouseX + ", " + mouseY);
}

Overzicht

Overzicht van je code is heel belangrijk. Goed geordende code is makkelijker te lezen voor jezelf, en voor een ander die met je meekijkt. Helemaal als je projecten met meerdere programmeurs gaat doen is het belangrijk dat je code goed gestructureerd en geordend is. Zorg er daarom altijd voor dat je de goede indents (taps) gebruikt: als je een { typt ga je een indent naar rechts, met een } juist weer naar links. Hiermee kun je in één oogopslag zien wat tot welke scope hoort!. Naast het kloppend maken van de intens is het ook belangrijk om goede comments te plaatsen in je code. Hiermee kun je in 'mensen taal' uitleggen wat een functie precies doet, of wat er in een variabele zit. Dit lijkt onbelangrijk, je weet immers op dit moment wat je programma doet, maar als je over een tijd nogmaals naar je code kijkt weet je vaak niet meer wat het precies doet!