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.

Saturday, November 14, 2009

Some time for reflection?

What do you do when you have two weeks off, after working hard for months to meet deadlines and ship a new module on time and per specification, with as only interruption: going to the RunRevLive'09 conference and doing three presentations? Well, I'm glad you asked - I started by catching up on sleep and doing the couch potato thing, watching Star Trek Enterprise season 3.

But along came the release of version 4.0 of the revPlatform, with the highly anticipated revWeb browser plug-in. A-ha, that means it's experiment time! So I sat down and made a little demo revlet, using nothing but graphics, gradients and a few lines of code to demonstrate one way of faking a reflection while playing a simple animation of three circles.


If you don't have the revWeb plug-in installed yet, get it here. Then open a new browser window to run the demo here. Click the 'Start' button to let the three circles roam around. I've included a link for you to download the original stack; if you don't yet have a copy of revMedia 4.0, you can download it here for free.

I'm sure the resident animation and graphics revGurus Ben Beaumont, Malte Brill or Scott Rossi could make it look so much better and smoother, but it's the result of playing around for about an hour, and I'm quite pleased with what I put together, given that my multimedia skills are virtually limited to stick figures.

The beauty of the revPlatform is its elegant language and extreme productivity. And now you can take revMedia for free and start creating revlets - no need to learn Flash ActionScript or JavaScript. Who can resist that?

Support questions and bug reports

They're part of every-day life in the software business: users or colleagues encounter problems, and they need some way to find solutions. Sometimes a quick email is the answer, but generally speaking, there should be procedures in place to handle these items efficiently and transparently.

At my day-job, we have an internal application called MIA, which offers a complete environment for running a software business. Aside from the more mundane tasks of accounting, billing and license keys, it also keeps track of complaints, incidents, modifications and releases.
There is a web interface where customers can enter their issues, the support team picks these from the queue and tries to resolve them; if necessary, these incidents are escalated to a software engineer, which can result in a solution or a modification. The customer can track the status of the incident and if development is needed and scheduled, the planned version is added to the display as well.
We have corrective, functional and structural releases of our products and depending on the type of incident, it will be resolved in one of the next releases. Overall, it is an efficient system that our customers appreciate very much - even if they may not always like how long it takes to actually implement the change.

Of course, the situation is a little different for Quartam Software. It is mostly a one-man-show - apart from the external accountant who takes care of the taxes, and some help for the other accounting duties, I take care of everything: sending license keys, answering support emails, developing new features and fixing bugs, plus the information requests as well as the consulting and custom software jobs.
Unfortunately, that means I can't get to everything as quickly as I wish. I do try and get back to people in a reasonable time frame, and try to offer patch versions when some big problem pops up and the next functional release is still some time off. Sometimes, I fail to meet my own standards. But I, Jan Schenkel, am the person to send emails to, for everything.

In a larger organization, that is simply impossible to do: the manager is there to run the business and ensure the commercial success while the operations run smoothly. When procedures are in place, they should be followed to ensure that every item is handled correctly by the designated person. Only when things don't appear to be moving or important issues seem to get ignored, should you go one step higher, and involve management.
But please don't do it for every item that crops up, and be willing to compromise. The customer may be king, but the guy with the compiler is not a serf.

Sunday, October 25, 2009

Revolution, Snow Leopard and AppleEvent handlers

As you know, Macs have always sported a rather different operating system - in fact, MacOSX is quite good at hiding its powerful UNIX underpinnings from the average user, while still offering its full power to the more advanced computer user. But tossing out the old brittle 'cooperative multi-tasking' MacOS Classic, and swapping in the modern, robust NeXtStep with a new coat of paint, required that the Apple developers came up with a whole compatibility layer, named Carbon.

And one of the things that Carbon brought with it, was the system of Apple Events - added in System 7.0, this new type of event allowed Mac applicatiosn to talk to one another. Later, these Apple Events formed the underpinning of AppleScript, the system-wide scripting mechanism that is widely used for automating processes (and in turn, underpins the Automator tasks that allow you to point-and-click automation workflows in MacOSX).

What exactly is an Apple Event? An AppleEvent has a generic Class and Id, as well as associated data - and our rev stacks can handle these events by implementing an 'appleEvent' handler in our (stack) script. Alright, but why should I care as revDeveloper? Because if you want to write a decent Mac application, you had better implement the 'core' events. These are apple events of class 'aevt' and have an id of 'oapp' (open application), 'odoc' (open document), 'pdoc' (print document) or 'quit' (exit application).

Now, the revEngine is kind enough to handle the 'oapp' and 'quit' events for us, translating them into built-in events for us to handle, So we can concentrate on 'odoc' and 'pdoc' - and in fact, we only need to bother with these if we have some sort of 'editor' application. Of course, the Quartam Reports Layout Builder is exactly such an application. So I implemented a simple handler in my stack:
on appleEvent pClass, pId
if pClass is "aevt" and pId is "odoc" then
-- get the associated data
request appleEvent data
put it into tFilePath
-- now handle opening the file somewhere else
OpenFile tFilePath
else
-- let the revEngine handle other events
pass appleEvent
end if
end appleEvent

However, with the release of Snow Leopard, the format of the associated data appears to have changed rather dramatically. In previous releases, we would get a file path that we could use directly in file processing. For instance, your 'OpenFile' handler could look something like this:
on OpenFile pFilePath
put URL ("binfile:" & pFilePath into tBinaryData
-- go ahead and parse the content of the file
end OpenFile

For Snow Leopard, the good engineers at Apple have decided to change the format and where a file path from an 'odoc' event used to look something like "/My Big Project/Layouts/My First Layout.qrl", it now looks something like "file://localhost/My%20Big%20Project/Layouts/My%20First%20Layout.qrl" - presumabl, they're paving the way for other protocls than "file://" in the future, but this change has left our apps in the dust. I'm sure they were kind enough to update the Cocoa framework to translate this automatically, but what about us who don't drink the Objective-C Kool-Aid?

Well, the '%20' substitute for the space character, clued me in that there was some sort of URL encoding going on. So after a bit of fiddling, I came up with the following workaround for this prickly problem:
on appleEvent pCLass, pId
if pClass is "aevt" and pId is "odoc" then
-- get the associated data
request appleEvent data
put NormalizedPath(it) into tFilePath
-- now handle opening the file somewhere else
OpenFile tFilePath
else
-- let the revEngine handle other events
pass appleEvent
end if
end appleEvent

function NormalizedPath pFilePath
put pFilePath into tNormalizedPath
-- workaround change in Snow Leopard
if char 1 to 7 of pFilePath is "file://" then
set the itemDelimiter to slash
delete item 1 to 3 of pFilePath -- remove "file://localhost" from front
repeat for each item pFilePart in pFilePath
put slash & urlDecode(tFilePart) after tNormalizedPath
end repeat
end if
return tNormalizedPath
end NormalizedPath

Splitting off the NormalizedPath function means I can reuse it for the 'pdoc' Apple Event, if I ever were to support that. Plus, I can now add that to my 'generic' library that I share among all my applications. Anyway, I hope this blog post is of help to some fellow revDeveloper baffled by this problem.