Tuesday, November 17, 2009

Error handling in revTalk

No matter how carefully we craft our code to do the right thing, there will always be error outside of our control: a disk may be full, we may not have sufficient privileges, the server connection is lost, etc. So how do we handle this sort of situation in revTalk? Depending on the situation, we check 'the result' after each potentially failing action, or we adopt the 'try-catch-throw' triad.

Personally, I favor the triad as it is common in languages such as Java or the .NET family. And that's why I used it extensively in Quartam PDF Library, throwing errors whenever something was wrong. Some people find this annoying, I feel that it keeps my code cleaner as there is no error checking all over my script, obfuscating what I'm really trying to accomplish.

The important thing to remember is that revTalk is a language which mixes the approaches: for a command like 'open file' you must check the result to see if it failed by any chance. However, if you try to set the 'angle' of an image to "foobar" it will throw an error as "foobar" is not an integer. It's probably due to the HyperCard heritage, where there was no try-catch-throw triad, but it's an inconsistency I'd rather see cleaned up.

Anyway, here's an example to get you started with the try-catch-throw paradigm. Just create a new stack, add a field named 'Log' and add a button with the following script:
on mouseUp
try
Foo
catch tError
Log "mouseUp: running catch block"
answer error tError
finally
Log "mouseUp: running finally block"
end try
end mouseUp

command Foo
try
Bar
catch tError
Log "Foo: running catch block (" & tError & ")"
finally
Log "Foo: running finally block"
end try
if tError is not empty then
Log "Foo: rethrowing error (" & tError & ")"
throw tError
end if
end Foo

command Bar
Log "Bar: before calling Snafu"
Snafu
Log "Bar: after calling Snafu"
end Bar

command Snafu
answer "Continue, throw or exit to top?" with \
"Continue" or "Throw" or "Exit to top"
Log "Snafu: user picked (" & it & ")"
if it is "Throw" then
throw "Snafu: user decided to throw"
else if it is "Exit to top" then
exit to top
end if
end Snafu

--

private command Log pText
put pText & return after field "Log"
end Log


Click the button and pick the option 'Continue' - the following text should be in the log field:
Bar: before calling Snafu
Snafu: user picked (Continue)
Bar: after calling Snafu
Foo: running finally block
mouseUp: running finally block

As you can see, everything went smoothly, and the code in the finally blocks of both the Foo and mouseUp handlers were called. This is where you should handle any cleanup (such as closing the database, files, cursors,...)

Now click the button again and pick the option 'Throw' - the following text should appear in the log field:
Bar: before calling Snafu
Snafu: user picked (Throw)
Foo: running catch block (Snafu: user decided to throw)
Foo: running finally block
Foo: rethrowing error (Snafu: user decided to throw)
mouseUp: running catch block
mouseUp: running finally block

As you can see, the throw in the Snafu handler caused the catch block to be executed in the Foo handler, followed by its finally block. In this case, I decided to re-throw the error after the try block of the Foo handler, so that the catch block in the mouseUp handler would also be called, automatically followed by its finally block. Note that the Bar handler was immediately interrupted so no 'after' log line was written.

Now click the button once more and pick the option 'Exit to top' - the following text should appear in the log field:
Bar: before calling Snafu
Snafu: user picked (Exit to top)

Yes, that's right, this means that execution halts completely, and not even the finally blocks in the Foo and mouseUp handlers are called.

This last bit teaches us to be cautious and not try and mix the different approaches to error handling without testing out the consequences.

No comments: