Thanks to Ryo Amano, there's also a Japanese version of this text available.
Just this morning (actually, still laying in bed in that kind of not-dreaming-anymore mode), it appeared to me how easy and nicely you can do exception handling with RB (I knew already its basics, but the realization that an Exception can be extended because of its nature of being a class, struck me like lightning and made me jump out of the bed right away and turning me Mac on to write this down for all of you):
Exception (or error) handling is hardly documented in RB; the docs only give you some short explanation of its syntax, but no real-world examples or other ideas for what it is good for.
OK, so let's make this one a little tutorial for all of you RB users that are kind of new to Object oriented and other modern programming techniques.
BTW, here's my little copyright notice: Redistribution of this is permitted as long as it is done for free. I retain the copyright. Thomas Tempelmann ( ).
Exception handling comes in handy generally any time you want to abort a process in a procedure (subroutine) because of some error condition.
Consider you write a procedure that adds a line of text plus a number, that always is supposed to be positive, to a file. The basic code would look like this:
Sub AddLineToTextfile(text as String, posNum as Integer, fileRef as FolderItem) Dim fileStream As TextOutputStream fileStream = fileRef.AppendToTextFile fileStream.WriteLine (text + " (the number is: " + Str(posNum) + ")") fileStream.Close End Sub
The code using that subroutine could be like this:
Sub WriteSomeLinesToAFile() Dim tmpFileRef as FolderItem tmpFileRef = GetFolderItem("tmpFile.txt") if tmpFileRef.Exists then tmpFileRef.Delete() end AddLineToTextfile("2*2", 2*2, tmpFileRef) AddLineToTextfile("5*5", 5*5, tmpFileRef) End Sub
The problem is that the AddLineToTextfile routine can not always be sure that its caller has set up everything correctly:
There's several conditions that the caller must meet:
As long as you know these conditions, you can make sure that you meet them before you call the subroutine.
For instance, if you later want to re-use the AddLineToTextfile routine in other projects, or even publish it to the public, you want to make sure in advance that your subroutine is called properly.
How can you make sure that these conditions are met?
When you detect an error condition in your subroutine, and you want to handle it, the classic way would look like this:
// make sure that the passed number is positive: IF posNum < 0 THEN // oops - error! RETURN END IF fileRef = NIL THEN // oops - another error! RETURN END // ... go on with the normal process
While that takes care of making your code fail-proof, it does not tell the caller that he didn't meet your conditions.
You could either handle that by returning some value that tells the caller whether he did everything right or not. However, that requires that the caller not only adds code to retrieve that additional return value, but that he also checks the value and handles it accordingly.
Or, you could simply signal the error condition by using the MsgBox() procedure like this:
IF posNum < 0 THEN MsgBox "error in AddLineToTextfile: posNum < 0!" RETURN END
That, however, is not recommended, because
Raising an Exception solved all these problems. Here's how it would look like in your code:
IF posNum < 0 THEN Raise new RuntimeException END IF fileRef = NIL THEN Raise new RuntimeException END
If you raise an Exception in your code, it works like this: First, it acts as if you had used the RETURN statement instead: The procedure will be exited. And here comes the smart part: If the calling procedure does not explicitly handle the exception, it will not get forgotten (as in the case when you had returned a success value), but will get passed on to its caller, and then to that one's caller, and so on, until finally a caller handles the exception. However, if no one handles the exception explicitly, RB will invoke a standard error dialog that stops execution of your application and shows you the error condition in the editor.
So, to handle the exception, you'll have to add a so-called exception handler to the end of your procedure:
Sub WriteSomeLinesToAFile() ... Exception exc as RuntimeException MsgBox "oops - there's something wrong" End Sub
Catching exceptions as seen above is the most generic way of catching any exceptions in your application, including cases of using a nonexisting Object (happens when an object reference is NIL), a stack overflow (happens when subroutines are too deeply nested, especially in recursive calling), and array indexing errors (when the addressed array element is out of bounds).
When you add such code to all of your your event handler routines, you will be able to trap any error conditions in your application and gracefully handle them instead of having your application automatically quit each time such a exception occurs.
Raising a "RuntimeException" is a generic and quite unspecific message to the caller. It would be nicer if the subroutine can raise its exceptions with some more information, like a text message explaining the error condition in more detail or even giving more information about the parameters that are bound to the condition (like the file name or the value of the non-positive number).
For that, we have to create a new subclass of a "RuntimeException" class: In the RB editor, create a new class, name it "MySpecialException" and set its Super class to RuntimeException.
Now, instead of raising a RuntimeException, we can raise our own one:
IF posNum < 0 THEN Raise new MySpecialException END
In an exception handler, we can make use of this:
Exception exc as RuntimeException IF exc isA MySpecialException THEN MsgBox "oops - AddLineToTextfile failed" ELSE MsgBox "oops - there's something else wrong" END End Sub
Now, since we do not want to handle those unknown exceptions in our WriteSomeLinesToAFile procedure, but only the exceptions that we are aware of (those explicitly raised in AddLineToTextfile), we can pass the other exceptions up to higher levels in the calling stack by raising them again:
Exception exc as RuntimeException IF exc isA MySpecialException THEN MsgBox "oops - AddLineToTextfile failed" ELSE // some other exception - we don't handle that here Raise exc END End Sub
As a short form, the above can be written like this:
Exception exc as MySpecialException MsgBox "oops - AddLineToTextfile failed" End Sub
(this means that only exceptions of type MySpecialException are handled here, all others are passed on to higher levels.)
Thanks to the concepts of Object Oriented programming and to the fact that an Exception is an object, too, we can benefit from this by making our exception signalling even smarter and quite universal:
In the RB editor, let's add a property to the MySpecialException class.
Add the property: msg as String
Now, when we raise an exception of that type, we can add some information in its msg property, like this:
Dim myExc as MySpecialException ... IF fileRef = nil THEN myExc = new MySpecialException myExc.msg = "fileRef = nil (posNum = " + Str(posNum) + ")" Raise myExc END IF posNum < 0 THEN myExc = new MySpecialException myExc.msg = "file " + fileRef.name + ": posNum < 0" Raise myExc END
The exception handler then can use that message:
Exception exc as MySpecialException IF exc.msg = "" THEN MsgBox "oops - AddLineToTextfile failed for unknown reason." ELSE MsgBox "oops - AddLineToTextfile failed: " + exc.msg END End Sub
There's another problem you can handle nicely with an exception handler: In a subroutine of yours, there can be many error conditions that you aren't even aware of and thus cannot explicitly handle and prevent them there.
However, what you can at least do is to take care of those exceptional cases and do a "cleanup" of the data you affected in that subroutine.
For instance, if you open and write to a file in a subroutine, and then some unpredictable error, like a stack overflow, happens in there, you should at least make sure that you close the file before you lose its reference.
Here's an example:
Sub AppendSomeDataToFile (fileRef as FolderItem) Dim fileStream As TextOutputStream Dim needsClose as Boolean fileStream = fileRef.AppendToTextFile needsClose = true fileStream.WriteLine ("just some data") needsClose = false fileStream.Close Exception exc as RuntimeException ' clean up in case of any error - in this case, close the file if it is still open IF needsClose THEN fileStream.Close END ' now pass on the exception because we're not able to handle it here Raise exc End Sub
These were the basics for handling exceptions in your code.
Sub Y DoSomeThingBad ... Exception x as XExc DoSomethingToFixX Exception y as YExc DoSomethingToFixY End Sub
Sub MySpecialException(msg as String) self.msg = msg End Sub
Then you can raise exceptions with messages easier using this single line:
Raise new MySpecialException("here comes the message")
Thanks to Anjo Krank for his constructive comments and to Robert Ameeti, who pointed out some spelling and phrasing errors.
That's it for now. Please let me know if you have additions, corrections or questions to this text. Write to:
Back to Thomas' REALbasic page
Written and copyrighted March 14, 99 by Thomas Tempelmann; last edit: July 12, 2000