So last time we talked, I promised that we'd take a look at how to debug some real problems in a hands-on way. I never realized just how difficult it was to create applications which need debugging to solve problems -- I usually just stumble on issues naturally instead of try to create issues! ;-) Unfortunately, all the issues I needed to debug in the last week or so are quite complex (some of which I've yet to solve even!) and so I figure I'll start off a little easier.
Before we begin, you should download the latest scratch program (DebugScratch3.rbp).
First, open the new scratch project up and then run it so you can see what we've got. You should be presented with a window like this:

What you are looking at is a listbox with a bunch of unsorted random numbers in it (which are sorted via the Sort button), and the EditField is an arabic number which will be convered to roman numerals and back when you press the Convert button.
Before we see what bugs we can discover, I want you to go back to the IDE and make sure Project->Break on Exceptions is turned off.
Now, in the running application, click the Sort button. You should have found our very first bug -- there's an OutOfBoundsException. Thankfully, exceptions are very easy to track down in REALbasic. Remember how I told you to turn Break on Exceptions off before? Well, if you go and turn it back on (which you should do right now), then any time an exception is raised in your application (note: the exception *may* be handled somewhere in the application -- this is not break on unhandled exceptions), you will break into the debugger at the place the exception happened.
So now that you've turn Break on Exceptions on, let's restart the application and see what happens when we press the Sort button. You should see something like this:

