REALbasic Cross-Platform Application Development
Runtime Exceptions
Runtime exceptions are how REALbasic informs you that a problem has occurred. It offers yet another wrinkle to object-oriented programming. It's a class and it can be subclassed. You should create your own exception classes because that will give you more control over how errors are handled. At the same time, exceptions are capable of behavior that is unique to exceptions. Exceptions are raised, which is not unlike triggering an event. Recall that when a user clicks a mouse on a control, the control can handle that event, or choose not to handle it, in which case the Window has the option. Exceptions are the same way, except that instead of filtering down through the layers of the user interface, exceptions percolate up the calling chain. An all-too-common exception is NilObjectException, which occurs when you try to access an object that has been declared but not instantiated. REALbasic will throw a NilObjectException error every time you do this. There are a few ways within the method the error occurs in to catch the error and hopefully do something with it. If that method doesn't do anything with it, the previous method that called this method gets the opportunity to do it. This goes on all the way to our App object and the App.UnhandledException event. This is the last stop on your errors train to oblivion. You've got to catch it here or the whole application crashes and burns. "Handling it" from REALbasic's perspective means returning true. If you do not do this, the program will exit. It's pretty bad form to have an application crash uncontrollably, and this gives you an extremely easy way to add a little grace to the unexpected error. Ideally, within this event, save any files or data that you can so that as little work as possible is lost. Try...Catch...End/Finally
I find this the most graceful and easy-to-manage way to catching exceptions. It allows you to isolate that part of your code that when executed may throw an error, and you can write your response to the error there. For an example, I'll use a common error situation where an object is supposed to passed as an argument, but the object passed is Nil: Sub someMethod(anObject as SampleObject) Try anObject.aMethod Catch err as NilObjectException anObject = New SampleObject() anObject.aMethod End End Sub
If you have code that you want to execute regardless of the outcome, you can use Finally, as follows: Sub someMethod(anObject as SampleObject) Try anObject.aMethod Catch err as NilObjectException anObject = New SampleObject() anObject.aMethod Finally // the rest of the method End End Sub
You can replicate this functionality simply by testing values: Sub someMethod(anObject as SampleObject) If anObject <> Nil Then anObject.aMethod Else anObject = New SampleObject() anObject.aMethod End If End Sub The limitation to this approach is that you have to explicitly test for each kind of error. In this example, all that's being tested is whether an object is Nil, but there other error conditions can arise and the syntax of TRy...Catch ends up being nicer and neater than lots of If...Then statements that test error conditions. One potential scenario is that there is one kind of error that you want to catch and handle within the method, but another kind of error that you want to have handled by the calling object. A good example of this situation is an error that is generated in the Constructor of an Object. If it's a recoverable error, you might be able to handle it there, but if it's an error that prohibits creating the object, the calling method needs to be notified. Logging
Use System.DebugLog to send messages to the screen about the state of your application during debugging. System.DebugLog(aMessage as String) It works slightly differently on each platform. On Windows, you will need a debugger to view the output. A good (and free) debugger is called DebugView and is available here: http://www.sysinternals.com/Utilities/DebugView.html On Macintosh, you can use the Console application to view the message, and on Linux, the output is sent to SdtErr (you'll need to start the application from the terminal to see the messages). The use of DebugLog is best for sending messages that you want to view while the application is running. This is a much better alternative than using things like MsgBox to identify the current state of your program. Bear in mind that consistently writing to the console will impact the performance of your application, so you should distinguish between "debugging" and regular logging. One simple way to do this is to use compiler directives. The following snippet means that the data will be logged only when you are in DebugMode, which is when you are running an application from the IDE. It will not log this information after the application has been compiled: #if DebugMode then System.DebugLog("Some message") #endif The System object also offers the System.Log method: System.Log(logLevel as Integer, message as String)
The use of a logLevel allows you to specify the nature of the debugging or logging message. The class constants are as follows: System.LogLevelAlert as Integer System.LogLevelCritical as Integer System.LogLevelDebug as Integer System.LogLevelEmergency as Integer System.LogLevelError as Integer System.LogLevelInformation as Integer System.LogLevelNotice as Integer System.LogLevelSuccess as Integer System.LogLevelWarning as Integer
When using System.Log, the output is sent to EventWatcher on Windows. Macintosh users can use Console again, and Linux users can usually find the file in /var/logs. The reason for the levels is that applications like EventWatcher and Console are able to filter messages, so you may configure it to only show you the errors, if that's all you want to see, or you can look at everything. You may want to use System.Log all the time that your application runs, unlike SystemDebug. There are a lot of reasons for doing this, but often it's used for applications that run unattended so that the logs can be reviewed at a later date. Note that even though there are different levels, the messages are all written to the log, even if you have them filtered in the viewer. In some cases it makes sense to make the level of logging detail configurable, and it's very easy to do. All you really need is a method that wraps the System.Log method and determines, based on preferences or something similar, whether to actually write out the message to the log. One scenario is to create a module called myLog with a method, Logger, that you send all the logging messages to: myLog.Logger(System.LogLevelError, "An error!")
The module could also have some properties that define which messages get logged: myLog.Debug as Boolean myLog.Warnings as Boolean myLog.Errors as Boolean // and so on
The myLog.Logger method would operate like this: Sub Logger(logLevel as Integer, message as String) If logLevel = System.logLevelDebug and myLog.Debug Then System.Log(logLevel, message) End If If logLevel = System.logLevelWarning and myLog.Warnings Then System.Log(logLevel, message) End If If logLevel = System.logLevelError and myLog.Errors Then System.Log(logLevel, message) End If End Sub
Debugger
In one way or another, debugging generally means tracking the value or state of objects as your program executes. Logging is one way to get a glimpse, because you can watch the results as they are sent out to the debugger. The problem with logging is that you have to write a little code at every point that you want to log information. Ideally, you would log certain critical pieces of information, but you wouldn't want to log every last little detail about the state of things while running your application. This is where REALbasic's debugger comes into play. REALbasic has an excellent debugger, in my opinion, and it works as well as any other debugger I've seen in other IDEs that are used with other languages. The debugger allows you to peek into your program's internal state and look at every instantiated object, every property of every module, and so on. Nothing is hidden from you. Not only that, it also allows you to step through each line of code in succession so you can see exactly how values change as your program executes, line by line, as shown in Figure 5.22. Figure 5.22. Debugging code.
This is great, because now you don't have to write a line to a log file for absolutely everything. The trick to making effective use of the debugger is to know when to pause your program and take a look at what's going on inside. There are three ways you can make your program pause and view the information in the debugger. The first is with the Break statement. You can add it to particular parts of your code that, if executed, you know you will want to see. In addition to typing in Break in code, you can also set a BreakPoint in the IDE by clicking the column to the left of the editor. This is most effective for temporary breaks when you want to check something. If you think you are going to want to Break every time a certain condition arises in your program, then use Break. The other advantage of using Break rather than BreakPoints is that you can use your application's logic to determine whether you should break. Finally, you can tell REALbasic to break every time there is an exception. This is a good thing to do to help you track down exceptions and see what might be causing them to happen. There is a point at which this becomes a nuisance because you may have code that handles exceptions that you know might happen, but the IDE will break anyway. As the program gets larger, I tend to get more specific about when and how breaks are made. After you break, you can do two things. First, you can examine the values of any object that is running your application as shown in Figure 5.23. Figure 5.23. Examine the values of variables using the debugger.
One of the options is Runtime, which lists all the objects in memory, as shown in Figure 5.24. You can also access the Runtime object while your application is running if you want your program to do so. Figure 5.24. The Runtime object within an application.
Second, you can step through your program. Step Over
When you Step Over a method, the debugger stays within the current context. Step Into
If your code is executing a method and you select Step Into, the debugger will take you into the code of the method being called and let you step through it line by line as well. Step Out
This lets you skip to the end of the current method (skip not the best word to use herethe intervening code is executed, but you are not made to step through each line). Pause
With Pause, you can stop code from executing and jump into Debug Mode. If your program appears to be in a loop and not responding as expected, you can make the REALbasic application active and click the Pause button to stop it and see the values of current variables. Stop
Stop stops the application being debugged from running. It's useful when the application locks up. EditCode
When you click the EditCode button, you will jump to the tab that contains the code that is currently being executed and you can make changes to it. If you resume running the application, do not assume that that it will now be using the code you just fixed. You have to stop the program and then run it again for that to work. The primary benefit of this feature is to easily find the current code rather than being able to dynamically edit it. |
Категории