Troubleshooting Scripts and Calculations

 <  Day Day Up  >  

There are many specific areas of potential trouble in FileMaker, and we'll get to those in the next section. Here, though, we want to discuss some general principles for dealing with errors in scripts and calculations.

Handling Errors in Scripts

Many FileMaker actions can result in an error. "Error" in this context can mean any exceptional condition that needs to be reported to the user . This can be something as simple as a search that returns no records or a field that fails to pass validation, or it can be a more esoteric error involving something like a missing key field. In general, in the normal operation of FileMaker, these errors get reported to the user via a dialog box of some kind, often with some sort of choice as to how to proceed.

For example, as shown in Figure 17.3, if a user performs a search that finds no records, she gets a dialog alerting her to the error and she has the option to go back and redefine her search.

Figure 17.3. FileMaker generally provides a lot of feedback on error conditions, such as this "no records found" message.
graphics/17fig03.gif

This is fine, up to a point. But you, the developer, may not want the user to see this default FileMaker dialog. You may want to present a different message, or none at all. Well, if your user performs her searches by dropping into Find mode, filling in some search criteria, and clicking the Find button, there's not much you can do. But if your user is performing a find via a script that you've written, you can intervene in such situations.

There's a very important script step called Set Error Capture. It's worth your while to become very familiar with it. This step allows you to tell FileMaker whether or not to suppress error messages while your script is running. The use of the step is shown in Figure 17.4.

Figure 17.4. The Set Error Capture script step is useful for controlling how FileMaker errors are displayed to the user.
graphics/17fig04.gif

If this step is not present in a script, or if it's present and set to "off," then FileMaker reports errors to the user directly. If your script performs a search, and no records are found, your users see the usual FileMaker dialog box for that situation (shown in figure 17.3). However, if you have error capture set to "on," the user sees no visible response of any kind. After you've set the error capture state (on or off), this setting is carried down through all subscripts as well, unless you explicitly disable it by using Set Error Capture [Off] somewhere down in a subscript.

In general, you don't just turn error capture on and walk away. In fact, error capture obliges you to do a lot more work than you normally might. With error capture on, FileMaker error dialogs are suppressed, so it's up to you to check for errors and either handle them or inform the user of those that are important.

As an example of custom error handling, suppose you've built a system that primarily stores text content in a variety of forms: press releases, biographies, articles, and so on. If a user does a search in one area and finds no results, you'd like to offer the option to search in other areas as well. Rather than let FileMaker intervene and show the standard No Records Found dialog, you want to pop up a custom dialog that allows the user to run a different scripted search on a different table or just give up. The script might look like the one in Figure 17.5.

Figure 17.5. Within a script, you can use Set Error Capture to hide the usual error messages and display custom feedback to the user.
graphics/17fig05.gif

Here, Set Error Capture is turned on and the script is configured to check whether the user's search found any records. If so, he's sent to the article list. If not, the custom dialog with other search choices is offered . If he picks a different type of search, the appropriate script runs; otherwise the script is exited and he is left where he was.

In addition to checking for specific conditions (such as a found count of zero), it's also possible to check more generically to determine whether the previous script step produced an error. This technique was shown in Listing 17.1, which used the Get(LastError) function. This function returns whatever error code was produced by the most recent operation. An error code of means "no error." Otherwise, an error of some kind has occurred. In Listing 17.1, as written, it didn't matter too much exactly which error had occurred ”it was enough to know that some error had happened .

NOTE

The FileMaker online help provides a complete listing of error codes and their more verbose descriptions.


Get(LastError) can be tricky. It reports on the most recent action taken no matter whether the action was triggered directly by a user or by a script. Let's say you have the following script fragment:

 

 Set Error Capture [On] Perform Find[] Go To Layout ["Search Results"] If[ Get(LastError)<>0 ]     Show Message ["An error has occurred"] End If 

This is not going to do quite what you would hope. If the Perform Find script step found no records, then at that point the "last error" would be 401 (the code for "no records found"). But after the Go To Layout step runs, that error code no longer applies. If that step runs successfully (which it might not if, for example, the particular user didn't have privileges to view that layout), then the last error code would now be . So, if you want to check for errors, check for them at the exact point of possible failure, not a couple of steps down the road.

Note also that the concept of "last error" is relative to each client session. This means that if another user, in his session, generates an error, this has no effect on the "last error" status in other sessions.

Tracking Down Errors

Suppose, despite your best efforts at defensive programming, some aspect of your system just doesn't work right. (Actually, you don't need to suppose. This will happen, and what's more, it'll happen after the software is delivered. That's not cynicism; it's reality.)

