LessonsAdvanced: Bigger programsLesson 37

Debugging

Find out why a script does what it does: read the error, trace values with put, step through with the debugger, and test handlers in small pieces.

  • Advanced
  • Lesson 37 of 83
  • About 10 minutes
  • 8 examples

Every programmer debugs

A bug is a difference between what you meant and what you wrote. Finding one is not a sign you are bad at programming: it is most of programming. The trick is to stop guessing and start looking. This lesson is a toolbox for looking.

1. Read the error

When a script stops, xTalk tells you where and why. The playground highlights the line and explains in plain words; the Script Editor shows the error under the script and puts its line in view. Read the whole message: it usually names the problem exactly.

A mistakeIt stops with an error on line 3
put "apple,pear,fig" into tFruit
put the number of items of tFruit into tCount
put tCount / (tCount - 3)

Here, line 3 divides by zero, because there are exactly three items. The message says so, and the line number says where.

2. Look at the values: put

The quickest way to see what a script is doing is to make it tell you. Put the values you are unsure of as it runs, with a word to say which is which:

ExampleRun it in the playground
function average3 pA, pB, pC
   put pA + pB + pC / 3 into tResult
   put "trace: sum part =" && pA + pB && "result =" && tResult
   return tResult
end average3

put average3(3, 6, 9)
Output
trace: sum part = 9 result = 12
12

The average of 3, 6 and 9 is 6, not 12. The trace shows the sum is right, so the division is wrong: / goes before +, so only pC was divided. Brackets fix it:

ExampleRun it in the playground
function average3 pA, pB, pC
   return (pA + pB + pC) / 3
end average3

put average3(3, 6, 9)
Output
6

In a stack, a put with no container goes to the Message Box (Tools > Message Box), where it replaces what was there. To keep a running log, put each value after msg with a return, or write to a field of your own.

3. Stop and look around: the debugger

Click in the Script Editor's left margin, next to a line, to set a breakpoint: a red dot. When the script reaches that line it pauses, and the Script Editor becomes the debugger. Now you can:

  • see every variable of the handler and its value, in the Variables pane at the bottom;
  • Step Into the next line (and into any handler it calls), Step Over it, or Step Out of the handler;
  • look at the stack trace: which handler called this one, and which called that;
  • Run on to the next breakpoint, or Stop.

A script can set a breakpoint too, with the command breakpoint, handy for pausing only in an unusual case:

ScriptPut it in an object's script
on mouseUp
   repeat with i = 1 to 100
      put someCalculation(i) into tValue
      if tValue < 0 then breakpoint
   end repeat
end mouseUp

function someCalculation pN
   return 50 - pN
end someCalculation

4. Test in small pieces

A long handler that does everything is hard to debug, because a wrong answer could come from anywhere in it. Split the work into small functions, and check each with a few values whose answers you know. When one fails, you know exactly where to look:

ExampleRun it in the playground
function initials pName
   put empty into tLetters
   repeat for each word tWord in pName
      put toUpper(char 1 of tWord) after tLetters
   end repeat
   return tLetters
end initials

command expect pGot, pWanted
   if pGot is pWanted then
      put "ok" && quote & pGot & quote
   else
      put "FAILED: got" && pGot && "but wanted" && pWanted
   end if
end expect

expect initials("ada lovelace"), "AL"
expect initials("Grace  Brewster Hopper"), "GBH"
expect initials(""), ""
Output
ok "AL"
ok "GBH"
ok ""

Keep checks like these in your stack, and run them again after every change: they catch a new bug in old code the moment you make it.

5. The usual suspects

Most bugs in xTalk are one of a handful. Check these first:

  • A misspelled variable, which xTalk reads as its own name, as text. Turn on Variable Checking (Edit > Options > Variable Checking in the Script Editor) and these become errors (Where variables live).
  • A misspelled property, which silently becomes a custom property (Properties).
  • The wrong order of operations, as above: add brackets.
  • A trailing return or space in a value, which makes is say false: compare the length of the two values.
  • A message that never arrives: a handler further along the message path caught it and did not pass it.
  • The wrong itemDelimiter (Chunks), left set to something else by earlier code.
ExampleRun it in the playground
put "yes" & return into tAnswer
put tAnswer is "yes"
put the length of tAnswer
put line 1 of tAnswer is "yes"
Output
false
4
true

Try it yourself

This function should count the words longer than three letters. There are five in the sentence, but it says 0. Add a trace to the loop to find out why, then fix it.

ExampleRun it in the playground
function longWords pText
   repeat for each word tWord in pText
      put 0 into tCount
      if the length of tWord > 3 then add 1 to tCount
   end repeat
   return tCount
end longWords

put longWords("the quick brown fox jumps over the lazy dog")
Output
0
Show a solution

A trace after the if shows the count each time round: it goes to 1 for each long word, then back to 0 for the next word. The put 0 into tCount is inside the loop, so it starts again every time, and the last word, dog, leaves it at 0. Move it above the loop:

ExampleRun it in the playground
function longWords pText
   put 0 into tCount
   repeat for each word tWord in pText
      if the length of tWord > 3 then add 1 to tCount
   end repeat
   return tCount
end longWords

put longWords("the quick brown fox jumps over the lazy dog")
Output
5

Words in this lesson

Each one finds every lesson that uses it. The Dictionary has the whole story of each, with its syntax and more examples.