We Built This Project with a Debugging Scaffold
This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
We Built This Project with a Debugging Scaffold
On one of his first comedy albums, Bill Cosby spent five or six minutes telling a story and, after the punch line, explained, "I told you that story so I could tell you this one." Same thing here. I'm going to describe a project that I needed to do. Then I'm going to describe how I did it–using a debugging scaffold.
But wait! What's a "debugging scaffold?" This term isn't one we bandy about much in the Fox community, so before I get started on my first story, it's worth a bit of background. I was cruising through a C++ book a month ago and the author used this term. Although I've finished that C++ book and still don't know a pointer from a sharp stick in my eye, I immediately comprehended debugging scaffolds. So the last few weeks I've been thinking about them while I've been watching the evening news or Leno, and here's my first shot at implementing one.
A scaffold is an infrastructure that supports the construction of another object, and is built before the object. In order to build a wall, for example, you'd first build a scaffold on which you'd stand while building the wall.
In programming, a scaffold (a debugging scaffold) is a multi-level construct that supports ongoing programming debugging. The construct consists of debugging commands and functions that help you debug a chunk of code. So what's the difference between a few cursory DEBUGO statements ("come on, do you really type out the entire "DEBUGOUT" command?) and a trace window tucked in the corner, and a debugging scaffold?
A debugging scaffold has four attributes. It can be turned on and off, much like ASSERTS. You don't want to have to wade through a bunch of debugging structures when you don't need them.
Second, it's designed to be used over and over again, often with the same code segment. How many times have you written a long, complex routine, debugged it, and then deleted all of your debugging code–only to have to create it again when a change in the surrounding environment or requirements in the routine itself force changes–and the insertion of new bugs–into the routine? Yes, we've all been there, but it never seemed worthwhile to formalize this debugging mechanism–surely, this set of modifications to this hairy routine is positively, absolutely the last set you'll have to make, right?
Third, it should be multi-level, so that you can turn on and off just a certain part of the scaffold's functionality. You don't want to have to wade through dozens or hundreds of messages or prompts just to debug one small function, for example. What I'm thinking about here is being able to "step into" part of the scaffold that's several levels deep without having to drudge through all of the stuff in the first few levels.
Finally, it should support anticipated growth. Nothing would be worse–well, few things, at least, would be worse–than building a carefully designed scaffold, only to find that you had to tear it all down and rebuild it to accommodate a few minor items that were added late in the game. For example, imagine building a 10-story scaffold, only to find out that the owners want a different type of window that juts out a bit, and thus you have to tear down the whole scaffold and rebuild it 12 inches farther away from the wall?
All of these attributes point to one more "macro" attribute–it must be carefully thought out and built. Yeah, I know, programming was a lot more fun when you could just slam code into a text editor and run it, right? This design business, first with databases, then with OOP, and now debugging–it's enough to make you think you're becoming a grown-up!
Okay, on to our story.
The requirement for project version numbering
You are probably familiar with the Project Manager's ability to capture a major, minor, and revision version number–and to automatically increment that revision number each time you build the project to an executable or server. See Figure 1.
Unfortunately, while this might superficially seem to be useful, there are two major problems with the current implementation. First, you have no control over how the major, minor, and revision values are changed. Either you manually enter values or you settle for having the tertiary value incremented each time you create a new build. What if you want a different scheme?
Furthermore, if you've tried to use this capability yourself, you're also aware that's it was, until 6.0, a real pain in the butt to get that version number (as well as any of the other information in the Version dialog box) while the application was running–you needed to access the WinAPI, and, of course, the process for doing so isn't the same across all the many Windows platforms. Unless you enjoyed messing with structures and DECLARES, you probably decided to give up.
Well, for a project I've been working on, I needed to be able to control how the version number is created and incremented, and I needed to get at the version number while my app was running. Because of these issues, I couldn't use the native VFP version number, so I decided to use a different approach.
I created three properties in my global application object that held the information I wanted to use in my application.
The first property was called cBuildNo, which contained a character string of the form v.nnnn.dd where all of the segments are numeric. The first character, "v," was simply the version number, and was incremented only when a major update of the project was introduced. The second set, nnnn, was the day of the build. Each new day a build was produced, this number was incremented, ignoring days without a build. Thus, if the project was built on Monday, Tuesday, and Friday, the number would be, successively, 00034, 00035, and 00036. And the last three characters were the number of the build on that day. So the first time the project was built on a specific day, dd would be 01, while the third time would be represented by 03. I hope you'll agree that the limits that I assumed–a maximum of about 10,000 build days, with a maximum of 99 builds on a single day–are reasonable. It's actually longer to explain it than to understand it.
The second property, cBuildDateCCYYMMDD, held the date when the build was produced, and the third property, cBuildDateHHMMSS, held the time when the build was produced.
Obviously, it was no problem to access these properties while the application was running, either for dumping to an error handler or simply to display on an About screen. But how did these values get incremented automatically? Each time an .EXE was built, the cBuildNo property had to be updated according to whether it had already been built that day or not, and the cBuildWhen had to get the current date and time inserted.
Building a debugging scaffold
I decided to use a project hook to run a routine that would open the application object's VCX, search for those properties in the appropriate memo field in the VCX, and change their values as needed. But this wasn't quite as simple as it sounds. Hacking the VCX isn't necessarily for the faint-hearted in any case–screwing around with a memo field that contains many properties with an automated routine is like clipping your toenails while wearing welding goggles, oven mitts, and standing one-legged on a bed with the Magic Fingers turned up to "11."
Before I get into the code, I'll remind you about how project hooks work–if you want more details, check out Doug Hennig's article, "The Happy Project Hooker," in the September 1998 issue of FoxTalk.
In brief, a project hook is a class (based on a project hook class) that you attach to a project. When the project is opened, VFP automatically instantiates a project hook object from your class. From then on, whenever something is done to the project, like adding a file or building an .EXE, project hook events are fired. You can put code in these events or add your own methods that are called from the events. (If you're trying this for the first time, be sure to attach the project hook class to the project, then close the project and open it again. Opening the project again instantiates the project hook object–merely adding the class to the project doesn't do it.)
To keep things simple, I added code to the BeforeBuild event that would be fired before the user built the project. This code would open the application object class VCX; increment the cBuildNo, cBuildDateCCYYMMDD, and cBuildDateHHMMSS properties; close the VCX; and then let the build process go on.
The p-code for the routine looks like this:
* look for the application object class in the list of * all of the files in the current project * open the VCX as a table * spelunk through the Properties memo for the properties * and their values: * - cBuildNo * - cBuildDateCCYYMMDD * - cBuildDateHHMMSS * update these properties * write the properties back to the memo * close the VCX
Fairly straightforward, but in my initial attempts (yes, that's plural), I've found that I made many assumptions–assumptions that I'm sure you too would assume are reasonable–that didn't play out as expected. Some were my own fault; others were due to, err, idiosyncrasies in the tool. As a result, I finally tired of the goofball's approach to debugging, and built a scaffold that helped out immensely. Here's what I did:
First, I examined "what could go wrong" because, sure as shootin', Murphy was going to visit. There were basically five areas of potential discontent:
- Project not open or class not found
- Properties not found in the memo
- Property values not parsed correctly
- Property values not calculated correctly
- Property values not written back to the VCX correctly
I didn't bother with other things that could go wrong if I felt they were generally "one-off" errors that, once fixed, would stay fixed. Of course, in another situation, where those things were getting mixed up, a scaffold to examine these values would be a good idea.
First, I wanted to make sure I could turn each discrete area on and off by itself–once I solved the issues associated with property values being parsed correctly, I wanted to ignore that scaffold from then on. Naturally, the scaffold would stay in place so I could use it again when I, or VFP, broke something in the future.
A technique I've used for a long time is to create a series of "debugging flags" at the beginning of a module, then set their values to True or False as needed:
llDebug1 = .t. llDebug2 = .f. llDebug3 = .f.
If you have more than a couple of areas that you need to separate, you might consider (1) more friendly names for the flags, or (2) breaking the routine out into separate methods or procedures.
Once you have flags set up, you can use them as needed:
if m.llDebug1 debugo "The initial amount is: " + tran(m.lnAmount) debugo "The initial name is: " + tran(m.lcName) endif m.llDebug1
The second goal wasn't as easy as it seemed. You'd think that all you'd have to do would be to leave the scaffold in place for future use. But there's more to it than that if you want the scaffold to be useful in the future. If you're just using a series of DEBUGO statements to echo values being incremented in a loop, you could do this:
for each loFile in application.activeproject.files if m.llDebug1 debugo loFile.name endif m.llDebug1 <some code> next
When you run your app, you might see a list like so:
HWAPP65.VCX HWCTRL65.VCX HWLIB65.VCX HWLIB65.VCX HWLIB65.VCX
The lack of incrementation after the first three files indicates where your problem is. But when you run this debugging code three months later (or, for me, three hours later), you'll have no idea what this list means.
Thus, I include documentation in my DEBUGO commands, like so:
if m.llDebug1 debugo "Rolling through files in current project" m.liHowMany = application.activeproject.files.count m.liCurrent = 0 endif m.llDebug1 for each loFile in application.activeproject.files if m.llDebug1 debugo "File name " ; + tran(m.liCurrent) + " of " + tran(m.liHowMany) ; + ": " + tran(loFile.name) m.liCurrent = m.liCurrent + 1 endif m.llDebug1 <some code> next
Running this code will display the following in the Debug Output window:
Rolling through files in current project Filename 1 of 3 HWAPP65.VCX Filename 2 of 3 HWCTRL65.VCX Filename 3 of 3 HWLIB65.VCX
It's easy to see in the Debug Output window that this segment of code is going to display the files in the active project, and then each file is listed, along with the number of the file and how many total there will be. Sure, it takes a bit longer to include the extra documentation, but that time will be saved several times over when you have to examine this code later on. In this particular case, it's easy to see that the problem lies somewhere in the fact that there are only three files in the project–and it will be easy to spot this again in six months when the same problem, or one like it, crops up again.
As I mentioned, it's also important to be able to turn a part of the scaffold on or off as desired. Because each of these five potential areas of discontent (Hmmm, that comes out to "PAD": another Microsoft TLA?) runs in serial, it seems that this particular example doesn't have much in the way of demonstration. But you're wrong!
The fourth area, property values not incremented correctly, provides two potential levels of scaffolding. The first level of the scaffold would be a "wrapper" (you didn't think I'd be able to cover a topic that required "design" without using the term "wrapper" as well, did you?) for the area. For example, you could test the input and output of the incrementing function or functions, to see if indeed this area was causing a problem at all. If it was, you could turn the first level off but turn the second level on, to examine "jes' whut in tarnation wuz goin' on in thar."
Depending on the complexity of what you're testing, you could even provide a series of nested levels that could be used independently or with each other.
Finally, the scaffold should be designed and constructed to be able to handle anticipated growth. Of course, since growth usually occurs in the future, this comes down to reading the mind of the future, and that can sometimes be difficult. What I do to compensate for this difficulty is to ask myself what types of things are likely to change in the foreseeable future. This won't cover all possible avenues of change, but many times a future modification will be evolutionary, rather than revolutionary, and these can be anticipated to some extent.
In this example, one possible source of change would be the numbering scheme itself. Perhaps the three-segment version number will be changed to four segments. Or maybe the number of digits will change–once this project goes global, we'll have seven teams working on this project, in Milwaukee, L.A., Honolulu, Melbourne, Cairo, Lisbon, and Rio, and thus it will be very possible to have more than 99 builds in a single calendar day.
If you write your code modularly, it will be easier to make the scaffolding modular as well, and that lends itself to being able to be changed easier. The rest of this topic–modular code–is material for another article.
Formal debugging scaffolds are a new concept to our community, but I hope it's one that catches on with you. It should–it's helped me write better code, and do it more efficiently–and (this is your mother/editor talking) it'll be good for you too!
Whil Hentzen is the editor of FoxTalk.
To find out more about FoxTalk and Pinnacle Publishing, visit their website at http://www.pinpub.com/html/main.isx?sub=57
Note: This is not a Microsoft Corporation website. Microsoft is not responsible for its content.
This article is reproduced from the May 2000 issue of FoxTalk. Copyright 2000, by Pinnacle Publishing, Inc., unless otherwise noted. All rights are reserved. FoxTalk is an independently produced publication of Pinnacle Publishing, Inc. No part of this article may be used or reproduced in any fashion (except in brief quotations used in critical articles and reviews) without prior consent of Pinnacle Publishing, Inc. To contact Pinnacle Publishing, Inc., please call 1-800-493-4867 x4209.