When this happens, of course, you'll want to track the problem down and fix it. There are a couple of verbs you'll want to keep in mind: reproduce and isolate .

Reproducing Errors

The first thing to do with any problem is to render it reproducible. Bugs that occur only occasionally are a programmer's worst nightmare. Often the circumstances are clear and entirely reproducible: "If I hit Cancel in the search script, I end up on some goofy-looking utility layout, instead of back at the main search screen." At other times, the problem is more slippery : "Sometimes, when I mark an invoice as closed, the system creates a duplicate of that invoice!"

If the bug is not transparently reproducible, you need to gather as much data on the bug as you can. Who experienced it? What type of computer and what operating system? Has it been experienced by one user, or several? Does it appear consistently? Look for hidden patterns. Does it occur more at certain times of day? Only from specific computers? Only for a particular account or privilege set? Only during the last week of the fiscal quarter? And so on.

Reproducing the bug should be your first priority, because you can't isolate it until it's reproducible, and isolating it is your best means of fixing it.

You might find that you, yourself, are unable to make the bug happen. This may be a sign that you are using the software differently from your users. Your usage pattern might never cause the bug to happen. One way to leap this hurdle is just to sit down with a user and watch him work. You might find that he's using a feature of the software differently than you had intended or expected, or that he performs functions in a different order. This may give you the clue you need.

What if, despite all these efforts, the bug remains elusive ? You can see its tracks but can't catch it. Well, you have a few choices. One is to program around it. If users report that, very occasionally, they get duplicate invoices when they close an invoice, you can rewrite the invoice-closing script to check for duplicates and eliminate an extra one when it appears. This doesn't eliminate the bug, but it does repair the effects. This is a distinctly less-than -perfect solution, but if it solves the problem and makes the system work again, that's what counts. But the bug is still in there, possibly causing other mischief.

Another possibility is to try to do some kind of logging. For example, in the duplicate invoice case, you could add some logic to the Close Invoice script. In addition to all the work it normally does, it would also create an entry in a new Log table. It would write out the time the script was run, the username, the system IP address, and the platform. Then, at the end of the script, it would check for duplication and log the findings. Even if you were programming around the bug at this point, to buy time, you could still use this log information to try to pin down the bug. Let the system run for a month, then look at the log and try to pick out any patterns in when the bug happens.

Debugging Calculations

After you've reproduced the bug (you hope), it's time to isolate it. Let's think about calculations for a moment. Suppose you have a complex calculation like the one in Listing 17.2. This calculation expresses a person's age (the DOB field) as something like "38 years , 5 months, 9 days."

Listing 17.2. Complex Calculation Example
 Let (  [ Today = Get(CurrentDate); DOBDay=Day(DOB); TodayDay = Day(Today) ] ; GetAsText(Year(Today) - Year(DOB) - Case(Today< Date(Month(DOB); DOBDay; Year(Today)); 1; graphics/ccc.gif 0)) & " Years, " & GetAsText(Mod(Month(Today) - Month(DOB) + 12 - Case(TodayDay <> DOBDay; 1; 0); 12)) & " graphics/ccc.gif Months, " & GetAsText(TodayDay - DOBDay + Case(TodayDay >= DOBDay; 0; Day(Today- TodayDay) < DOBDay; DOBDay; Day(Today - TodayDay))) & " Days") 

Actually, as written, the calculation does have a bug, and will generally give the wrong value for the "days" element. How do you debug this lengthy calculation?

NOTE

graphics/new_icon.jpg

Lengthy though this calculation is, FileMaker 7's Let statement makes the formula much more elegant than it would be in previous versions of the product.


