A tutorial for the beginner on error handling in VB.NET. This - part two - is about basic structured error handling with "Try - Catch" pertaining to run time errors. More info on this can be found by highlighting "Try" and pressing <F1> in the IDE.
1) Basics of error handling.
1.1) Simplest possible - catch all.
1.2) Catching specific errors
1.3) Error propagation
1.4) Execution paths and the lifetime of an error
1.5) Priority rules of error catching
1.6) Nested error handling
1.7) Finally...
1.8) Conclusion and a not so bright idea
Having written this second tutorial I find it kind of bloated. I'm sorry about this, the thing is that I don't know how to make it shorter and still explain all these things about error handling.
Anyway, you might wonder why such a simple thing as error handling needs such an in depth tutorial - even on this basic level. This is because I want to explain as much as possible since I think error handling is really important.
Why so? Well, you do your best to foresee any problems but there are always ingenious errors introduced by users or by the complexity of a solution that you keep building on. Good error handling can save your application in many cases and might save the user from loosing data and/or difficult decisions. A friendly "Well, things didn't work out so well but the program has saved as much as possible of your work in your default directory. Check your configuration and restart." is always better than "Error writing file. Continue or end?". Try to take care of stuff as far as possible and when your program dies, try to die gracefully.
Once you figure out how this works it's pretty straight forward, however there are a lot of special cases one needs to explore.
1) Basics of error handling.
So then, you have your program compiled with option strict on and option explicit on. You have checked that the output is correct within defined limits of your goal. And now you want to take care of those ugly run time errors? (If this doesn't make sense to you please check part one of this tutorial).
1.1) Simplest possible - catch all.
In the first tutorial we had this overflow error that we handled nicely enough.
Build and run the following code. (By this I mean that you do Build -> Build in the IDE and then run the exe.)
Option Explicit On
Option Strict On
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim counter As Integer, sum As Integer
sum = 0
Try
For counter = 1 To 1000000
sum += counter
Next
Catch ex1 As exception
MsgBox("There's an error: " & vbCrLf & ex1.ToString)
End Try
End Sub
End Class
What happens here is that while executing between the Try and the Catch there is a special lookout checking for exceptions (errors). Whenever one is present execution jumps to the part between Catch and End Try. Without the error handling the error would be thrown in the face of the user - who presumably isn't happy about it.
You could, and probably should, have more code to handle the errors where the message box is. This code just shows - as an example - where to put code for dealing with the problem.
Checking for exceptions and dealing with them costs a lot in performance compared to checking input and other circumstances. Because of this I strongly suggest that you do not - repeat not - use error handling as a substitute for checking data before you try to use it. One more reason not to use error handling instead of checking data is that it inevitably leads to sloppy coding.
One thing introduced here is the possibility to use the error in your error handling code. As you see I use the ex1.ToString to get more information on the error. Going in to this any further is beyond this tutorial - you're on your own.
The code above is a bit dull, let's make things a bit more interesting.
1.2) Catching specific errors
A Short can only hold a little more than 32000. The following program will overflow on the row test *= test and throw an OverflowException. This can be caught in different ways, check it out.
Build and run the following.
Option Explicit On
Option Strict On
Public Class Form1
Private Sub HandledError1()
Dim test As Short
Try
test = 16000
test *= test
'Catch ex1 As IndexOutOfRangeException
'Catch ex1 As OverflowException
Catch ex1 As Exception
MsgBox("We caught an exception in the sub: " & ex1.ToString)
End Try
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
HandledError1()
End Sub
End Class
The Catch ex1 As Exception will catch any error, the other two will catch only their specified errors. Try commenting out one "Catch" row at a time then build and run, notice what happens.
Since we are generating an overflow exception the row Catch ex1 As IndexOutOfRangeException will not catch our error and it will be thrown at the user, the Catch ex1 As OverflowException however will catch it. We have here a way of deciding what errors to take care of! Lets take this a little further.
One reason to use procedures (subs or functions) is that we want to use the same code in different places in our programs, we do this by calling procedures from these places. Handling the error in the procedure might not always be the best way of dealing with the problem. Could we do it some other way?
1.3) Error propagation
Lets try to take care of the problem in the calling code...
Option Explicit On
Option Strict On
Public Class Form1
Private Sub HandledError1()
Dim test As Short
Try
test = 16000
test *= test
Catch ex1 As IndexOutOfRangeException
MsgBox("We caught an Index out of range exception in the sub: " & ex1.ToString)
'Catch ex2 As OverflowException
'MsgBox("We caught an overflow exception in the sub: " & ex2.ToString)
End Try
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Try
HandledError1()
Catch ex1 As Exception
MsgBox("Caught an unspecified error in the calling code: " & vbCrLf & ex1.ToString)
End Try
End Sub
End Class
This time, build and run the program then un comment the two commented rows, build and run and notice what happens.
First - it's kind of remarkable that errors that aren't handled where they happen might be taken care of at a higher level - by the calling code! This is called Error propagation - errors that are not taken care of "floats" (they are propagated) up through code levels until something (or someone) takes care of them. Ultimately the highest level is a poor user who will have to deal with the error, unless it's taken care of somewhere on the way.
Second - we now have a way to decide what errors should be taken care of where! But! You must take care of specific errors first and less specific errors later - further up the propagation hierarchy.
1.4) Execution paths and the lifetime of an error
Try this
Option Explicit On
Option Strict On
Public Class Form1
Private Sub HandledError1()
Dim test As Short
Try
test = 16000
test *= test
Catch ex1 As IndexOutOfRangeException
MsgBox("We caught an Index out of range exception in the sub: " & ex1.ToString)
'Catch ex2 As Exception
'MsgBox("Caught an unspecified error in the sub: " & vbCrLf & ex2.ToString)
'Exit Sub
End Try
MsgBox("Just to know we are here")
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Try
HandledError1()
Catch ex2 As OverflowException
MsgBox("We caught an overflow exception in the calling code: " & ex2.ToString)
End Try
End Sub
End Class
Build and run the code then un comment the lines Catch ex2 As Exception and MsgBox("Caught an unspecified error in the sub: " & vbCrLf & ex2.ToString) , build and run again, finally un comment the line Exit Sub, build and run.
There are three things to notice here:
First - when there is an error that is not taken care of execution stops and jumps to a higher level. When the commenting out is in place you never get the "Just to know we are here" message box.
Second - when you do take care of the error at a certain level the code keeps on executing after the Try - Catch block. This behavior can be changed by putting a Exit Sub statement in the Try - Catch block.
Third - when an error is taken care of it is "used up" and code higher up won't know anything about it.
In the above code I use Exit Sub to jump out of the sub routine after the error has been handled. There is also a Exit Try statement that might come in handy. When encountered, either in the Try part or in the Catch part, it will make execution to jump out of the Try - Catch and start executing the first line of code directly after the Try - Catch. I really don't know when I'd use it but it's there in case you need it.
Great! Now we can take care of the error in different ways depending on the context. Suppose you're trying to open a file. In one case it's some function of yours that wants to open and read a file, in another case it's the user requesting a file. Those would need to be handled differently and it can be solved by putting different error handling code in the Try - Catch block surrounding each call to the procedure that opens the file.
This might raise some concern about where to put error handling code, close to the error (in this case in the sub) or at a higher level (in this case in the calling code)? Well, that requires some thinking. If - I say if - there is a straight answer I'd say that you put error handling code where the knowledge is. What knowledge you might ask - the knowledge of how to deal with the problem. Put the error handling code where you can take care of the error in some intelligent way. File read errors might be handled a bit up in the code calling hierarchy, at least if both humans and programs are using it. Other errors e.g. such as being unable to connect to a database might be taken care of in the sub - close to the data base work.
1.5) Priority rules of error catching
Just to show how this works - build and run...
Option Explicit On
Option Strict On
Public Class Form1
Private Sub HandledError1()
Dim test As Short
Try
test = 16000
test *= test
'Catch ex0 As Exception
'MsgBox("Caught an unspecified error 1 in the sub: " & vbCrLf & ex0.ToString)
Catch ex1 As OverflowException
MsgBox("We caught an Index out of range exception in the sub: " & vbCrLf & ex1.ToString)
Catch ex2 As Exception
MsgBox("Caught an unspecified error 2 in the sub: " & vbCrLf & ex2.ToString)
End Try
MsgBox("Just to know we are here")
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
HandledError1()
End Sub
End Class
Now un comment, build and run again - ignoring the squiggly lines. You'll see that catching an unspecific error in a Try - Catch with several catches inhibits more specific error handling. This shows there are rules of priority, the order of your catch statements is important.
If you haven't noticed it before, now observe that I'm catching the errors using different names, e.g. ex1 and ex2. This isn't strictly necessary here but further on it will come in handy.
1.6) Nested error handling
You might find yourself in situations where taking care of one error results in a new error.
You need a text box and a button on your form; build and run the following code.
Option Explicit On
Option Strict On
Public Class Form1
Private Sub HandledError1(ByVal upLimit As Integer)
Dim test As Short
Dim ourArray(3) As Integer
Try
test = 16000
test *= test
Catch ex1 As OverflowException
MsgBox("We caught an OverflowException in the sub.")
Try
For i As Integer = 0 To upLimit
ourArray(i) = i
Next i
Catch ex2 As IndexOutOfRangeException
MsgBox("We caught an index out of range exception in the subs error handling")
End Try
MsgBox("Sub after catch two")
End Try
MsgBox("Sub after catch one")
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim forLimit As Integer
Try
If isnumeric(textbox1.text.trim) Then 'Checking user input - could be improved.
forLimit = CInt(TextBox1.Text.Trim)
HandledError1(forLimit)
Else
MsgBox("Please provide a number")
End If
Catch ex1 As Exception
MsgBox("We caught an exception from the sub in the calling code.")
End Try
End Sub
End Class
Run the above and put a number < 4 in the text box before clicking the button, then try with a number >= 4. Notice how a number >= 4 gives an error in the error handling code of the first error.
Now this example is really stupid but the method is useful. Lets say you are saving stuff like user data and configuration data to two different files. Writing the second file fails which makes you want to delete the first file. In this case you might want to have a delete statement in the error handling for writing the second file, that delete statement should be wrapped in its own Try - Catch in case anything goes wrong.
1.7) Finally
So, we have taken care of our errors. We have learned to keep the program running after an error or jumping out of the sub or error handling code in case of an error. There's also a way to make sure that a certain piece of code is executed whether or not the Try - Catch encounters an error. Say hello to "Finally"!
Build and run...
Option Explicit On
Option Strict On
Public Class Form1
Private Sub HandledError1()
Dim test As Short
Try
test = 16000
'test *= test
Catch ex1 As Exception
MsgBox("We caught an exception in the sub: " & vbCrLf & ex1.ToString)
Exit Sub
Finally
MsgBox("We are in finally.")
End Try
MsgBox("We are after the Try.")
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
HandledError1()
End Sub
End Class
Build and run the code, then un comment the line 'test *= test, build and run again.
Now this is a method that is very handy. Let say you have a data base connection open. If there's no error you want to close the connection and continue running the sub, i.e. showing the MsgBox("We are after the Try."). However if there is an error you want to close the connection and then jump out of the sub. This can be achieved by ugly coding - or by using finally.
The "Finally" will always be run, without regard to the presence of an error and without regard of any instructions to jump out of the Try - Catch or the sub. Of course this can be misused too - I think it's possible to cause confusion by using a combination of Exit and Finally in a really large piece of error handling code. But then the code would probably be kind of messy anyway.
1.8) Conclusion and a not so bright idea
What have we learned?
* Errors that are not handled immediately will propagate up through the code until something takes care of them.
* Error handling can deal with specific errors or unspecified errors. If we want to combine these we have to deal with the specific errors first.
* You can have multiple Catch-statements in a Try - Catch block in order to deal with different kinds of errors in different ways.
* By ignoring (all but certain) errors, those ignored will be propagated to a higher code level.
* Once an error have been dealt with it is "used up" and can't be handled at another place.
* Error statements can be nested. This can come in handy but don't overdo it.
* Error handling code can force execution to jump out of a Try - Catch or out of the sub where it's running.
* Finally - make sure a piece of code is executed whether or not there's an error and/or exit.
* There's a wealth on error handling by highlighting "Try" and pressing <F1> in the IDE.
A not so bright idea...
I can see one or two of you guys getting a really bright idea about now: Why not put one big "Try - Catch" around all the outermost code and not bother in other places... Since any error propagates to the outermost calling code we could let it handle everything and be done with all this.
NO! No, no, no! That's NOT how you're supposed to take advantage of error propagation, not ever. Do NOT do that. You have been warned! Only if you have been a good boy (or girl) and taken care of every error you can think of after securing your code from invalid input and strange circumstances you may put a "Try - Catch" around the code in your main - as a very last resort and then mainly to die gracefully.
This concludes part two of my error handling tutorial. While working on it I found some more interesting things to do with errors. I'll put them in a part three.
Regards
/Jens
PS: Please comment if you like the tutorials approach to the subject. Please criticize the presentation. I very much want to write clear and easy to follow tutorials - help me!
This post has been edited by jens: 23 February 2009 - 05:50 AM






MultiQuote





|