Ü vbg.vbnet.beginner | Foutafhandeling binnen Visual Basic .NET |
Fouten in software zijn onwenselijk, maar bijna niet te voorkomen. Er zijna namelijk zaken waar uw software geen invloed op uit kan oefenen. Het verbreken van een netwerkverbinding is een voorbeeld hiervan. Waar u wel zelf invloed op heeft is het omgaan met fouten of onverwachte gebeurtenissen. Visual Basic .NET maakt dit met haar ‘structured exception handling’ een stuk eenvoudiger.
Goede sofware kenmerkt zich onder meer door het feit dat het niet telkens vast loopt en dat er daardoor data verloren gaat. Helaas ziet men regelmatig software die totaal geen code voor foutafhandeling bevat, laat staan dat er fouten gelogd worden of iets dergelijks. Bij het minste of geringste ‘klapt’ de applicatie doordat we aan het einde van een recordset zijn belandt of doordat er ‘toevallig’ toch een Null waarde in de database zat. Simpele voorbeelden van praktijksituaties waar we als programmeur dagelijks mee van doen hebben.
Unstructured Exception Handling Met Visual Basic 6 kunt eenvoudig op bovenstaande gebeurtenissen anticiperen en er voor zorgen dat òf een bepaald gedeelte van de code niet wordt uitgevoerd òf dat de fout wordt afgevangen. Het afvangen van een fout binnen Visual Basic 6 mag dan wel niet zo lastig zijn; wat wel lastig is, is de plek wáár de fout moet worden afgevangen en vervolgens welke code er wordt uitgevoerd. Binnen Visual Basic 6 kunt u foutafhandeling toevoegen door het On Error statement. Dit statement kan op twee manieren worden gebruikt, namelijk als On Error Goto – en als On Error Resume Next statement. Waarbij de laatste variant er voor zorgt dat er geen melding van wordt gemaakt. Met het On Error Goto statement kunt u de fout afvangen en met behulp van het Error object is er meer informatie over de fout te verkrijgen. Dit concept moet u bekend voorkomen wanneer u zelf al eens foutafhandeling heeft geïmplementeerd binnen uw Visual Basic 5 of 6 applicatie. Deze aanpak heeft toch een aantal grote nadelen en beperkingen. Code is lastig te debuggen wanneer er procedures worden aangeroepen waarbij de één juist wel en de andere geen ‘errorhandler’ hebben. Persoonlijk vind ik het feit dat sommige code totaal niet wordt uitgevoerd wanneer er naar een errorhandler in een andere procedure wordt gesprongen, veel erger. Bekijkt u de code eens in Listing 1 en bepaal welke messageboxen en in welke volgorde deze worden getoond. Als ik u vertel dat alleen ‘A1’, ‘B1’, ‘C1’ en ‘A3’ wordt getoond, begrijpt u gelijk wat ik bedoel. Daarnaast is het een probleem dat verschillende programmeertalen geheel anders omgaan met fouten of uitzonderingen. Een programma geschreven in C++ weet zich geen raad met een Visual Basic fout. Het ‘praat’ immers een andere taal. Hoewel Visual Basic .NET omwille van backward compability dit concept nog wel ondersteunt, is er een betere manier om fouten te onderscheppen en af te handelen: ‘Structured Exception Handling’.
Listing 1 | Unstructured Error Handling in VB6 Sub cmdTest_Click() On Error GoTo PROC_ERR MsgBox "A1" Me.Caption = FormateerBreuk(3, 0) MsgBox "A2" Exit Sub PROC_ERR: MsgBox "A3 : " & Err.Description End Sub Function FormateerBreuk(ByVal intTeller As Integer, _ ByVal intNoemer As Integer) As String Dim dblBreuk As Double MsgBox "B1" dblBreuk = BerekenBreuk(intTeller, intNoemer) FormateerBreuk = Format$(dblBreuk, "0.00") MsgBox "B2" End Function Function BerekenBreuk(ByVal intTeller As Integer, _ ByVal intNoemer As Integer) As Double MsgBox "C1" BerekenBreuk = intTeller \ intNoemer MsgBox "C2" End Function
Het gebruik van foutafhandeling binnen Visual Basic 6 kan dus voor verwarring zorgen en niet eens zozeer door bovenstaand voorbeeld. Stelt u zich eens voor dat uw procedure veel langer is. U kunt testen op specifieke fouten binnen de errorhandler, door te testen op Err.Number, maar een buitenstaander ziet niet direct op welke gedeelte van de bovenstaande code het betrekking heeft.
Exceptions Hoewel u binnen Visual Basic .NET nog steeds gebruik kunt maken van On Error Goto .. en Resume Next statements, raadt men dit af. Visual Basic .NET biedt u de mogelijk om gebruik te maken van structured exception handling. Exceptions zijn objecten die u, tijdens het ‘runnen’ van uw applicatie, melding kunnen geven van bepaalde uitzonde-
| VB Magazine Online | 2004 – 01/02 | 1/6
Ü vbg.vbnet.beginner | Foutafhandeling binnen Visual Basic .NET |
ringen of fouten. De eigenschappen van het Exceptionobject komen sterk overeen met het Err object van Visual Basic .NET en in de verte ook nog wel die van Visual Basic 6.
een 0; dit zal leiden tot het delen door 0, wat vervolgens weer leidt tot een exception. We onderscheiden: Try, Catch, Finally en End Try. Listing 2 | Exceptionhandling (VB.NET)
Property
Omschrijving
HelpLink - RW
Bepaal URL of URN naar het helpbestand voor de exception of stel deze in. Een voorbeeld kan zijn: file://c:/vbg/mijnapp.htm#error_10
InnerException - RO
Bepaal de interne verwijzing van de exception.
Message - RO
Bepaal de omschrijving van de exception. Dit komt min of meer overeen met Err.Description.
Source - RW
Bepaal de bron (naam van de applicatie of component) die de exception veroorzaakt of stel deze in. Dit komt min of meer overeen met Err.Source.
StackTrace - RO
Bepaal de string die de aanroepketen beschrijft van waar de exception is opgetreden tot waar de exception wordt afgevangen.
TargetSite - RO
Bepaal de informatie over de methode waarin de exception optrad. De TargetSite property is een ook een object, die men via properties kan uitlezen.
Sub btnTest_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles _ btnTest.Click Me.Text = BerekenBreuk(3, 0) End Sub Function BerekenBreuk(ByVal intTeller As Int16, _ ByVal intNoemer As Int16) As Decimal Try Return intTeller \ intNoemer Catch objException As System.SystemException MessageBox.Show(objException.Message) Finally MessageBox.Show("Hier komen we altijd") End Try End Function
De exceptionhandling geldt voor alle code die tussen Try en Catch staat. U mag meerdere Catch-regels toevoegen en u mag het blok: Finally eventueel achterwege laten. Bovenstaande code leidt in eerste instantie tot het volgende dialoogkader.
Tabel 1 | Exception class (RW=Read/Write, RO = Read only)
Hoewel het Exception object op het eerste gezicht wel iets weg heeft van het alom bekende Err object van vorige versies van Visual Basic, zijn er toch een aantal basale verschillen: • Exceptions kunnen proces- en machinegrenzen overschrijden; • Alle exceptions worden op dezelfde wijze afgehandeld, ongeacht de taal waarin deze optreedt of wordt afgehandeld; • Alle exceptions zijn gebaseerd op de classes: System.Exception of System.ApplicationException; • Afhankelijk van uw instellingen kunnen fouten worden herleid tot regelniveau en de aanroepketen bepaald met behulp van de StackTraceproperty. De gedachte achter exceptions is dat deze min of meer zijn ingebouwd, dan een proces dat achteraf nog moet worden geregeld. Laten we het gebruik van exceptions eens nader bekijken. In dit voorbeeld gaan we de BerekenBreuk() implementeren en we roepen het wederom aan met als tweede parameter
Afbeelding 1 | Exception.Message
Try… Catch… Finally … End Try We hebben nu gezien hoe men een eenvoudige exceptionhandler kan toevoegen aan een procedure. In basis bestaat het dus uit minimaal een Try, Catch en End Try. U kunt meerdere Try … End Try blokken opnemen binnen een procedure; hierover later meer. Try In dit gedeelte plaatst u code die u wilt controleren op exceptions. Code in dit blok noemen we ook wel protected code. Als u dit achterwege laat wordt de exception doorgegeven aan de aanroepende procedure en wordt er daar gekeken of er wel een exceptionhandler aanwezig is. Dit proces van ‘opborrelen’ van exceptions gaat net zo lang door totdat het een procedure tegenkomt die wel een exceptionhandler heeft. Komt het uiteindelijke geen enkele procedure tegen die de exception afvangt, dan zal de .NET runtime (CLR) de fout tonen. Het is uw taak om dit te voorkomen, omdat de eindgebruiker niet op zulke gedetailleerde informatie zit te wachten. Daarnaast kan de gebrui-
| VB Magazine Online | 2004 – 01/02 | 2/6
Ü vbg.vbnet.beginner | Foutafhandeling binnen Visual Basic .NET |
ker, en dat is waarschijnlijk nog veel erger, er voor kiezen om door te gaan met code waarvan de ‘state’ op dat moment op zijn zachtst gezegd zeer twijfelachtig is.
fout altijd al wordt afgevangen, doordat elke fout al tot dit algemene type behoort. U zult de volgorde dus van specifiek naar generiek moeten opbouwen om de fout optimaal te kunnen afvangen. System.Exception en System.ApplicationException Zoals u reeds heeft kunnen lezen zijn sommige exceptions afgeleid van System.Exception en andere weer van System.ApplicationException. Exceptions kunnen ook weer een afgeleide van een afgeleide zijn.
Afbeelding 3 | DivideByZeroException Class Afbeelding 2 | Een exception die niet afgevangen is
Catch Wanneer er een fout optreedt binnen de protected code zal Visual Basic automatisch naar het eerste Catch-statement springen. In deze exception-filter kunt u de properties van de exception uitlezen en afhankelijk daarvan bepalen wat het verdere verloop is van de procedure. Wanneer het de code in het Catch-blok heeft uitgevoerd zal het direct daarna naar de eerstvolgende regel na het End Try statement gaan. U kunt meerdere Catch-statements opnemen in een procedure. Hiermee kunt u beter bepalen welke exception er is opgetreden.
Wanneer u de objecthiërarchieën eens nader zou bestuderen zou u er achter komen dat bepaal systeemuitzonderingen niet afgeleid zijn van SystemException en dat bepaalde applicatie-uitzonderingen niet zijn afgeleid van ApplicationException. U zult hier rekening mee moeten houden wanneer u op exceptions gaat testen.
Meerdere exceptions Tot nu toe hebt u alleen kennisgemaakt met het System.Exception object. Er zijn echter veel meer exceptions, welke allen zijn afgeleid van System.SystemException of System.ApplicationException. Hierdoor kunt u bijvoorbeeld direct testen op de DivideByZeroException. Listing 3 | DivideByZeroException Function BerekenBreuk(ByVal intTeller As Int16, _ ByVal intNoemer As Int16) As Double Try Return intTeller \ intNoemer Catch objException As DivideByZeroException MessageBox.Show(objException.Message,"A") Catch objException As SystemException MessageBox.Show(objException.Message, "B") Finally MessageBox.Show("Hier komen we altijd") End Try End Function
De volgorde van de Catch-statements is erg belangrijk, omdat wanneer u als eerste test op een SystemException de
Afbeelding 4 | Objecthiërarchie Exception
Het When-keyword Het Catch-statement kan ook nog een optionele Whenexpressie bevatten. Deze extra expressie moet True zijn wil de code in de Catch worden uitgevoerd. Het lijkt wat op een If …. ElseIf statement. Listing 4 | Gebruik van When Catch objException As DivideByZeroException When intTeller = 0 MessageBox.Show("Teller = 0, Noemer = 0") Catch objException As DivideByZeroException MessageBox.Show("Alleen Noemer = 0") Catch objException As SystemException MessageBox.Show(objException.Message)
| VB Magazine Online | 2004 – 01/02 | 3/6
Ü vbg.vbnet.beginner | Foutafhandeling binnen Visual Basic .NET |
Met het When-keyword kunt u uw foutafhandeling nog beter organiseren. U kunt namelijk ook bepaalde properties van het Exceptionobject evalueren en op basis daarvan andere handelingen uitvoeren. In het boek ‘Programming Microsoft Visual Basic .NET’ van Francesco Balena wordt nog een interessant gebruik van When beschreven. U kunt een interne variabele bij houden (bijvoorbeeld: intCurrentAction) met de huidige status van uw code. Telkens als u code vordert wordt deze waarde verhoogd. In de Catchstatement kunt u vervolgens ook testen op deze waarde en bepalen in hoeverre uw code al gevorderd is. Dit kan handige zijn om een bepaalde actie terug te draaien. Exit Try Met het statement Exit Try kunt u ten allen tijde de Try …. End Try verlaten. Net als een Exit For of Exit Do. De code in Finally wordt overigens nog wel uitgevoerd.
Throw Onder Visual Basic 6 kan u ook zelf fouten laten optreden door de Err.Raise opdracht. U genereert een fout op basis van een foutnummer en vervolgens kunt optioneel een aantal variabelen meegeven, zoals: Description, Source of HelpFile. Dit mechanisme werkt onder Visual Basic .NET nog steeds, maar u dient eigenlijk een exception op te laten treden. Hiermee blijft uw foutafhandeling compatible met andere .NET talen. U kunt een exception laten optreden met de Throw-opdracht. In tegenstelling tot de Err.Raise opdracht kent de Throw slechts één parameter. Dit is een exception opdracht waar u eventueel nog wat eigenschappen op kan instellen. Als u dit exceptionobject achterwege laat, dus alleen Throw aanroept, kunt u een afgevangen exception doorgeven aan de aanroepende procedure. Dezelfde exception wordt opnieuw gegenereerd, waardoor de eventuele StackTrace wel gerest wordt. U begint in dit geval wel weer met een schone lei. Listing 5 | Throw een eigen execption ‘ Voorbeeld van functieaanroep ‘ Let op naam Maxima MessageBox.Show(LastBornPrincess("Willem Alexander", _ "Maximama")) Function LastBornPrincess(ByVal strFather As String, _ ByVal strMother As String) As String ' Test eerst op de vader Try If strFather = "Willem Alexander" Then Exit Try Else Throw New Exception("Naam vader onjuist") End If Finally End Try ' Test nu op moeder Try If strMother = "Maxima" Then Exit Try
Else
Dim objException As New _ ArgumentException("Naam moeder onjuist") With objException .HelpLink = "http://www.vbgroup.nl" .Source = "LastBornPrincess in VBG-project" End With
Throw objException End If Finally End Try Return "Christina Amalia" End Function
In Listing 5 ziet u een voorbeeld van hoe u een exception kunt genereren. Bij de test op de geldigheid van de vader wordt een algemene exception gegenereerd; bij de test op de moeder gebruiken we een meer specifiekere, afgeleide class: ArgumentException.
Afbeelding 5 | Gebruik van Throw
Enkele regels omtrent het gebruik van Throw • Genereer een ArgumentException of een afgeleide ervan wanneer er een ongeldig argument (parameter) is meegeven (zie Listing 5); • Genereer een InvalidOperationException wanneer er een property of methode op uw object wordt gebruikt, terwijl uw object op dat moment niet in staat is om op de juiste wijze die procedure af te handelen. Uw object kan als voorbeeld een DataSet retourneren, maar er is bijvoorbeeld nog geen ConnectionString ingesteld op het object. In dit geval kan uw object nooit succesvol een DataSet ophalen omdat er essentiële gegevens ontbreken; • Genereer geen exception voor redelijk gangbare zaken. Wanneer u bijvoorbeeld door een DataSet loopt en u bereikt .EOF; handel dit gewoon af; • Gebruik zoveel mogelijk gedefinieerde exceptions en maak niet voor elke, iets afwijkende fout een eigen, afgeleide exception.
| VB Magazine Online | 2004 – 01/02 | 4/6
Ü vbg.vbnet.beginner | Foutafhandeling binnen Visual Basic .NET |
Eigen Exception Objecten In sommige gevallen kan het handig of zelfs noodzakelijk zijn om toch uw eigen exception-object te maken. Deze moet een afgeleide zijn van System.ApplicationException.
Else
Throw New InvalidParentException End If Finally End Try Return "Christina Amalia" End Function
Het aardige is dat u uw eigen exceptionobject kan uitbreiden met extra functionaliteit. Als u wilt dat fouten gelogd kunnen worden, dan kunt een eigen methode toevoegen en deze dan aanroepen. Listing 8 | Voeg eigen methode toe <Serializable()>Public Class InvalidParentException Inherits System.ApplicationException
Public Sub LogException() MessageBox.Show("Doe logging...") End Sub
Afbeelding 7 | Hiërarchie eigen Exception objecten
Overrides ReadOnly Property Message() As String Get Return "Een van de ouders is niet geldig" End Get End Property
Het maken van een eigen Exception-object is vrij eenvoudig. U erft ApplicationException over en zorgt de Message property wordt ingesteld. Listing 6 | Uw eigen exception Public Class InvalidParentException Inherits System.ApplicationException Overrides ReadOnly Property Message() As String Get Return "Een van de ouders is niet geldig" End Get End Property End Class
Het aanmaken van uw eigen Exception-objecten is erg eenvoudig. Het gebruik ervan idem dito.
End Class ‘Voorbeeld van gebruik, zie Listing 7 ‘ …. Catch objException As InvalidParentException MessageBox.Show(objException.Message)
‘
….
objException.LogException()
Wanneer uw eigen object tussen verschillende assemblies of applicatie afgevangen moet kunnen worden, dan moet er wel voor zorgen dat uw eigen class ‘serializable’ is. Het voert nu te ver om hier dieper op in te gaan, maar u kunt hierover meer informatie vinden in MSDN. De class in Listing 8 is serializable gemaakt door het attribuut voor de class-definitie.
Listing 7 | Gebruik van InvalidParentException Try MessageBox.Show(LastBornPrincess2("Willem Alexander", _ "Maximama")) Catch objException As InvalidParentException MessageBox.Show(objException.Message) Catch objException As Exception 'Een andere fout... netjes afhandelen End Try Function LastBornPrincess2(ByVal strFather As String, _ ByVal strMother As String) As String Try
If strFather = "Willem Alexander" AndAlso _ strMother = "Maxima" Then Exit Try
Performance Het werken met exceptions geeft uw applicatie extra ballast. Hoewel dit op dit moment waarschijnlijk vreemd in uw oren zal klinken, moet u zo weinig mogelijk gebruik maken van Exceptions. Exception hebben een zeer negatieve invloed op de performance van uw applicatie. U kunt dit doen door eventuele exceptions intern af te vangen door zelf waarden van variabelen te evalueren en vervolgens een specifieke returnwaarde terug te geven wanneer er iets niet in orde is. In de voorbeelden met de DivideByZeroException kunt u natuurlijk ook zelf eenvoudig testen of de noemer niet 0 is.
| VB Magazine Online | 2004 – 01/02 | 5/6
Ü vbg.vbnet.beginner | Foutafhandeling binnen Visual Basic .NET |
U moet exceptions dus niet misbruiken om speciale waarden uit functie terug te krijgen en ze alleen inzetten in de situaties die werkelijk relevant zijn.
Exception Management Application Block Microsoft stelt op haar website een aantal ‘Application Blocks’ beschikbaar. Dit zijn volledige werkende codeblokken in zowel C# als Visual Basic .NET, die u eenvoudig in uw eigen applicatie kan opnemen. Een van die Application Blocks is de ‘Exception Management Application Block’. Deze sourcecode, die te compileren is naar een DLL, biedt u een raamwerk om nog eenvoudiger - en flexibeler met exceptions om te gaan. Met slechts één regel code kunt u exceptions loggen naar het Event Log, of na een kleine code-aanpassing naar elke datasource die u maar wenst. Daarnaast maakt deze Exception Management Application Block het u mogelijk om alle exception managementcode te scheiden van de business logic code. Deze Application Blocks zijn over het algemeen zeer goed gedocumenteerd; de Exception Management Application Block vormt hierop geen uitzondering. Het loont zich zeker de moeite om dit eens nader te bekijken.
Wie is André Obelink? André Obelink (1969) is werkzaam als technical manager en consultant bij AcouSoft Informatisering B.V. AcouSoft is marktleider in Nederland op het gebied van software voor de audicienbranche. Daarnaast bouwt AcouSoft maatwerkapplicaties en ondersteunt zij andere bedrijven bij allerhande softwaretrajecten in VB6/VB.NET/MS Access 97/XP en SQL-Server 7/2000. André is een MCSD, programmeert in Visual Basic sinds 1991 (ja… versie 1.0 dus..) en behoort tot een van de oprichters van de Visual Basic Groep. Als hij niet programmeert, doceert, leest, schrijft of droomt over programmeren, brengt hij graag zijn tijd door met zijn vrouw en twee kinderen. U kunt hem bereiken via
[email protected] © 2004 - Copyright Visual Basic Groep. Dit artikel is auteursrechtelijk beschermd. Afdrukken voor eigen gebruik is toegestaan. http://www.vbgroup.nl
Tot Slot Exceptionhandling is de nieuwe manier van foutafhandeling voor .NET platform. De techniek om ze toe te passen is niet erg lastig, u moet echter voorzichtig zijn om ze integraal in applicatie op te nemen. Door gebruik te maken van structured exception handling bent u echter zeer zeker in staat om robuustere en beter onderhoudbare applicatie te schrijven. Bronnen • Programming Microsoft Visual Basic .NET Francesco Balena (0-7356-1375-3) • .NET magazine for developers #3 Exceptions en Events Microsoft / Sander Gerz • .NET magazine for developers #4 Exception Management Microsoft / Alex Thissen • Exception Management Architecture Guide Microsoft / http://msdn.microsoft.com • Exception Management Application Block Microsoft / http://msdn.microsoft.com
| VB Magazine Online | 2004 – 01/02 | 6/6