As you can see, we've stopped on the exact line where the OutOfBoundsException has occurred (and we know what exception it is by checking out the text in the status bar). So now that we know we've got an exception, how do we debug it? Well, this one is a logic puzzle. The fist thing we need to do is figure out what in that line of code can possibly trigger an OutOfBoundsException. Thankfully, the line is fairly terse, and the only thing there which can cause this exception is the ListBox1.List call (we know that Val doesn't care about arrays, and Append sticks things onto the end of an array). So it's got something to do with the i variable being too large for the ListBox. We can see that 1 = 1001, but what's the last possible index for the ListBox? Let's dig in and find out! First, click on the self link, since that brings us to Window 1. Then click on contents, and since we want to see about the ListBox, click on that link. Now we're getting somewhere! The next problem we need to solve is to figure out how much data the listbox is currently holding. Thankfully, there's not one but two properties on ListBox which tell us this information. ListCount tells us the number of items in the list, and LastIndex tells us the last valid index. Uh oh! i = 1001, but ListBox1.LastIndex = 1000. Bingo! We've found our issue! Silly me, I forgot that we want to loop to ListCount - 1 since this is a 0-based list, not a 1-based list! So let's fix that bug by pressing the Stop button and changing the for loop to be this:[rbcode]
for i as Integer = 0 to ListBox1.ListCount - 1[/rbcode]
So let's make sure we've fixed our exception; run the application again and press the sort button. Ta da! No exceptions fired, so that bug's fixed. However.... there's another bug lurking in the sort, which the astute may have picked up on. The numbers aren't all sorted in order properly (for example, mine goes: 141, 345, 405, 352, ...). You can really see this bug by pressing the Sort button a second time. If sort was working properly, no numbers would change. Shoot! We've got a logic error somewhere in our code.
While leaving the application running, go back into the IDE and check out PushButton1.Action (which is the button labelled Sort). Let's see if we can pick any errors out on our own here.
The first chunk of code simply sticks all the numbers from the listbox into an array. The second chunk sorts it. The third chunk clears the listbox of its old contents. And the fourth chunk just adds the sorted numbers back into the listbox. Looks proper to me! I suspect that the QuickSort call is wrong, since after-all, the numbers aren't sorted properly. So let's set a breakpoint on the QuickSort line and go back to the running application. If you click Sort again, you'll break into the debugger at your breakpoint.
Now, press the Step Into button because we want to go into the contents of the QuickSort method. Phew! There's a lot of dense code in there. But if you've ever had a computer science class before (or if you can read comments), the code should still be fairly approachable. So let's keep hitting step until we see something that doesn't look quite right.
First, we step over the Dim statements. There can't be a bug there. Then we see the min = 0 and max = 0, so we're setting the true min and max to be from 0 to the last index in the array. That all looks fine. Min is still less than Max, so we're not done sorting, which is correct. Now we pick a random index somewhere in the middle of min and max and get the value at that index. That looks fine to me. And we move the min to the front of the partition -- again, normal (trust me on this one). After setting our lo and hi values, we get into the first while loop, which is looking down the hi list for something that is less than our middle value. But wait, what's this? Why would we be skipping down TWO high values? If we do that, we must be missing every other value in the hi partition. This looks fishy. Let's see what some of the other code does. If we scroll down just a little bit, sure enough, we see lo = lo + 1 in another while loop, and that makes more sense. I bet this is our bug. We don't really even need to bother looking at the contents of the array or anything -- just the fact that we've been stepping thru code brought us to this suspicious looking place. But since this is fairly dense code, let's edit it before stopping the debug session -- this way, if we get lost, we can go look at the debugger pane to see where we want to be editing. So click the Edit Code toolbar button. Now we're in the Algorithms.QuickSort method and we're able to modify our code. So scroll down to the part where you see hi = hi - 2 and change it to:[rbcode]
hi = hi - 1[/rbcode]
Now, hitting the Resume button will not recompile this piece of code. So we want to stop debugging and see if our change really fixed the problem (because we were really just assuming that -2 looked fishy and reasoned that it should be -1 instead). So go back to the Run tab and click the Stop toolbar button. Then hit Run so we can see how things are going.
When you press the Sort button, you'll break back into the debugger. Oops! We forgot to remove that breakpoint, so click on it now to remove it and press Resume. Now you'll notice that no matter how many time we press Sort, the numbers stay sorted. Sweet! Our guess was accurate, and we've fixed the QuickSort algorithm.
Now, let's check out that Convert button. If you press it, we should see the arabic 923 converted into the roman numeral equivalent. You get CMXXIII -- so let's check that out. C = 100, and M = 1000, but since C preceeds M, it really means 1000 - 100, which is 900. Then two X's for 20. And three I's for 3. Add them together and you get 923. Awesome! Right so far. Now, if you click the OK button, the reverse process should happen. We should be taking CMXXIII and converting it from roman back into arabic. Uh oh, we get 922. Something's fishy here.
So click back into the IDE and go to PushButton2 (the Convert buton). We know the ArabicToRoman conversion worked right, but we're not so certain about the RomanToArabic call. So put a breakpoint on the line with the RomanToArabic call and go click that Convert button again.
Step into the method call, and then step over all the Dims. You should now be at a screen like this:

Ok, so doing a quick code inspection, I don't see anything jumping out at me that might be wrong. So let's just step thru the code and see what the values are at each step and see if we can't discover something amiss.
The roman string looks correct, it's our proper value. So let's start stepping thru that for loop. Wait a second, I already spotted something that might be strange -- ch is blank. Is there a blank character at the front of our roman string? Click on the string viewer button for "roman" and... shoot, that's not it. There's no space there. Well, let's keep stepping and see whether this keeps happening. After stepping thru everything to get back to the top of the for loop, we notice that we essentially skipped the whitespace. But after we step over the Mid call a second time, we notice that we get ch = "C" Well, hmm... So when i = 0, ch = "", and when i = 1, ch = "C". I bet when i = 2, ch will equal "M" -- let's test this guess. Set another breakpoint on the ch = line and hit Resume. Step once to execute the ch = line, and sure enough, ch = "M" -- that means that we're grabbing the wrong character from Mid somehow. A quick check of the documentation shows the silly mistake we made -- Mid is a 1-based function, and we're starting i off at 0. So change i to be starting at 1, and clear the breakpoints by going to Project->Clear all Breakpoints, and re-run the application quick.
Click the convert button. Yup, we know that CMXXIII is right. But wait, we still get 922 for an error. How's that happening? We must still have a bug in there. So go back and put a breakpoint on the ch = line and let's step thru the entire for loop and see what's going on.
We see that ch = "C" on the first step. Good! Then it equals M. Then X, and X again. Man, what could be going wrong here? Then I and I.... but wait, we don't get the third I! Oh shoot, since Mid is 1-based, we remembered to change i to start at 1, but we forgot to change it to end and Len( roman ) instead of Len( roman ) - 1 (which was the old 0-based end point). So strip off the -1 since we want to go from 0 to the length of the entire string. Clear our breakpoint again, run, and voila! The bug is fixed. Be sure to change the value to convert a few times though just to make sure we really did fix it for all cases.
So how does it feel to know how to use the debugger? You learned a lot of very practical things today:
- How to use Break on Exceptions to see what state the appliction is in when an exception occurs. You also used this chance to see how to properly devle into the contents of a control to find the bug.
- Then you learned all about scoping out fishy-looking code just by doing some controlled code inspection. The debugger forced you to look at things line by line instead of just reading comments and assuming the code is correct.
- Finally, you learned how to step thru code to find logic errors that are very hard to track down by code inspection. By watching the values at each step of the application, you were able to spot the error in your code and fix it.
I think I've taken you about as far as you can go in terms of how to debug your applications. I'll finish the article up by explaining how debuggers actually work, as well as some final gotchas (and solutions) to watch out for when debugging.
Thanks for the debugging tips. Possible suggestion for the next set of topics: how about some write ups on enhancing performance. One of the issues many people have with RB is speed, and we don't all know all of the tricks to speeding up our apps.
A couple sample questions:
- What would be the fastest way to store and access a number of the same class while allowing you to reference it by an ID. An array of the class would force you to loop through and check each items ID and then return that item. A dictionary may be faster?
- What are some steps we can take to speed up commonly used loops? For example, some people think that using ubound as a reference in a for loop will cause a drastic slow down as it gets checked every time.
- Any other helpful tips to help improve speed or memory usage.
Good idea, I'll put it in the queue of things to talk about some day.