Debuggen in C#
- Inleiding
- Een Console App resultaten laten tonen
- Debug Object
- Trace Object
- Trace Listener
- Debuggen
- Assert
- Slot
- Voorbeeldprogramma
Inleiding
Het oplossen van storingen bestond bij de eerste computers uit het verwijderen van klein ongedierte (bugs) dat zich ophield tussen de contacten van de relais. Debuggen staat heden ten dage voor het opsporen en het verhelpen van fouten in een computerprogramma en daarbij kunnen Debug en Trace objecten van nut zijn.
De eerste computers waren uitgerust met schakelaars die door elektromagneten werden bediend (relais). Deze relais waren nog niet ingekapseld in een behuizing waardoor motten (bugs), vliegen en ander klein ongedierte zich tussen de contacten van het relais konden nestelen.
De ongewenste huisdieren veroorzaakten vervolgens de nodige storingen en het oplossen van de storing bestond uit het vinden en het verwijderen van de ongewenste gasten. Men moest dus “debuggen” om de storingen opgelost te krijgen.
De term “debuggen” wordt heden ten dage nog steeds gebruikt als zijnde het opsporen en het verhelpen van fouten in een computerprogramma en met een “bug” bedoelt men de op te sporen fout. Wat gaan we doen als een programma niet werkt zoals verwacht? Door welke “bug” wordt dat veroorzaakt en hoe gaan we die “bug” vinden? We kunnen dat doen door het programma te laten tonen wat het doet en waarmee.
Een Console App resultaten laten tonen
Op een Windows computer maken we met Visual Studio een Console App (met de naam “Bug“):
De Solution is aangemaakt en we willen dat resultaten e.d. in een pop-up worden getoond. We leggen vanuit de Solution Explorer met Add Reference een link naar een assembly waarmee een pop-up kan worden getoond:
We gaan gebruik maken van de functionaliteit in de PresentationFramework assembly:
En we schrijven dit programma waarin we een deling doen:
try
{
// De deling
uitkomst = teller / noemer;
// het resultaat
MessageBox.Show(
teller + " / " + noemer + " = " +
uitkomst, "Pop-up");
Console.WriteLine("{0} / {1} =
{2}", teller, noemer, uitkomst);
// Gezien?
Console.WriteLine(
"Gezien? Hit any key to continue...");
}
Als de deling fout gaat dan laten we dat zien in de exception handler. We laten zien waarmee we de deling hadden willen doen en wat er fout gaat:
catch (Exception ex)
{
// Op scherm
Console.WriteLine("Iets gaat fout...\r\n");
// Op scherm
Console.WriteLine("- teller: {0}", teller);
Console.WriteLine("- noemer: {0} \r\n", noemer);
Console.WriteLine("Fout:" + ex.Message + "\r\n");
// Pop-up
MessageBox.Show(
"Iets gaat fout..." + "\r\n" +
"- teller: " + teller + "\r\n" +
"- noemer: " + noemer + "\r\n" +
"fout: " + ex.Message,
"pop-up.");
// Gezien?
Console.WriteLine("Gezien? Hit any key to continue...");
Console.ReadKey();
}
We gaan een deling door nul doen en we zien op het scherm en in een pop-up dat het fout gaat. De bug is in dit geval de noemer. De noemer staat nog steeds op nul en “delen door nul is flauwekul“:
Debug Object
Tot dusver hebben we het één en ander op het scherm getoond met Console.WriteLine(s). Ook hebben we dingen in een pop-up getoond en daarvoor hebben we de MessageBox.Show gebruikt. Op zich niks mis met deze aanpak, maar op een gegeven moment wil je de meldingen niet meer laten doen verschijnen. Je moet dan de desbetreffende Console.WriteLines(s) en MessageBox.Shows(s) uitzetten.
We kunnen ook een debug object gebruiken om het programma te laten tonen wat het doet en waarmee. Een debug object heeft weer haar eigen plekje voor het tonen van haar output. We vervangen in de exception handler de Console.WriteLine(s) door een aantal Debug.WriteLine(s) :
catch (Exception ex)
{
// Op scherm
Console.WriteLine("Iets gaat fout...\r\n");
// Debug object
Debug.WriteLine("--------------------------------");
Debug.WriteLine("* Debug object *");
Debug.WriteLine("Input:");
Debug.Indent();
Debug.WriteLine("- teller: {0}", teller);
Debug.WriteLine("- noemer: {0}", noemer);
Debug.Unindent();
Debug.WriteLine("Fout:");
Debug.Indent();
Debug.WriteLine(ex.Message);
Debug.Unindent();
Debug.WriteLine("--------------------------------");
// Gezien?
Console.WriteLine("Gezien? Hit any key to continue...");
Console.ReadKey();
}
En we zien dat de melding “Iets gaat fout…” nog steeds op het scherm verschijnt en dat komt doordat we een Console.WriteLine hebben gebruikt. Het resultaat van de Debug.WriteLine(s) krijgen we daarentegen niet te zien op het scherm, maar in de Visual Studio Output Window:
Een debug object is alleen maar bedoeld voor de ontwikkelomgeving en in de ontwikkelomgeving is meestal een Output Window aanwezig waarin een debug object haar output kan tonen.
Trace Object
De C# broncode zal uiteindelijk gecompileerd worden naar een build. Een build is in het geval van een Windows Console App (meestal) een .exe bestand dat direct uitgevoerd kan worden door vanuit de File Explorer erop te dubbelklikken.
We zien in de IDE (Integrated Development Environment) van Visual Studio dat we bij het maken van een build de opties Debug en Release hebben. De Debug optie is bedoeld voor het maken van een build voor de ontwikkelomgeving. De Release optie is bestemd voor het maken van een build voor een productieomgeving. De build voor de productieomgeving zal uiteindelijk verschillen van de build voor de ontwikkelomgeving. Bij de build voor de productieomgeving ligt de nadruk op performance waarbij overhead zoals debug objecten niet wordt meegenomen.
Maar wat als het programma draait in een productieomgeving en het programma doet het niet of het doet niet datgene wat we verwachten? Ook dan willen we detail informatie hebben over wat het programma doet zodat we aan de hand daarvan naar de bug kunnen zoeken.
Een debug object wordt niet meegenomen naar de release build en een Visual Studio Output Window zal meestal ook niet aanwezig zijn in een productieomgeving. We kunnen voor productieomgeving toestanden gelukkig gebruik maken van een Trace object.
Trace Listener
Voor een trace object moeten we aangeven waar de output naar toe moet. De output mag in dit voorbeeld gewoon naar het scherm en we gebruiken daarvoor een ConsoleTraceListener:
// Trace object - waar moet de output naar toe?
TraceListener consoleTraceListener =
new ConsoleTraceListener();
Trace.Listeners.Add(consoleTraceListener);
Je kan de output ook naar een bestand/medium sturen en C# biedt een ruime keus aan te gebruiken trace listeners. Trace listeners die je kunt gebruiken:
– de XMLWriterTraceListener
– de TextWriterTraceListener
– de EventLogTraceListener
– de DelimitedTextTraceListener
– de EventSchemaTraceListener
Wij doen in de exception handler een aantal Trace.WriteLine(s) en het interesseert ons verder niet waar de output naar toe gaat. Dat wordt geregeld door de eerder gekozen trace listener.
catch (Exception ex)
{
// Op scherm
Console.WriteLine("Iets gaat fout...\r\n");
// Trace object - waar moet de output naar toe?
TraceListener consoleTraceListener =
new ConsoleTraceListener();
Trace.Listeners.Add(consoleTraceListener);
// Trace object
Trace.WriteLine("--------------------------------");
Trace.WriteLine("* Trace object *");
Trace.WriteLine("Input:");
Trace.Indent();
Trace.WriteLine("- teller: " + Convert.ToString(teller));
Trace.WriteLine("- noemer: " + Convert.ToString(noemer));
Trace.Unindent();
Trace.WriteLine("Fout:");
Trace.Indent();
Trace.WriteLine(ex.Message);
Trace.Unindent();
Trace.WriteLine("--------------------------------");
// Gezien?
Console.WriteLine("Gezien? Hit any key to continue...");
Console.ReadKey();
}
En we zien dat de output van het trace object naar het scherm wordt gestuurd omdat we een ConsoleTraceListener hebben gebruikt:
Debuggen
Debug en trace objecten, heel leuk en je zal met de eerder besproken stack trace ongetwijfeld veel aanwijzingen krijgen over de plek waar de bug zich kan ophouden. Maar het zijn en blijven aanwijzingen en voor het echte zoekwerk hebben we toch een debugger van een IDE nodig waarmee we het betere debug werk kunnen doen.
Zie onderstaande afbeelding hoe we kunnen debuggen in Visual Studio. Je kunt break points plaatsen vanaf waar je het debuggen kunt laten beginnen. En je kan, gewapend met een Step Into (F11) en een Step Over (F10) beginnen met je jacht op de bug..
Assert
De Assert methode is de moeite van het vermelden waard. Zowel de debug als de trace object hebben deze methode en je kan de Assert methode toepassen als een extra controlemiddel.
In het voorbeeld in deze posting doen we een deling en als de noemer nul is dan heeft het eigenlijk geen zin om verder te gaan. We voegen derhalve wat Assert(s) toe aan de code:
// Assert voor Debug object
Debug.Assert(noemer != 0,
"Debug.Assert:Delen door nul is flauwekul.");
// Assert voor Trace object
Trace.Assert(noemer != 0,
"Trace.Assert: Delen door nul is flauwekul.");
// de deling
uitkomst = teller / noemer;
En we krijgen, als de noemer nul is deze pup-up waarbij de optie aanwezig is om door te gaan (ignore), maar eigenlijk heeft dat geen zin, want delen door nul blijft flauwekul:
Slot
Programmeren en debuggen; ze zijn onlosmakelijk met elkaar verbonden en debuggen kan zeer uitdagend en frustrerend zijn. Met name als het een door meerdere programmeurs uitgewoond stuk programmatuur betreft waar ongebreideld steeds meer aan is toegevoegd waardoor de programmatuur een verschrikkelijk (spaghetti) monster is geworden:
Alle hulpmiddelen ten spijt, voor het echte zoekwerk hebben we toch een debugger van een IDE nodig waarbij we vanuit een ontwikkelomgeving het betere debug werk kunnen doen.
Hopelijk ben je met deze posting weer wat wijzer geworden en ik hoop je weer terug te zien in één van mijn volgende blog posts. Wil je weten wat ik nog meer over C# heb geschreven? Hit the C# button…
Voorbeeldprogramma
using System;
using System.Diagnostics;
using System.Windows;
namespace Bug
{
class Program
{
static void Main(string[] args)
{
int teller = 10;
int noemer = 0;
int uitkomst = 0;
try
{
// Assert voor Debug object
Debug.Assert(noemer != 0,
"Debug.Assert:Delen door nul is flauwekul.");
// Assert voor Trace object
Trace.Assert(noemer != 0,
"Trace.Assert: Delen door nul is flauwekul.");
// De deling
uitkomst = teller / noemer;
// Het resultaat
MessageBox.Show(teller + " / " + noemer + " = " +
uitkomst, "Pop-up");
Console.WriteLine("{0} / {1} = {2}",
teller, noemer, uitkomst);
// Gezien?
Console.WriteLine(
"Gezien? Hit any key to continue...");
Console.ReadKey();
}
catch (Exception ex)
{
// Op scherm
Console.WriteLine("Iets gaat fout...\r\n");
// Debug object
Debug.WriteLine("--------------------------------");
Debug.WriteLine("* Debug object *");
Debug.WriteLine("Input:");
Debug.Indent();
Debug.WriteLine("- teller: {0}", teller);
Debug.WriteLine("- noemer: {0}", noemer);
Debug.Unindent();
Debug.WriteLine("Fout:");
Debug.Indent();
Debug.WriteLine(ex.Message);
Debug.Unindent();
Debug.WriteLine("--------------------------------");
// Trace object - waar moet de output naar toe?
TraceListener consoleTraceListener =
new ConsoleTraceListener();
Trace.Listeners.Add(consoleTraceListener);
// Trace object
Trace.WriteLine("--------------------------------");
Trace.WriteLine("* Trace object *");
Trace.WriteLine("Input:");
Trace.Indent();
Trace.WriteLine("- teller: " + Convert.ToString(teller));
Trace.WriteLine("- noemer: " + Convert.ToString(noemer));
Trace.Unindent();
Trace.WriteLine("Fout:");
Trace.Indent();
Trace.WriteLine(ex.Message);
Trace.Unindent();
Trace.WriteLine("--------------------------------");
// Op scherm
Console.WriteLine("- teller: {0}", teller);
Console.WriteLine("- noemer: {0} \r\n", noemer);
Console.WriteLine("Fout:" + ex.Message + "\r\n");
// Pop-up
MessageBox.Show(
"Iets gaat fout..." + "\r\n" +
"- teller: " + teller + "\r\n" +
"- noemer: " + noemer + "\r\n" +
"fout: " + ex.Message,
"pop-up.");
// Gezien?
Console.WriteLine(
"Gezien? Hit any key to continue...");
Console.ReadKey();
}
}
}
}