One nice thing about this calculation is that it's actually three smaller calculations merged into one. To isolate the problem in this calculation, you need to pull out each individual piece and see whether it gives the expected result. Here you could pull out just the piece that computes the "days" value, like so:

 

 Let (  [ Today = Get(CurrentDate); DOBDay=Day(DOB); TodayDay = Day(Today) ] ; GetAsText(Mod(Month(Today) - Month(DOB) + 12 - Case(TodayDay <> DOBDay; 1; 0); 12))) 

Turn this snippet into its own calculation and see how it behaves. You'll find it behaves the same way when isolated from the rest of the calculation, and now your problem has shrunk in size . It shouldn't take you long to find that the <> (not equals) in the second line is a slip of the hand, and should just be < (less than).

As a general rule, we recommend that you debug complex calculations by breaking them down into smaller pieces and pulling those pieces into their own calculations if possible, for testing purposes. This suggestion contains a strong implication for how you should build complex calculations in the first place: Define and test the smaller pieces of functionality in their own calculations first, then merge them into the larger one. Or, if there's anything at all reusable in the smaller pieces, don't just fold them into a larger calc, but define them as custom functions instead.

TIP

For easy debugging of "calculation fragments " like the one shown previously, define two new fields in the problem table: a CalcSource field (which holds a calculation fragment), and a CalcEval field (well, the names can be whatever you like) that's a calculation defined as Evaluate(CalcSource) . Anything you cut and paste from the problem calc into the CalcSource field will be evaluated by the CalcEval field. This beats defining entirely new fields to hold your calculation fragments.


The key to the idea of "isolation" is specifically to isolate the broken part. Pull out the pieces that are known to work. As you test each piece, remove it if it tests out correctly. As you do this, the area that contains the problem grows smaller and smaller.

Debugging Scripts

The principle of isolation applies to scripts as well as to calculations. Your problem may lie inside of one script, or you may have a complex chain of scripts and subscripts that's exhibiting failure. By far the best tool available for this now is the Script Debugger, which is part of FileMaker Developer 7.

For a full treatment of FileMaker Developer 7 and the Script Debugger, see "Script Debugger," p. 778 .


The Script Debugger vastly simplifies the process of script debugging, which used to rely chiefly on the insertion of numerous Pause Script and Show Message script steps! But debugging scripts is still not an automatic process. A few points are worth noting.

Placing Breakpoints

The Script Debugger enables you to place a breakpoint in a script so that execution stops there and you can see what's happening. In theory, if you have a troublesome script or script chain, you could place a breakpoint at the very start and step through the script. But if this is a lengthy script chain, much less one than contains a loop that might run many times, this may not be very time effective.

Consider a case where you have a complex set of scripts that call each other ”let's say that there are three scripts total. Somewhere in the middle of that script, a date field on the current record is getting wiped out, but you don't know where.

In a case like this, you can use a classic isolation technique called "binary search." If you have no idea where the problem is happening, place a breakpoint more or less in the middle of everything, say halfway through script #2. Turn on the Script Debugger, let the script run, and see whether the field has been wiped out by the time you stop at the breakpoint. If the problem has already occurred, move the breakpoint to around the midpoint of the first half of the script chain (that is, 25%) and try again. If it hasn't happened by the 50% mark, move the breakpoint to 75%. Repeat until you narrow the possible range to one or two lines. This may sound like it's not much of a time-saver, but using this technique can find the error in a script of over 1000 lines using at most ten of these check-and-move operations.

TIP

If you have to a debug a looping script, it's worthwhile to try to reduce the number of records on which the script runs. In general, if you need to debug the loop itself, one internal breakpoint should suffice at first, either at the beginning or end of the loop.


Inspecting Values

One of the most important uses of a debugger is to watch certain values and see how they change. These could be database fields, global variables , or aspects of FileMaker state such as the current layout.

Checking which layout is current while you're debugging is easy, but it may be harder to inspect certain values in which you're interested. If they're present on layouts, and the script executes on those layouts, you're fine. Otherwise you might need to create special "debug layouts" that show the fields in which you're interested. You can then alter the script to show that layout, where possible.

CAUTION

Be aware of the potential for error here ”make sure these layouts keep the appropriate table context, or they could greatly alter the script's behavior.


 <  Day Day Up  >  


QUE CORPORATION - Using Filemaker pro X
QUE CORPORATION - Using Filemaker pro X
ISBN: N/A
EAN: N/A
Year: 2003
Pages: 494

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net