Debugging Visual FoxPro Applications 9781930919211, 9781930919204

235 30 3MB

English Pages 133 Year 2002

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Debugging Visual FoxPro Applications
 9781930919211, 9781930919204

Citation preview

Debugging Visual FoxPro Applications

Nancy Folsom

Hentzenwerke Publishing

Published by: Hentzenwerke Publishing 980 East Circle Drive Whitefish Bay WI 53217 USA Hentzenwerke Publishing books are available through booksellers and directly from the publisher. Contact Hentzenwerke Publishing at: 414.332.9876 414.332.9463 (fax) www.hentzenwerke.com [email protected] Debugging Visual FoxPro Applications By Nancy Folsom Technical Editor: Kelly Conway Copy Editor: Farion Grove Copyright © 2002 by Nancy Folsom All other products and services identified throughout this book are trademarks or registered trademarks of their respective companies. They are used throughout this book in editorial fashion only and for the benefit of such companies. No such uses, or the use of any trade name, is intended to convey endorsement or other affiliation with this book. All rights reserved. No part of this book, or the ebook files available by download from Hentzenwerke Publishing, may be reproduced or transmitted in any form or by any means, electronic, mechanical photocopying, recording, or otherwise, without the prior written permission of the publisher, except that program listings and sample code files may be entered, stored and executed in a computer system. The information and material contained in this book are provided “as is,” without warranty of any kind, express or implied, including without limitation any warranty concerning the accuracy, adequacy, or completeness of such information or material or the results to be obtained from using such information or material. Neither Hentzenwerke Publishing nor the authors or editors shall be responsible for any claims attributable to errors, omissions, or other inaccuracies in the information or material contained in this book. In no event shall Hentzenwerke Publishing or the authors or editors be liable for direct, indirect, special, incidental, or consequential damages arising out of the use of such information or material. ISBN: 1-930919-20-4 Manufactured in the United States of America.

This book is dedicated to my mom, Janet Folsom, and to the memory of my dad, John Folsom, for teaching me to think critically and for encouraging curiosity as a charm against boredom.

v

Our Contract with You, The Reader In which we, the folks who make up Hentzenwerke Publishing, describe what you, the reader, can expect from this book and from us.

Hi there! I’ve been writing professionally (in other words, eventually getting a paycheck for my scribbles) since 1974, and writing about software development since 1992. As an author, I’ve worked with a half-dozen different publishers and corresponded with thousands of readers over the years. As a software developer and all-around geek, I’ve also acquired a library of more than 100 computer and software-related books. Thus, when I donned the publisher’s cap almost five years ago to produce the 1997 Developer’s Guide, I had some pretty good ideas of what I liked (and didn’t like) from publishers, what readers liked and didn’t like, and what I, as a reader, liked and didn’t like. Now, with our new titles for 2002, we’re entering our fifth season. (For those who are keeping track, the ’97 DevGuide was our first, albeit abbreviated, season, the batch of six “Essentials” for Visual FoxPro 6.0 in 1999 was our second, and, in keeping with the sports analogy, the books we published in 2000 and 2001 comprised our third and fourth.) John Wooden, the famed UCLA basketball coach, posited that teams aren’t consistent; they’re always getting better—or worse. We’d like to get better… One of my goals for this season is to build a closer relationship with you, the reader. In order for us to do this, you’ve got to know what you should expect from us. •

You have the right to expect that your order will be processed quickly and correctly, and that your book will be delivered to you in new condition.



You have the right to expect that the content of your book is technically accurate and up-to-date, that the explanations are clear, and that the layout is easy to read and follow without a lot of fluff or nonsense.



You have the right to expect access to source code, errata, FAQs, and other information that’s relevant to the book via our Web site.



You have the right to expect an electronic version of your printed book to be available via our Web site.



You have the right to expect that, if you report errors to us, your report will be responded to promptly, and that the appropriate notice will be included in the errata and/or FAQs for the book.

Naturally, there are some limits that we bump up against. There are humans involved, and they make mistakes. A book of 500 pages contains, on average, 150,000 words and several megabytes of source code. It’s not possible to edit and re-edit multiple times to catch every last

vi misspelling and typo, nor is it possible to test the source code on every permutation of development environment and operating system—and still price the book affordably. Once printed, bindings break, ink gets smeared, signatures get missed during binding. On the delivery side, Web sites go down, packages get lost in the mail. Nonetheless, we’ll make our best effort to correct these problems—once you let us know about them. In return, when you have a question or run into a problem, we ask that you first consult the errata and/or FAQs for your book on our Web site. If you don’t find the answer there, please e-mail us at [email protected] with as much information and detail as possible, including 1) the steps to reproduce the problem, 2) what happened, and 3) what you expected to happen, together with 4) any other relevant information. I’d like to stress that we need you to communicate questions and problems clearly. For example… •

“Your downloads don’t work” isn’t enough information for us to help you. “I get a 404 error when I click on the Download Source Code link on http://www.hentzenwerke.com/book/downloads.html” is something we can help you with.



“The code in Chapter 10 caused an error” again isn’t enough information. “I performed the following steps to run the source code program DisplayTest.PRG in Chapter 10, and I received an error that said ‘Variable m.liCounter not found’” is something we can help you with.

We’ll do our best to get back to you within a couple of days, either with an answer or at least an acknowledgement that we’ve received your inquiry and that we’re working on it. On behalf of the authors, technical editors, copy editors, layout artists, graphical artists, indexers, and all the other folks who have worked to put this book in your hands, I’d like to thank you for purchasing this book, and I hope that it will prove to be a valuable addition to your technical library. Please let us know what you think about this book—we’re looking forward to hearing from you. As Groucho Marx once observed, “Outside of a dog, a book is a man’s best friend. Inside of a dog, it’s too dark to read.” Whil Hentzen Hentzenwerke Publishing January 2002

vii

List of Chapters Chapter 1: Introduction Chapter 2: Quality Ensurance Chapter 3: Applying the Scientific Method to Debugging Chapter 4: Visual FoxPro Debugging Tools Chapter 5: A Taxonomy of Common Visual FoxPro Bugs Chapter 6: Fitting into Enterprise Solutions Appendix A: Additional Resources Appendix B: What’s Behind the Curtain?

1 5 21 35 69 89 103 105

ix

Table of Contents Our Contract with You, The Reader About the Authors How to Download the Files Foreword Chapter 1: Introduction Organization

Chapter 2: Quality Ensurance Lies, damned lies, and… statistics Today Test early, test often But… I have a test team “Houston, we have a problem.” Debugging during development Debugging test versions Debugging in post-release or maintenance Designing with diagnostics in mind When are you done debugging? Risk Measure twice, cut once Bug tracking Source code control Irreverent evangelizing The best offense is a strong defense

Chapter 3: Applying the Scientific Method to Debugging Observation Inquiry Hypothesis Prediction Experimentation Analysis Decision Conclusion

v xiii xv xvii

1 3

5 6 8 10 11 13 13 14 14 15 15 16 17 17 18 18 19

21 22 23 28 30 30 33 33 34

x

Chapter 4: Visual FoxPro Debugging Tools Debug options Font and colors Display timer events Environment Call stack options Trace window options Output window options The native debugger About the debug output window About the call stack About the trace window About the watch window About the locals window About breakpoints About configuration files Debugger odds and ends: Stepping, resume, cancel Coverage logging Event tracking Language elements Error handling Using views Syntax coloring and IntelliSense Rushmore optimization—SYS(3054) The command window The Help file Tools outside of Visual FoxPro Understanding the tool

Chapter 5: A Taxonomy of Common Visual FoxPro Bugs Requirement-related bugs Exceptions to business rules The technique Design-related bugs Misunderstandings Conflicts Omissions The technique Characteristics Variant variables Implicit variable declaration Comparison and assignment are the same (“=”) Multiple RETURN statements

35 35 35 35 36 37 37 38 38 38 40 41 46 48 52 54 56 57 61 63 64 65 66 66 67 68 68 68

69 69 70 70 70 71 71 71 72 72 73 73 75 75

xi Optional parameters Ambiguous fields, memory variables, and objects Implementation bugs Syntax bugs Logical bugs Performance Data performance Code System bugs The technique Deployment Maintenance bugs Effective debugging Conclusion

Chapter 6: Fitting into Enterprise Solutions Using ActiveX and OLE controls Using Automation servers Building Automation servers Windows API Conclusion

76 77 78 78 80 85 85 86 86 86 87 87 87 87

89 90 96 97 98 101

Appendix A: Additional Resources

103

Visual FoxPro books Debugging and good coding practices Requirements gathering and design Web resources

103 103 103 104

Appendix B: What’s Behind the Curtain?

105

xiii

About the Authors Nancy Folsom Nancy Folsom wrote her first bugs on punch cards in 1976 for a high school geometry class. She took a detour from computer science to study fine art, and then returned to computer science in 1982, when the PC revolution was blossoming. PCs allow programmers to more efficiently introduce bugs into code! Nancy has been programming full-time since 1989, and has specialized in Fox products since 1993. Her experience includes project management, requirements gathering, design, modeling, data design, programming, end user and technical documentation, training, and support. She has written software in the domains of accounting, time and billing, point-of-sale, cost management, construction cost estimating, and inventory management. She has been an independent consultant since 1998. Nancy has been a Microsoft Most Valuable Professional since 1998, and she co-authored a series of articles for CoDe Magazine about managing customer relations during the software development process. She still draws and paints, and she is currently reading about visual intelligence since she is interested in how our brains process visual information. She lives with her dog and cat. Nancy likes to take walks in the rain, which is good since she moved to Seattle in 2001, where everything is done in the rain. Web: http://www.PixelDustIndustries.com, e-mail: [email protected].

Kelly Conway Kelly Conway has been developing software since 1985, using FoxPro/VFP since 1992. His articles have been published in FoxTalk and FoxPro Advisor, and he has appeared as a speaker at Microsoft Dev Days and the Great Lakes Great Database Workshop (“Whilfest”). As Director of IT for WSA Corporation (Shawnee, KS), he plans and develops VFP applications and also works with Oracle, SQL Server, and ASP. e-mail: [email protected].

xv

How to Download the Files Hentzenwerke Publishing generally provides two sets of files to accompany its books. The first is the source code referenced throughout the text. Note that some books do not have source code; in those cases, a placeholder file is provided in lieu of the source code in order to alert you of the fact. The second is the e-book version (or versions) of the book. Depending on the book, we provide e-books in either the compiled HTML Help (.CHM) format, Adobe Acrobat (.PDF) format, or both. Here’s how to get them.

Both the source code and e-book file(s) are available for download from the Hentzenwerke Web site. In order to obtain them, follow these instructions: 1.

Point your Web browser to http://www.hentzenwerke.com.

2.

Look for the link that says “Download”

3.

A page describing the download process will appear. This page has two sections:



Section 1: If you were issued a username/password directly from Hentzenwerke Publishing, you can enter them into this page.



Section 2: If you did not receive a username/password from Hentzenwerke Publishing, don’t worry! Just enter your e-mail alias and look for the question about your book. Note that you’ll need your physical book when you answer the question.

4.

A page that lists the hyperlinks for the appropriate downloads will appear.

Note that the e-book file(s) are covered by the same copyright laws as the printed book. Reproduction and/or distribution of these files is against the law. If you have questions or problems, the fastest way to get a response is to e-mail us at [email protected].

xvi

Icons used in this book



Indicates that the referenced material is available for download at http://www.hentzenwerke.com. Indicates information of special interest, related topics, important notes, or version issues.

*

Indicates a tip, trick, or workaround.

xvii

Foreword “A book on debugging? But everybody knows how to debug!” Apparently not, because I can cite at least three studies that demonstrate a large gap between the abilities of experienced programmers to find a problem in a software application. Those same studies show differences in the percentage of fixes that are correct. Many programmers make the mistakes of guessing at a fix, failing to understand the root cause of the problem, and hacking a fix instead fixing the real problem. Even a good fix stands a decent chance of introducing more bugs, let alone amateur-level hacks. As software test lead on the Visual FoxPro team, I spend a lot of time in the VFP debugger while writing utilities and testing the VFP product. But I recall all too well my early days as a programmer, and the days before visual debuggers such as the one included with VFP. Tracking down bugs was a frustrating exercise in echoing output to the printer, hardcoding variable values to change behavior, and enduring long waits during recompiling. But better tools don’t necessarily make for better debugging. Having the tools of a woodworking shop won’t make you a cabinet-maker. In the same way, having modern tools such as visual debuggers doesn’t mean you will necessarily debug your applications in the most efficient and effectual manner. In this book, Nancy walks you through the scientific method, and applies it to debugging, giving you a systematic approach to debugging instead of flailing about as you try to find the cause of that pesky bug. In this book you’ll find lots of real-world examples gleaned from Nancy’s experience as a consultant working with VFP, not cooked-up scenarios from the halls of academia. Though geared toward the VFP developer, I think you’ll find good information for debugging applications written in any language. In addition, there’s a chapter that delves into the VFP debugger and other tools included with VFP. Though I work extensively with VFP every day, I still learned a thing or two from that chapter about the tools and the techniques used when working with them. If you’re looking for a highly researched book that brings together the best debugging practices of the software industry, then I highly recommend Debugging Visual FoxPro Applications. Mike Stewart Software Test Lead Microsoft Visual FoxPro Team

Chapter 1: Introduction

1

Chapter 1 Introduction Doctors, plumbers, and mechanics do it. Engineers, scientists, and tailors, too. What do they all do? They’re problem solvers. They meet and try to solve problems in the normal course of their work. A large part of their expertise relies on their ability to identify that a system has a problem. They then have to accurately detect in what part of the system a problem is located and the likely causes. Finally, each of these professionals has to identify and evaluate options for correcting or mitigating the problem and either administer the correction or pass the problem on to a specialist. A plumber is called in because a drain has backed up, but she doesn’t know what has caused it until she’s investigated it. A tailor alters clothing to fit an individual. A doctor might be faced with a patient who is suffering from chronic headaches. Before any of these folks can act, they have to understand the situation. In order to be successful, they must separate the relevant and irrelevant facts of a situation. The tailor doesn’t care that the suit is blue, but he cares that it is plaid. The doctor needs to ask questions that will help her narrow down the possible causes of the headaches. A mechanic will first try to duplicate the strange knocking sound in your car. It might be bad gas or the old 8088 you keep in the trunk for added traction on snow days. For each of these professionals, it’s assumed that a task will start with fact-finding, proceed through diagnosis, and only then move on to corrective measures. Experience, training, journals, and peer communities support the efforts of these professionals. Similarly, software developers are problem solvers, of course, and in many ways: from detecting the customer’s needs to evaluating applicable technologies and building the solution. There is one particular time, of course, when developers are problem solvers, and that is when they are debugging code.

I wrote this book as a discussion of debugging techniques in general, and debugging in the Visual FoxPro environment in particular. This book is intended primarily for software developers—the folks who write and debug code, either their own or that of others. Since I use Visual FoxPro as the basis for this book, it is expected that the majority of readers will be Visual FoxPro developers. However, I also intend the book to be a general guide to an effective debugging approach. I’ve included material, too, that should be of interest to people who are stakeholders in software projects but who are not programmers themselves. Specifically, I discuss the process of debugging—what it is, what it isn’t, why we do it, and why it’s not an aberration in the software development project lifecycle, but rather is, and rightly so, an integral part of the process. Since the targeted audience is the developer community, I focus on debugging during implementation. However, since bugs and debugging don’t happen neatly in isolation of the other project phases, I touch on requirements gathering, design, and testing to varying degrees as they apply to debugging during implementation. Developer testing, in particular, has an important role to play during implementation and is closely related to debugging. Debugging is simply the process by which problems in the software are identified, described, located, and diagnosed. Notice that I did not include programming in this list of debugging activities. Why not? Programming a correction is functionally no different from

2

Debugging Visual FoxPro Applications

writing maintenance code. All code needs to be tested, and code written to solve a software issue is not special in that. However, some code will be written specifically to support a debugging effort. I discuss this in the context of debugging techniques. Coding is, however, relevant to the topic of debugging insofar as it applies to good coding practices that prevent bugs from occurring. So, what exactly constitutes the focus of debugging? Well, bugs, of course. It’s easy to identify the classic bug, which is a program error that results in an abnormal termination of an application. A number of authors have defined bugs. For example, in The Science of Debugging, Matt Telles and Yuan Hsieh say bugs “…are things the software does that it is not supposed to do (or, alternatively, something the software doesn’t do that it is supposed to).” John Robbins in Debugging Applications: The Bugslayer’s guide to finding and fixing coding errors in Microsoft Windows-based applications says that a bug is “anything that causes a user pain.” He goes on to list four categories of bugs: inconsistent user interfaces, unmet expectations, poor performance, and crashes or data corruption. A software bug is a real or perceived behavior that occurs when software halts unexpectedly, loses data, or fails to satisfy users’ expectations in either performance or behavior. It’s common to generalize these phenomena as defects, and leave “bug” to mean specific programming errors. Debugging occurs for reasons in addition to the obvious system crashes or calculation errors. There can be a fine line between a feature and a bug. Sometimes a user’s expectation for program behavior differs from the designer’s. Sometimes the source of a problem is not in the code, but in the environment within which the code operates. Famously, Microsoft refers to issues rather than bugs. This is not just euphemism, but an acknowledgement that the root of a problem may be subtle. Focusing on a neutral evaluation is more effective than assigning blame for the problem—especially before the culprit is identified! I will use defect in this book to mean any less-than-satisfactory behavior software exhibits—whether the result of a coding fault or not. However, debugging is not limited, even, to this broad category. The term debugging is somewhat misleading since developers use the same process to investigate any behavior of interest in a program1. Developers might debug prototypes, specialized utilities used by other developers, business rules encapsulated in COM objects distributed over an intranet, and large-scale business applications. Debugging can occur during any stage from design to implementation to maintenance. Whatever the venue, whatever the stage, effective debugging follows the same general technique. What will vary is the evaluation of whether something constitutes an issue severe enough to debug and what solution, if any, should be implemented. For example, a prototype will not have the same requirements as a final product for functionality. Whatever the root cause of a problem might be, debugging can seem like a nightmare from which one will never wake. It feels like one is trapped in a murky cellar: running the program, crashing it, changing a line of code here, or that variable there, then running the program, then crashing it. A bug can be a monster under the bed. Each time one bug is hunted down and fixed, one gladly moves on to another task, only to find, sure enough, another bug, another issue. One might dread answering the phone the day after software is released to users, as if Freddy Krueger (famous horror movie villain) might be on the other end. No, Stephen King 1

It can be argued that if a developer has to investigate code to understand the behavior of a program, there is a documentation defect.

Chapter 1: Introduction

3

didn’t write this book or the code we have to debug. If only a supernatural goblin could be blamed! Unfortunately, defects occur in programs because of something somebody did or didn’t do. Since defects come about because of rational reasons, they can be subdued through a reasoned approach.

Organization This book is focused on a single subject. Ideally, you should be able to read it in a few hours’ time. I want to specifically talk to programmers who perhaps haven’t systematically developed a debugging process, don’t use the Visual FoxPro debugger, have wondered how to educate their customers or management about this aspect of development, or are frustrated by debugging. I hope that even advanced programmers with a solid understanding of effective debugging will find information that is helpful to them. In Chapter 2, “Quality Ensurance,” I describe how debugging fits into the software project lifecycle. I include data to substantiate how important it is, when combined with systematic developer testing, for improving the robustness of a software product. Normally debugging is dreaded as a distraction from the real job of programming. In actual practice, however, debugging is a normal part of programming. If it’s acknowledged as such, then it is possible to consider ways in which it can be done effectively. The goal will be to combine effective debugging with thorough testing, defect tracking, and good coding practices so that the frustrating aspects of debugging are minimized. Chapter 2 is not specific to Visual FoxPro and may be of interest to non-programming partners in the software development process. Chapter 3 (“Applying the Scientific Method to Debugging”), like Chapter 2, is not specific to Visual FoxPro. In the chapter, I apply the scientific process to debugging. Excellent developers use some form of the scientific process. Visual FoxPro developers Jim Booth and Steve Sawyer, for example, have written about this topic, as have Telles and Hsieh. If one looks at the leading professionals to emulate their characteristics, then it’s reasonable to consider adopting a similar approach to theirs when it comes to debugging. In Chapter 4, “Visual FoxPro Debugging Tools,” I illustrate the Visual FoxPro debugger and productivity tools that support debugging. Along with good coding practices, one of the best preventative debugging techniques is a thorough knowledge of the language and development tools. In Chapter 5, “A Taxonomy of Common Visual FoxPro Bugs,” I propose a taxonomy of common defects that occur in Visual FoxPro applications. By common I mean that they are defects I’ve either seen in my code or seen in other people’s code. Most commonly, the latter experience arises from participating in the online newsgroup and Web-based communities for several years. I do not attempt to exhaustively catalogue defects. Rather, I introduce categories of defects, and include a recommendation for how to use the Visual FoxPro debugger components for each. I close the book with Chapter 6 (“Fitting into Enterprise Solutions”) and a brief discussion of some of the debugging issues that arise when VFP applications need to reach beyond their traditional boundaries to include or communicate with external controls, applications, and tools. While there are too many people to thank by name than these pages can contain, I’d like to thank especially a few people. Any of the credit for the information in this book goes to the

4

Debugging Visual FoxPro Applications

following people and the many other fine members of the programming community who have helped me become a better programmer. Any mistakes are mine alone. I want to thank Whil Hentzen for giving me the opportunity to set these thoughts down in a permanent state, and Eric Moore for the chat in Orlando that made me think this might be a topic of interest to the community. Thanks go to Paul Maskens, Andy Kramek, Steve Sawyer, Maurice de Beijer, Mike Helland, and Carl Karsten for providing ideas about debugging. I want to thank Zane Thomas for his moral support and for sharing his technical experience. Next, I couldn’t have asked to have a better editor to work with than Kelly Conway. He patiently and conscientiously reviewed this text, usually more than once. He always offered helpful, clear, and pointed suggestions that clarified the ideas contained herein. I’m also grateful to the entire Microsoft FoxPro development team but to Calvin Hsia and Mike Stewart, in particular, for answering questions I had about the product I love to use: Visual FoxPro.

Chapter 2: Quality Ensurance

5

Chapter 2 Quality Ensurance Debugging is just about everybody’s least favorite activity. On the other hand, a lot of time and money goes into debugging. Rarely is it possible to estimate accurately how long debugging will take, and generally project management systems fail to capture debugging time. Thus, it bears discussing how debugging fits into the project lifecycle, and to examine whether the task of debugging is necessary.

Debugging may be hard to define formally, but every developer knows it when he sees it. It is the process developers undertake to locate the source of a program’s questionable behavior. Debugging code may result in code changes, but that’s not necessarily so. That depends on what the source of the problem turns out to be, when it’s found, and what the impact of correcting it will be. Problems arise because of inadequate requirements, design defects, coding errors, and documentation errors. Participants can, and should, uncover whatever problems exist no matter what stage a problem is introduced. Once a problem is defined and the cause determined, participants should evaluate alternative solutions, identifying the weaknesses and strengths of each. The best solution might turn out to be leaving the software as-is. A bug is ultimately a coding error, albeit, not necessarily simple or easy to correct. A defect is any way that a program deviates from the published specifications, user expectations, or correct computation. Since debugging is the process of uncovering the source of problems in software, and since the source may be any number of things, it is misleading to think of debugging as only applicable to bugs. At it’s most general, it’s an investigation prompted by some event. In this sense, debugging can be applied to any defect at any stage in the project lifecycle from requirements gathering to deployment. In this sense, defects result for many reasons and at all stages of the development process. Starting an investigation implies two things. One, debugging is done after a project has started. Two, there must be a way to trigger an investigation. It’s somewhat obvious, but still bears discussing. Unless a developer knows that the system is behaving in a way that deviates from the expected behavior, there’s no way to know it needs debugging: except, of course, in the case of the most obvious program error. A number of questions follow from this, then. When should one start looking for problems in a project? How does one know when the system is not operating as expected? What are effective techniques for uncovering problems? How can one manage the process of finding and fixing problems so that the quality of software improves? Debugging is often difficult, time-consuming, and hard to predict. When an unexpected defect shows up, it disrupts planned work, drives everyone crazy, slows down progress. The best way to avoid these problems is to have very few defects. One good way to have few defects is to have good, comprehensive tests. —Ron Jeffries http://www.xprogramming.com/xpmag/PracticesForaReason.htm

6

Debugging Visual FoxPro Applications

To find problems in a process or product, it must be subjected to tests. When can testing begin? It can begin before any code is written. While the initial project phases such as requirements gathering, modeling, and system design are outside of the scope of this book, it’s important to note that planning for testing can begin as soon as a project has been kicked off. I have read frequently that test cases should be written before any code for a number of reasons, not least of which is to keep the participants focused on the goals of the project and to encourage thorough planning. Like many FoxPro developers, I work mostly independently on projects. While that advice made sense, I was a bit lost as to how to approach it, especially when I could barely get customers to spend time planning their applications. Then a client and I worked together to learn how to do use cases, and I found they have an unexpected benefit. Not only do use cases pin down the details of a business or system scenario in terms familiar to customers, but use cases also make superb foundations for test cases1. Not only can the early phases of a project set the stage for testing, but they also can be tested. Indeed, it is critical that every stage of the software development process include a mechanism to identify and correct problems. It’s critical because many, if not most, software defects occur before implementation begins. According to Engin Kirda in “Integrating Design and Automated Test Generation” (http://www.infosys.tuwien.ac.at/Staff/ek/ projects/idatg.html), only 30% of software defects occur during implementation, whereas 70% of defects occur during the requirements gathering and design phases. Not surprisingly, the later a defect is discovered from the time it is introduced, the more costly it is to fix. For example, finding and fixing a defect resulting from a missed or misunderstood requirement can cost 14 times as much to fix during the coding phase as during the requirement phase. Fixing the same defect found in post-release code can cost as much as 100 times more (Cigital Corp., http://www.cigital.com/presentations/testing_objects/ sld008.htm). According to W. Wayt Gibbs in “Software’s Chronic Crisis” (Scientific American, 1994), perhaps as few as one-third of the bugs introduced before release are caught before release. The process of developing software is an integrated, iterative process in that each stage builds on the prior one. Sometimes one stage even influences the previous one. Regardless of when a defect is introduced, at least some part of the analysis of the problem will likely involve development time debugging. How one gets from one stage to the next with a minimum of defects speaks to the quality of the development process.

Lies, damned lies, and… statistics The issue of quality is the business of software metrics, which is really just a name for having some way to objectively measure and predict the quality of software. Software development has been the subject of many attempts to quantify and predict quality. For example, a casual search on Amazon for “software metrics” results in 41 hits. The software metrics clearing house at http://user.cs.tu-berlin.de/~fetcke/metrics-sites.html includes a number of links 1

There are many resources for learning about use cases, which I use, and use case diagrams, which I don’t find as useful. The Internet is a free and quick place to start, simply by searching in your favorite engine for “use case.” However, a site with several papers on the subject is http://members.aol.com/acockburn, as recommended by Martin Fowler in UML Distilled, Second Edition.

Chapter 2: Quality Ensurance

7

(24 as of this writing) to sites with publicly available publications on the subject. I encourage you to make a study of some of the issues in software metrics. In particular, compare different methods. In general, formal metrics are useful when a project or team is facing a particular problem. Applying the correct metric can help a team overcome a problem and measure the success of process changes. However, when metrics become institutionalized and the process is more about the metric than about the product, metrics are less useful and perhaps even cause trouble in the development process. In addition, maintaining the procedures for a formal metric is difficult, if not impractical, for small teams and solo developers. Yet these developers also are concerned with quality and improving quality. There is a lot an individual programmer can do. After all, she owns her code until she incorporates it into the team’s work or delivers it to the client. So, there are situations where formal metrics are impractical, or where a metric has been adopted because of a management trend rather than to meet a specific challenge. Human nature inclines to fudging, slacking, or malicious compliance—a wonderful term I first heard while working for an oversight agency. When the agency enforced reporting requirements, some contractors took the approach of providing an overwhelming amount of information that buried the relevant data under an impossible weight. The goal of a process is always to build a high-quality product and ship it. It is not to have a nice-looking QA document. QA programs do not ship products, but they do help development teams ship high-quality products. That said, QA programs and software metrics arose for a very good reason: The perception is that “software quality” is an oxymoron. While many quality assurance systems may be effective under different conditions, I believe that what is more important than which system you follow, is that you have one and follow it. When a deficiency becomes apparent (and all systems can be improved), be willing to focus objectively on what is ineffective and make tactical changes. It’s likely that deficiencies occurred as a side effect of a process, so identify the reasons for them and institute a mechanism to avoid it in the future. Steve McConnell provides a handy quality checklist in Code Complete. He has also put the checklist on his Web site at http://www.construx.com/doc/chk04.htm. McConnell points out the importance of quantifying what exactly the measure of “quality” in a system is. Is quality measured by efficient memory use? Or is it the maximum time to enter a data set, or query speed? In order for the quality measure to be meaningful, it must be objective and not subjective. Contrast the difference between “checking a customer’s purchases should be fast” and “scanning three items should be able to be done in under 10 seconds, and the receipt should print within three seconds of posting the purchase.” The first is subjective and not quantifiable. There is no way to know when, or whether, you’ve been successful. The second example is objective. It’s harder to come up with objective goals, but it is easier to create tests to measure the goals, and to agree upon the results of the tests. Goals should be unambiguous. In addition to quantifying goals, McConnell recommends identifying the purpose of the goals. This will help develop priorities in case two goals are contradictory. For example, a goal of entering one item in a grid in no more than five seconds combined with a goal of validating every item entry against a complicated set of lookup tables, on a slow LAN, may not both be achievable. If one knows the purpose of the goals—for example, to minimize the customer’s wait for a receipt, to free the clerk to talk more with the customer, or to help unfamiliar users enter correct data—then programmers and designers can concentrate on solving the problem with the highest priority.

8

Debugging Visual FoxPro Applications

There are many predictive metrics for defects, including lines-of-code (LOC) measures and complexity measures. Some were developed as long ago as the 1960s, when languages and the applications written were different than a Visual FoxPro developer would probably write today. We are writing more applications with larger data stores that communicate with multiple back ends, perhaps over an intranet or the Internet. The applications have graphical and non-graphical elements, and they support users with accessibility limitations. We’re using more third-party tools and frameworks, and, as a community, FoxPro developers are making the transition to object-oriented development. Measures, or specifically, predictors of bugs per line of code and where bugs occur depend on many variables. Unless an analysis accounts for these variables and clearly states what the variables are, quality statistics should be taken as a guide and not a hard and fast rule. Variables include definition of bug, experience level of the programmers, type of project, language, development environment, process, and so on. What these numbers illustrate, if nothing else, is that the effect of bugs on the process can be significant and certainly feel significant whenever a test run is unsuccessful.

Today As far as we know, our software has no undetected failures. —Anonymous newsgroup poster Whether metrics are useful for your organization is an issue and subject outside the scope of this book, which focuses on what developers can do right away on a modest scale to improve their debugging process. It’s a bit of a cliché that the best way to debug programs is to avoid putting bugs in them in the first place. Like defensive driving, which is the idea that the best way to avoid an accident is to anticipate how one can happen at any moment, that’s easier to say than do. Testing, debugging, and good coding practices are inseparable when it comes to building quality software. Even when one has worked from the start to avoid writing bugs, some defects are inevitable. The goal should be to find as many as possible as soon as they are introduced. Testing and debugging during development qualitatively improves the experience of using the applications developers build. This assertion sounds like common sense. Evidence supports the idea that modules with more bugs reported during development have fewer bugs reported after release2. The evidence isn’t overwhelming—certainly more study could be done—but it is interesting and feels correct. A possible explanation is that those are the modules that are tested the most and, presumably, debugged. It is perhaps less obvious why the experience is better for testers and why that’s important. The fewer egregious bugs—fatal errors, corrupted data, or faults—the more time the testers can spend on robust tests of capability, usability, and correctness (of calculations, for example). The goal is to end up with a product that testers verify against a test plan and test for truly extraordinary conditions. It frustrates a test plan if 2

From Fenton, Norman E., Neil, Martin, Software metrics: successes, failures and new directions, Journal Of Systems And Software (47)2-3, pp. 149-157, 1999. http://www.agena.co.uk/new_directions_metrics/HelpFileWhat_you_can_and_cannot_do _with_.htm.

Chapter 2: Quality Ensurance

9

the first fatal bug is reported 10 minutes after tests begin. In this I speak with the voice of hard experience. It’s frustrating for everyone, of course, but it is a particular waste of a tester’s time, which can be expensive. There is no firm analysis of the impact of bugs on software, and what analysis has been done has been done against ever changing processes and tools. A survey of the literature finds references to anywhere from 3 to 5 bugs per 100 lines of released code for good programmers, which seems atrocious. However, that also equates to a 95-97% accuracy rate. Can the accuracy be 100%? Even if code is 100% accurate, it is impossible to certify that it is. Even if one could certify it so, what does 3 bugs per 100 lines of code mean? Are they “big” bugs or inconsequential? Is it a bug only if someone other than the programmer sees it? Do the bugs we fix in the normal course of writing code count? Were the mistakes I made writing this sentence typos? These are just some obvious criticisms of LOC. According to Fenton and Neil, neither the number of lines of code nor the complexity of code is an accurate predictor of defects. In any case, it’s impossible to certify code as 100% correct because of the variables involved between all components both internal and external to the software. It is impractical to do so because of the pressure of releasing software in a suitable—as in, profitable or useful—timeframe. One conclusion I’ve drawn is that some bugs are inevitable and that debugging is simply a part of the job of programming. This approach has freed me to look at debugging as a normal part of the process of programming, which means it’s a process that can be improved upon and that is deserving of quality support tools. Why, then, does time spent debugging feel like a waste of time? Why doesn’t it feel like something we should be doing? After all, debugging, especially during implementation, is great. It means that the product is on the way to being one step up in quality, and that’s a good thing. Right? I contend that debugging often seems unproductive because the amount of effort put into tracking down the source of a defect is often far out of proportion to how long it takes to fix. If the cause, or the solution, is complex, schedules and budgets may be seriously impacted, other programmers’ modules affected, or customers made unhappy. A simple cause is rarely a comfort since one knows it would have been better to avoid the bug in the first place. Debugging unfamiliar, complex, or undocumented code is particularly frustrating. Everyone is familiar with the “build it from scratch” syndrome. This seems to be an occupational hazard. Programmers want to rewrite code: others’ and their own. It seems easier to write it anew than to trace through and understand the existing code, or one simply knows more today than yesterday. One’s own recently written code is easier to understand than either unfamiliar code or old code. How much easier is it? An interesting experiment would be to ask developers to describe the functionality and logic of a recently completed routine. The experiment would have the programmer do the same thing after 6 and then 18 months. Perhaps one study could let the programmer review the code for a short period of time before writing the description, and another would ask them to write the description based on rerunning the module from the user’s point of view. It would be an interesting result set, and it would be instructive to include a variety of skill sets, languages, and development styles. Debugging also feels unproductive when one has to track down and fix the same bug, or the same sort of bug, repeatedly. Sometimes even in the same code. Finally, debugging is frustrating when it interrupts the flow of work. This is probably one reason bugs introduced and

10

Debugging Visual FoxPro Applications

fixed during development are faster and cheaper to find and fix. The interruption to the flow of programming is minimized.

Test early, test often It’s heard often. Test early, and test often. A not-so-pithy corollary to this maxim might be test early, test often, and fix what’s found. Testing alone does not improve quality. Debugging alone only documents defects, which may be the best short-term solution available. However, quality is ultimately assured by fixing defects. Computer software is the brightest of bright spots on the American economic landscape, a consumer product evolving in a floodtide of innovation and ingenuity, an industry that has barely noticed the recession or seen any challenge from overseas. Bugs are its special curse. They are an ancient devil—the product defect—in a peculiarly exasperating modern dress. As software grows more complex and we come to rely on it more, the industry is discovering that bugs are more pervasive and more expensive than ever before. —James Gleick “Chasing Bugs in the Electronic Village” http://www.around.com/bugs.html Why test early? Why not wait until the end and test an application all at once? As I mentioned previously, Fenton and Neil present evidence that the more a module is tested before release, the less likely it is to have bugs reported post-release. Finally, defects are cheaper to fix the sooner they are caught. Period. Any defect that makes it into production code will have escalated in criticality simply because a user found it or will have to upgrade her software when a patch is issued. Any disruption to a user is serious. How often should you test? Test new code always. It is possible to argue that quality in software has improved over the years—the applications I use do more and crash less than ever before. Yet the perception is that quality is still the same awful mess. Using Visual FoxPro, an object-oriented language, facilitates writing encapsulated code, thereby achieving one of the long promised benefits of reuse. Reusing tried-and-tested code improves software quality. The trouble with reuse comes when code is reused inappropriately. This can lead to the temptation to sneak in special conditions. Instead, step back and reconsider the design. If a routine or class can’t be reused as-is, then perhaps it shouldn’t be reused at all. There are usually other solutions. Object-oriented design principles and design patterns are a worthwhile study for this problem. In any case, the risk of changing existing, encapsulated code needs to be weighed as a risk. It is no longer strictly code reuse but modification that has a cost associated with it of retesting the module—thoroughly. Once code has been tested and is correct, however, will it always be correct? Not necessarily. Retest modules whenever any code has changed. In addition, retest whenever reusing code in a new environment, and it’s helpful to retest legacy code whenever a fundamental change is made. An example of a fundamental change is when a form base class has a save method that is called before the form closes. Any change in such fundamental code should prompt a smoke test and an integration test. I go into detail on these tests later in the chapter.

Chapter 2: Quality Ensurance

11

But… I have a test team Your job, I presume, is to program—not test. You may work in a shop that has a formal test team, or a shop that measures productivity in lines-of-code written. I hope I’ve convinced you that testing and debugging during the implementation phase is cheaper and more efficient than debugging later in the process. If you do not have any explicit project responsibility for schedule or budget, then the benefit to you may seem tangential. If a shop is so ruthless and short-sighted that taking time to test and thoroughly debug your code really does constitute a risk to your job, then it’s possible the job is already threatened either because of the whims of an unreasonable management team or because the company will be out of business through poor customer support. This is an extreme case, however. There are several arguments to be made for persisting with rounding out your immediate (programming) responsibility by verifying your product. All professionals have a responsibility to do what’s right for quality. It’s not enough for a bridge engineer to use substandard materials because a client demanded it. But, really, whom does it hurt? Surely, the testers will find the bugs, won’t they? The bugs will be reported and then there will be time to fix them. An operating procedure that relies on these notions is a good indicator of a project and a team in trouble. Joel Spolsky tells a perhaps apocryphal story of a Microsoft Word 1.0 developer who coded a function that was supposed to calculate line height. He didn’t have time to write it properly because the project was far behind schedule. Since debugging wasn’t part of the schedule, however, programmers were writing buggy code, counting on time to fix bugs after testing. So, the programmer in question just wrote a function that returned the value of 12. Since this was correct most of the time, the code made it quite far into production before it was caught. As James Gleick points out in “Chasing Bugs in the Electronic Village,” the tactic of delaying testing and debugging wasn’t successful—customers were more than a little inconvenienced. According to Spolsky, Microsoft changed its policy to emphasize fixing a bug before going on to write the next bit of code. In other words, they embraced developer testing and debugging as part of the development process. If developers should test their code, what kinds of tests are appropriate? John Robbins discusses the “smoke test” in Debugging Applications. A smoke test is a list of minimal features an application must support. A developer performs smoke tests by building a private copy of the whole application and testing his new code in context before moving on to the next task, in the simplest case. A development team conducts smoke tests on the same set of features, or perhaps a more extensive set that includes specifically the new work that’s been done, before deciding the build is ready to go to testing. A smoke test compares an application’s functionality against the basic points of what it is supposed to support. Any changes to code should be checked against these points. It’s surprising how often a core piece suddenly stops working. If you don’t remember the last time it was working, you have a hard time knowing where to look for what broke it, and the more changes you’re likely to lose by rolling back to an earlier version. Smoke tests fit into the software project process during implementation or prototyping whenever a developer has written a unit of code that he wants to check against the specification. Unit testing is like a smoke test, except that it should test the code against the complete specifications for the code. Unit testing can be incremental (“test often”). For example, when you create a new class, test each method as you write it. You can save your tests to retest the

12

Debugging Visual FoxPro Applications

class as a whole before turning it over for project integration. You can also reuse these tests when retesting the class after making changes. Is one kind of test enough? Probably not, according to Steve McConnell in Code Complete. It is likely that adding a second kind of test to your protocol significantly and effectively improves the quality of your product. In particular, McConnell mentions studies that have found simple code reviews are 70% more effective than unit testing and less expensive. If two tests are better than one, code reviews can be a great addition to your repertoire. They have the additional benefit of letting colleagues share tips and tricks. Unit testing includes glass box and black box testing. Glass box testing, not surprising, is testing that is done while specifically examining the code that is being tested. You check whether all the lines of code are being tested during glass box testing. You also monitor the effects of tests, such as examining variables, for example. With glass box testing you might even change the state of the routine being tested. Glass box testing uses the traditional debugger tools: the trace window, debug output window, call stack, and locals window. Black box testing examines the output of code without looking into the details of what happens internally to the routine. This means that you have to have some way to input data and get results. This is one of the reasons that it’s important to consider how you will test your code while designing and implementing it. In short, to make testing (and debugging) easier, always include some kind of a return value in every procedure or method. Even if the return is a generic success flag, and it seems that success is guaranteed, there is nearly always some way that a routine can fail. If you have a return value, it’s sometimes easier to set breakpoints for specific methods or procedures. Programmers should also perform integration testing, which tests whether the code works correctly with the larger code base into which it fits. Integration testing fits into the software project process during the implementation phase and subsequent to successful unit testing. Integration testing checks whether the new code breaks the existing code base. An example of how code might pass a unit test, but fail an integration test is common in modifying an existing module. For example, let’s say a routine has been written that has different branches depending on what parameter has been passed in. If you have a requirement to add a branch to the code (not a great example of a well-designed module, perhaps), then it might be that you’ll inadvertently change the return value to a data type that calling code will not expect. Code should never be incorporated into the code base until it’s passed its integration tests. Of course, this means that there has to be a clear idea of how the new code will be used in the system. Robbins writes that he has participated in teams that used the convention that the person who checks in code that “breaks the build” becomes responsible for managing each subsequent day’s build… until the next person breaks a build. A build is the term used for creating the application—commonly the EXE or APP. On some teams, each night’s builds are smoke-tested by the test team to make sure the application is still working with regard to at least its basic functionality. I’ve been guilty of sending an update to a client, customer, or colleague that I thought was working but that broke as soon as the recipient tried to use it because I’d forgotten to update a related piece of the application. This is not only embarrassing, but it’s a waste of effort, budget, and schedule.

Chapter 2: Quality Ensurance

13

System and beta testing is done by the test team or individual—a party other than the developer should do this testing. When are you done testing? When your code compiles without any errors, and when it has passed smoke tests, unit tests, and integration tests, you’re ready to move on. If any test fails, then that is when debugging starts (or continues).

“Houston, we have a problem.” Tests by themselves don’t remove defects. First, it’s necessary to understand what the results of the tests show well enough to be able to examine, diagnose, and correct discovered bugs. “I don’t know if I would use that word,” a Microsoft support engineer said. “What word?” I replied innocently. “You know—that three-letter word you just used.” Of course he wouldn’t use it. He’s under strict instructions never to say “bug” to a customer. In the official parlance of the world’s most powerful software company, when a product is defective, one may speak delicately of an “issue.” This could be a “known issue” or an “intermittent issue.” Then again, it could be a “design side effect” or “undocumented behavior” or perhaps a “technical glitch.” Excuse me while I go powder my nose. —James Gleick “A Bug By Any Other Name” http://www.around.com/microspeak.html

Debugging during development In the opening paragraph of this chapter, I say that there is a lot of development time spent debugging. How much time is “a lot”? Unfortunately, but perhaps not surprisingly, there is no easy answer to that question, and most answers interrelate testing with debugging. Answers, however, range from “any time is too much time” to “60-70% of development time” (ParaSoft, http://www.parasoft.com/papers/bugfree.html). According to Steve McConnell, debugging can represent as much as 50% of development. Debugging during development means less debugging later. At the very least, code you are working on currently is easier to debug than code you wrote two months or a year in the past. As I write code, I think about situations that can result in errors in my code. Am I making an assumption about a parameter type? Am I assuming a table will be open? Those vulnerabilities point to potential test cases and preventive code.

*

The Task List that Visual FoxPro 7.0 introduced is a good tool to make notes of test cases you think of as you’re coding.

“Bug” is used casually to mean a problem with program code—usually still undetermined as to the exact cause or fix. The way a program behaves (or doesn’t behave) that is in contradiction to the way it is supposed to behave (or not behave) is a bug. Bugs can exist in

14

Debugging Visual FoxPro Applications

documentation so that a program doesn’t operate the way the documentation says it should. Bugs can exist in the way a design choice results in users consistently making incorrect choices or being confused about the intent of a prompt, for example. There are defects that are faults, errors, or, sometimes, features. Dieter Kranzlmüller says, “A failure is the inability of a system (or one of its components) to perform a given function within given limits. Additionally, a failure is often defined as a loss of some service to the user” (http://www.gup.uni-linz.ac.at/~dk/thesis/html/problema3.html#828911). Errors (incorrect calculations, for example) and failures are the result of faults. A fault is a mistake introduced into the application by a programmer with incorrect logic or a poor implementation of a design feature. Faults may be dormant or unobtrusive, or just rare for a long time, even years. This is, at least partly, why the literature refers to the bug potential, rather than the bug rate. Effective debugging starts by recognizing that defects are errors in the original requirements or design, in implementing the design, or in coding. Defects in code can be introduced by modifying code or as side effects to changes to other code, as when a new use exposes a hidden bug. Defects can result from running the same code base in a new environment. In short, every defect is the result of some change to the system. When searching for the cause of a defect, it is critical that only one thing be changed at a time so that 1) you know whether the change had the effect you expected and was solely responsible for the defect, and 2) you can roll back any changes that are unsuccessful.

Debugging test versions Every so often, a build of all the code the team is working on will be shipped to the testing department. That might be nightly, monthly, or at the end of a project. Even though it’s a test build, treat it as if it’s going to the user. The testers will be looking at it from that point of view. What testers will expect is robust code with whatever functionality the development team has said it should have. What you can expect in return is an extensive and knowledgeable report of any problems or questions. Where a user might not be able to tell you how much RAM they have, your tester should be able to tell you operating system, version, service packs applied, and specifically what steps they took to get the error including any sample data if that’s applicable.

Debugging in post-release or maintenance Debugging production code is more difficult than debugging during development. It’s also politically more sensitive since somebody besides the programmer has probably witnessed the problem. Debugging now has a significant psychological component. How clearly the program informs the user about what has occurred and recovers from an error affects how difficult the problem is to debug. If you don’t include custom error messages, the user will see the default error message—no line number, either, unless you ship your EXE with the debug information compiled in, which is not the recommendation for Visual FoxPro applications. The user will be, by definition, frustrated and probably impatient when an error occurs. Many users do not bother to write down messages, especially since many program errors are cryptic.

Chapter 2: Quality Ensurance

15

Designing with diagnostics in mind One part of a program’s responsibility is the obvious functionality. Another part is maintainability, and part of maintenance is how accessible the logic is for debugging and enhancing. Later chapters discuss using Visual FoxPro features to aid debugging. Any useful technique ultimately involves getting information out of the system when it’s needed in a reasonable format. If debugging becomes an integrated part of the software development lifecycle, then elements can be designed in from the beginning that support debugging and therefore minimize the cost of debugging. This can mean designing in a message path for objects to communicate with the system. Consider whether messaging can use an interface or not. For example, is the code running in a server-side component? The University of California, Riverside, Computer Science Department recommends “including a dump member function for each object class” (http://www.cs.ucr.edu/content/documentation/docs/programming/design.htm). For example, error methods could call this dump method and get back a string representing the current state of the object. The string can be displayed to a user, written to disk, or passed to an error handler.

When are you done debugging? How confident are you that you found the source of the defect? If this one item is changed, does the problem change or go away? Have you identified at least one solution? If you have implemented a solution, have you tested it? If you aren’t able to implement a solution right away, have you identified a workaround or the steps to recover from the problem? Have you documented the problem, source, status, solution, and action taken? If the effort was frustrating, consider ways it might have been better. It’s important to test that a solution is correct. If I’ve done much experimenting to find the problem or solution, even though I’m careful to roll back changes, I often revert to my source control version. Then I run the error test, make the planned change, and rerun the error test. This assures me I’ve fixed the problem. According to the University of California, Riverside, more than half the bugs introduced by professional programmers are introduced during the debugging process (http://www.cs.ucr.edu/content/documentation/docs/programming/ debugging.htm). Whether this is the result of forgetting to remove test code or of coding errors, it shows the importance of thoroughly retesting after making debugging changes. Finally, have you considered whether the problem occurs anywhere else in your application? For example, the following code sample has a latent bug in it that is not immediately apparent from casual reading. USE (HOME() + "SAMPLES\TASTRADE\DATA\CUSTOMER.DBF") SET ORDER TO Customer_I LOCAL lcSetOrder lcSetOrder = SET("ORDER") IF !EMPTY( lcSetOrder) SET ORDER TO &lcSetOrder ENDIF

The bug occurs in the combination of SET('ORDER') with a space in the path of the table’s file name, as is the case with the default Visual FoxPro installation directory, which

16

Debugging Visual FoxPro Applications

in this case is in \Program Files\. I would find this out by examining the value of lcSetOrder when the SET ORDER command causes the error “Command contains unrecognized phrase/keyword.” As a part of debugging this single bug, I would ask myself whether I use this convention in other parts of my application and, if so, evaluate the cost of fixing the problem against the cost of not fixing all instances. I don’t start fixing multiple occurrences of one problem until I’ve tested one thoroughly. Only after you’ve confirmed that a defect has been fixed (or carefully documented) should you consider moving on to write the next new bit of code.

Risk Risk management is the process by which the cost of an action is weighed against the risk of taking no action. Risk is measurable with a number of criteria, and risk management is only as good as the criteria and metrics for measuring it. Today’s feature may be tomorrow’s bug. For example, the millennium bug resulted from trying to save memory and disk space, and time doing data entry. When faced with a clear diagnosis of a bug and a likely solution, come up with at least two alternatives, even if there seems to be only one. The choice to do nothing is frequently overlooked, but can always be weighed. The reason to have a handle on your options is to manage the risk inherent with making any changes compared with making no changes. Some elements that go into managing the risk of code changes include the following questions. What is the effect of doing nothing? This means, what’s the impact of the bug? Is it something that only 5% of your users will ever find but will put the project 25% over budget? Is it something that will cause 90% of your customer’s monthly accounts receivable processing to be out of balance? Every time you find and fix a bug, you can make this assessment. From the obvious, a syntax error the first day you’re coding on the project, to changing the name of a base table in a database container an hour before FedEx is supposed to leave with the CD for headquarters. Every bug has a cost, both to fix and to leave alone. Whichever is the lesser cost is, usually, the correct action to take. However, every bug, even ones too costly to fix, should be documented, along with the reason for not fixing it. In the case of the infamous Pentium floating-point bug, Intel decided too few people would ever encounter the bug and refused to acknowledge the problem. Then, when they acknowledged it, they refused to acknowledge that it was serious, and then that it needed to be fixed. Intel’s initial assessment was, arguably, correct, but the approach they took with their customers was not, as they eventually decided. The previous SET ORDER example is from a legacy application I’ve worked on that predates long file names—an example of yesterday’s feature leading to today’s bug. Even though the solution is simple in this case—I can use ORDER() instead of SET("ORDER")—the team had to weigh the risk of making extensive changes in established areas of the code base during final testing. It was decided that the risk of introducing more bugs or delaying the schedule for more testing was higher than for not making the change at that time. The situation and solution are documented and planned for a future release. Whether the choice is to implement a solution or not, there will also be a cost to the customer. A change that means an entire application’s code base must be searched for a specific, common condition will necessitate retesting the entire application, and testing is expensive.

Chapter 2: Quality Ensurance

17

Measure twice, cut once I once worked in a cabinet shop as a detailer. The detailer translates a layout and design into a cut list and construction instructions for the shop to build the cabinets. I worked with an experienced craftsman a few years from retirement—a curmudgeon if ever there was one. Bob built some of the custom cabinetry. My boss frequently dashed through my office demanding that Bob speed things up (Bob wasn’t in my office). I learned two things from this particular relationship. First, Bob took however long he was going to take. Second, even though he took longer to build a cabinet than other craftsmen, he built things once, only. None of the other craftsmen could claim this. One or two cabinets were returned to the shop from nearly every job because other people cut corners or rushed through their tasks to meet schedules. Bob told me once when I asked him about this that carpenters have a saying, “Measure twice, and cut once.” The analogy fits debugging and programmers in a couple of ways. Debug a problem until you’ve identified the real cause, corrected it, and tested the correction. It’s especially hard for junior programmers to resist being bullied by external pressures while not resorting to obstinacy that can be equally as frustrating as bugs. Experience helps balance these extremes. Unless one’s ambition is to always work with other people’s designs, a programmer will have to eventually step into a critical role. My experience is also instructive in that Bob had to be firm repeatedly. Once wasn’t enough. No matter how many times my boss acknowledged that Bob’s work was better and in the long run more cost-effective, the next project went the same way. “Measure twice, and cut once” is the equivalent of taking the time to identify the real cause of a defect and the best solution instead of writing code around the symptoms of a problem. This approach might feel like it takes longer in the short term, but it is often more effective in the long run. Otherwise, it is likely you’ll be debugging that same problem again, or it may be even harder to find the real source when it starts causing different behaviors. Another problem with this approach is apparent when you find that the solution is difficult to change later because other work has subsequently been built that depends on the quick fix. I’ve never mastered the art of the quick fix even though I have tried to cultivate a bit more of the fighter pilot mentality. I’m more of a Bob. In my experience it’s usually just as easy to solve a problem correctly than to kludge a temporary solution. It is probably wise, in this regard, to know what your particular strengths are as a programmer. In some cases, it won’t be possible to identify the real cause of a problem with complete assurance. In this case, weigh the option of taking no action. If the error doesn’t cause fatal errors, and if there is a reasonable workaround, patching over a problem without finding the cause may introduce even more bugs, which makes finding the real cause harder, and misleads the team into thinking the problem was solved. If the action is to do nothing, then document the debugging process: What was tried, what were the results, and what hunches do you have about where to look next? This saves having to retrace old ground when the issue next arises. Sometimes, too, in writing notes, I’ve seen where I hadn’t done what I thought I had and was able to find the problem. I had a bug in my debugging process, as it were.

Bug tracking I mention that documentation is as important during debugging as it is elsewhere. It’s probably just as unlikely to be done, especially with pressure to find and fix a bug.

18

Debugging Visual FoxPro Applications

Documenting is important for several reasons, and it can save money—if not on the current project, on a future one. Document code you write to test hypotheses about bugs. This will help you find and remove code that should just be temporary. Failure to do so can introduce new bugs or cause embarrassing messages. Who hasn’t left a message like “I have no (*#(@ idea if this will work” in a production application? Document code that is written to solve a problem: what it does and why it was needed. Most programmers have revisited mysterious code and wondered why it’s there. It may not appear to do anything, but the fear is that it’s some bit of special glue holding a bug fix in place. The time spent wondering about the code will exceed the time it takes to document the code. Document the bug in project documentation so that the rest of the team can learn from it, and so that you can remember how you fixed it when you see it again in a few months or a year.

Source code control Source code control is a general term for a process that saves versions of a code base. This can be as informal as a careful use of WinZip or as formal as third-party tools such as Concurrent Versions Systems (CVS) or Microsoft Visual SourceSafe. The former (http://www.cvshome.org/) is a free source code system. The latter (http://msdn.microsoft.com/ssafe/) is included with Visual Studio Enterprise edition3. Whatever tool is used, a careful procedure is critical to using source code control successfully. Source code control aids debugging. I recommend using a formal source code control system since they can be used effectively by solo programmers as well as by teams, and since source code control systems offer versioning specific capabilities such as labeling, version comparisons, and histories. Use Visual SourceSafe either from within the Visual FoxPro IDE or from the Visual SourceSafe manager. Any type of file can be tracked in it, and it can be used over the Internet through virtual private networking or in combination with Source OffSite (http://www.sourcegear.com/sos/), which optimizes remote access to Visual SourceSafe. If you don’t use source code control, it might be for a variety of reasons. My reason was that I didn’t want to bother installing and learning it. My projects had only one or two developers, and WinZip was adequate. Once a third programmer was added to a particular project, our team decided to move to Visual SourceSafe and we have never looked back. Visual SourceSafe specifically supports debugging by being a repository of versions. Any file can be rolled back to an earlier version. If the developer who checks in a file is careful to comment each check-in, a history of a file will give the team a good idea of what may have broken in a particular module, if you’re in Cancun on a well-deserved vacation. My technique for Visual SourceSafe is to check in known good builds of my code so that each comment will be specific to what has changed in the file.

Irreverent evangelizing No, no, not to you. After all, you are reading this. It’s my experience that bugs rarely occur because programmers don’t care about them. Programmers generally loathe bugs. And almost 3

As of this writing, it’s not clear what purchase options will be available for Visual Studio .NET.

Chapter 2: Quality Ensurance

19

to a person developers are stubborn enough that there’s no way they’d let a bug defeat them. Bugs exist in software for any number of reasons other than apathy. Programmers can work within the system to insinuate, encourage, spread, and otherwise evangelize good processes. Bugs happen, even if your users haven’t found one in a long time. There is no way to certify a program of any useful complexity as bug-free, and if you could, no one would buy it because it would have cost an infinite amount of money to develop and would never be ready. In addition, there’s a fine line between a bug in code and a user’s perception of a bug. If a user’s program crashes because of a printer driver, and other programs don’t crash using that printer driver, you can quarrel with her until night falls that the bug is in the driver, and it won’t matter. Browbeating users is rarely effective at anything but ensuring they will avoid calling you back, which is not optimal for job security. Bugs exist because of schedule, budget, and process pressures. Bugs exist because the tools we use have bugs: the operating system, the language, and the video drivers. Bugs exist because systems are complex and interactions between components are sometimes unpredictable. Bugs exist because a scientific process for software development is notoriously difficult to pin down. So bugs exist in production software because customers want something, anything, in their hands. However, customers also dislike buggy software. So, there seem to be divergent but concurrent goals. Bugs also exist because the time-to-market pressures in the computer industry are intense. The pressure to ship is measured in months rather than years, and yet Joel Spolsky makes the case that good software takes 10 years to build (http://www.joelonsoftware.com/stories/ storyReader$368). No one in his or her right mind expects to have 10 years to complete software, however. So, logically, successful software evolves over time. It may even be necessary for it to be in use, real use, in order for it to develop robust characteristics. Given all of that, what can be done other than wringing hands and hiding in cubicles or snarling at users when they report bugs? Other than keeping a sense of humor and a dash of humility, programmers can evangelize to their bosses and colleagues about the process. Learn how software projects work and develop ways to work the process into the corporate, or customer’s, culture. Let people know, gently, what debugging and testing is, what it accomplishes, and what it costs. Then adopt and hone skills that avoid bugs in the first place, and help find and squash bugs effectively when they do appear. Evangelize the importance of requirements gathering and design. Not once, not twice, but repeatedly. It’s a cultural shift and will require the patience of a stream wearing a sharp stone round. It’s a slow process but effective. Finally, you can rate your team according to Spolsky’s own metric for software team quality at http://joel.editthispage.com/stories/storyReader$180.

The best offense is a strong defense If the majority of software defects is in a product by the time coding starts, then it is reasonable to presume that debugging is a beneficial process throughout a product’s lifecycle—the earlier the better. Good debugging skills are good problem solving skills, and, thus, can be applied even to the early stages of software projects—requirements gathering and design. Testing and good coding practices are complementary to debugging. Early effective testing finds defects before customers see them, and the earlier defects are uncovered, the cheaper they are to fix. Given that developers will continually improve their programming skills, and since it’s cheaper and more cost-effective to fix bugs as soon as possible after they are introduced, it

20

Debugging Visual FoxPro Applications

makes sense for programmers to test and debug their own code throughout the implementation phase. Even where the work environment isn’t conducive to formal testing and debugging, there are ways to introduce good habits if only to improve one’s skills enough to seek out that dream job. Even if I achieve my goal and convince you that debugging isn’t an annoyance or tangent, but is, rather, an integral part of programming, debugging is often frustrating nevertheless. Anything that minimizes the impact of debugging is a good thing. However, whistling past the graveyard, pretending no bugs could have possibly crept into my code does not qualify as a quality assurance program. If debugging is a full partner in the process of developing software, it should be part of the project planning, but it is hard to quantify. It’s hard, but not impossible. If you use a timer to log project time, set up a debugging task for significant efforts. Use the research that shows that debugging can be 50% of development time to estimate how long you will need for debugging, even if only for your personal information. Review difficult debugging sessions to consider how the debugging approach you chose worked, or didn’t work. Is there a process or technique that would avoid similar bugs in the future? Even a programmer who is not a designer or project manager can have an impact on the quality of her product if she considers her coding techniques and how they can be improved. Clearly and calmly explain the different aspects of programming—coding, testing, debugging, documenting—and put each part of the process in business or layperson’s terms. Prepare to be able to explain why each is important. I’ll wager most programmers have had the experience of a customer saying, “You’re the expert, you should know all this, I’m not paying you to learn it and I pay you too much for you to make mistakes.” Analogies can be helpful, especially if your customer’s process has a natural affinity with the software development process. Doctors? Try an analogy to diagnosing what’s wrong with a patient. Counselors? Investigating the source of a conflict between two people. Lawyers? Both programmers and lawyers are experts but the domain is too large for any one person to have it all in mind at once. All three disciplines require research, strategies, and specialists. Managers and clients can be anxious when a developer seems to be sitting and staring at his monitor, muttering, not making any visible progress, or, conversely, tearing his hair out and complaining about all the problems he sees in his code. Better, I think, is the image of an emergency room doctor with a professional and reassuring manner. An ER doctor remains focused on the highest priority issue until a patient is stabilized, at which time other problems can be addressed. In our case, the immediate priority is diagnosing the cause of a defect, or bug, in an application, so that the defect can be corrected or mitigated. Debugging is a matter of remaining calm while examining the patient, our software, exhibiting a problem, and identifying the most effective approach to correcting the problem.

Chapter 3: Applying the Scientific Method to Debugging

21

Chapter 3 Applying the Scientific Method to Debugging Can debugging be described as a formal process? In Chapter 2 I define “bug” and “debugging.” I have claimed that good programmers use the scientific method when they debug programs. How is it helpful to consider debugging as a scientific process? If the goal of debugging is to fix a bug, then it makes sense that the process of debugging must reveal enough about a behavior to decide it is a bug, what its exact cause is, and how it can be effectively changed. As you recall, the scientific method is a strategy for understanding something about the world. It is an iterative strategy comprising the following, ordered activities: 1) observation, 2) inquiry, 3) hypothesis, 4) prediction, 5) experimentation, 6) analysis, and, finally, 7) decision of how to proceed. This method is formal, common, and easy to remember. “Formal” is a slightly scary description to use. It needn’t be, however. Formality simply means that the methodology is well understood and tested. As such, we can rely on the experience of others to help facilitate our own, specific efforts debugging applications. New programmers can confidently rely on the process to learn how to effectively debug their own code and evaluate other people’s code. Experienced programmers rely on it by using it explicitly for new, difficult problems or implicitly as second nature.

The scientific method is designed to systematically gain an understanding of phenomena. In this book, of course, the phenomena are behaviors, usually undesirable, exhibited by an application. The science of debugging isn’t a boilerplate for finding and fixing bugs. There are no rules for how to evaluate when each step is complete, and there are no guaranteed steps to finding any specific bug, nor 100% assured fixes. The method does provide a strategy for deciding what steps are more likely to be effective, and to identify holes in one’s knowledge about a behavior that will impede finding a satisfactory solution. If the scientific method is a strategy or plan for achieving our objective, which is fixing a bug, then the actions I take during debugging are the tactics for accomplishing the plan. In other words, the strategy is to use observation and experiment to understand a behavior. The tactics are how I make observations and carry out experiments. I find it useful to clearly separate the activities of designing, coding, testing, and debugging, and so I refer to “debugging sessions” even if a session ends up being just a few minutes. It’s important to differentiate the activity because the system that contains the bug needs to remain static except for tightly controlled changes designed to uncover the bug. If you start dropping in code that is unrelated to the problem, you introduce complexity that may mask or change the original problem, and you’re distracted from the issue, too. Sometimes debugging requires making one change in code and then carrying out a specific set of steps to test the results of the change. Being distracted means the test may not be completed or a detail might be missed. To carry the scientific analogy a bit further, isolating the debugging activity is a bit like donning protective

22

Debugging Visual FoxPro Applications

lab gear and starting an experiment with clean pipettes. A dirty pipette will contaminate a sample, thus leaving the results ambiguous and suspect. There is one exception to writing code while debugging: comments. If I am spending time in a block of code while experimenting and it turns out that the code is unrelated, it might be because the function of the code is not well understood. However, even commenting code while debugging another behavior is problematic since it’s easy to mistype the comment character or to leave a semicolon at the end of a comment, thus commenting the next line. If the next line is code that should be executed, suddenly it won’t run, thus leading to unexpected results. In any case, resist the temptation to write code. Use the task list that was introduced in Visual FoxPro 7.0 to make a note to come back to the code, or leave a comment in the code with a reminder of what needs to be done. If you have a standard tag for “to-do” comments, it’s simple to search a project for the flag and come back to the code later when you can give it your full attention.

Observation How does the process of debugging start? Obviously, a debugging session starts because someone noticed some interesting behavior—interesting in that the behavior is noticeable enough that it is reported and is, therefore, anomalous. An anomalous behavior might be suspected or obvious, devastating or inconvenient. A behavior might become apparent while running an application, stepping through code for an unrelated reason, or just reading through code. A behavior might be revealed during pre-release testing, or an end user might find a bug in production code. Until a bug is identified with some certainty, it can be thought of as a behavior. Thinking about it as a “behavior” instead of a bug immediately satisfies the first ground rule for debugging. Make no more assumptions than are necessary. No matter how a behavior is identified—either by a developer, tester, or end user—in all cases an effective debugging strategy starts with a simple observation. “Oh, my daily report didn’t print the permits I know were entered today,” for example. The observation is that the daily report didn’t include some information, and the reason that is interesting is because the expectation was that the information would be printed. This is a simple example. Another might be, “When we enter a new space, we cannot get to the locations information.” At this point, I don’t know whether that is a coding bug or not. It might not have been known during design that this would be desirable. While the observation is straightforward—try to use one sentence to capture it—there are two things to keep in mind: Keep each observation to one behavior, and determine whether the observation can be repeated. Now, initially, one might be inclined to think that it’s hard to keep the observation to one behavior… sometimes problems seem more complex than that. Usually that’s a sign that one has already jumped ahead to trying to figure out why a behavior is occurring. For the most part the thing that catches one’s attention is specific. It is sometimes the case that where the initial behavior is easy to describe—“My reports didn’t print”—the cause may end up being far more complex and might require splitting up the problem. This is when the process becomes iterative. It’s not necessary to drag this step out. The next step, inquiry, is designed to elicit a complete description of the behavior, including how to repeat it.

Chapter 3: Applying the Scientific Method to Debugging

23

Inquiry Observation is followed by inquiry. Inquiry should result in a complete description of the behavior such that the behavior can be communicated to another person and repeated reliably. Repeatability is important both in causing behavior and in the experimentation that investigates the behavior. In order to come to a complete understanding of a behavior, make few assumptions. There are some minimal assumptions one cannot do without. First, assume there is an objective reality. It’s safe to assume the behavior is not a curse visiting you from a past life, although it can surely feel like that some days. Second, assume that your code is the source of the behavior and it is not due to external software, hardware, or a bug in Visual FoxPro (or the end user). Although all of those items can be culprits, it’s more commonly somewhere in the code. Often it doesn’t matter whether the source of the bug is outside of the code: Developers usually have to deal with it in the code, or support users who face it, anyway. So, getting started with an inquiry, I have found a template to be helpful. The template I use is based on formats used by beta software testers. The template has eight items: 1) Description, 2) Steps to Reproduce, 3) Observed Behavior, 4) Expected Behavior, 5) Bug Type, 6) Priority, 7) Version, and 8) Notes. The format is clear, clean, and thorough. It’s also flexible and should be filled out in a way that makes sense for the behavior that is being reported. The way it’s filled out might be different depending on who is reporting the behavior and to whom they are reporting. The ways a programmer and an end user might use the template are included in the discussion of the template. End users may not want to use a template, although if you can get your users comfortable with one, and demonstrate how it helps you help them more effectively, then they might be amenable to it. Even if an end user doesn’t care to use a template, I use it, filling it in with information solicited from the user.

*

An alternative to users filling out a form is to write error trapping and auditing routines that trace critical program information in a text file. When end users send in bug reports, have them send you the diagnostics log, or, better yet, have your application automatically e-mail it to you.



A Microsoft Word template file of this format is available to download from http://www.hentzenwerke.com.

1.

Description The description is simply the observation about a behavior that is interesting. For example, a programmer might notice “We have a custom container with a MiddleClick event that only fires when it’s empty.” The earlier example—“When we enter a new space, we cannot get to the locations information”—is a real-life example a tester reported.

2.

Steps to Reproduce Completing this step can accomplish a lot of the work in debugging. It is always

24

Debugging Visual FoxPro Applications necessary, and sometimes sufficient, for determining the source, if not the cause, of a behavior. For the MiddleClick report, a program should at least list the steps another programmer can take to repeat the behavior. It’s even better when the programmer can provide a sample of code that establishes all the conditions necessary to observe the behavior. The following is an example. I often answer my own question when I’m preparing a sample to post on a newsgroup.

************************************************** *-- Class Library: debugging.vcx ************************************************** LOCAL loForm loForm = NEWOBJECT('chapter3MouseClick') loForm.SHOW(1) DEFINE CLASS chapter3mouseclick AS FORM HEIGHT = 206 WIDTH = 515 CAPTION = "Form" NAME = "chapter3mouseover" ADD OBJECT mouseclickcontainer1 AS mouseclickcontainer WITH ; TOP = 4, ; LEFT = 2, ; NAME = "Mouseclickcontainer1" ADD OBJECT mymouseclick1 AS mymouseclick WITH ; TOP = 4, ; LEFT = 210, ; NAME = "Mymouseclick1", ; Shape1.NAME = "Shape1", ; Text1.NAME = "Text1", ; Text2.NAME = "Text2", ; Command1.NAME = "Command1" ENDDEFINE DEFINE CLASS mouseclickcontainer AS CONTAINER WIDTH = 200 HEIGHT = 200 NAME = "mouseclickcontainer" PROCEDURE MIDDLECLICK WAIT "In MiddleClick" WINDOW TIMEOUT 1 ENDPROC ENDDEFINE DEFINE CLASS mymouseclick AS mouseclickcontainer

Chapter 3: Applying the Scientific Method to Debugging

25

WIDTH = 269 HEIGHT = 39 NAME = "mymouseclick" ADD OBJECT Shape1 AS SHAPE WITH ; TOP = 0, ; HEIGHT = 39, ; WIDTH = 269, ; NAME = "Shape1" ADD OBJECT Text1 AS TEXTBOX WITH ; FONTSIZE = 8, ; HEIGHT = 27, ; LEFT = 6, ; TOP = 5, ; WIDTH = 100, ; NAME = "Text1" ADD OBJECT Text2 AS TEXTBOX WITH ; FONTSIZE = 8, ; HEIGHT = 27, ; LEFT = 108, ; TOP = 5, ; WIDTH = 100, ; NAME = "Text2" ADD OBJECT Command1 AS COMMANDBUTTON WITH ; TOP = 5, ; LEFT = 211, ; HEIGHT = 27, ; WIDTH = 53, ; FONTSIZE = 8, ; CAPTION = "Click me!", ; NAME = "Command1" ENDDEFINE

When a tester or end user is reporting, they will include the exact steps they take to cause the behavior to occur. For the spaces example, the following steps might be listed:

3.

a.

Start the application.

b.

Select the File menu.

c.

Select Classrooms.

d.

Click “Add A New Space.”

e.

Enter some information on the space.

f.

Try to select the Location tab.

Observed Behavior After the steps are followed, what happens? This should also be fairly straightforward. In the MiddleClick example, the observed behavior is “When you middle click on the container on the left, the wait window displays, but it doesn’t show when you middle click on the container on the right.” In the second

26

Debugging Visual FoxPro Applications example, the observed behavior is “The Location tab doesn’t let me click on it.” It is surprising how frequently people report application crashes without noting the error message that occurs. Granting that many error messages are less than informative to users, they might tell a programmer exactly where to look for the source of a crash. Absolutely include the exact error message in this step, and look up the error message in the manual, if it’s a Visual FoxPro error. Look up the error message in the Microsoft MSDN Knowledge Base, too, to see whether the error is associated with a product bug. 4.

Expected Behavior What was expected instead of what occurred? This is, essentially, why the behavior is interesting. Again, this should be straightforward, but clear as to what the expectation is. If this isn’t included, then it is too easy to make assumptions about what needs to be changed. In the first example, the expected behavior is that the same wait window appears when the middle button is clicked on the right-hand container. In the second example, the expected behavior might be “The Location tab should let me click on it, and show a page with a new location ready for data entry.”

5.

Bug Type An end user may not distinguish between a design bug and a program bug. Depending on the experience level of the end user, she may be able to evaluate this, however. A program bug to an end user is a crash or calculation error, for example. A design bug is when the application doesn’t allow the end user to do something the user wants to do, or does something the user doesn’t want it to do. In any case, this might be filled in later after more inquiry is done.

6.

a.

Program

b.

Documentation

c.

Design

Priority Priority is an admittedly subjective measurement, but valuable never the less. It can focus development time where it will be most effective, and it lets end users communicate the degree of frustration they feel with a behavior. Something that may not seem difficult to a professional programmer, or someone unused to dealing with the software day in and day out may well miss how annoying the behavior is to an end user. It is important when debugging to decide what bugs to fix in what order. a.

High

b.

Medium

c.

Low

Chapter 3: Applying the Scientific Method to Debugging 7.

8.

27

Version a.

Program The version of the program is absolutely necessary. Even when a solo programmer is documenting his own software bugs, this information is valuable as the knowledge base of what has changed in the software as it evolves. If a report is made on a behavior that has already been reported, and corrected, the debugging process may be complete at this point. For an end user report, the version is critical, especially if you support multiple versions. The version number of an EXE is determined by right-clicking on the EXE in Explorer and looking at the version tab. This requires that the Project Build include a version number. You can do this automatically, or manually. To use version numbers, open the Project Manager, click on Build, and then on Version. If you create “About” information in your application, your users can get to this information from within the application. Use the AGETFILEVERSION() function for this.

b.

Operating System For behaviors that are difficult to repeat, this can be of vital importance. There are other versions that may be necessary to gather. For example, if an application uses the Microsoft XML Parser, one may have to know what version of Internet Explorer is installed, since at least some versions update the parser. Service Pack 2 of Windows 2000 also updates this file.

c.

Network

Notes Notes cover anything unusual at the time of the occurrence. This can be vitally important in the case of hard-to-repeat behaviors. For example, the end user might report, “Right before all my data disappeared from my form, a huge lightning bolt struck my building!” If a programmer is reporting to another programmer, she will indicate here whether she is using a framework.

This template is a systematic approach to gathering information about a behavior, but the inquiry doesn’t end here. Sometimes more information is needed as you try to duplicate the steps. Sometimes what appears to be one behavior is really two, or more, in which case a separate report should be made for each behavior. Also, some of the information in a report may turn out to be extraneous. While duplicating the steps, see whether some of the steps can be left out and still result in the same behavior. The two examples have extraneous information. The MiddleClick steps include text boxes and a command button that can be left out, and the second example might not need the step where some space information is entered. Spend time on the report so that it includes the necessary (nothing extra) and sufficient (just enough) steps to duplicate the behavior. Once you have the template filled out, proceed through the steps exactly as noted. If a step is unclear, go back and clarify it before proceeding. Is it really the Add that triggers the

28

Debugging Visual FoxPro Applications

behavior, for example? Or does the same behavior occur during an edit? These are questions that can develop into hypotheses. Make a note of these questions. If there is a choice that’s not explicitly stated in the steps, find out which choice leads to the observed behavior. Experimenting should determine what effect a different choice might have. For example, as you follow the steps, it may become clear that the user doesn’t need to enter any space information in order to cause the behavior to occur. Or, it might be important to know exactly what data the end user entered in particular fields. For example, the end user may have checked a value that logically excludes location information. If the information doesn’t affect the findings, remove it. Or, if it’s still possibly important, include it in the notes. Basically, try to limit the steps to the minimum required to cause the behavior, but leave them complete enough that anyone can follow the instructions to repeat the behavior. Once you have this information, you should have a location of the program—a form, a report, a class—in which to start looking. Looking. Not programming. Not changing. Looking. Open the suspected part of the code and review it. Or you may want to run the suspected area of code through a debugger. While you’re investigating, don’t make any unnecessary assumptions about cause, not even a firm commitment that you’re in the right area of code since you’re still just gathering information. Basically just familiarize yourself with the area of the application you suspect has something to do with the cause of the behavior. In the first example of the MiddleClick, the relationship of the container that works to the container that doesn’t is that of parent class to subclass. In the second space/location example, data is involved. So a fact that might be relevant is that there is a one-to-many relationship between spaces and locations. Whatever hypotheses are developed must account for all the facts that are gathered during the inquiry. Sometimes some of the information you’re gathering is about differences. If you have something that used to work but stopped working, a question to ask is “What’s changed since the last time it worked?” If you have some similar bits of code that work but this one doesn’t, how are they different? As you proceed with the inquiry, make notes of your suspicions about what might be causing the behavior, but don’t get so attached to an idea here that you miss seeing other critical evidence. Just because a problem looks like one you’ve solved before with a particular technique, it doesn’t mean it is the same.

Hypothesis The next part of our scientific method is to form one or more hypotheses about what is causing the behavior. This is an educated guess, with an emphasis on educated. Experience is a great help, of course—experience with your own code and experience with the tool you are using. Experience might be your own experience, or you might need to rely on the experience of others. Even if you work alone, there is a community of peers available through various resources such as the Microsoft Newsgroups (http://support.microsoft.com/ directory/content.asp?ID=FH;EN-US;news&FR=0&SD=MSDN&LN=EN-US&CT= SD&SE=NONA), CompuServe (http://www.compuserve.com), the Virtual Users Group (http://www.vfug.org), and the Universal Thread (http://www.universalthread.com). Regularly reading the posts on these boards, even when you don’t have a specific problem, can save you time by preparing you for bugs you might meet up with in your code. Literature

Chapter 3: Applying the Scientific Method to Debugging

29

is available about Visual FoxPro and its use (see http://www.hentzenwerke.com), and the manual is even, surprisingly, a good resource, too. How do books and the manual help develop a hypothesis about a behavior? If an end user reports an error that is a Visual FoxPro error, for example, the first action should be to look up the error and understand what it’s telling you since some errors clearly point to specific solutions. The second step might be, depending on the error, to search on the error in the Microsoft Knowledge Base (http://search.support.microsoft.com/kb/c.asp). If a problem is with a particular control—say, a grid—with a specific problem—it blanks out—you can search in the Microsoft Knowledge Base and look for any known issues or bugs. For example, searching on “grid blank” in the July 2001 Knowledge Base does indeed display the article (Q140653) about the common cause of this behavior. Just as scientists need libraries and journals to keep abreast of their specialties, learning is critical for programmers to understand how things might go wrong in our programs and how other people have dealt with similar situations so that you are prepared. However you come by it, gather enough information so that you can make a best guess about the cause of the observed behavior. When you have a guess, form it into a hypothesis. Put the hypothesis into the form of a testable, falsifiable statement that fits the facts that are currently known. So, for example, a hypothesis about why the Location tab isn’t enabled when a new space is added will focus on places in the code that enable and disable tabs, and on what occurs when a space is added. The MiddleClick behavior will focus on the differences between the two containers, and, since one of the containers contains objects, you may need to consider what those objects are and what their methods and properties do. However, developing a hypothesis doesn’t mean that you can stop acquiring information that might be relevant. If your hypotheses don’t pan out in the experiments, you’ll need to come back to this step. A hypothesis should include the following assertions: In what part of the application does the behavior occur, what sort of programming cause might be the culprit—logical or syntactical—and what code might change the behavior from the observed behavior to expected behavior. There may be more than one hypothesis. Develop as many alternatives as fit the facts. Experimentation will determine which, if any, is correct. Keep them separate, however, so that each hypothesis can be tested separately. It’s a bit like washing the pipette between runs. It is at this point in debugging that it is useful if the behavior triggers the error handler. From our examples, the MiddleClick example would have two hypotheses: 1) the subclassed container has MiddleClick event code that is overriding the parent class event code, and 2) the subclassed container contains an object that is intercepting the MiddleClick event and the container’s event is not firing. The space/location example hypothesis might be something like the following: “Since there is a one-to-many relationship between spaces and locations, when a new space is added, there isn’t a corresponding location. Perhaps I disable the Location tab when I add a space, and forget to enable it after the user has saved the space.”

30

Debugging Visual FoxPro Applications

Prediction Based on the hypothesis, which has built on the observation and accounts for all the observations made during the inquiry as well as the original observation, one next makes predictions based on the hypothesis. Predictions are generally in the form of “If I [take some particular action], then I expect [some specific result].” Phrase predictions so that once the experiment is run, the results are meaningful. This isn’t a matter of the prediction being right or wrong, but of the results being informative. For example, I recently sent a new module to a team member to review. When the team member ran it, she got an error that I did not see. However, the error was similar to another error that occurs when a supporting file isn’t up-todate or in the correct location. Based on the report, and based on experience, I predicted that the supporting file wasn’t current or was missing. The statement “The file is missing and is causing the error message” is falsifiable. I was not asserting a fact, and when communicating with a team member or end user, the communication would be more like “Maybe the XML document isn’t up-to-date.” But for internal purposes, make the assertion as forceful as possible. As we gain experience, our hypotheses will get better. It doesn’t matter whether the hypothesis turns out to be true or not—although I would have liked it to be, since that would be an easy fix! The point is to state the hypothesis in such a way that you can design a test to try and disprove the hypothesis. This isn’t always terribly intuitive as to why it’s important, but the following section on experimenting explains why. For now, it’s important to notice that phrasing the statement like this points the way to an experiment that can be run. Prediction shows how each step in the scientific method builds on the other. Without sufficient inquiry, it’s difficult to make predictions that are clear. And a clear, true or false prediction will point the way to an experiment. Carl Karsten, a Visual FoxPro developer, explained his thinking during debugging once. “If I am having trouble using something, one or more of the following must be true, and therefore should be fixed: 1) the product is working as planned, but the documentation is flawed (a documentation bug), 2) the documentation agrees with the original plan, but the product is flawed (a program bug), or 3) the plan, product, and documentation are all in agreement, but I am misunderstanding the documentation or misinterpreting my observations of the product.”

Experimentation Experiments test predictions. They can be can be fancy or plain. The debugger tool to use depends on the hypothesis. Chapter 5, “A Taxonomy of Common Visual FoxPro Bugs,” discusses classes of common bugs and suggests specific debugging techniques. Generally, design your experiment so that you are trying to disprove your hypothesis. What?! Does that sound odd? As counterintuitive as that may sound, it is arguably the single most important, and subtle, point of the scientific method.

Chapter 3: Applying the Scientific Method to Debugging

31

Previously, I gave an example of a hypothesis I devised to explain a bug. The hypothesis was that a required file was missing. “File X does not exist and that is causing the error.” What experiment to run? Why, try to find the file, of course. If the file is found, the hypothesis is disproved. (As it was, indeed, and sent me back to the inquiry stage.) Had the file been missing, the hypothesis would not have been proven. Instead, another experiment would need to be devised. The next experiment would be to install the necessary file and retry the steps to see whether the error occurs. However, this begins to encroach on analysis, and for now it’s important to keep an open mind and simply observe the effects of the experiment. In the MiddleClick example, the first hypothesis is easy to test. The hypothesis is “The subclass MiddleClick event overrides the parent event.” It is easy to check whether the subclass method overrides the parent method. In our example, it does not. An experiment that tests the second hypothesis would remove all the contained objects, run the program, and see whether the error occurs. If it doesn’t, then add each item back in to the container, one object at a time. While this example is simple, and the solution is rather obvious, it does illustrate two important points. Make one change at a time, and simplify the environment if at all possible. ComboBoxes on pages gave me fits when I was first learning to use Visual FoxPro. In some particular cases, if the RowSource had an error in it, the ComboBox would fail to initialize, and the entire form would fail to instantiate. The error message was cryptic, and it was impossible to tell which control was causing the problem. The first hypothesis was that the Init was returning .F. for some reason. However, that was easily disproved. Stepping through all the initializations wasn’t helpful because not all the controls had Init code, and there were hundreds of controls anyway. The way the problem ComboBox was identified was by removing all the controls from one page at a time until the problem stopped occurring. Then, each control was methodically added again, one at a time, until the problem was narrowed down to one control, which was reviewed until the cause of the problem was identified. Until one is experienced, it is better to be methodical than to shoot from the hip. While it was somewhat tedious to test the hypothesis this way, doing so was instructive because I learned a lot about how controls are instantiated and through this came to understand that container classes could be created and added to form pages as they were needed instead of instantiated always at the beginning of a form. Jim Booth talks about the value of learning from his bugs, too, in an article he wrote for FoxTalk (“Debugging Your Code,” September 1999). In this article he points out that one of the benefits to truly debugging code is that, by truly understanding what exactly was causing the behavior, you are able to exploit that behavior if it has some beneficial side effects. At the very least, you’ll be better able to support your peers in the public forums. Another important aspect to experimenting is housekeeping: Check for compile errors and know your environment. The first is so simple, and yet so easy to forget. Make sure that before you run any experiments the code compiles without error. It’s important to control the environment while experimenting. Controlling the environment means both being conscious of any SET values that might affect your experiment and controlling the code that gets introduced into the system during testing. If a behavior is related to data, for example, experiments should be run under the same environment settings in which the behavior is seen—SET DELETED, SET EXACT, and SET NEAR, for example. If you cannot test in an exact replica of the environment in which the original behavior was reported, understand how it is different. If you’re on a development machine,

32

Debugging Visual FoxPro Applications

you will have Visual FoxPro installed, so if your code includes the command DO MyProgram.PRG it will work on your system but not on an end user’s computer, unless they have a corresponding compiled version (FXP). Other settings that are often different between a production and development machine are SET PATH and SET CLASSLIB. You might be working with tables that are compiled into an EXE on an end user’s computer, and are therefore read-only for the end user, but not for you.

*

While debugging a module, leave LockScreen = .F. Even if a bug is unrelated to a LockScreen, it is far more difficult to see what is occurring in your application if it’s .T. Use a #DEFINE to globally change your LockScreen settings between run time and development time.

Some experimentation will require that you write code. The only code you write during debugging should be for the express purpose of testing your hypothesis or for gathering additional information. As mentioned previously, the only exception to this is comments. While I am about as keen on writing comments as the next programmer, I am faithful about writing comments for code introduced during debugging. Using source code control software such as Microsoft Visual SourceSafe can be a lifesaver for rolling back unsuccessful experiments. However, it’s often easier to manage experimentation with comments. Plus, if a particularly brilliant piece of code is written that fixes the bug, it will be easy to identify so that it’s left while other debugging code is stripped out. Other commands to be alert to are SUSPEND and SET STEP ON commands. These will cause errors in the run-time version. Instead, use breakpoints, watch points, and SET ASSERTS/ASSERT, which have no effect in run-time code. While SET COVERAGE TO won’t cause errors in run-time releases, it can slow down the application and create huge files. Put SET COVERAGE TO in a conditional compile block (#IF … #ENDIF) to limit this tool to development. Unfortunately, not all behaviors are easily tracked down, and these require creative thinking. When it’s difficult to come up with more than a general location in which to look for a hypothesis, it might be useful to write code designed to, for lack of a better analogy, doggedly flush the pheasant from the brush. This might take the form of replacing a custom class with a standard base class, and then adding the functionality from the custom class back in one method or property at a time. It might mean commenting out an entire section of code to determine which specific line is causing, for example, a general protection fault. In any case, change only one thing at a time, so you know when you’ve provoked the behavior in question. Another sort of problem that is difficult to track down is when you cannot duplicate a behavior that a tester or end user sees and there is no error state triggered, but just a lack of action. Take the following as an example: A user makes a series of selections and then clicks a button that is supposed to apply those selections. On some systems the selections are applied. A result is shown in the application. On the troublesome system, though, nothing occurs. Selections are not apparently applied, but there is no error message (either Visual FoxPro or application-level that the system couldn’t process the selections). If the end user is a tester, there is more latitude in what can be tried, so it is reasonable to include some general diagnostics code that traces the code and important values between the time the button is clicked and the time the changes should have appeared. The following is an example of code that tests the programmer’s assumptions about what is happening in the code.

Chapter 3: Applying the Scientific Method to Debugging

33

#DEFINE DIAGNOSTICS .T. #IF DIAGNOSTICS LOCAL lcDiagnostics lcDiagnostics= DTOC( DATE() ) + ; [ | ] + PROGRAM() + CHR(13)+ ; [ | THIS.cErrorReturn= ] + THIS.cErrorReturn + CHR(13)+ ; [ | THIS.WhereClause = ] + THIS.WhereClause STRTOFILE( lcDiagnostics + CHR(13), 'Diagnostics.Debug', 1 ) #ENDIF

The result of the preceding diagnostics looks like this: 08/22/01 | MyLibrary.MyContainer.MyClass.MyMethod | THIS.cErrorReturn= | THIS.WhereClause = Type = 'P'

Chapter 4, “Visual FoxPro Debugging Tools,” covers how to use the Visual FoxPro tools for debugging in detail, and Chapter 5, “A Taxonomy of Common Visual FoxPro Bugs,” applies specific debugging techniques to specific types of common bugs.

Analysis As Visual FoxPro developer and Microsoft Most Valuable Professional Paul Maskens said about a particular piece of code, “This is doing exactly what it should, but not what you might think it should!!” Once you have the results of your experiment, you’re ready to analyze them. Notice that this is a separate step from experimenting, and certainly from observing. This is important because if you start analyzing before you have all the data, you might well miss a critical piece of information. However, in the world of debugging, experimenting alone usually clearly points to what the source of the behavior is, whether it is a bug, and what code will correct it. After all, experimenting is basically a matter of trying out possible solutions in a methodical way, without making any premature conclusions at the expense of finding the real source. However, in some cases this might need to be an extensive process. If, for example, in the spaces/locations example, the Location tab is not enabled because it wasn’t designed to allow entry of locations until other actions are taken, the correct solution should be analyzed, the design documents should be revisited, the client should be consulted, and so on. Deciding what to do once the results are in is sometimes obvious and sometimes not so obvious.

Decision Some analyses will point to solutions that turn out to be so complicated that rewriting a logical portion of the code might be in order, in which case the change should be done intentionally by designing, implementing, and testing it as new code development. Sometimes the cause will turn out to be the result of some error or behavior in a third-party control or in the language itself. In these cases, you will need to consider your options. Can you replace the third-party control with another? Can you write error trapping that catches these errors and at least gracefully recovers from them? Another complexity is when experimentation does not point to a solution at all. You’re stumped, in other words. In that case, do you start randomly adding code, hoping to make the

34

Debugging Visual FoxPro Applications

problem go away? Or do you SET ERROR *? No, that’s probably not a wise choice. As David Frankenbach has said, “Bugs that mysteriously go away, usually just as mysteriously reappear.” Another reason to avoid this tactic is that any new code that is written that does mask the original behavior may cause other unintended, and worse, side affects. For example, a record may not be updated, or some conditions used later in the application won’t be set up. In this case, post a question on the newsgroup with the pertinent information you’ve discovered through this careful process. Search on the Microsoft Knowledge Base for related articles. If the behavior is a program crash, write wrapper code that traps the error and logs it, and then either returns to the application or gracefully exits the application. If it’s not an error, log the diagnostic information to disk for later analysis. But don’t try to mask the problem, no matter how great the pressure is to do so.

Conclusion Debugging software is a complex process that is usually done during stressful times and isn’t the activity that gets all the glory. Unfortunately, it’s a fact of a developer’s life. The goals are to minimize the time spent debugging by using error handlers, good coding practices, public resources, and knowledge bases; to improve one’s knowledge of the tools; and, finally, when debugging becomes necessary, to use a methodical, rational strategy and sensible tactics to uncover and fix—truly fix—problems. Programming is a logical process, and it’s sensible to use a logical process to diagnose the product of programming. The scientific method provides a tested, proven model for conducting debugging. The key points to remember about the method are to clearly describe the behavior that is of concern, understand everything about it that is possible, propose possible reasons for the behavior, and then test these possibilities in a controlled manner. While debugging a behavior, change only one thing at a time. There are no exceptions. Do not write new code or change unrelated code while debugging. Comment all code written during debugging, and consider the best strategy for conducting your experiments. Feel free to use your peers for an impartial opinion, but give them the information you’ve gathered through this process. If you haven’t applied a methodology to your debugging before, don’t let the formality of the process scare you off. The best developers use some modified version of the scientific method, and as you become more practiced, it becomes like second nature. If you learn the process, it will be useful no matter what language you program in, and it’s even useful for anything that requires troubleshooting.

Chapter 4: Visual FoxPro Debugging Tools

35

Chapter 4 Visual FoxPro Debugging Tools In Chapter 3, I discuss the general process of applying the scientific method to debugging. In this chapter, I present the debugging tools in Visual FoxPro and show how to use them. The tools include the native debugger, productivity add-ons, and language elements that either directly or indirectly support debugging activities.

Debugging activities include starting a debugging session, checking variable values and scope, verifying the state of data, testing fixes, changing the execution order of lines of code, validating tests, and evaluating performance. A developer starts debugging when a problem is encountered or suspected in a program. At this time, a developer suspends the program in order to evaluate the problem, locate the source of the problem, and, possibly, test a fix. A program must be suspended in order to trace through code or use the command window. However, you can still make use of the debugger to do other debugging activities such as set breakpoints and watch points even while the program is not suspended. This is useful when you want to suspend an already running program. The native Visual FoxPro debugger is a collection of components that support traditional debugging activities from within Visual FoxPro. It is not possible to run the debugger unless the Visual FoxPro interactive development environment (IDE) is also running. In other words, it is not possible to start a Visual FoxPro EXE from File Explorer and, if it generates an error, start the debugger and “attach” to the EXE process that is running. However, you do have the option to run the debugger in the Visual FoxPro desktop or in its own window. The debugger components have other options that you can customize to suit your programming style.

Debug options Visual FoxPro debugging can be configured in a few ways. As with other IDE settings, the options can be set to persist just for the active session of VFP, or settings can be made the default, and thus global to all VFP sessions on one computer. The options are available under the Tools | Options menu option, on the Debug tab.

Font and colors The font style and size, and colors for selected and normal text in each of the five windows (call stack, locals, output, trace, and watch) can be set here either permanently or for the current Visual FoxPro session.

Display timer events By default, timer events are skipped while debugging, unless you set a breakpoint in a timer event or method, or an error occurs in a timer.

36

Debugging Visual FoxPro Applications

Environment The debug environment refers to whether the debugger runs in the FoxPro frame or in the Debug frame. FoxPro frame Select this option to run the debugger components in individual windows in the Visual FoxPro desktop. This is useful if you want to display just one window, such as the trace window, sideby-side with your application. Displaying the debugger components in the Visual FoxPro desktop decreases screen real estate, though, and it may not be a practical option if your application takes up a full screen. When the debugger is set to run in the FoxPro frame, there is no way to open a debugger window once the application is running, unless you have written scaffolding code. For example, you can use an ON KEY LABEL to start the debugger components, as the following example shows: *!* Main program file ON KEY LABEL F9 do debugcommands in DebugCommands.PRG with "Call Stack"

Next, add a new program to your project—I called mine DebugCommands.PRG—with the following code: LPARAMETERS lcCommand IF VERSION(2) = 2 && Are we in the development environment? ACTIVATE WINDOW (lcCommand) ENDIF

Notice that the use of VERSION() will ensure that the user doesn’t get an error by inadvertently clicking F9 in the production version. Many programmers successfully use this convention to leave debugging backdoors in their applications. However, it does require extra coding and will need to be clearly documented for new team members. In my experience, this functionality has less utility when programmers are working independently on modules and have their own style of debugging, which should be acceptable so long as their style doesn’t affect the code base. Finally, this becomes more complicated if your application also uses function keys for user tasks. You will have to write code that branches depending on whether the application is being run from the IDE or the runtime.

* *

Every time you write or change code, you introduce the potential for a new bug. Every time code branches differently for development mode than it does for production mode, a potential user path may be bypassed in testing or difficult to debug. The debugger windows are dockable in 7.0 when using the FoxPro frame.

Chapter 4: Visual FoxPro Debugging Tools

37

Debug frame Select this option to run the debugger components in a single window outside of the Visual FoxPro IDE. This option has the advantage of not overlapping with your application, and it lets you Alt-Tab to the debugger task from your application. The option also gives you access to setting a breakpoint even after the application has started. If you use a CONFIG.FPW in your Visual FoxPro startup directory (project directory), put the command STEP = ON in it to automatically start the debugger every time you start Visual FoxPro. Alternatively, you can define a function key in your application to start the debugger. If you do this, write code wrapping the functionality so that a user doesn’t inadvertently hit the code in the production application and cause an error. The example in the previous section, “FoxPro frame,” shows just such an example of wrapping the call. When the debugger is running in the Debug frame, activating any of the debugger windows will activate the debugger.

Call stack options The call stack displays a list of executing routines. You can choose to have each routine numbered, with the highest number being the currently executing routine. You can also choose to display the current line indicator, which will display an arrow next to the currently executing routine. This same arrow will appear next to the current line of code in the trace window. If you choose to display the call stack indicator, then an indicator will appear next to the call stack item for which the trace window is displaying code. The same indicator will appear next to the line of code in the trace window that called the next routine in the call stack. This is useful for when you are backtracking through code to diagnose a problem. You have a visual reminder of which method or procedure you’re looking into. These options are also available from the right-click context menu in the call stack window. However, to make the changes persist to all Visual FoxPro sessions, make the changes in Tools | Options and click on the Set as Default button.

*

Since the call stack shows the order of calls from most recent at the top, numbering them only reinforces what is already apparent.

Trace window options The trace window displays the code for routines currently running. The routine selected in the call stack window is the routine that is displayed in the trace window. Set options to display line numbers in the trace window, to trace between breaks, and to specify what throttle value to use when tracking between breakpoints. Tracing between breaks means that the code will be animated in the trace window as a program runs. This functionality is available programmatically by setting the TRBETWEEN system setting ON or OFF. The option “Pause between line execution” sets the system variable _THROTTLE, which can also be set programmatically to any value between 0 and 5.5 (seconds). The _THROTTLE setting is ignored unless the trace window is open and TRBETWEEN is ON.

38

Debugging Visual FoxPro Applications

This setting will delay the program at each line so that it is easier to see the lines as they are executed. Since the throttle value can be set in the command window and in the debugger under the Debug menu option, I find that it is more practical to set it in one of those places than in Options. Otherwise, each line of the program will be delayed by the throttle value, which can make running a long program impractical. In any case, tracing between breaks and using the _THROTTLE variable are tools better suited to specific debugging tasks than to a global environmental setting. Tamar E. Granor and Ted Roche, in The Hacker’s Guide to Visual FoxPro 6.0 (Hentzenwerke Publishing), point out that tracing between breaks with the _THROTTLE set is useful when stepping through code might affect the way the code runs, which is an occasional, undesirable side effect of debugging.

*

The option I set here so that all my Visual FoxPro sessions share it is the line number display.

Output window options The output window displays system messages and text sent to it using the DEBUGOUT command. Options are available to send the output to a file as well as to the window and to specify what log file to use. Unlike the rest of the options, the option to log output to a file is unchecked with each new Visual FoxPro session. According to the Help file, this is so that a log file isn’t inadvertently overwritten. As with tracing between breaks, setting the debugout options is better suited to specific debugging situations and thus is more effective when set in the debugger or in code specifically when it’s required. This technique is discussed more in the section “About the debug output window.”

The native debugger The native debugger comprises five components and a facility for managing breakpoints. The debugger also interacts with two independent Visual FoxPro productivity add-ons. The five components are the call stack, trace window, watch window, debug window, and locals window. The productivity add-ons are coverage logging and event tracking.

About the debug output window The debug output window displays output messages from ASSERT and DEBUGOUT commands and event tracking results. I discuss event tracking and how it interacts with the debug output window in the section “Event tracking.” The debug window output from the following code sample is shown in Figure 1. IF FILE('DEBUGLOG.TXT') DELETE FILE DEBUGLOG.TXT ENDIF SET DEBUGOUT TO DEBUGLOG.TXT SET ASSERTS ON ASSERT VERSION(2)#2 MESSAGE "Do you want to suspend " + PROGRAM() + ; " before running the form?"

Chapter 4: Visual FoxPro Debugging Tools

39

LOCAL loMyForm loMyForm = NEWOBJECT( 'MyForm' ) loMyForm.SHOW(1) DEBUGOUT "That's all folks!" SET DEBUGOUT TO DEFINE CLASS MyForm AS FORM AUTOCENTER = .T. PROCEDURE INIT THIS.ADDOBJECT('text1', 'MyTextBox' ) THIS.Text1.VISIBLE = .T. ENDPROC ENDDEFINE DEFINE CLASS MyTextBox AS TEXTBOX PROCEDURE INIT THIS.VALUE = "Foo" ENDPROC PROCEDURE GOTFOCUS DEBUGOUT PROGRAM() + ", " + STR( LINENO() ) THIS.VALUE = "Bar" THISFORM.RELEASE ENDPROC ENDDEFINE

Figure 1. The debug output window. Use the debug output window instead of WAIT WINDOW and MESSAGEBOX() for non-invasive debugging messages. DEBUGOUT will not impact the performance of code or affect the way the code runs. It is also a good technique because it’s ignored in run-time applications so you don’t have to do any special coding for production and development releases. This is always a goal

40

Debugging Visual FoxPro Applications

for coding. Do as little as possible that makes the code run differently between development time and production so that what you debug, test, and fine-tune is the same code the users see. The debug output window is a handy developer’s tool for this reason. The context menu for the debug output window, shown in Figure 1, gives you the option to change the font, hide the window, and invoke Help. These three options are available in each of the five debugger components. Note that a change to the font in the context menu is not persisted between Visual FoxPro sessions. To change the default font, change it in Tools | Options and click on the Set as Default button (see the earlier section, “Output window options”). The other debug output window options are to save the debug output to a text file and to clear the window. These last two options are only available when there is some text displayed in the window.

About the call stack The call stack displays a list of procedures and methods that are currently executing, with the most recent at the top of the list. The call stack and trace window are synchronized so that as you click on different routines in the call stack, the code for that routine will be displayed in the trace window. The currently executing routine is displayed at the top of the list. It does not display all the routines that have been called, only those that are active in the hierarchy. The call stack might appear modest at first glance, but it is really quite powerful for debugging. Figure 2 shows a sample error message. When a program first hits a suspend condition because, let’s say, an error was generated, the top item in the call stack is the name of the routine that caused the suspend condition, as shown in Figure 3.

Figure 2. Custom error message.

Figure 3. Call stack example.

Chapter 4: Visual FoxPro Debugging Tools

41

*

If you use ON ERROR to set error trapping to a procedure, the first routine in the call stack will be that procedure. The second routine will be the ON ERROR command. The third routine will be the source of the error. In the case of ON ERROR MESSAGEBOX(), the line in the call stack will be the ERROR command.

About the trace window The trace window is the way that the Visual FoxPro native debugger supports the debugging activities related to stepping through code in order to evaluate how a program got into a state— usually an error condition. It comprises two drop-down lists and a code window. One dropdown list is for selecting objects, if any are available. Use the second drop-down to select the method or event for the object selected in the object list. Only events and methods with code will be available. Figure 4 shows an example program running that has a form with one text box on it.

Figure 4. The trace window showing the Object drop-down list. When you select different objects from this list, the Procedure drop-down will change to list the methods and events for the selected object that have code, which is shown in Figure 5.

42

Debugging Visual FoxPro Applications

Figure 5. The trace window showing the Procedure drop-down list. The Procedure drop-down will show methods and events that have code at any level of the object hierarchy. Therefore, for example, an object might be instantiated from a subclass that does not have code in the Init event. So long as an antecedent class does have code in it, the Init event will be listed. The trace window displays code when a suspend condition is met in the code. A suspend happens in a number of different ways: because a breakpoint has been reached, because a program error has occurred and the error handling allows for a suspend, or because code has invoked a suspend. In order for the trace window to display code, the code must be available. This means that if you are debugging an EXE, you must be running a version that has the debug information compiled into it. This option is available under the Project | Project Info menu option, on the Project tab. You can also turn debug information on and off programmatically using the project object debug property as illustrated in the following code snippet. MODIFY PROJECT MyProject _VFP.PROJECTS(1).DEBUG = .T.

Of course, any Visual FoxPro code—be it program, visual form, or visual class—can be debugged without compiling it into an EXE first, assuming the application’s dependencies make that possible. The trace window is coordinated with the call stack to show the code for the routine selected in the call stack. As you click on routines in the call stack, the trace window shows the code for that routine. Figure 6 shows the call stack from Figure 3 together with the trace window. Notice the call stack indicator reinforces the line of code in the trace window from GetCustomers in the call stack.

Chapter 4: Visual FoxPro Debugging Tools

43

Figure 6. The trace window with the call stack. Notice the call stack indicator () in Figure 6. The arrow indicates that the line of code is coordinated with the call stack. This is a wonderful tool because the line of code that generated the suspend might not be the line of code that is the source of the error. By that, I mean when you suspend a program, the call stack may be displaying your error routine. Step back through the call stack to see the line of code that invoked the error handling in the first place. In the example, the SELECT statement is the source of the “Operator/operand type mismatch” error. To diagnose this error immediately, a neat trick you can use relies on an error handling strategy. The strategy is to include options that help you debug. The tactic is to include a way to either return to the calling procedure to continue program execution or retry the code that invoked the error handler. The error handler this example uses is listed here: PROCEDURE MyError(tnError, tcMESSAGE, tcMESSAGE1, tcPROGRAM, tnLINENO ) ASSERT .F. MESSAGE tcMessage + CHR(13) + ; "in " + tcProgram + " on line #" + LTRIM( STR( tnLINENO ) ) + CHR(13) + ; "Do you want to diagnose this?" IF .F. RETRY RETURN ENDIF ENDPROC

44

Debugging Visual FoxPro Applications

Notice, in particular, the IF/ENDIF block. I’ve included this code so that during debugging I can easily control the program’s next action. In this example, I’d like to retry the SELECT statement after I check variables and expressions, trying to locate the source of the type mismatch. I can set the next statement to execute to be the RETRY in the IF .F. block. By enclosing the RETRY and RETURN commands in an IF .F. block, I’m assured there is no way the code will execute by mistake in a production environment. To set the next statement, I rightclick on the line of code I want to execute and then select the choice “Set Next Statement,” as shown in Figure 7.

Figure 7. Error handler with RETRY/RETURN. The screen shot does not show the cursor location, so I’ve selected the line to reinforce that you have to click first on the line you want to execute before you set the next statement. Once you’ve set the next statement, the call stack current line indicator (a yellow arrow) will move to that statement. After setting the next statement, I step the code one step. I do this either by pressing F5 or by clicking the “Step Into” button on the toolbar. Another way I could have examined this problem would have been to set a breakpoint on the line with the error (the SELECT statement) by stepping back through the call stack to the line of code. Then I can double-click the left margin of the line with the error, or click on the toggle breakpoint toolbar button in the debugger. The result of doing this is shown in Figure 8. Then I would have to end the program and then rerun it, which would suspend at the breakpoint. From this point, the two techniques are the same.

Chapter 4: Visual FoxPro Debugging Tools

45

Setting breakpoints in events that fire frequently, such as mouserelated events, and are triggered by common events, as with Activate and Deactivate, can be frustrating since the breakpoint may fire far more than is desirable. In addition, Activate and Deactivate will fire every time a breakpoint transfers program control to and from the debugger. Once the program has suspended before the SELECT statement executes, other capabilities of the trace window come into play. The trace window gives developers more than just the basic capability of stepping through code. I know that from the error in my example (“operator/operand type mismatch”) there is a comparison of two values of different types. I know on which line the error occurred. I have the line of code ready to execute (and error again if I run it as-is). Now it’s a matter of examining the line for the problem. There are only two comparisons in the SELECT statement, both in the WHERE clause. The trace window helps here, too. Either I can hold the mouse cursor over any code in the trace window to examine the value of variables and expressions, or I can highlight a variable or expression and drag it to the watch window. I describe more about using the watch window in the next section, “About the watch window.” For this example, I used the latter technique, as is shown in Figure 8, since ToolTips don’t show up easily in screen shots. Normally, though, for a simple expression I use the former technique.

Figure 8. The trace window with the call stack and watch window.

46

Debugging Visual FoxPro Applications

The watch points show the probable cause of the error: I’m comparing a character (“ERNSH”) with a numeric (10023). Admittedly a simple example, it demonstrates a common process of debugging that developers undertake. A program generates an error or there is a strange behavior to diagnose, and the developer suspends the program in the general vicinity of the error and then starts to backtrack through the code to narrow down the problem. I describe in the next section how it will help me confirm my hypothesis about the problem that I’ve investigated in the trace window.

About the watch window The watch window consists of a text box to type in new watch point expressions and a list of existing watch points, their values, and their types. In addition to displaying the values of variables and expressions, you can set breakpoints on those values changing. You can also change the value of variables in the watch window. The watch window is not synchronized with the trace window, call stack, or locals window. Variables and expressions are in scope relative to the routine topmost in the call stack. In other words, if an expression is in scope in the currently executing code, then a watch point set on it will be evaluated no matter what routine is highlighted in the call stack, and no matter what code is displayed in the trace window. The debugger includes the ability to copy expressions from the trace, locals, and debug output windows to the watch window. I created the watch points in Figure 8 by clicking and dragging the values from the trace window. I make it a habit to use cut and paste from the trace window, rather than retyping the expression. This habit helps me find places where my errors are typographic errors. Otherwise, I might type what I think I’m seeing in the code, and what should be there, instead of what is there. Nearly any legitimate Visual FoxPro expression can be entered into the Watch text box. Even arrays, objects, macro expressions, and functions, such as STRTRAN(), ACOPY(), and EVAL() are legitimate expressions. Since functions are reevaluated at each breakpoint, use functions judiciously since they may conflict with one another. While writing this chapter, I experimented with two arrays and watch points on ACOPY and ADEL. The results were, to say the least, unreliable. Arrays and objects will be displayed “collapsed” with a plus symbol to the left. Click on the plus symbol to display the contents. Arrays and objects are useful watch points. Set watch points on arrays and objects to easily drill down into the elements or object members to change properties or set breakpoints on property values. Press Enter after typing in the expression to actually create the watch point. The great thing about this is that the watch window is very forgiving of errors. If an expression is illegal, the debugger won’t crash. It will simply display the usual message that the expression couldn’t be evaluated. As an example, I use this technique when I have a complicated series of AT() expressions embedded in SUBSTR() that isn’t doing what I expect. I work out the correct logic in the watch window, and confirm it’s what I need. I can change a variable to the corrected resulted and continue executing the code. Or I can opt to fix the code immediately by choosing the Debug | Fix menu option, which will cancel the program, release objects from memory, and load the code for me to edit.

Chapter 4: Visual FoxPro Debugging Tools

47

I hadn’t used the fix option much until I was reviewing features of the debugger for this book, and since I have been, I’m finding it to be an extremely effective tool. Normally, even though I work and test on an object or module basis, sometimes the path can get complicated between class interactions and this makes it much easier to get to the code I need to work on.

*

In the previous section, “About the trace window,” I suspected my bug was due to comparing a string with a numeric. The watch window will help me test and confirm that hypothesis, and then make sure I don’t have any other problems lurking. I can change the value of tCustomerID if I first select that watch point, and then click once more in the value. When I change the value to a character, as shown in Figure 9, and then step into the SELECT statement, I find that that does solve the problem. At least, it solves the immediate problem.

Figure 9. Changing a value in the watch window. To fully debug this problem, I would step back one procedure further in the call stack to see why the calling code passed a numeric when GetCustOrders assumed the parameter is a character. Is the underlying problem that the function GetCustOrders is poorly documented so programmers don’t clearly understand what parameter types should be passed? On the other hand, should the function test the type of the parameter before using it? If so, what should the function do if the incorrect type is passed? Should it return an error message or cast the incorrect type into the correct type and use that? These are all questions that take a step back from simply fixing an immediate bug to reconsidering the design of the function. By taking the additional time now, I may avoid repeating this particular bug. For the purpose of this chapter, I want to point out not one solution or another, but the idea that the debugger helps us solve

48

Debugging Visual FoxPro Applications

the immediate causes of bugs, and relieve the symptoms the program exhibits, but that the “correct” fix may involve more than that. I discuss this more in Chapter 5, “A Taxonomy of Common Visual FoxPro Bugs.” I’ve discussed at some length how to use the watch window with the trace window, but the watch window has more qualities that are interesting. As with the trace window, breakpoints can be set on expressions in the watch window. Double-click in the left margin to set a breakpoint on an expression changing. Even when I want a more sophisticated breakpoint on an expression, I will often start by highlighting and dragging an expression from the trace window into the watch window, and then I set a breakpoint in the margin. I can then use this breakpoint as a template for the one I really want in the Breakpoints dialog, which I discuss in a later section. This saves having to remember a complicated object hierarchy while in the Breakpoints dialog, which is modal, and therefore doesn’t let me switch back to the code or trace window to check an expression. I can then remove, or just disable, the first breakpoint. I may not want to remove it since my second breakpoint may not help me as much as I think. I can then go back to the first one and refine it.

*

Each time the program is suspended, the expressions that have changed since the last breakpoint are shown in red.

Breakpoints set on a watch point expression changing can be frustrating since the expression will change each time the expression can’t be evaluated because one or more variables are out of scope. What is really needed is the ability to set a breakpoint on an expression when the expression is defined. Barring that ability, it is possible to work around this, as I discuss in the section “About breakpoints.” An example of using watch points is diagnosing bugs due to errors like “Alias is not found” or “Class definition ‘name’ is not found.” If, in the first case, one is sure the alias should be in use, or the class library in memory, then you can set a watch point on USED('MyAlias') or SET('CLASSLIB'), respectively. Then set a breakpoint on those values changing. Rerun your code, and the program will break each time the values change. You can determine exactly what line of code is closing the cursor, for example, or changing the SET CLASSLIB TO setting.

About the locals window The locals window is similar to the watch window in that it display variables, their values, and their types. You can’t add your own expressions or set breakpoints in the locals window, but you can change values of variables, array elements, and object properties. The locals window consists of a drop-down list of procedures and methods and a window to display the variables. The drop-down list is synchronized with the call stack. Indeed, as you make selections in the call stack, the “Locals for:” drop-down in the locals window will be refreshed to match the selection. However, the reverse is not synchronized. Changing the drop-down selection will not change the call stack indictor, and therefore, not the code that is displayed in the trace window. Right-click in the locals window to select the scope of variables you want to hide or show. The choices are Public, Local, Standard, and Objects. Unlike the watch window, the locals window’s context is relative to the routine selected in the drop-down list and is related to the

Chapter 4: Visual FoxPro Debugging Tools

49

scope selected in the context menu. The choices for public and local are straightforward since the scope is simple. If a variable is PUBLIC, it’s in scope everywhere, and if a variable is LOCAL, it is only in scope in its routine. Check or uncheck these choices to either show or hide variables within these scopes for the routine selected in the drop-down. The standard option is less obvious than the others are. Check this option to show private variables that are in scope for the selected routine. Note that if you declare a variable PRIVATE in a routine, the variable will not be displayed until it is used, since the PRIVATE keyword does not create variables but hides any existing private variables from the current routine. This can be a little confusing, and the following screen shots show how this choice works. The code that is used for the example is a simple program that has a single procedure in it. The main program sets a private variable Y to the value of “Hello, World.” The program then calls FooBar(). I’ve set a breakpoint at the top of the procedure, and I’ve chosen to display only the Standard (private) variables, as illustrated in Figure 10.

Figure 10. The locals window showing a private variable Y initially in scope for procedure FooBar. Figure 10 shows the trace window, the locals window, and the context menu for the locals window. Notice that Y is in scope as a private variable for the procedure FooBar initially. Figure 11 shows the effect of the PRIVATE Y command. The private command hides any existing private variable Y from the current procedure, in this case FooBar. Therefore, the variable Y is not displayed in the locals window. However, when I choose to display the variables for ImplicitDeclarations.PRG in the “Locals for:” drop-down, the variable Y is a private variable in scope and is thus displayed in the locals window, as shown in Figure 12.

50

Debugging Visual FoxPro Applications

Figure 11. The locals window showing private after hiding with the PRIVATE command.

Figure 12. The locals window after the PRIVATE command from ImplicitDeclarations.

Chapter 4: Visual FoxPro Debugging Tools

51

Once Y is assigned a value—“In Proc FooBar”—it is created with private scope, and is once more available to be displayed in the locals window. This is shown in Figure 13.

Figure 13. The locals window showing standard (private) variables after Y is initialized. Figure 14 shows that I’ve used the locals window to confirm that the private variable Y in the calling procedure is, as expected, still “Hello, World.”

Figure 14. The locals window showing private from the calling procedure.

52

Debugging Visual FoxPro Applications

The Object option is relatively straightforward, but it is related to the other options. If an object is in one of the selected scopes within the executing routine and the Object option is checked, then the object will be shown in the locals window. If is the Object option is checked but the object is not within one of the selected scopes, it will not be displayed. The locals window is helpful, since it provides an easy way to test the values of variables in different routines in the call stack. As with the call stack, only the current routines are available. Procedures and methods that may have been called, but that have ended, are not available. This could mean, for example, that a procedure along the way changed the value of a privately scoped variable. In this case, set a breakpoint on the value of the variable changing, and rerun the code to debug the problem.

About breakpoints Breakpoints are marks set in code that tell the debugger to suspend a program given a certain condition. This chapter has already examined two ways to set breakpoints, by using the trace window and the watch window. Breakpoints can also be inserted in the code editor in much the same way they are set in the trace window. Breakpoints set in the trace window are the type “Break at location,” and breakpoints set in the watch window are the type “Break when expression changes.” If you need finer control than that, use the Breakpoints dialog. To set a breakpoint from a code window in Visual FoxPro 7.0, simply double-click in the left margin of any code window. To set a breakpoint in a code window in Visual FoxPro 6.0, use the code window’s context menu. This is the only way conditional breakpoints—“Break at location if expression is true” and “Break when expression is true”—can be created. An example of the dialog showing the four kinds of breakpoints is shown in Figure 15. Breakpoints set in the trace window or code window are “Break at location” breakpoints. Location is an executable line of code. Breakpoints set in the watch window are “Break when expression has changed” breakpoints. In the former case, the suspend state occurs before the line of code executes. In the latter case, the suspend state occurs after the watch point is evaluated and its expression evaluates to .T.

Figure 15. The Breakpoints dialog.

Chapter 4: Visual FoxPro Debugging Tools Trace (and code) window breakpoints and watch window breakpoints can be modified to create conditional breakpoints. Figure 16 shows how a breakpoint set on a watch point in the watch window—when USED("lvwCustomerOrders")—can be used to create a related breakpoint at a location if the expression is true.

Figure 16. The Breakpoints dialog with “Break at location if expression is true.” While you can’t edit breakpoints, you can use them as a template for a new breakpoint. Simply click on the one that is close to what you want in the Breakpoints dialog, change whatever you want, and then click on the Add button to add the new breakpoint. In the earlier section “About the watch window,” I mention that the breakpoint that gets set when you click on the margin of the watch window is the type “Break when expression changes.” I also mention that this can be frustrating when a part of the expression in the watch window goes in and out of scope frequently. If you know the value of the expression you’re looking for, you can create a “Break when expression is true” breakpoint and enter a conditional IIF() in the Expression field. The expression would look like the following: IIF( TYPE( "SomeExpression" ) = "C", SomeExpression = SomeValue, .F. )

This expression will cause the program to be suspended only when the value of the expression is what you expect. Another technique I use when I really do want to see every place that changes a value is to have two breakpoints. I set one expression on the expression changing, and another on the TYPE() of the expression. Then I disable the first, enable the second, and run until the expression is defined. Then I disable the second—the TYPE()— and enable the first. You can only disable breakpoints in the Breakpoints dialog, so this can be almost as tedious as simply pressing F8 whenever the program is suspended when the expression goes out of scope. A location can be a method or event plus a line number, as in “Init,4” for visual classes and forms. Alternatively, the location can be just a line number, as shown in Figure 16, for

53

54

Debugging Visual FoxPro Applications

PRGs. Use the file selector button to choose the source file to use, or type in a fully qualified path name to a file. If a breakpoint is entered for a method or event, and a file is selected, only that method or event in classes in the selected class library or form will cause a break. If no file is specified, then all instances of that method or event will cause a break. In Visual FoxPro 7.0, you must enter a line number for a method in the location field. In Visual FoxPro 6.0, you could leave the line number off, and the first line would be used. The pass count is relevant to location breakpoints. Set this to any positive number and the breakpoint will be delayed until the code has executed the specified number of times. Leave the field blank to have the break occur always. The option to display breakpoint messages is checked by default. If it’s checked, each breakpoint will result in a modal message telling you which breakpoint has occurred. Uncheck it to bypass the message. If you are stepping through code and a breakpoint will be firing frequently, the message may be an annoying extra step. At the time this is written, the current version of Visual FoxPro is 7.0, build 9262. In this version there are occasions when setting a breakpoint in the margin of the trace window will fail to set a breakpoint that the debugger recognizes. The symptom of this is that the normal red dot does not appear in the margin after clicking. When this happens, open the Breakpoints dialog. A breakpoint may have been created using the scope resolution operator (“::”) instead of an explicit object hierarchy. For example, if the breakpoint appears like the following: parentclass::method,lineno

try adding another breakpoint in the following format: objectname.method, lineno

*

Breakpoints can be disabled, instead of deleted, in the Breakpoints dialog. A disabled breakpoint will show as a red circle instead of a solid dot. The breakpoint will remain for future use, but it will not be functional until you choose to enable it again.

About configuration files I was once building an import routine and testing it on Microsoft Excel workbooks. In some rare cases, my import routine would generate a general protection fault (this was using Visual FoxPro 6.0 with Service Pack 3). Trying to find the line that was causing the code to crash so badly was tedious because there was no obvious clue—the code was somewhere in processing the import, but somewhere innocuous enough that I hadn’t originally programmed any status messages. Once I could reliably duplicate the crash, my next task was to locate the offending line. My general tactic was to first narrow down the location by gradually honing in on the section of code that was responsible. Unfortunately, each time I crashed, my breakpoints (and therefore

Chapter 4: Visual FoxPro Debugging Tools

55

the record of my progress) would be lost. When Visual FoxPro suffers a fatal crash, the debugger’s current settings will be lost. Normally, the breakpoints and watch points are saved when a Visual FoxPro session is closed. It was during this process that I discovered the debugger’s configuration files. I wish I’d found them earlier, since they are useful for debugging different projects and different problems. They are also helpful for testing corrections. Today, I would debug this problem differently using a combination of coverage profiling and event tracking. However, it didn’t take very long to track down by setting progressive breakpoints, since I knew the problem was related to one particular import type and only certain inputs. Once you have breakpoints and watch points set the way you want them, save the setup to a file in the debugger under the File | Save Configuration menu option, or press Alt-F2 in the debugger. To restore debugger settings, simply select the File | Load Configuration menu option from the debugger menu and navigate to the DBG file you want. Note that loading a configuration file will not override any existing settings, so you can easily add and combine configurations. An example listing of a DBG file is shown here: DBGCFGVERSION=4 WATCH=message() BPMESSAGE=ON BREAKPOINT BEGIN TYPE=0 FILE=c:\debugging with visual foxpro\samples\callstack_1.prg LINE=5 DISABLED=0 EXACT=1 BREAKPOINT END EVENTWINDOW=ON EVENTFILE=c:\debugging with visual foxpro\samples\debugout.log EVENTLIST BEGIN GotFocus, Activate, Init, Show EVENTLIST END

The configuration file example was created using the Visual FoxPro 7.0 debugger. In Visual FoxPro 6.0, DBGCFGVERSION is equal to 3 instead of 4. You cannot open debug configuration files from one version of Visual FoxPro in another version. I offer this example as interesting for the possibilities it holds for writing debugger enhancements.

*

If you occasionally find the debugger has decided it can only either be minimized to just a bit of the title bar or maximized, maximize the debugger and then choose Window | Restore to Default to correct the problem. According to Mike Stewart, Microsoft Visual FoxPro Software Test Lead, this was occurring when the debugger was minimized and Visual FoxPro crashed.

In case you are wondering where my general protection fault was coming from in this section’s example, I will close with a brief explanation. I was using a workbook with

56

Debugging Visual FoxPro Applications

worksheets that I had created using my export routine. The routine would, helpfully, name the workbook, and thus the worksheet, with a human-readable description. Occasionally these descriptions could get quite lengthy and indeed exceeded Excel’s maximum allowable length. The export worked without error, and I could open the worksheets in Excel. However, using the Visual FoxPro IMPORT command on a worksheet with a name that was too long was the culprit and caused the GPF.

Debugger odds and ends: Stepping, resume, cancel Before leaving the native debugger, there are a few other items of interest, including the allimportant ability to actually resume, cancel, or step through code. Most of these functions are available through the debugger toolbar and menus and through function keys. However, not all are available everywhere. Figure 17 shows the debugger toolbar and Debug menu. Notice that Set Next Statement is not available in the menu or on the toolbar. It’s only available in the context menu for the trace window, as shown in Figure 7.

Figure 17. The debugger toolbar and Debug menu. I use each of the step options in about equal measure. Their names aren’t entirely intuitive. Step Out will continue the current routine until the next executable line following the routine, at which time the program will again be suspended. If there isn’t another executable line, the program will continue to the wait state (usually a READ EVENTS). Step Over is useful when the line that is about to execute is a function or method call, and you don’t want to step through it. Step Over it to suspend the program on the line following the current line. Step Into is somewhat misnamed: It simply means execute the current line and then stop on the next line, which might be in a procedure or method. Run To Cursor is useful when you want to skip sections of code and stop again further on in the program’s execution. You can use this even on lines of code that are in a different module than the current line of code. I discuss Throttle in the section “Trace window options,”

Chapter 4: Visual FoxPro Debugging Tools

57

Set Next Statement in the section “About the trace window,” and the Fix option in the section “About the watch window.” The debugger toolbar includes toggles for activating and deactivating the debugger windows. The windows can also be activated and deactivated programmatically with the following the commands: *!* Activate debugger windows ACTIVATE WINDOW "Locals" ACTIVATE WINDOW "Debug Output" ACTIVATE WINDOW "Call Stack" ACTIVATE WINDOW "Trace" ACTIVATE WINDOW "Watch" *!* Deactivate debugger windows DEACTIVATE WINDOW "Locals" DEACTIVATE WINDOW "Debug Output" DEACTIVATE WINDOW "Call Stack" DEACTIVATE WINDOW "Trace" DEACTIVATE WINDOW "Watch"

The debugger toolbar also has buttons for breakpoint tasks: one to toggle a breakpoint on the line of code, or watch point, that is highlighted, one to clear all breakpoints, and one to call up the Breakpoints dialog that is also available under Tools | Breakpoints, which I discuss in the section “About breakpoints.” Finally, the debugger toolbar links to two debugging tools I discuss next: coverage logging and event tracking.

Coverage logging Coverage is a combination of logging executed lines of code and the time each line took to execute, plus analyzing—profiling—the log. The log collects information about what lines of code execute, and profiling shows how long each line took to execute on average and how many times each line executes. Profiling graphically describes what exactly has executed and, therefore, been tested. Before Visual FoxPro 6, it was difficult to do much with the coverage files generated with SET COVERAGE TO, because it was difficult to assess the raw dump. An excerpt of a coverage log is shown here: 0.000051,,addpage,49,c:\debugging\samples\coverageprofiler.fxp,2 0.000052,,addpage,50,c:\debugging\samples\coverageprofiler.fxp,2 0.001136,,addpage,51,c:\debugging\samples\coverageprofiler.fxp,2 0.000584,,addtextbox,56,c:\debugging\samples\coverageprofiler.fxp,3 0.000167,,addtextbox,57,c:\debugging\samples\coverageprofiler.fxp,3 0.000110,,addtextbox,58,c:\debugging\samples\coverageprofiler.fxp,3 0.000065,,addpage,52,c:\debugging\samples\coverageprofiler.fxp,2 0.000068,,addpage,49,c:\debugging\samples\coverageprofiler.fxp,2 0.000050,,addpage,50,c:\debugging\samples\coverageprofiler.fxp,2 0.001003,,addpage,51,c:\debugging\samples\coverageprofiler.fxp,2 0.000445,,addtextbox,56,c:\debugging\samples\coverageprofiler.fxp,3 0.000176,,addtextbox,57,c:\debugging\samples\coverageprofiler.fxp,3 0.000110,,addtextbox,58,c:\debugging\samples\coverageprofiler.fxp,3 0.000049,,addpage,52,c:\debugging\samples\coverageprofiler.fxp,2

58

Debugging Visual FoxPro Applications

Once the Coverage Profiler application was added to Visual FoxPro, it became far easier to evaluate code coverage and performance. The following code creates a form that in turn creates a number of objects. The form is not of particular note, but it executes enough statements that take enough time that the coverage profile is interesting. *!* This program creates a form and adds a page frame to it. *!* It then adds pages to the page frame, *!* and then adds textboxes to the page frame. #DEFINE nPAGES 10 #DEFINE nTEXTBOXES 50 CLEAR CLOSE DATABASES ALL *!* Turn Coverage Logging on, and direct the output to a file lnSeconds1 = SECONDS() SET COVERAGE TO CoverageLog1.TXT LOCAL lni, loForm, lcPage, lcNum loForm = CREATEOBJECT( 'Form' ) loForm.ADDOBJECT('PageFrame1', 'PageFrame') FOR lni = 1 TO nPAGES lcNum = LTRIM( STR( lni ) ) lcPage = 'Page' + lcNum AddPage( loForm.PageFrame1, lcPage ) NEXT lni loForm.PageFrame1.ACTIVEPAGE = 1 loForm.PageFrame1.VISIBLE = .T. OPEN DATABASE (HOME() + "SAMPLES\TASTRADE\DATA\TASTRADE.DBC") SELECT CUSTOMER.*, ORDERS.*, ORDER_LINE_ITEMS.* ; FROM CUSTOMER ; LEFT JOIN ORDERS ON CUSTOMER.CUSTOMER_ID = ORDERS.CUSTOMER_ID ; LEFT JOIN ORDER_LINE_ITEMS ON ORDERS.ORDER_ID = ORDER_LINE_ITEMS.ORDER_ID ; INTO CURSOR lvwCustomers SET COVERAGE TO lnSeconds2 = SECONDS() ?lnSeconds2 - lnSeconds1 *!* loForm.SHOW(1) MODIFY FILE CoverageLog1.TXT *!* Runs the coverage profiler unattended. Creates a table *!* of the results for later viewing. *!* DO (_COVERAGE) WITH "CoverageLog1.TXT", .T. DO (_COVERAGE) WITH "CoverageLog1.TXT" PROCEDURE AddPage( toPageFrame, tcPageName ) LOCAL lnj, lcTextBox, lcNum toPageFrame.ADDOBJECT( tcPageName, 'PAGE') FOR lnj = 1 TO nTEXTBOXES lcNum = LTRIM( STR( lnj ) ) lcTextBox = "Text" + lcNum AddTextBox(toPageFrame.&tcPageName, lcTextBox )

Chapter 4: Visual FoxPro Debugging Tools

59

NEXT lni ENDPROC PROCEDURE AddTextBox( toPage, tcTextBoxName ) toPage.ADDOBJECT( tcTextBoxName, 'TextBox' ) toPage.&tcTextBoxName..VALUE = tcTextBoxName toPage.&tcTextBoxName..VISIBLE = .T. ENDPROC



This code is also included in file Chapter4coverageprofiler.PRG, available with the Developer Downloads at http://www.hentzenwerke.com.

The code example will start the Coverage Profiler application. The Coverage Profiler application has basically two views: coverage mode and profile mode. Coverage mode is shown in Figure 18, and profile mode is shown in Figure 19.

Figure 18. Coverage Profiler—coverage mode.

60

Debugging Visual FoxPro Applications

Figure 19. Coverage Profiler—profile mode. As the figures show, the Coverage Profiler application provides a more easily digestible view of the raw coverage log. Use the Coverage Profiler application to identify bottlenecks in code execution—notice even the SQL Select statement is profiled—and to check that a test plan really exercises all the paths through your code. Lines not covered by the log are shown, by default, with a pipe symbol (“|”) to the left. As with the debugger, the Coverage Profiler application can be run in either the FoxPro frame or the Coverage frame. Set the option by opening the Coverage Profiler from the Tools menu, and select the Options button from the Coverage Profiler toolbar. Note that you will need to open a coverage log in order to open the Coverage Profiler application. An XML stack-level analysis has been added in Visual FoxPro 7.0.

*

Make the most of coverage profiling by compiling the code immediately before running coverage logging.

Chapter 4: Visual FoxPro Debugging Tools

61

Event tracking Visual FoxPro includes a utility that will log Visual FoxPro native events. Event tracking is available from the debugger, via either the toolbar or the menu, as well as programmatically. By default, the output from event tracking is sent to the debug output window. However, output can be sent to a text file in addition to, or instead of, the debug output window. If you set event tracking through the debugger or by using the PROMPT clause of the SET EVENTTRACKING command, you’ll be given a dialog window to select the events to track, a check box for turning event tracking on, and a choice of output, as is shown in Figure 20.

Figure 20. The Event Tracking dialog. Event tracking can be handled programmatically. The following code example is a slightly different version of the code in the “About the debug output window” section. IF FILE('EVENTLOG.TXT') DELETE FILE EVENTLOG.TXT ENDIF *!* SET *!* *!*

Specify which events to track EVENTLIST TO GOTFOCUS, INIT, SHOW, ACTIVATE Specify output file for the log. By default, output will go to the Debug Output window

SET EVENTTRACKING TO EVENTLOG.TXT *!* Finally, turn on Event Tracking SET EVENTTRACKING ON LOCAL loMyForm loMyForm = NEWOBJECT( 'MyForm' ) loMyForm.SHOW(1)

62

Debugging Visual FoxPro Applications

*!* Turn event tracking off and close the output file SET EVENTTRACKING TO SET EVENTTRACKING OFF DEFINE CLASS MyForm AS FORM AUTOCENTER = .T. PROCEDURE INIT THIS.ADDOBJECT('text1', 'MyTextBox' ) THIS.Text1.VISIBLE = .T. ENDPROC ENDDEFINE DEFINE CLASS MyTextBox AS TEXTBOX PROCEDURE INIT THIS.VALUE = "Foo" ENDPROC PROCEDURE GOTFOCUS THIS.VALUE = "Bar" THISFORM.RELEASE ENDPROC ENDDEFINE

The result of this code is shown in Figure 21. The text stored in EventLog.TXT is exactly the same output. Event tracking is useful when trying to narrow a behavior down to a particular section of code. Many events can be triggered depending on the catalyst, and events can fire frequently. Debugging events is easier when targeted. First, narrow down the list of events to log, and then narrow the amount of code that’s executed during event logging. Second, turn on event tracking just before the suspected area and turn it off just after. New with version 7.0, the event tracking log includes a time stamp.

Figure 21. Event tracking Debug Output.

Chapter 4: Visual FoxPro Debugging Tools

63

Notice that I included “Show” in the command: SET EVENTLIST TO GOTFOCUS, INIT, SHOW, ACTIVATE Show() is a method, not an event. While the command won’t generate either a compile or a run-time error when methods are listed, they won’t be logged, either. To be certain to select only events that can be logged, use the dialog.

New with version 7.0, SYS(2801) lets you control whether events are logged for only Visual FoxPro object events, Windows mouse and keyboard events, or both. Event tracking is non-invasive and therefore good coding practice. Even if event tracking is directed to a text file in code, the runtime will ignore the command.

Language elements Some other language elements exist that either aid debugging or are commonly used for debugging. Commands that are commonly used to debug, but that have less invasive, and therefore, better, alternatives are: •

WAIT WINDOW



MESSAGEBOX()



SUSPEND

The first two aren’t dangerous like the last one is. Calling SUSPEND is a brute-force way to stop a program in order to debug it. Effective, but in a run-time application it causes a program error that is, at best, only irksome to a user if the error handling is robust, but can be much worse if not. At the very least it adds an extra burden to the developer to remember not only what the bug’s cause is, and what a likely fix is, but she also must remember to remove the suspend in the final release. All three cases can be replaced with DEBUGOUT and ASSERT. You have finer control with these commands, and they are ignored in run-time applications. Therefore, they have no impact on the quality of the final application. Using an ASSERT is accomplished in two steps. First, enable ASSERTS (SET ASSERTS ON), and, second, issue an ASSERT statement. Getting the logic right on an ASSERT can be a little tricky at first. If what you assert is false, then the ASSERT will pause the program and give you the opportunity to suspend. If the assertion is true, the program ignores the ASSERT. This is great, since you can force an assert with just ASSERT .F. Alternatively, you can only assert if there’s a problem. Say, for example, a parameter is passed that’s an incorrect type. That assert might look like the following: FUNCTION MyFunction( tcCharParm ) ASSERT VARTYPE( tcCharParm ) = "C" ; MESSAGE "Error: tcCharParm was not passed as a character"

I make it a habit to include an ASSERT in my error handling, as in the following example:

64

Debugging Visual FoxPro Applications

… SET ASSERTS ON ASSERT .F. ; MESSAGE "Error# : " + LTRIM( STR( nError ) ) + ' on ' + "Line No: " + ; LTRIM( STR( nLine ) ) + CHR(13) + ; "Method : " + cMethod + CHR(13) + ; "Message: " + lcMessage …

The ASSERT in an error routine is in addition to the normal user messages the error routine will display. However, when I’m in development mode, I want to be alerted when an error routine runs (even if the routine can handle the error). This way, I have an opportunity to suspend the program and diagnose it, and I have some additional information. Be sure to include a meaningful message with the ASSERT. If you omit the MESSAGE clause, the default message will be “Assertion failed on line [lineno] of procedure [procedure name].” If an error is one that should be raised in the distributed application, instead of (or in addition to) an ASSERT, you can use the ERROR command to raise an error. If there is an ON ERROR setting, or if the error is raised in an object that has code in the error method (or a parent class has code in the error method), then those will be called with precedence going to the object’s error method, as is normally the case with error handling. If there is no error handling in place, the error will generate the standard Visual FoxPro error message with the options to cancel or ignore in a distributed application, and suspend if in the Visual FoxPro IDE.

Error handling It is essential to know where errors occurred to successfully debug them. It is helpful if, when a user calls about an error in production code, the error message is informative. Partly this is a result of knowing what sorts of problems can occur in your code as well as using a specific error handling strategy. There are different schools on error handling and good and bad points about each. Which is appropriate will depend on several factors including whether you develop in a team, use a framework, or have control over the application’s architecture. Do you work on sub-units (classes and class libraries, for example)? At least a brief discussion of error handling methodologies is relevant to a discussion about debugging. The choices for error handling are error methods, ON ERROR, and no error handling. When an error occurs, Visual FoxPro will first look at an object’s error method for code to handle the error. Then, if there is none, Visual FoxPro will look at the ON ERROR setting. If ON ERROR is empty, then the generic system error handler will be called. The last choice is not optimal since the error will likely not be meaningful to users, may not include relevant program information, especially if the line numbers were not compiled into the EXE, and will offer the user the options to Ignore and Retry, even though they may not be appropriate. The ON ERROR command directs Visual FoxPro to do whatever command you determine whenever any error occurs. You can change the ON ERROR setting anywhere in your code that it makes sense. However, as with all other environment settings, save the original value and restore it when the code block is complete. The following code snippet illustrates this.

Chapter 4: Visual FoxPro Debugging Tools

65

LOCAL lcOnError lcOnError = ON('ERROR') ON ERROR WAIT WINDOW MESSAGE() TIMEOUT 1 *!* Do some code ON ERROR &lcOnError

You can choose to have objects handle errors that occur within their methods and events. Otherwise, the Visual FoxPro error handler is called. If an object can’t handle an error, you can decide to call the parent class method. As an example, I’ll assume a hypothetical data object that has an OpenExclusive() method. I’ve decided the simplest way to know whether I can open a table exclusively, while at the same time getting it open if I can, is to try it. If it fails, I’ll trap that error. *!* MyDataObj.OpenExclusive() … LPARAMETER tcMyTable USE (tcMyTable) EXCLUSIVE …

Now, if the USE command causes an error, Visual FoxPro will first try the object’s error method to handle the error. An example of what that might look like is as follows: … *!* MyDataObj.Error() LPARAMETERS nError, cMethod, nLine IF nError = 3 && File is in use. *!* Return, Retry, USE the table non-exclusively…whatever makes sense ELSE *!* Invoke the Parent's Error event DODEFAULT(nError, cMethod, nLine) ENDIF …

Error methods can be useful for handling errors since they are relevant to the context of the error, which makes error handling simple (no need to save the current ON ERROR, set it to a custom error routine, and then restore it).

Using views While views—environmental views, not SQL SELECT views—aren’t a technique much in use in current Visual FoxPro applications, they can be helpful for debugging. Use views to easily set up an environment for testing a bug fix or for debugging. A view will save all the variables, SET values, and the state of the data including open cursors, relationships, and record pointers. For example, while debugging (or when an error occurs), suspend the application and do the following: CREATE VIEW FileName

A file with the extension VUE will be created. After closing the application, you can easily set up the same environment from the command window with the following command:

66

Debugging Visual FoxPro Applications

SET VIEW TO FileName

Now, you can examine the variables and open data on which your application code was operating.

Syntax coloring and IntelliSense Syntax coloring and IntelliSense are explicitly developer productivity tools. They aren’t normally considered debugging tools. However, it can be argued that one measure of productivity is quality. Since these features, specifically, inform developers immediately as they code, they improve the quality of code and are helpful for debugging from this standpoint. Syntax coloring was introduced in Visual FoxPro 5.0, and it is helpful in preventing syntax bugs from occurring in the first place by giving developers a quick visual cue that a command was mistyped. IntelliSense, introduced in Visual FoxPro 7.0, is another developer productivity tool that helps developers produce robust code. Specific to the purpose of helping developers write robust code more easily are its features Quick Info, List Members, and Auto Completion. Auto Completion will expand abbreviated commands, which aids comprehension and, in turn, makes debugging more effective and helps you to avoid typos. Auto Completion is customizable (as are all the aspects of IntelliSense) and so can be expanded upon to suit your particular coding style. Quick Info is the ToolTip description that is displayed as you type commands. This feature is a wonderful tool for minding the parameters to functions, and their order and type. Similarly, List Members is the drop-down list that displays the properties of an object. It’s similar to the code window context menu’s Object List that has been available from visual class code windows. However, List Members is available programmatically for native Visual FoxPro objects by using the AS clause of the LOCAL command, and for any object in the visual class designer. Even if you chose to turn Quick Info or List Members off, you can still get to the information manually with either Ctrl-I or Ctrl-J, respectively, or from the code window context menu.

Rushmore optimization—SYS(3054) When debugging performance bugs related to SQL-Select statements, the SYS(3054) function is invaluable for evaluating the degree to which a SQL statement is optimized—full, partial, or not at all—and what index, if any, is being used for optimization. Additionally, you can choose to have the function report what index is used for any joins, and to send the output to a memory variable instead of the _SCREEN. If a query is slow, enable the display of the level of Rushmore optimization used for the query just before it runs and then disable it afterwards by calling SYS(3054,0). The following example: SET DELETED ON LOCAL lnOrders, lcCustID lcCustID = "ERNSH " LOCAL lnOrders lnOrders = GetCustOrders( lcCustID )

Chapter 4: Visual FoxPro Debugging Tools

67

.

?"Customer " + TRIM( lcCustID ) + " has " + ; LTRIM( TRANSFORM( lnOrders ) ) + " orders." FUNCTION GetCustOrders( tcCustomerID ) OPEN DATABASE (HOME() + "SAMPLES\TASTRADE\DATA\TASTRADE.DBC") SYS(3054, 11, "lcRO1" ) SELECT CUSTOMER.*, ORDERS.* ; FROM CUSTOMER ; LEFT JOIN ORDERS ON CUSTOMER.CUSTOMER_ID = ORDERS.ORDER_ID ; INTO CURSOR lvwCustomerOrders ; WHERE CUSTOMER.CUSTOMER_ID = tcCustomerID SYS(3054,0) ?lcRO1 RETURN _TALLY ENDFUNC

will display: Using index tag Customer_i to rushmore optimize table customer Rushmore optimization level for table customer: partial Rushmore optimization level for table orders: none Joining table customer and table orders using index tag Order_id

Pass 2 (or 12) instead of 1 (or 11) to include the SELECT statement in the result. This would be useful if you are debugging several SELECT statements at a time. This option is new with Visual FoxPro 7.0. In the example, if this query was slow, I would look into whether I have indices that exactly match my field comparisons and whether I have an index on DELETED(), and whether it’s desirable to have one.

*

The Microsoft Knowledge Base article “INFO: SQL SELECT Optimization Levels and Performance” (Q248608) discusses the issue involved with SET DELETED and Rushmore optimization levels.

Refer to the Visual FoxPro manual subject “Using Rushmore to Speed Data Access” for a full discussion of Rushmore optimization and how to optimize expressions that aren’t currently optimized.

The command window Don’t underestimate how powerful the Visual FoxPro command window can be for debugging. As long as a program’s execution can be suspended, the command window is available—unless it’s been closed and the menu option isn’t available. This means you can open tables, create variables, write programs, and compile and run them, all while the application is suspended. Let’s take, as an example, a bug with a SQL-Select statement that doesn’t operate as expected. You might put a breakpoint on the line and suspend the program before it executes. You can then copy the statement from the trace window into the command window, or create a new PRG in the command window and paste the SQL-Select there. Then, modify the command and test it before continuing the program or fixing the error.

68

Debugging Visual FoxPro Applications

Use the command window to diagnose errors that result when code tries to use data it assumes is open. Suspend the program, and then type SET in the command window to check what datasession is active, what cursors are available, and what other datasessions exist.

The Help file Perhaps underestimated as a debugging tool, the Help file is a good resource too. When an error occurs, if it’s a new error or one that is just not as familiar, look the error up in the Help file. It won’t usually give the exact solution, but if you’re not sure what the error message is telling you, you’ll be sure to have a harder time finding the solution.

Tools outside of Visual FoxPro In addition to the native debugger and supporting language elements, Visual FoxPro developers have one more resource—the community of fellow developers. Available fairly well around the clock in some form and forum or another, it is an irreplaceable resource. Regularly stay abreast of at least one of the online forums. Even if you are not having a specific problem that you want to post a question about, browse threads to forearm you against similar bugs that may sneak into your code. In addition, some bugs are nearly impossible to meet and then beat on your own. For example, a common post concerns a question along the lines of “Why doesn’t my application close when I’ve closed my main form?” This can be due to an outstanding, or orphaned, object reference. Visual FoxPro 5.0 had a bug whereby using PEMSTATUS in a compound condition would leave an outstanding object reference. This is an extremely difficult bug to track down. However, since it’s a common question, it’s relatively easier to find by searching in newsgroups or learning through casual reading. For bugs that are difficult to track down, do use the community as a resource. At the very least, sometimes simply writing the problem out clearly enough to post will help point out a line of investigation or even a solution. Appendix A, “Additional Resources,” lists a few of the community resources. Check your area for user group meetings, too. Similarly, check the Help file if a command or feature is giving trouble and the reason isn’t obvious. Finally, look up the error or subject in the Microsoft Developer’s Network Knowledge Base library to see whether there is a reported language bug or article on using the feature.

Understanding the tool No language is completely bug-free, and no programmer codes perfect code. Even when code is working as expected thus far, it is often still necessary to use the debugging tools to analyze, evaluate, and test one’s code. As a developer becomes more experienced with his language, more time is spent in preventative debugging—testing and checking code coverage, stepping through the code in new classes, and so on. The Visual FoxPro debugging tools are useful and important to helping even experienced developers produce quality code. Finally, experience is one of the best debugging tools a developer has.

Chapter 5: A Taxonomy of Common Visual FoxPro Bugs

69

Chapter 5 A Taxonomy of Common Visual FoxPro Bugs In Chapter 3 I discuss applying the scientific method to debugging, and I describe the Visual FoxPro debugging tools in Chapter 4. In this chapter I propose a taxonomy of common bugs Visual FoxPro programmers encounter. A taxonomy provides a useful structure in which to categorize bugs so that you can determine an appropriate strategy for debugging them.

Bugs can happen throughout a project’s lifecycle. An exhaustive listing of all bugs developers encounter would be impossible, and is certainly outside the scope of this book. However, a taxonomy of common classes of bugs can help the developer to choose a debugging strategy when meeting new bugs of a known type. Bugs can be first classified by the developmental stage in which the bug is introduced: requirement, design, implementation, deployment, or maintenance. The bug introduced in one stage can be caught at any later stage. However, the bugs that are of particular interest for the purpose of this book can be debugged using the Visual FoxPro debugging tools. While requirement and design flaws result in software defects, they’re still bugs, of course, even though they don’t result in the system crashing, loss of data, or incorrect totaling. I also differentiate between bugs and defects in the degree to which they impact a product. Defects are corrected by redesigning and recoding, or at least wrapping, significant modules or chunks of code. Bugs might be nasty, and though they may require a step back to reconsidering design, they usually do not require, except in the most drastic case, a rewrite. An example of a drastic case would be if during implementation a bug turns out to be a product bug and the product makes an implementation impossible. Requirement and design-related bugs aren’t generally investigated or discovered using the Visual FoxPro debugging tools. However, there are some common requirements and designrelated bugs, and thus I include them here.

Requirement-related bugs Requirement bugs are exhibited by the failure of the software to do something the user expects, or behavior on the part of the software that the user doesn’t expect. These bugs might result from missing requirements, conflicting requirements, or misunderstood requirements. While not strictly bugs in the sense “bug” is normally used, these do result in software defects (a subtle and usually unimportant difference from a user’s point of view). Requirement-related bugs might be found when reviewing the functional requirements with your customer, during design when you realize data necessary to support an analysis hasn’t been specified as a requirement, or after delivery. Obviously, the first and second checkpoints are the best times to catch these bugs. If this sort of bug isn’t found until delivery, it is one of the most costly bugs to fix (Telles and Hsieh). However, requirement-related bugs might also

70

Debugging Visual FoxPro Applications

be found during implementation, at which time they can also be corrected, although still more expensively than during review or design.

Exceptions to business rules This is one of the most common requirements-related bugs in my experience, and it is one of omission. These bugs become easier to catch the more experience one has with a domain, in particular, and with development projects, in general. However, they creep easily into a project because of two main reasons, in my experience. First, as people get experienced with their work, they perform much of it without being conscious of all the steps they take and the choices they make, and so the domain experts sometimes forget to mention the requirements. Second, the connection between practice and policy is rarely one-to-one, and often the first requirements documentation presented for a new project is the old system’s procedures or specification even though that might not be the way things are done in practice. For example, a recent project of mine had the requirement that data could only be changed during the shift in which it was entered. So, in this case, if a user tried to edit an item he’d entered the day before, the system was supposed to disallow this. As it turned out, once the field offices received this software, it became apparent that users might legitimately need to make changes up to a specific date trigger associated with the data, after which time no edits could be made. Luckily this was relatively easy and inexpensive to correct, since all of the fundamental pieces of information were being collected, and since the design had included edit business logic. However, it did require an hour of programming time and the corresponding testing and documentation. The EXE had to be rebuilt, redistributed, and redeployed at several sites. All of this would have been unnecessary had the requirement been identified in the beginning.

The technique The best technique for debugging requirements-related bugs is preventative: A robust requirements gathering process will help avoid many potentially costly omissions and misunderstandings. Gathering requirements is a field worthy of extensive study. For example, Mastering the Requirements Process, by Suzanne Robertson and James Robertson, is an excellent study of the subject. For the requirements-related bugs that slip through to other stages, however, there are techniques that can help uncover them. These techniques are the same for design-related bugs, and I discuss them together in the following section.

Design-related bugs Design bugs are roadblocks to implementing solutions that satisfy requirements. Design bugs fall into the same subcategories that requirements-related bugs do. Design-related bugs result from forgetting a requirement in the design, from conflicting designs, or from incorporating requirements incorrectly or with an incorrect understanding of the requirement. As with requirements-related bugs, these are best found during reviews as they become more difficult and costly to correct the further into the process they are discovered. Again, as with requirements-related bugs, design-related bugs that directly affect other areas of the system can be found during implementation before they are ingrained in the system and become full-fledged defects. Discover bugs resulting from omissions by considering what

Chapter 5: A Taxonomy of Common Visual FoxPro Bugs

71

inputs a module requires, and understanding whether they will come from within or outside your system. Discover bugs that result from misunderstanding requirements by understanding the context of the system in which you’re working. Direct experience with the application domain is helpful here. For example, if you work with the same domain—legal time and billing, for instance—when you’re implementing your tenth system, you will probably know to ask about how the firm wants to handle pro bono time, and whether they want to track it at the regular rate and then discount it.

Misunderstandings No matter how clearly ideas are communicated, and no matter how well intentioned all parties are, misunderstandings are inevitable. I’m reminded of a classic “Saturday Night Live” skit with Ed Asner as a nuclear power plant manager who, before leaving on a well-deserved vacation, warns his staff, “You can never add too much water.” All the staff nod sagely and send Ed off on vacation. After he leaves, the staff starts arguing about whether he meant, “You can never add too much water” or “You can never add too much water.” I was once tasked with creating a search and replace routine that my client and I finally nicknamed the Mona Lisa because it grew into something far more than what the intentions were. Aside from the usual occupational hazard of feature creep, I ended up delivering something that worked, but it wasn’t what the client wanted. When faced with alternative meanings, or implications of a requirement, it’s best to step back and determine which is the correct alternative. Whichever it was for the power plant, the last shot of the skit was a mushroom cloud blooming over a happily fishing Ed Asner.

Conflicts Another time I was tasked with creating a module that would allow a user to sell batches of booklets. The design called for allowing the customer to track sales by batches, but the design also specified a non-normalized table that only stored the booklet data and no data about how the booklets had been batched. Given that the module was already partially implemented, I had a couple of choices. I could choose one of the designs to implement and ignore the other. However, that would have institutionalized the bug into a gross software defect, and only put off the inevitable pain. Instead, my client and I backed up to the requirements step, clarified what the customer needed from the module, reworked the design, and then implemented the corrected design.

Omissions Unless an omitted requirement has a direct bearing on another part of the system that wasn’t forgotten, this is somewhat more difficult to uncover during implementation. A design review is the best time to find this; the second best time is during a periodic review during implementation. One technique I see surprisingly often, especially in lone-wolf programmers, is mindreading. For example, I once worked for a programmer who thought calling the customer with questions was a sign of a weak character. When we built reports, for example, and found we didn’t have the necessary calculation in our specification, he would make a guess as to which one to use. Often, the guesses were wrong or incomplete and resulted in design flaws in the final product. As is often the case in life, this was a great, if painful, experience full of learning

72

Debugging Visual FoxPro Applications

opportunities for a young programmer. The lesson I took away from this was that if a question isn’t clearly answered by the project’s documentation, then it’s best to take a step back to the point where it’s unclear. Did I miss a requirement? Or did I misunderstand a requirement and thus design an incorrect solution? After that, it’s usually a simple matter to get the answer.

The technique There are good coding practices that can help debug potential design and requirements-related bugs. Depending on the size of the team and system being implemented, there’s never any harm in revisiting the project documents periodically during the course of a project. My experience is with small projects that I have a good overview of, and I usually have access to whatever documentation exists. Project documentation might be in the form of a functional specification, UML diagrams, data diagrams, or test plans, or it might be informally kept in Word or Excel documents. Review the documentation for a module when you are close to being complete with the module. It’s easy to lose track of the details in a design specification while writing code. Memory is an unreliable record at the best of times. Over time, details are invented, drop out, or are transformed. It’s human nature to see what we’re building and think that just this little change would be a great addition. For example, while writing this chapter, I’m working on a project that is near official release. While correcting a design bug in a module that re-titles a report, I was tempted to allow the user to temporarily rename the report. The change wouldn’t be permanent. While it might be useful, it’s not in the design specification, it would have required additional testing, and it violates the rule of changing one thing, and one thing only, when fixing bugs. Not to mention that no one else on the project had had a chance to comment on the idea. By reviewing the documentation (in this case, more of a metaphorical review), I was able to refocus my debugging activity. Another example where reviewing the design specification helps is when functionality is reported as a bug. This is where the funny, but true, question comes from. Is it a bug, or is it a feature? If the behavior is repeatedly reported as a bug but is an artifact of the design, then it might be a design bug, or a training bug. However, that is a different issue and should be addressed separately. If the module you are working on is particularly complicated, review the documentation also after you create the shell of the module, and then again when the module is near completion. Finally, if I have a question about what a requirement means, or if two aspects of a design seem to be in conflict, and I can’t answer it by researching the project documentation, I step back from the code and review the design with the customer or project team.

Characteristics Each language has its characteristics that may lead to common errors. For example, a C++ programmer can access pointers that haven’t been initialized, which can result in page faults. Understanding the vulnerabilities of a language’s characteristics is a big part of becoming an expert in the language. Visual FoxPro has its own characteristics that are prone to bugs. These characteristics include variant variable typing and optional parameters. Since these are wellknown characteristics of the language, preventive measures are effective for stopping bugs before they occur.

Chapter 5: A Taxonomy of Common Visual FoxPro Bugs

73

Variant variables Visual FoxPro variables are variants. This means that I can declare a variable as one type, but assign another type to it. Or, I can assign an integer to a variable that is currently charactertype. Since Visual FoxPro doesn’t complain, or warn, bugs can creep in because the code is unclear to a programmer. Hungarian notation is a useful naming convention that prescribes scope and type as the first two letters of a variable name. For example, a numeric variable with local scope should be prefixed with “ln” plus the variable name: lnCustNo, for example. Hungarian notation is a self-documenting coding style that helps programmers maintain variant variable code, in particular. I use Hungarian notation for this very reason, and normally it helps me maintain my code, as intended. However, I once caused a bug for myself because I trusted the Hungarian notation of a return value of a method. The variable name indicated that the method returned a Boolean, when in fact the method returned a string. The bug manifested itself as a data type mismatch when I tried to compare a character and logical. Debugging entailed setting a breakpoint on the line that caused the error, and then checking the values of all variables in the watch window, which is where I discovered my error. This sort of error is a documentation error and a logic error. It was a documentation error since code relied on the variable naming convention to tell me what type to expect. It was a logic error because the method was not returning the correct type of value (it really should have been Boolean) and, so, I compared two different variable types. The logic error was the most visible and prompted the debugging technique, even though the fundamental problem lay elsewhere in the program. This example, though simple, points out two useful lessons. It’s important to treat the fundamental problem and not just the symptom of a bug, and in order to fix, really fix, a bug, one needs to be aware of how the change will impact other areas of the system. I could have stopped at changing the comparison, but that would have left the system vulnerable to the same bug in the future. Since the misunderstanding happened once, it would be likely to happen again. Changing the return type of the method wasn’t feasible either, however, because other code depended on the method returning a character value. The next best solution was to change the return value documentation so that it was clear a character value was being returned. The technique Use the watch window or locals window to check the values and existence of variables in lines of code that cause errors like “Data type mismatch.” Use the trace window to check preceding lines of code for variables to trace how the variables are being manipulated, and check your assumptions about what values variables should have. If code might be executing against variables with varying types, or that are initialized by client code, and passed in to a procedure as a parameter, then it might bear checking the type of the variable before operating on it.

Implicit variable declaration Visual FoxPro allows implicit variable declaration, which means that you can use a variable without declaring it. Using a variable without declaring it can have one of two unintended effects. Either you will be using an already existing private variable from a calling routine when you think you’re using a locally scoped variable, or you might be implicitly passing a variable to a called routine that might, then, change the value in the local routine. Programmer

74

Debugging Visual FoxPro Applications

habit plus Visual FoxPro’s tolerance for undeclared variables caught me once, as the following example shows. The example creates a form, adds a page frame with an arbitrary number of pages, and then adds text boxes to each page. #DEFINE NUMPAGES 4 LOCAL loForm loForm = CREATEOBJECT('Form') loForm.AddObject('PageFrame1','PageFrame') FOR lni = 1 TO NUMPAGES loForm.PageFrame1.AddObject('Page' + LTRIM( STR( lni ) ), 'Page' ) AddTextBoxes( loForm.PageFrame1.Pages(lni) ) NEXT lni loForm.Setall('Visible',.T.) loForm.Show(1) FUNCTION AddTextBoxes( toPage ) IF VARTYPE( toPage ) "O" RETURN .F. ENDIF LOCAL lnToAdd *!* If this is page1, add two textboxes, if page3, add 4 textboxes, *!* and so on. lnToAdd = toPage.PageOrder + 1 FOR lni = 1 TO lnToAdd toPage.AddObject('Text' + + LTRIM( STR( lni ) ), "TextBox" ) NEXT lni RETURN ENDFUNC

The call to AddTextBoxes is the line that generates the error. When you break the program on that line when it errors, you will find that lni = 4, but loForm.PageFrame1.PageCount = 2. How can this be? I habitually use the variable name “lni” for counter variables. Unfortunately, in the case this example represents, I forgot to declare a local variable for it in both the main function and in the called function. So, as the example shows, the counter is pointing to the same variable, and so, the second loop in AddTextBoxes is also incrementing the first. Interestingly, if I had remembered to declare one of the counters, I would not have had an error, but my code would have had the potential bug buried… waiting. It was lucky that I did forget both and that the mistake resulted in an obvious error. Otherwise, it would have been difficult to track down a bug caused by loops not incrementing correctly, but not resulting in any obvious errors. As it is, I went back through the affected code base and looked for similar omissions, and while it was tedious, I did find a couple of places I’d forgotten to declare my counter variables. Taking the time to check the code base helped reinforce the necessity of declaring variables. The technique When you run this code, you should get the error “Pages is not an object.” This is a good example of how an error message has little or nothing to do with the actual problem. However,

Chapter 5: A Taxonomy of Common Visual FoxPro Bugs

75

the standard debugging technique, when a line of code causes an error, is to first break on that line and evaluate what is happening with the code at the time. As with the variant variable example earlier, the line of code that throws the error is a symptom of the underlying problem, but it is not the cause. Breaking on the line of code and checking the values of variables gives a clue about the source of the problem. In this example, the next step is to rerun the code after setting a breakpoint earlier in the code and stepping through the code to confirm the problem. Or, you might see the problem immediately. In either case, once you’ve made the correction, rerun the code in question, with the same operating conditions, to verify that you have corrected the problem, and that there aren’t any other problems lurking in the background.

Comparison and assignment are the same (“=”) In some languages the assignment and comparison operators are different. For example, in C#, assignments are made using the “=” sign, but logical comparisons use “==”. In Visual FoxPro, both comparisons and assignments use the same syntax, the “=”. What sort of bug results from this innocuous fact? Since the equals and plus signs are on the same key, this can result in an easy-to-miss typo, as in the following example: foo = bar = 3

Instead of foo being a numeric, it is a Boolean. This error would probably be very apparent, and not too tricky to track down, but it is the sort of easy-to-miss mistake that can leave even veterans scratching their heads if they’ve had a long day. The technique Errors that result from this mistake usually show up as data type mismatch errors downstream from the source of the problem. This is an example of how a line that throws the error in a program might be a symptom and not the underlying source of the real error. Diagnose this error by tracing back through code that has thrown the data type mismatch error to find where the value for the offending variable was set if the code is relatively tight. If the assignment is made in distant code, it might be helpful to set a break or watch point on the variable’s type. For example, set a watch point on TYPE('MyVar') in the debugger’s watch window, and then double-click on the margin to set a breakpoint on the expression changing. Or create a breakpoint in the Breakpoints dialog on the expression TYPE('MyVar') # 'C', and choose to break when the expression is true. While the source of data type mismatch errors is usually fairly simple mistakes, those mistakes can be hard to “see” when you’ve been looking at the same code for a long time.

Multiple RETURN statements Visual FoxPro allows any number of RETURN statements in a procedure, including none. While most academicians and formalists insist that each procedure should have exactly one exit point, this isn’t necessarily desirable. Multiple RETURN statements can be appropriate when a routine has natural exit points and when reading the code easily identifies these points. For example, after checking critical parameters in a method or procedure, returning in the case of a bad or missing parameter

76

Debugging Visual FoxPro Applications

(after an appropriate message is displayed) can make the rest of the routine more legible than enclosing the remainder of the routine in an ELSE block. However, the ability to have multiple return statements is a feature that is easily abused, and easily confuses a maintenance programmer, especially in a routine that is longer than a screen, or that has several conditional blocks. Multiple returns make maintenance difficult and are error-prone when cleanup must be done before the routine exits. For example, it’s common to save environmental settings—SET EXACT or CURDIR(), for example—at the beginning of a routine, and then reset them before the routine exits. If you have coded two or three returns, then the cleanup code will need to be repeated for each return. Duplicating code is vulnerable because it’s easy to forget it in one place, and it’s hard to maintain because any changes to the cleanup code will need to be made in the other return blocks. In this case, it is better to set a success flag and return from one place once the routine has kicked off its work. Multiple RETURN statements make debugging more difficult also, although testing requires about the same effort. The reason testing requires about the same effort is because the RETURN statements represent possible paths through the routine that need to be tested. Whether the return is handled there or whether a success flag is set, it is about the same amount of work. Debugging is more difficult because it is simply harder to determine which execution path resulted in the incorrect return. It is also more difficult because it is harder to spot the problem while reading, reviewing, or maintaining the code. Maintenance exasperates the problem if a programmer uses the “wrong” return as a model for what return value she should use for a modification. The technique Bugs that result from an improper return can be of several varieties, including unexpected return values or types, or premature returns. In my experience, I’ve found them when using the trace window to step back through the code when an unexpected return value caused an error in the calling code. In any case, preventative debugging is the most effective, since manifestations can be so varied and troublesome to diagnose. Use coverage logging with coverage profiling while testing all logical branches and boundary conditions in a routine.

Optional parameters All parameters in Visual FoxPro user-defined procedures are optional, at least so far as Visual FoxPro is concerned. Not so, usually, for the business of a routine. Checking the validity of inputs to a procedure is an example of boundary checking. Boundary checking means that the limits of what some code can expect to see and is expected to handle is verified by the code instead of assuming that only valid cases will be met. Although parameters are optional for passing, they are positional, and they are initialized to .F. if not passed explicitly. This means that if a procedure has four formal parameters, then four will be available, even if one wasn’t passed. If it’s a Boolean parameter, then the parameter will be processed as if it was passed. This may not be a safe assumption if the programmer simply made a mistake in the procedure call. In this case, the user will wonder why it never seems to matter whether they make a Yes or a No choice. If procedures are designed to handle optional parameters, then those parameters, in addition to being documented, should be tested at the top of the routine, or code that assumes

Chapter 5: A Taxonomy of Common Visual FoxPro Bugs

77

the value of a parameter is correct will throw an error. There are three pieces to checking parameters: Check the type or PCOUNT(), have a default if the parameter is optional, and use local variables to store the parameters (or defaults if parameters aren’t passed). The first piece is important since it’s the easiest way to check that a parameter is the type the code is expecting. If the parameter type is Boolean, however, and if .F. is not the default value for the parameter, then you must use PCOUNT() to determine whether the parameter was passed. The second piece is also important, and what the default is should be carefully considered so that it doesn’t cause any unintended effects. The third piece can be equally important, although it is not always necessary. Whether parameters are optional or not, if their values are manipulated in the routine, but the module isn’t designed to explicitly modify the parameters, assigning them to local variables protects against changing the variables in the calling code if the parameters are passed by reference. There is a trade-off to checking parameters. In routines that are called frequently and that take time to execute, it might not be advisable to check every parameter. Also, if a procedure shouldn’t execute if some or all parameters aren’t passed, then it might be a programmer’s error in misusing the procedure, in which case you do want to raise a program error. The technique Bugs due to incorrectly passed or assigned parameters might result in data type mismatch errors when the routine tries to use a parameter that’s not the correct data type, which will flag the error close to the source. In this case, use the same technique for finding the cause of a mistyped or missing variable: the watch window or locals window to determine variables and expressions, and the call stack and trace window to see how the routine is being invoked. These bugs also cause downstream program anomalies that are hard to trace back to the source. Anomalies might be manifested in a routine that has the same effect no matter what choice the user makes. In any case, a combination of tools will be useful in hunting down the problem.

Ambiguous fields, memory variables, and objects In Visual FoxPro, fields can look like variables or object properties, and vice versa. In the following, is lcCustomer being assigned the value of a field or an object property? lcCustomer = Customers.Customer

From the example, it’s impossible to tell. Is the following assigning a variable or a field value to lcCustomer? lcCustomer = Customer

Again, just from the example, it’s impossible to tell. Given an ambiguous situation, Visual FoxPro will first look for a table with a field name that matches the assignment. Therefore, in the former case, if a cursor named “Customers” is available, or if, in the latter case, the currently selected alias has a field named “Customer,” the field will take precedence over a variable. A bug that results from this characteristic of Visual FoxPro might result in the error “Alias ‘oForm’ not found,” for example, when referencing oForm.Name. If you’re certain that oForm should be an object, the error might be confusing.

78

Debugging Visual FoxPro Applications

The technique If an error occurs that an alias is not found when you are referring to an object, check that the object has been initialized. The error should occur on or near the source of the bug, and so the locals window will confirm for you whether the variable has been defined, and the trace and call stack windows will help you backtrack to the line of code where the object variable should have been defined. When using an ambiguous object reference, prefix memory variables with “m.” to clearly differentiate them from fields. Prefix fields with aliases, and name objects using a convention such as Hungarian notation (or prefix object variables with “m.” also).

Implementation bugs Implementation bugs are what developers normally mean by “bug.” These include syntax, logic, and documentation errors, misunderstanding the language, coding mistakes, and pushing a feature past the point where it’s stable. Every language has capabilities that have optimal performance conditions. Many capabilities can operate to varying degrees of acceptability outside the optimal conditions. For example, SET FILTER will work well with small, optimized data sets or in quick-and-dirty direct data manipulation. However, it is not suitable for gigabyte-sized datasets. The native grid is useful and is powerful enough that many applications don’t require any third-party grid. However, many experienced programmers prefer to avoid using grids to edit data since it becomes a complex task quickly in any but the simplest editing scenarios, and for moderately sized data sets. That said, the grid is capable of complex data entry, and it can even be used to display containers in cells—containers that have any number of objects, including other grids! The more complex any unit of code becomes, be it an object or a function, the more vulnerable it is to bugs. Understanding the best use of a language’s capabilities is largely a matter of experience.

Syntax bugs In The Science of Debugging, Telles and Hsieh do not consider syntax bugs to be implementation bugs, because, in their experience, syntax bugs would never make it into the final product. However, Visual FoxPro does sometimes compile syntax errors into an EXE, and it can be easy to miss a compile warning. Therefore I include them here. As with other bugs, prevention is the best debugging technique for syntax errors. Otherwise, breakpoints combined with watch points will likely reveal the source of syntax errors. Preventing syntax bugs involves getting in the habit of checking for errors during compilation (and compiling frequently). This suggestion might be surprising to some since it seems so obvious. Never the less, if a project normally compiles with warnings, then noticing when there are five rather than four errors requires an attention to detail that might lapse out of habit. I work on a project that normally compiles with some errors. Even though I’ve worked on this project for several years, I occasionally forget to check that my compiles are clean. An option when building is to display errors after a build. If your weakness is like mine, you may want to choose this option. Another preventive tool is the interactive development environment (IDE). Each Visual FoxPro version has added more productivity features, including some terrific helpers for writing syntactically correct code. The syntax coloring that Visual FoxPro 6.0 introduced is a

Chapter 5: A Taxonomy of Common Visual FoxPro Bugs

79

good tool for catching syntax bugs such as DO FRMO before they get too far. However, it’s not a panacea. For example, how many times have you typed DO FROM, which syntax coloring won’t point out as an error? IntelliSense, introduced to the language in version 7.0, helps where syntax coloring can’t. It can’t guarantee correct command syntax, but it puts the syntax at our fingertips. Use the beautify application to help identify syntactical correctness. Beautify clearly and consistently displays keywords and symbols, and even misspelled memory variables. The latter can be identified if you tell beautify to match the first occurrence. It doesn’t happen often, perhaps, but beautify will expose the difference if you’ve typed a variable differently with different capitalization. Beautify will indent code, and this can help point out a line or two that’s on the wrong side of an ENDIF, for example. Syntax bugs that make it into an EXE can cause hard-to-find bugs. Whenever I meet a bug that doesn’t quickly start revealing itself, the first step I take is to recompile, usually the entire code base, and then I check for compile errors. Syntax errors and macro expansion Since Visual FoxPro has macro expansion, some syntax errors simply are not discoverable before the program runs. Macro expansion is the ability to embed any legitimate Visual FoxPro command in a character string, and then execute it as if it were a normal line of code, as in the following example: LOCAL lcCmd lcCMD = "RUN /N NOTEPAD" &lcCmd

Macro expansion is one of the special features of Visual FoxPro and leads to many clever possibilities. Unfortunately, it will also let a programmer shoot herself in the foot, too. The following example shows a macro expression that is vulnerable to causing a run-time error. FUNCTION GetCustomer( tcCompany ) LOCAL lcSelect LcSelect = "SELECT * FROM Customers WHERE Company = '" + tcCompany + "'" &lcSelect RETURN

When strings are used to build a command that will be the result of a macro expansion, consider what delimiters to use and what the data is likely to have in it. In the example in the text, it’s not unreasonable to assume that a company name might contain an apostrophe. The example will cause an error if tcCompany is, for example, ELLIOT'S TRUCKING INC, in which case the expression, when macro-expanded, would be as follows: SELECT * FROM Customers WHERE Company = 'ELLIOT'S TRUCKING INC'

Using the bracket as the interior delimiter is safer in this particular case. Another mistake is to miss the internal string delimiters altogether, as in the following example: LcSelect = "SELECT * FROM Customers WHERE Company = " + tcCompany

80

Debugging Visual FoxPro Applications This example macro-expands to the following:

SELECT * FROM Customers WHERE Company = ELLIOT'S TRUCKING INC

In this case, evaluating the variable tcCompany when the program throws the inevitable “Command contains unrecognized phrase/keyword” error should clearly point out the problem. When an expression will be macro-expanded and part of it will be a character value that is being built from a variable, remember to embed string delimiters in the expression. The technique Errors that result from macro-expanding are almost always program errors, and so it’s fairly easy to identify the line of code that is the source of the problem. The trick to diagnosing the problem is to use the locals and watch window to evaluate the string expression that is being macro-expanded. If you cannot take the outer string delimiters off and see a reasonable command, then reconsider how the expression is being built. Sometimes it’s tempting to put several string concatenations in one line of code, perhaps with line continuation marks, which further confuses diagnosing macro expansion errors, once the offending line has been identified. Visual FoxPro is fast, and simple string assignments, broken down into multiple steps, should be adequate for most purposes. For example, in some code I wrote that builds a SQL statement that will be stored in a property for later use in a macro expression, I divide up the tasks into logical sections and then combine them, like so: lcSelectCMD

= lcSelectCMD + lcFrom + lcWhere + lcOrderBy + lcGroupBy

This approach let me diagnose the routine easily. I stepped through the routine line by line and checked the code and pieces for correctness. Once I put the pieces together, I could check the resulting string for correctness (one of the bugs I had in this routine was no space before the FROM clause). If processing time is critical, the code can be restructured to a one-line assignment after getting the code, first, to work in all test cases.

Logical bugs Logical bugs are monsters that go bump in the night. They aren’t flashy like syntax errors that cause program error messages or spectacular fatal errors. They might be typographic, or simply the result of a bad implementation of a design. They are definitely bugs, though, and can be tricky to track down. Typographic errors Since variables can be implicitly declared in Visual FoxPro, it is likely you will meet this class of bug at least once in your career, if not every week. It depends a bit on how careful you are. LOCAL lcVegetable, llSoupoftheDay, lcSoupoftheDay lcVegetable = "Bokchoy" llSoupftheDay = .T. IF llSoupoftheDay lcSoupoftheDay = "Green Chile Stew" endif

Chapter 5: A Taxonomy of Common Visual FoxPro Bugs

81

Lost in a block of ordinary code, using long variable names that are hard to parse in a glance, one might be annoyed to find that the soup of the day does not print on the menu because llSoupftheDay is mistyped. While long variable names are a boon to programs because they dramatically improve readability, they can be too long if they are problematic to type accurately. I have sworn to myself that I will avoid all use of what I call “Mississippi” variable names. This example, while admittedly contrived, shows how hard it can be to spot a misspelling in a variable name as the names grow longer and use too many similar looking, or repeating, letters, as when typing llistitle too quickly, instead of llListTitle. The technique Since variables in Visual FoxPro do not have to be declared, this can be hard to track down. However, bugs that result from this mistake usually manifest themselves as program errors because code tries to use the wrong variable and it’s not defined, or the wrong type. Use the _VFP.LanguageOptions value to set a form of strict typing on, in which case using undeclared variables will echo a message to the debugout window. This is only enforced at run time, however, so you have to test all code thoroughly. Passing variables by reference or value When variables are passed into a procedure, there is no way to determine whether the passed parameter was passed by reference or by value, unless the parameter is an array or object, both of which are always passed by reference. When variables are passed by reference, any modifications made to the variable will be permanent. If the variable is passed by value, changes will persist only within the scope of the procedure. Unless a routine is designed to create or change a variable, all parameters should be treated as if they’ve been passed by reference and not changed. This way, unintended side effects are minimized. The symptom of bugs that result from unintentionally changing variables in procedures is unexpected changes in data or comparisons that don’t match and should, or vice versa. For example, one might pass a variable to a function that pads it with blanks before a comparison is made to a field value. If the calling routine is depending on the value to remain the same as when it was passed, a bug might occur downstream of the function when the variable is used in another situation. The flip side of changing parameters in procedures is that bugs can result from failing to pass a parameter correctly. If the intention is that a variable retains its value after a function executes, then you pass the variable by value. Conversely, if the expectation is that the function updates the value of the variable, pass it by reference. Passing a variable the wrong way may result in either unexpected changes or the failure of a change. In either case, the bugs will be similar. Another common bug that results from how parameters are passed involves arrays. When UDFPARMS is set to VALUE, the array must be passed with the @ token, or only the first array element will be passed, which is usually not the intention. However, if UDFPARMS is set to REFERENCE, then the entire array will be passed even if the @ token is not used. The technique When a parameter is passed by reference to a function, and then changed, unexpectedly, bugs might be manifested by a comparison, for example, not being valid, as in the earlier example.

82

Debugging Visual FoxPro Applications

The technique to diagnose these bugs will be similar to the general technique when facing a behavior, the cause of which isn’t immediately apparent. First, demonstrate that the problem is reproducible. Second, verify that what you think is happening is what is happening. Third, try to simplify the environment to the essentials of what demonstrates the problem. Fourth, confirm that you understand what the control, or the command, is supposed to do and the environment. Verify your expectation of how a variable is manipulated in a function, and confirm the environment is set the way you expect. The SET variable UDFPARMS affects how parameters are passed. Specifically, a function like MyFunc(MyVar) will treat MyVar differently depending on UDFPARMS. If UDFPARMS is set to REFERENCE, the variable is passed by reference. If UDFPARMS is set to VALUE, it will be passed by value. A simple change in a SET command will have a ripple effect throughout the application. Fifth, develop a hypothesis about the cause of the problem. Sixth, try your hypothesis by changing one, and only one thing at a time. If a hypothesis turns out to be incorrect, reset the code back to the original state before trying the next hypothesis. Use source control software to manage versions as an aid with debugging. It’s simple to roll back changes to the previous copy. However, simply copying or zipping the source code will work, too. Environmental variables As we saw with UDFPARMS in the previous example “Passing variables by reference or value,” changing a SET command may have a considerable impact on an application. In addition to UDFPARMS, these include DEFAULT, PROCEDURE, CLASSLIB, FULLPATH, STRICTDATE, ESCAPE, and so on. Similarly, some SET commands affect only the current data session in which they are issued, and can cause more than a little consternation when a new form behaves differently from existing forms. Common and pernicious examples are DELETED, EXACT, NEAR, ANSI, and TALK. These are SET commands that affect the display and manipulation of data. The technique Diagnosing bugs that result from unexpected SET values can be obvious, as when command results scroll by on a form, or when deleted records unexpectedly show in a grid. In the former case, check the SET TALK WINDOW setting; in the latter, check the value of SET DELETED. These bugs can also be less obvious, as when FULLPATH is set off and code expects it to be on, or when NEAR is set on, when code is relying on FOUND() to return false if a SEEK doesn’t find a match. Preventing these bugs is easier than diagnosing them. Design your code to have one central location for managing SET commands, and design a way that forms can call the appropriate SET command routine depending on the sort of form it is. For example, you might want different settings depending on whether a form explicitly shows deleted records so a user can recall them, or whether a form is a search routine, in which case NEAR might need to be on and EXACT off. Subclass from an abstract form class that calls the SET routine for commands scoped to the local data session in the LOAD() method. These are methods that help avoid errors.

Chapter 5: A Taxonomy of Common Visual FoxPro Bugs

83

Controlling controls A common newsgroup subject is questioning why a control doesn’t respond to events and property settings. For example, setting a ComboBox to ReadOnly doesn’t have an effect, or setting the FontBold property on a TextBox doesn’t have an effect. The first example is one I’m embarrassed to say I banged my head on when I was a rookie using ComboBoxes. ReadOnly ComboBoxes To diagnose a ComboBox ReadOnly property-setting bug, I took the standard approach of first setting a breakpoint on the line of code that set the property so that I could make sure the code was executing. If this had been in Visual FoxPro 6.0 or later, I could have used the Coverage Profiler, but that wouldn’t have given me the complete picture, and would have been overkill for this task. I set the breakpoint and ran the code. I confirmed that the line ran and that the ReadOnly property was set. And yet, the ComboBox wasn’t ReadOnly! You likely know the reason, but it wasn’t obvious to me at the time, partly because the ComboBox also happened to be in a grid, which is a fact that distracted me into taking several unrelated paths of investigation. Finally, I tried my code with a simple ComboBox on a simple form. I discovered the problem remained and thus was unrelated to the grid. For once I couldn’t blame the poor grid. Once I determined this, I finally took the all-important first step when diagnosing a problem with a control’s behavior: I read the manual, and discovered that the property has no effect on list-style ComboBoxes. Bold TextBox In another example of diagnosing a logical error in a control, a developer asked in a newsgroup post why a TextBox wasn’t respecting a FontBold setting. Diagnosing this problem followed a standard path. First the developer posted the routine with the offending code. I asked the developer to confirm that the FontBold setting was actually running. After he confirmed the FontBold was being set, I asked him to post the facts about the control so that I could try to duplicate the problem in a simple environment. Once I could duplicate the problem, the developer and I worked out a solution. The following code duplicates the problem. PUBLIC oform1 oform1=NEWOBJECT("form1") oform1.Show RETURN DEFINE CLASS form1 AS form Height = 84 Width = 104 Caption = "Form1" Name = "Form1" ADD OBJECT text1 AS textbox WITH ; FontSize = 7, ; Value = "Foo", ; Width = 100, ; Name = "Text1"

84

Debugging Visual FoxPro Applications

ADD OBJECT command1 AS commandbutton WITH ; Top = 29, ; Caption = "Toggle Bold", ; Name = "Command1" PROCEDURE command1.Click WITH THIS.PARENT .Text1.FontBold = !.Text1.FontBold ENDWITH ENDPROC ENDDEFINE

In this example of a TextBox FontBold having apparently no effect, the developer and I discovered that some fonts don’t display bold in point sizes less than 8-point. Once I had a simple, reproducible example of the problem, I next looked at what was different between the problem TextBox and normal TextBoxes. That difference was FontSize. From there it was simple to uncover the problem with FontSize. The developer had to choose between changing the font to one that could correctly display bold with FontSize 7, or he could set the FontSize to 8 or higher. I’m happy to report that he chose the latter, kinder option. This answer raised another question, of course. What about other fonts? Once again I set the FontSize back to 7 and tried different fonts, and that’s how I found that some fonts would display bold correctly with FontSize 7. Notice that if I had changed two variables, both the font and size, I might have “fixed” the problem, but I wouldn’t have known which change corrected it. In this case, each independently will affect the behavior. Commands Another common mistake is to reverse the order of operands for the $ operator. I am somewhat dyslexic, and since I know I often confuse the order, I always check the manual before I use it.

*

Another solution to idiosyncratic bugs is to use alternatives. There is a cliché in the FoxPro community that there are at least three ways to do anything. So, instead of using something like lcAnswer$"Y,y,N,n,T,t,F,f", I chose to use the INLIST() function, which does not give me the same trouble.

Another problematic function is AT() and other functions that take two strings as arguments. It’s sometimes difficult to remember whether the “searched in” or “searched for” parameter goes first. Does ACOPY return the copy of the array or the number of array elements copied? When a programming language is new and unfamiliar, spend a lot of time in the documentation for functions. IntelliSense, new in Visual FoxPro 7.0, is a convenient reminder for not only correct syntax, but also seldom used options. Often, logical programming errors result from a lack of experience with the control or command. Even experienced programmers need to review the documentation for controls if versions of Visual FoxPro have changed—for example, Visual FoxPro 7.0 added an nTimeout parameter to the MESSAGEBOX function. Even when I’ve used a command or a control for some time, since I usually use controls and commands in the same way, sometimes after I review a Help entry I’ll find a different feature of the control or command.

Chapter 5: A Taxonomy of Common Visual FoxPro Bugs

85

In any case, browsing the Help file and reading about commands and controls can be an eyeopening experience. The technique Bugs that result from mistakes in using controls are diagnosed using the same debugging technique that works well for diagnosing undesirable behavior, as described in the section “Passing variables by reference or value.” Preventative debugging is also helpful here, and that means, simply, know the tool. The more experience you have using a language and the tools and controls in the language, the better able you are to use the language correctly, and to understand its foibles. When you use a new control or technique, study it first in the program documentation and in the MSDN Library. Regularly read newsgroups to familiarize yourself with common issues and possible solutions.

Performance Performance-related bugs are related to either data or the time it takes the program to execute some routine. Unacceptable performance is a subjective evaluation that can sorely impact an otherwise good application.

Data performance Acceptable data performance is a result of design since it involves identifying the likely size of the application’s database, data back end technology, and strategy—will data be accessed through Xbase commands or SQL-Select statements?—and tactics. For example, what indices will be maintained? Indices should capture the most common ways of slicing the data, but there shouldn’t be so many that, in the case of hundreds of thousands of records, data access is slowed by having to maintain many large index files. Will there be many deleted records? In this case an index on DELETED() may be required. Will your environment use SET DELETED ON? In this case a DELETED() index can impact data performance by limiting Rushmore optimization to partial optimization levels—Rushmore will use the deleted tag for SQL-Select statements. Unacceptable data performance is identified by slow-loading forms, unresponsive grids that take a long time to scroll, and long response times updating data. The bug is due to nonoptimized data access or to not limiting the displayed data to a reasonably sized subset of data. The latter case is a design bug, and requires rethinking the approach. For example, if an application has a large customer base, you might be able to display a grid of customers who are ordered by ZIP code, or province. Since Visual FoxPro specializes in fast data, poor performance can be especially troubling. However, the good news is that, since Visual FoxPro has a fast data engine, data performance bugs can usually be resolved. Bugs related to data performance can be generally subclassed into non-optimizable or non-optimized data strategies. Poor data performance in the case of SQL-Select statements can be debugged, in the common sense, by using the SYS(3054)— Rushmore Optimization Level—function for SQL-Select statements. This tool will identify SQL-Select commands that aren’t optimized. Once a SQL-Select statement is fully optimized, if the problem remains, then the data design must be called into question. SYS(3054)will not

86

Debugging Visual FoxPro Applications

help debug Xbase data performance bugs. In this case the best debugging strategy is, again, preventive—understand the tool. Diagnosing performance bottlenecks in remote data is particularly troublesome to track down and requires knowledge of the remote data tool by either you, or the people responsible for maintaining the remote data.

Code Symptoms of code that takes an unacceptable amount of time to execute are fairly obvious, and any impact is more or less critical, depending on the system. If the system tracks timesensitive information, runs real-time systems, or manages intense data entry and updating, then performance standards will be higher than they might be for applications that track relatively static data, or have few users. In any case of code performance bugs, use the Coverage Profiler, discussed in Chapter 4 (“Visual FoxPro Debugging Tools”), to identify bottlenecks. The Coverage Profiler will identify the parts of the code that require the most execution time, so that you can better concentrate your coding efforts on optimizing those areas.

System bugs Some bugs aren’t bugs in an application at all. These bugs are determined by first eliminating other possibilities. Bugs can occur in the way the application interacts with the environment. There are probably as many different bugs as there are combinations of networks, hardware, operating systems, DLL versions, and virus checkers. There are some common symptoms of system bugs. These errors are usually fatal and sometimes result in general protection fault— C0000005—errors, or the errors might be a sudden loss of a data connection. In the case of chronic C0000005 errors that occur in distributed applications, the first line of debugging should probably be to look up the error in the Microsoft Developer Network (MSDN) Knowledge Base (KB). If the error occurs always while printing, or on one user’s computer, and that user has a different hardware configuration than another user who doesn’t have the problem, then make sure the user has updated drivers for her monitor, printer, and other devices. A rare, but not unusual, occurrence reported on the newsgroups is occasional and random errors accessing data in forms that are normally stable and that can’t be tracked down to any programming logic or syntax error. In this case a candidate cause is the network stability. Operating system, resources, hardware incompatibilities, and resource incompatibilities can also be culprits. These errors are dependent on versions and installed software, and so each piece must be evaluated on its own merits.

The technique While the literature on the subject of debugging techniques fairly well agrees that the first place to look for a bug’s cause is in the application being debugged, it does happen that other tools or environmental conditions might be a cause. In these cases, if you suspect a graphics driver in the case of display anomalies, or a printer driver when printing a report, then first

Chapter 5: A Taxonomy of Common Visual FoxPro Bugs

87

make sure the installed drivers are the latest drivers from the vendor. Next, search for a general description of the error on the vendor’s newsgroup or Web site and in the MSDN Library. Next, search for the problem on the Visual FoxPro newsgroups, and ask about the behavior in a local user group. Generally, professional peers will want to verify that all software causes have been eliminated from consideration first, unless the problem is obvious to someone who has experienced it.

Deployment Deployment bugs include those bugs that result from distributing incorrect versions of application elements or failing to distribute needed components. While it’s possible to distribute an application without using an installation tool such as the Setup Wizard in version 6.0 and earlier, InstallShield, or Wise, it’s not advisable. Installation tools bundle the controls your application uses, install the application with the correct subdirectory structure, and register ActiveX and OCX files for you.

Maintenance bugs Maintenance bugs are highly subjective bugs and might arguably be considered not bugs at all. I mention them because of the part of the definition of bugs that it’s some failure of the software to perform in an expected manner. Sometimes future needs can be anticipated as being so likely that they are incorporated into the requirements specification and design, at least as stubs for future development. An example is a retail store that does business in a U.S. state that collects sales tax. Governing bodies are prone to change the amount of tax, certainly, and they are likely to change the way that the taxes are allocated. Specifically, different tax rates might be set for different items. An experienced designer will recognize areas of a system’s domain that are likely to fluctuate and even the ways in which they might fluctuate.

Effective debugging Effective debugging involves several components: Identify and locate the source of a behavior that is a problem; determine whether the behavior is a bug or is a symptom of a bug; decide what kind of bug it might be; locate the source of the bug separate from the symptom; and then, finally, identify the possible corrections that will fix the bug. Actually fixing a bug is a matter of coding that is little different from normal coding. The difference is that the code must be implemented and tested with no other changes to be assured a solution is correct and sufficient.

Conclusion In this chapter I discussed types of bugs and gave common examples of bugs and their solutions with a description of the debugging tools that can aid in diagnostics. Good coding practices and preventative debugging, such as learning, practice, code analysis, and testing, go a long way toward minimizing time spent debugging. Debugging time is minimized because

88

Debugging Visual FoxPro Applications

less time is spent fielding bug reports from users and identifying sources of the problem. Once code has grown “cold,” understanding the current state of the code as well as what impact any change will have is far more difficult than when the code was fresh in mind. The more familiar the code is, the easier it is to identify the cause of a problem and to resist coding around a problem or treating just a symptom.

Chapter 6: Fitting into Enterprise Solutions

89

Chapter 6 Fitting into Enterprise Solutions In the previous chapters I write about the task of debugging as it would apply to a straightforward, traditional Visual FoxPro application. The model is a multi-user, all Visual FoxPro application that resides on a network, for example. A significant amount of work has been done for years on enterprise-wide, distributed applications that integrate several tools and are integrated into other systems. FoxPro has held a special place as an all-in-one solution that allowed its practitioners to build and deploy robust applications that were 100% native FoxPro code. Increasingly, even the independent consultant is seeing a change in the business model to progressively more complex systems made up of components that may be widely distributed across the Internet or intranets.

In an increasingly connected world, there is a growing issue of debugging across boundaries. Whenever a Visual FoxPro application extends itself beyond the application boundary— beyond all native, built-from-scratch code—debugging becomes a more complicated process. Examples of this include using ActiveX or OLE controls, Web Services, Automation servers, and Windows API functions. For example, you might use an ActiveX control that a Visual Basic colleague wrote, and the control might have an error in its code. Unless the control returns a meaningful error message, it can be very difficult to pin down the source of the problem. Sometimes it’s simply tricky to understand the correct use of an ActiveX or OLE control. Building and using Automation servers is a similar issue, but may be complicated further by remote distribution issues. Publishing Web Services may require that you debug not only your FoxPro code that comprises your server, but also problems arising from interacting with Internet Information Services (IIS) or the SOAP Toolkit from Microsoft. Programming that includes any of these aspects requires a certain background in each subject that is significant—however, using ActiveX and OLE controls is fairly common. In any case, it is not my intention to go into detail of debugging these processes since each would require a lengthy discussion of the technology first. However, there are common bugs that arise when using these technologies. I touch on these here. I also point you to resources on each topic for further, detailed, information. I’m a proponent of subscribing to the Microsoft Developer Network (MSDN) Library1. While it is available online for free, there is usually a substantial rebate for owners of Visual FoxPro. MSDN comes out quarterly2 with documentation on the range of Microsoft products and a Knowledge Base. It’s an invaluable resource for not only FoxPro but also the other tools with which application developers have to interact. It comes on DVD or CD

1

As of this writing, MSDN subscription information can be found at http://msdn.microsoft.com/subscriptions/prodinfo/pricing.asp#lib. 2 As I write this, I’m using the October 2001 edition of the MSDN Library.

90

Debugging Visual FoxPro Applications

so it’s easy to carry along on travel. The library is also available online at http://msdn.microsoft.com/library/. I refer to the MSDN Library in this chapter.

Using ActiveX and OLE controls It’s common for Visual FoxPro applications to use a third-party ActiveX or OLE control such as a file compression utility or an enhanced grid. There are a number of Microsoft common controls, too, that are available to Visual FoxPro programmers, such as a TreeView control. Visual FoxPro cannot be used to create ActiveX or OLE controls, so debugging is limited to the client, which is your Visual FoxPro application. As with all programming, preventing bugs from occurring is easier than finding and fixing them. However, it’s not always possible to do this. Common issues that arise from using controls are: •

COM errors are often notably uninformative, as the example in Figure 1 shows.

Figure 1. OLE error example. •

The control is often poorly documented, so it’s difficult to know the correct use.



Controls may not be distributed with your setup, or they may fail to register on the end user’s system.



ActiveX and OLE controls in Visual FoxPro are wrapped in an OLE container, which means that you sometimes need to drill down one more layer to access the control’s properties, events, or methods.



Sometimes only Visual Basic examples are provided, or the Visual FoxPro examples are written by non-Visual FoxPro developers.



Many controls are tested primarily in Visual Basic, and there are differences in the way COM is implemented in each language. There is just enough difference that it can be a problem if the vendor isn’t VFP-aware. Even then, it’s still sometimes a problem.

How can you address these areas—areas over which you may have limited control? It seems that there are a fair number of tried and tested controls in common use in the community. Use well documented controls that are in common use, whenever possible. Your use of the control will be more robust if you can rely on the experience of others in the case of implementation questions. Check the online resources mentioned in Appendix A for samples and frequently asked questions (FAQs). Check the MSDN Knowledge Base for articles on the

Chapter 6: Fitting into Enterprise Solutions

91

control before you begin. Sometimes it’s tricky to find the Help file for a particular control. I’ve often found it easiest to first try the context menu for a control-specific event in the designer’s property sheet, as shown in Figure 2. Alternatively, you can right-click on the control itself and select the control-specific Help from the context menu, if one is available. This assumes the control’s vendor has included Help.

Figure 2. Accessing OLE control Help from the Properties window. This is often the easiest way of opening the Help for a control. Notice that in Visual FoxPro 7.0, the names of properties, methods, and events that are specific to the ActiveX or OLE control are highlighted so it’s easy to tell them apart from the members that belong to the OLE container. Do read the documentation for the control if only to understand its basic functionality. In the case of using an ActiveX or COM control, reading the documentation is crucial since you won’t be able to step through the code of the object as you can through native Visual FoxPro code over which you have control. Many third-party controls are available for evaluation. An evaluation copy will help you determine whether the control is usable for your purpose. It’s doubly important if you have to use an unusual, uncommon, or custom control. Try a new control, or a new use of a control, in a simplified test environment designed to implement just the control. First, just drop it on a form, for example, and run the form. Then incrementally program the interface so you know immediately if some action has broken the implementation. Take advantage of how easy it is to test code in steps in Visual FoxPro. Implement one piece of functionality at a time in order to narrow down the likely source of any cryptic OLE error. Once you’re confident the control is going to work for your application, consider saving it as a class. Finish implementing the functionality in the class. It’s not necessary to do this, but I find that since I have to take an extra step to modify the control’s functionality, I’m less likely to change something inadvertently or haphazardly and forget to test it immediately.

92

Debugging Visual FoxPro Applications If you are coding and running classes, you may have to issue CLEAR ALL from the command window before you can edit again.

Sometimes the only available documentation uses Visual Basic for samples, or the Visual FoxPro documentation isn’t adequate (sometimes Visual FoxPro samples are written by nonVisual FoxPro programmers). When the documentation isn’t adequate, rely on the FoxPro community for help translating the Visual Basic code. Many Fox programmers also use Visual Basic and can help translate. If you develop a better or additional FoxPro sample, consider putting it in the public domain. For example, the Universal Thread has a downloadable files section, as does the Virtual FoxPro User Group (VFUG). The targeted audience for a control is often the Visual Basic community. I found one simple feature of Visual Basic particularly confusing when I first started translating examples. Visual Basic (version 6.0 or earlier) has a concept of a default property or procedure. In other words, if a Visual Basic object is referenced without specifying a particular member (procedure or property), then the member that is designated as the default will be executed (method) or used (property). Microsoft Knowledge Base article Q193022 explains it like this: “What a default Procedure ID setting means is that the default method or property will be executed when no specific member is specified.” The effect is when you see a VB example that looks like the following: oControl[1] = "Foo"

it is likely that the Items collection is being referred to implicitly. If you translate that directly in Visual FoxPro code, you’ll get an error. Instead, try the following: oControl.items[1] = "Foo"

It might be apparent from the context what the default property is, whether it’s a collection or caption, or text, for example. At the very least, you will know to ask the vendor what the default property is for the control so that you can explicitly refer to it. Another difference between Visual Basic and Visual FoxPro is in how containers are implemented. I erroneously assumed that the component object model was a strict specification, but that is not the case. Containers, in particular, are handled differently between the two languages. As a result, I’ve seen cases where the container of a control isn’t visible during design time, but is during run time. This makes debugging the user interface, to extend the process of debugging just a bit, difficult. I simply add a shape to my control’s subclass and set the Visible property to .F. so that it’s visible during design time. One last difference between Visual Basic and Visual FoxPro that can catch people unaware is that Visual Basic allows for, in our terms, an event and a property to be named the same. This isn’t legal in Visual FoxPro, of course, and so if you need to use either in a control, you would have to create a wrapper in Visual Basic that you can use in Visual FoxPro. In addition to the differences between consumers of ActiveX and OLE controls, controls are hosted on a Visual FoxPro form in a container. Therefore, when you refer to a control on a form as ThisForm.OLEControl1, you are really talking to the container for the control. It often doesn’t matter. However, while debugging, or in programmatically accessing the control, it may be necessary to refer to the container’s Object property. Figure 3 shows an example of

Chapter 6: Fitting into Enterprise Solutions

93

how the Object property looks in the debugger. For the example, I dropped a TreeView control on a plain form.

Figure 3. ActiveX/OLE control’s Object property. Notice that temp.olecontrol1.object.appearance and temp.olecontrol1.appearance refer to the same property even though Appearance isn’t really a member of the OLE container control, as evidenced in the locals window. If you are referring to properties directly in code along the lines of the second example, and if you get an error accessing a member of the control, then try inserting the Object property in the reference hierarchy. Once a control is working in your application, the next problem that may occur is that when a user installs your application, the control may not have been distributed with the setup or registered properly on the user’s computer. An example of such an error is shown in Figure 4.

94

Debugging Visual FoxPro Applications

Figure 4. OLE error code “Class not registered.” First, the distribution files must include the control, and, second, the control must be registered on the end user’s system. How the control is included with the setup depends on what you’re using to build your distribution. You must add the OCX or DLL that contains the control to your installation set. Visual FoxPro 7.0 comes with InstallShield Express— Visual FoxPro Limited Edition. Add the control in Files under Specify Application Data in InstallShield. Right-click the file, and select Properties and the Advanced tab to change the way the control is installed on the end user’s computer (see Figure 5).

Figure 5. Including components in Visual FoxPro 7.0 InstallShield Express—Visual FoxPro Limited Edition.

Chapter 6: Fitting into Enterprise Solutions

95

In Visual FoxPro 6.0, copy the control into the distribution directory. Then, during setup, when the list of files that will be included is displayed, mark the OCX or DLL as an ActiveX control (the system may do this for you automatically) and select the target directory (see Figure 6).

Figure 6. Including components in Visual FoxPro 6.0—Setup Wizard. If the control has been distributed, it may not have registered successfully on the end user’s computer, in which case you can try to register it manually. An example is shown in Figure 7.

Figure 7. Manually register a control. Register the OCX or DLL that contains the control. MSCOMCT2.OCX actually contains a number of controls. If this fails, then the control probably has a dependency on another file, or files, that either haven’t been included in the distribution, failed to register correctly themselves, or were

96

Debugging Visual FoxPro Applications

registered in the wrong order. This is a particularly frustrating problem to diagnose, unless you have access to the dependency file for the control. Dependency files are usually named similarly to the control with a .DEP extension. For example, the hierarchical flex grid control file is MSHFLXGD.OCX. Its dependency file is MSHFLXGD.DEP. The dependency file indicates that the control depends on COMCAT.DLL and MSSTDFMT.DLL, as shown in the following snippet from MSHFLXGD.DEP3: ; Default Dependencies ---------------------------------------------[MSHFlxGd.ocx] Dest=$(WinSysPath) Register=$(DLLSelfRegister) Version=6.0.88.4 Uses1=ComCat.dll Uses2=MSStdFmt.dll Uses3= CABFileName=MSHFlxGd.cab CABDefaultURL=http://activex.microsoft.com/controls/vb6 CABINFFile=MSHFlxGd.inf

If manual registration of a control fails, try to locate the dependency information for the control and verify that you are distributing each file and those, in turn, are being registered. In the preceding example, MSSTDFMT.DLL also has a dependency file that refers to COMCAT.DLL. Therefore, you may need to check the dependencies for any files upon which your control depends. Finally, if you manually register the controls, try to register them in the same order in which they appear in the dependency file. COM support has continued to improve as Visual FoxPro versions have been released. None of these issues should dissuade a developer from using ActiveX or OLE controls in Visual FoxPro applications.

Using Automation servers Automation servers are applications with a COM interface that allows them to be used by (automated by) other applications. For example, if you’ve ever created a Word document, inserted some text, and then saved it from a Visual FoxPro application, your Visual FoxPro application was a client to Word, which is an Automation server. The most common case I see in the newsgroups is automating the Microsoft Office family of products. However, there are developers who work with Lotus Notes, WordPerfect, and other non-Microsoft applications. There is an inherent difficulty when an application needs to use third-party tools: It can be difficult to narrow down on which side of the wall the problem is, and it can be difficult to decipher the error an Automation server returns to the client. COM includes a specification for COM error reporting using HRESULT, which is an internal structure comprising information about the severity of the error, the status code, and the group of codes to which the error belongs. By the time the error is relayed to the programmer or user of a Visual FoxPro application, it may be unclear what, exactly, went wrong, and where. 3

The files I refer to are in C:\WINNT\SYSTEM32 on my system. MSHFLXGD.OCX is dated 10/10/2000. MSHFLXGD.DEP is dated 3/13/2000. MSSTDFMT.DLL is dated 10/10/2000. COMCAT.DLL is dated 5/4/2001.

Chapter 6: Fitting into Enterprise Solutions

97

As with ActiveX and OLE controls, rarely are Automation server examples written in Visual FoxPro. Both Microsoft Word and Microsoft Excel allow developers to record macros that perform the steps they want to automate in Visual FoxPro. Macrocode, which is written in Visual Basic for Applications (VBA), is available then and the code can be translated into Visual FoxPro equivalents. However, it’s sometimes easier in principle than in practice. Microsoft Knowledge Base article “HOWTO: Convert VBA to FoxPro for OLE Automation” (Q160064) discusses the subject. In addition, Microsoft Office Automation with Visual FoxPro by Tamar E. Granor and Della Martin (Hentzenwerke Publishing) is a useful resource for anyone who is automating Microsoft Office applications. The MSDN Library includes all of the VBA functions for the Office products with examples.

Building Automation servers Not only can Visual FoxPro applications use Automation servers, they can also be Automation servers, either in-process (DLL extension) or out-of-process (EXE extension). Building Automation servers involves writing Visual FoxPro code, so debugging can be done normally within Visual FoxPro at the most basic level. While all testing and debugging should be thorough, it’s doubly so in this case since one of the main points of building a COM object is to reuse the component many times under different circumstances, and because it’s simply harder to do once the component is distributed. Let’s look at what happens when a component throws an error. The error will be passed back through the Windows interface to the client. For example, I created a simple DLL from the following class: *!* From Temp.VCX DEFINE CLASS com1 AS custom OLEPUBLIC Name = "com1" PROCEDURE Init ?"Hello, Universe" ENDPROC PROCEDURE causeerror ERROR 1526 ENDPROC ENDDEFINE

When I use this component like so: LOCAL lo AS temp.com1 lo = NEWOBJECT('temp.com1') lo.CauseError()

having compiled the COM object with Debug Info unchecked, I see the error message in Figure 8 displayed.

98

Debugging Visual FoxPro Applications

Figure 8. VFP component error. The Visual FoxPro COM object is passing back the message through HRESULT to the client, which displays it. Microsoft Knowledge Base article “HOWTO: Set Up OLE Automation in Visual FoxPro” (Q156014) is a resource for learning more about this subject. There are additional debugging issues if the Automation server will be remotely distributed. Whether or not the server will be deployed remotely, the Microsoft Knowledge Base article “HOWTO: Debug Remote Automation Servers in Visual FoxPro” (Q157049) is a good resource for debugging Automation servers and common bugs.

Windows API Where component architecture helps developers interact on the macro level with sophisticated capability encapsulated in applications and components, the Windows API lets developers interact on a micro level with the system. For a long time, I resisted using Windows API calls, partially because FoxPro does so much that it was often simply not necessary, partially because FoxPro developers have been generous making API snippets available in the public domain, and partially because it just seemed vaguely mysterious. After using some public domain utilities and reading the API-related posts and FAQs by Ed Rauh, George Tasker, Cristof Lange, and others, I worked up the nerve to tackle one on my own. I immediately ran into bugs that were a bit of a puzzle to untangle. I found I have three common problems with Windows API calls: understanding the correct data and types to pass to the function, translating C++ and Visual Basic samples into Visual FoxPro, and remembering that case sensitivity matters for WinAPI calls. Once these pieces are correct, I find the API, generally, is far more approachable than I once thought. There are pitfalls to be aware of, certainly. Another issue to be aware of is that not all data types used by API functions have one-toone equivalents in Visual FoxPro. Ed Rauh said in a Universal Thread Wednesday Night Lecture, “API calls have a much broader range of basic data types, and are commonly linked together into a complex data type which is a composite of basic data types, called a structure. You can think of simple structures in a similar fashion to how VFP defines a record.” It’s possible to use API functions that require the use of structures, but we have to do some special work. With that in mind, I’ll look at what an API call gone bad might look like. The first tricky bit about using the Windows API is finding out about the functions. You might have guessed that the MSDN Library is where I look for information on the API. The API documentation is included in the platform software development kit (SDK) documentation. The Windows API section of the MSDN has an overview that includes, for example, information on the file system, the Registry, and the graphics device interface. The

Chapter 6: Fitting into Enterprise Solutions

99

most interesting section, for our purpose here, is the reference, which includes a list of Windows data types and a breakdown of functions by category, alphabetical order, and release (the version of Windows in which the API function first appeared). Once you’ve located the function you want, there is the task of implementing it. Assuming you’re working from the platform SDK documentation, you have samples in C++ and Visual Basic. An example of a Windows API function is GetTempFileName. Here is the C++ example: UINT GetTempFileName( LPCTSTR lpPathName, LPCTSTR lpPrefixString, UINT uUnique, LPTSTR lpTempFileName );

 *!* *!* *!* *!* *!*

// // // //

directory name file name prefix integer file name buffer

The following is an example of the DECLARE – DLL syntax and of using it in Visual FoxPro—it’s available with the Developer Downloads at http://www.hentzenwerke.com.

This routine uses the Win32 API function GetTempFileName() to create a temporary filename that will be guaranteed to be unique for a specific directory. The routine creates a zero-byte file so that no other session will use the same filename. The file will have a .TMP extension, which can still be used for our FoxPro tables, for example.

*!* First declare the Win32 API DLL so that our program knows about it. *!* DECLARE LONG GetTempFileName IN "kernel32" ; STRING lpszPath,; STRING lpPrefixString,; LONG wUnique,; STRING lpTempFileName LOCAL lnReturn, lcTmpPath, lcTmpName, lcPrefix *!* The 3-character prefix for the file name lcPrefix = "TMP" *!* Initialize the temporary filename variable to a long string lcTmpName = SPACE(576) *!* What directory do we want the file to be created in? lcTmpPath = GETENV('TEMP') lnReturn = GetTempFileName( lcTmpPath, lcPrefix, 0, @lcTmpName ) lcTmpName = STRTRAN( lcTmpName, CHR(0), '' ) ?lcTmpName

Leaving aside the C++ syntax and an exhaustive discussion of the DECLARE syntax, I’ll point out some notable items in the example. First, and easiest, note that the case of the function name matters in the DECLARE statement. If the case is incorrectly typed, then the error shown in Figure 9 will be thrown.

100

Debugging Visual FoxPro Applications

Figure 9. DLL error—case-sensitive function name. Once it’s been declared, the API function will remain available in memory until the program terminates, or a CLEAR ALL or CLEAR DLLS issued. Issue DISPLAY DLLS to see what API functions are loaded. It’s not necessary, however, to CLEAR DLLs before issuing the DECLARE – DLL command. Second, declaring the wrong type of parameters or function return will cause the call to the function, not the DECLARE, to fail with an error. If the function is called with the incorrect number of parameters, the function will fail also. Errors range from “Data type mismatch” to “Declare DLL call caused an exception” to general protection faults. The Platform SDK contains a topic called “Windows Data Types” with an explanation of Windows data types. The Visual FoxPro DECLARE – DLL Help topic contains the Visual FoxPro data type keywords, which can be cross-referenced to the Windows data types. Third, I initialize lcTmpName as a 576-character string before I pass it by reference to the API function. This variable will be populated with a temporary file name. If the variable is passed without being initialized, or if it is initialized to a too small size, then the function will probably cause a program exception; if not immediately, then downstream. I normally initialize strings to be at least 256 characters long. If a return value is longer than the passed variable’s initial value, then the result will be truncated. If the initial value is longer than the return value, the result will be trimmed. Fourth, the function returns lcTmpName as a null-terminated string, hence the CHR(0) transformation. Finally, and perhaps most significantly, Visual FoxPro doesn’t have direct equivalents for some of the types that API calls require—for example, structures and pointers into functions, otherwise known as a “callback.” There are ways around this fact, and there are utilities and FAQs in the public domain on this subject. David Frankenbach has several Windows APIrelated FAQ’s available on his Web site (http://www.geocities.com/ResearchTriangle/ 9834/mainfram.htm). qGEN003 specifically discusses structures. Cristof Lange wrote “Struct,” a class library used to manipulate and manage structures. As of this writing, it’s available on the ProLib Web site (ftp://ftp.prolib.de/public/vfp/struct.zip). Ed Rauh wrote CLSHeap, which is similar but a more low-level approach. It’s available from the Universal Thread downloads (http://www.universalthread.com). The Universal Thread also maintains a list of Windows API functions. Another list is the one Anatoliy Mogylevets maintains on his Web site (http://www.news2news.com/vfp/index.php). Since there are Visual FoxPro examples of using many API functions—certainly many of the most commonly required ones— consider looking for an existing FoxPro example first. And then feel free to pick one out from the API that isn’t online and write an example and add it to one of the fine lists.

Chapter 6: Fitting into Enterprise Solutions

101

Conclusion The Windows API and ActiveX are two common ways Visual FoxPro applications can be extended. Visual FoxPro will continue to remain an excellent all-in-one tool combining a powerful design-time environment with a cost-effective, high-performance data engine and a stable language. As newer technologies such as Web Services become prevalent, however, our options for extending our applications will continue to grow. As these options grow, so will the complexity of ensuring that the applications we write are stable. One of the benefits to using component-based designs is that applications can, ideally, begin to seriously reap the benefit that reuse promises: Components and eventually the applications that comprise them will become increasingly stable. That said, the task of a developer essentially remains the same. Build applications and components using proven good coding techniques, and thoroughly test and debug work products in incremental and carefully controlled stages. The techniques we use today to build robust applications are based on sound reasoning that has been proven in many professions over time. They will still be useful even as the technology changes.

102

Debugging Visual FoxPro Applications

Appendix A: Additional Resources

103

Appendix A Additional Resources As I discussed in the introduction, this book addresses a practical approach to debugging during coding. Bugs can also be introduced at other stages of the development process, for example, during requirements gathering, modeling, designing, prototyping, and testing. While these processes are outside the scope of this book, they are related and so I’ve included a few resources for further study.

Visual FoxPro books •

Sawyer, Steve and Booth, Jim. Effective Techniques for Application Development with Visual FoxPro 6.0. Hentzenwerke Publishing, Milwaukee, WI, 1998.



Granor, Tamar E. and Roche, Ted. The Hacker’s Guide to Visual FoxPro 6.0. Hentzenwerke Publishing, Milwaukee, WI, 1998.



Granor, Tamar E.; Hennig, Doug; McNeish, Kevin. What’s New in Visual FoxPro 7.0. Hentzenwerke Publishing, Milwaukee, WI, 2001.

Debugging and good coding practices •

Telles, Matt and Hsieh, Yuan. The Science of Debugging. The Coriolis Group, Scottsdale, AZ, 2001.



McConnell, Steve. Code Complete. Microsoft Press, Redmond, WA, 1993.



Rosenberg, Jonathan B. How Debuggers Work: Algorithms, Data Structures, and Architecture. Wiley Computer Publishing, 1996.



Robbins, John. Debugging Applications. Microsoft Press, Redmond, WA, 2000.



Maquire, Steve. Writing Solid Code: Microsoft’s Techniques for Developing Bug-Free C Programs. Microsoft Press, Redmond, WA, 1993.



Mitchell, Will David. Debugging Java: Troubleshooting for Programmers. McGraw-Hill Professional Book Group, 2000.

Requirements gathering and design •

Robertson, Suzanne and Robertson, James. Mastering the Requirements Process. Addison Wesley Longman, Inc., 2000.

104

Debugging Visual FoxPro Applications •

Fowler, Martin and Scott, Kendall. UML Distilled: A Brief Guide to the Standard Object Modeling Language. Addison Wesley Longman, Inc., 1999.



Gamma, Erich; Vlissides, John; Johnson, Ralph; Helm, Richard. Design Patterns: Elements of Reusable Object Oriented Software. Addison Wesley Longman, Inc., 1994.



Booch, Grady. Object-Oriented Analysis and Design With Applications. Addison Wesley Longman, Inc., 1993.

Web resources •

http://fox.wikis.com



http://foxcentral.net



http://www.universalthread.com



http://msdn.microsoft.com/



http://c2.com/



http://www.sdmagazine.com/

Appendix B: What’s Behind the Curtain?

105

Appendix B What’s Behind the Curtain? Some of us live and die by the debugger. Others of us avoid it like the plague and rely solely on wait windows, message boxes, screen echoing, and, sometimes, wishful thinking. Those of us who were fortunate enough to have written debuggers for school or work appreciate them in a different way than mere mortals do. Most of us, though, run them, or avoid running them, without really understanding much about them. While understanding the details of how debuggers work is not necessary in order to effectively use one, it is interesting and can help us understand the sometimes-mysterious difference in behavior when an application is run under the debugger. This appendix gives a brief history of debuggers and discusses a select few types of debuggers.

As Jonathan B. Rosenberg says in How Debuggers Work, debuggers are not as well studied and discussed as compilers, but the developer spends far more time debugging than compiling. A debugger is an application, just as the Interactive Development Environment (IDE) and the code that’s being debugged (the target) are applications. Debuggers are somewhat misnamed because they’re useful even when you don’t necessarily have a problem, and because there are important debugging techniques that don’t rely on the debugger. For our purposes, a debugger is a specialized application or integrated tool that programmers use to methodically evaluate applications. In order to evaluate applications, programmers need to know facts about the applications such as the order in which events occur, the state of variables, the state of memory, and how well the code performs. We use debuggers to gather these facts. Even when the application is running in an unstable state to identify a bug, our application runs normally, more or less, and yet we can peek into it, poke it, tweak it, and execute whole portions of code while debugging. We can even write new procedures and force errors to occur. Sometimes, somewhat as a doctor or nurse performs triage and diagnosis prior to treatment, developers use the debugger to identify the source of, and possible solutions to, a problem prior to fixing it. Debuggers run the gamut from stand-alone to integrated, and from machine code representation to source code representation. For example, the Microsoft Visual FoxPro (VFP) debugger is integrated with the VFP IDE, whether it is run from within the VFP frame or from the separate Debug frame. In either case, the VFP debugger runs within the same process as the IDE and the target code, and it is used to debug only VFP components. That makes the VFP debugger a qualitatively different sort of debugger from WinDbg, kernel debuggers, and the Microsoft Visual C++ (VC) debugger, as Calvin Hsia, the Microsoft Visual FoxPro Lead, pointed out in a conversation. These debuggers are examples of stand-alone processes that are designed to debug other EXE processes. While a detailed discussion of all the different kinds of debuggers is a book in and of itself, a general discussion is included for those readers who are interested. Debuggers have evolved over time to continually improve on their basic function: to support programmers evaluating the correct running of an application or diagnosing problems with code. The original “debugger” was the programmer or engineer who “stepped” manually through a series of code instructions, looking for the source of an error, performance

106

Debugging Visual FoxPro Applications

bottleneck, or system crash. He might have had to literally pull apart the hardware to find the offending bug. The etymology of the word “bug” in this sense places its use to as long ago as 1896, where it is documented in Hawkin’s New Catechism of Electricity (Theo. Audel & Co.), which says, “The term ‘bug’ is used to a limited extent to designate any fault or trouble in the connections or working of electric apparatus.” The term “debugging,” however, was invented in Rear Admiral Grace Hopper’s oft-told story of the moth found in a computer relay. The daily logbook (see http://www.history.navy.mil/photos/images/h96000/h96566k.jpg) noted the “first actual case of [a] bug being found.” A problem occurred in a system, the system was examined, and the problem was identified and removed. This is the essence of debugging. Debuggers have grown with, though somewhat lagged behind, their platforms and languages, aiding programmer productivity and effectiveness. They have advanced from stand-alone, textbased, memory register dumps to integrated (in some cases) and visual tools with sophisticated breaking, tracing, profiling, and other diagnostic features. The first computer debuggers (vs. human debuggers) started out as crude procedures for dumping raw data or as statements sent to printers. Once a bug was found, a programmer tediously stepped through the code to identify the problem. In punch card days, a programmer was lucky if her program was run the same day she submitted her cards. Programmers methodically hand wrote and then hand ran their code by writing a log of variables, setting starting values. Then, as the programmer “ran” each line of code, she manually changed the values. Only after this dogged investigation was the program run. Output listings would come back with errors, and the code would then be tediously retraced to identify the source of the errors. It was difficult and expensive, because of the cost of computer time, to determine where a problem really was and then to determine which of the possible fixes was “best” and would not cause more problems downstream. Later, even though computing time costs dropped through the floor, newer generations of languages had long link and compile times. Because of the cost and difficulty, programmers had to give as much thought to what could go wrong with each line of code as to the line of code itself. When there was an error, it was imperative for programmers to gather as many facts about an error as possible before proceeding to correct it. The advent of command line editors with interactive displays made it possible to see the effects of a program’s execution directly and immediately. As Matt Telles and Yuan Hsieh say in The Science of Debugging, a book that discusses the algorithms and architecture of debuggers, assembly language programmers could use these debuggers to “look directly at the registers and the memory blocks that contained the addresses of the local variables in the program… these programs represented the first real attempt to turn debugging from a hit-ormiss proposition into a reproducible process.” As we know from the scientific process, reproducibility is key to debugging. Telles and Hsieh go on to mention that it was compiler vendors who first realized that source code in high-level languages could be stored and mapped such that programmers could examine their variables by name. Thus was born the first symbolic debuggers. In order for this to be possible, a version of the program containing extra debugging information must be available. Hsia describes it for Visual C++ as “Information such as how to translate the Assembler Source Language (ASM) code that’s running into the source C or C++ code. For VFP, the extra information is the source code from the class methods, for example.” It was at this time that breakpoints became possible, because instructions to break at a line of code could be written into the compiled code. This was critical for advancing the rationality of debugging because sometimes the point at which the program

Appendix B: What’s Behind the Curtain?

107

errs is not necessarily the source of the problem, but only a symptom. Without breakpoints and the ability to step through code, it would be much more difficult to understand how a program got into the state that threw an error. Especially with event-driven and object-oriented programming (OOP), it would be impossible to trace even some of an application’s paths, much less all of them. As time went on, other features that we now take for granted were added, such as the ability to have watch points and conditional breakpoints. The first visual debugger was for Turbo Pascal (Telles and Hsieh). Microsoft Quick Basic also included an early visual debugger. It was fun to be able to write code, run it, and see immediate feedback. This made it possible to try alternate approaches and test the best one for speed, or size (when size still mattered), or just for experimentation. The introduction of Windows and other multithreaded, multitasking operating systems (OS) with their GUI interfaces and alternative input devices, such as mice, forced new requirements onto debuggers for at least two reasons. Developers wanted debuggers with an easy-to-use interface, consistent with the programming IDE and the flexibility of working visually. In the case of the IDE, this means using form designers and builders. In the case of debuggers, it means being able to set breakpoints, for example, directly in our code. We also wanted to be able to see both our application and the debugging information running side-by-side. More importantly, debuggers needed to handle screen painting, interaction between multiple windows, arbitrary order of events, and mouse input in addition to keyboard input. Integrated, application-level debuggers are arguably better than stand-alone, for many reasons. However, stand-alone debuggers generally affect their target less and are affected less by the environment. Integrated debuggers operate in the same process space as the target and so can affect how the target runs. However, it would be worse to do without the powerful, flexible, and efficient tools that integrated debuggers have developed into over time. Today, debuggers allow us to control the execution of our code. They let us set breakpoints, step through or skip over lines of code, and set watch points on variables, expressions, and even functions. They display context information such as call stack, debug messages, and state. Some debuggers allow you to edit the source code and continue running the code. VFP doesn’t allow this, but it does allow you to, for example, write a new program and run its code while the original target is halted. And we can see all of this happen just as a user would see the application run through the same paces. Indeed, it’s not an overstatement to say that, without sophisticated debugging tools, eventdriven and object-oriented programming would not be feasible. While the OOP model leads us to build relatively small units of code that can be hand tested, the interaction of application pieces still frequently needs to be understood, and, since nobody writes perfect code and bugs do happen, our OOP code will sometimes need debugging, too. The continual improvement of debuggers is non-trivial and should be, arguably, as important an area for requested improvements as is the core language itself. We improve our techniques of debugging if we make good use of the different views into the operation of our code. Armed with several debugging options, we can choose the correct tool for a particular problem. For example, if a table is accessed and you think that the table should be open, a watch point on !USED('MyTable') can identify precisely where the table is being closed—once confirmed that it was open in the first place, of course. Another example is using coverage profiling to diagnose performance bottlenecks. By using the Coverage Profiler,

108

Debugging Visual FoxPro Applications

we can focus on solutions that demonstrably relieve the bottleneck instead of guessing which lines of code are the cause. For example, a particular routine might be tuned so that it takes 0.4 second rather than 0.5 second, a mere tenth of second difference. However, if that code is called in a loop that executes thousands of times every few minutes, it’s more important to tune than code that takes 45 seconds to run but only runs once a week. There are other kinds of debuggers to mention, although they do not apply to the spirit of this book. The VFP debugger is an example of an application-specific debugger. Other debuggers might interact directly with the OS or hardware. Kernel debuggers are an example, and they can be used to debug device drivers. In this case, debugging occurs between two computers, since a crash in the target will mean a crash in the OS. Kernel debuggers interact directly with the OS debug-related API functions that tell the debugger about the state of the process being debugged. Application-specific debuggers sit at the same level, generally, as the target application and either talk to the OS to get information about the application or monitor the application directly within the IDE. According to Jonathan B. Rosenberg in How Debuggers Work, incircuit emulators, on the other hand, sit between the hardware and OS. In-circuit emulators “watch and monitor all processes and all interactions between applications and the operating system. Typically these kinds of debuggers are lower-level and are used for development of add-on hardware or for very special types of heavily system interacting applications (such as debuggers themselves).” Real-time distributed systems are particularly difficult for debugging, as Tsai and Yang point out in “Monitoring and Debugging of Distributed Real-Time Systems” (QA 76.9.D5.M65, 1995, http://pbl.cc.gatech.edu/embed/28). “Real-Time systems seem to have a catch-22. They have hard timing constraints and performance requirements, but it is almost impossible to accurately measure whether a piece of code meets the timing constraints.” They go on to list the specific areas of difficulty: •

Continuous operation (prevents interactive debugging)



Stringent timing requirements



Asynchronous process interaction



Unpredictable communication delay and race conditions



Nondeterministic, nonrepeatable results



Global clock and global state



Multiple threads of process interaction

If one considers an abstract model of a debugging architecture, the computer’s hardware is the foundation, and then the OS lies on top of that. In-circuit emulators debug processes between these two layers. Between the OS and the next layer, the kernel, are the OS debug API. It’s at this level that real-time systems are debugged. On top of the kernel is the UI layer, which contains the debugging utilities such as source view, stack traces, breakpoints and conditional breakpoints, disassemblies, variables, and hardware registers (Rosenberg). Some of these are familiar to users of application-level debuggers. Debuggers might implement breakpoints by actually inserting code into the compiled code. A call stack view requires that

Appendix B: What’s Behind the Curtain?

109

the debugger keep track of all the lines of code executed. In debuggers that allow program execution to be run backward, the state of the program at each line also has to be saved. VFP’s debugger doesn’t store that level of state information, so if, for example, a variable used down in the call stack is out of scope in the currently executing line of code, its value is shown in the watch window, as “Expression could not be evaluated.” Some other debugging utilities are less familiar. The CPU view of debugging is important because, while it may appear to the programmer that the source code view is the accurate representation of what is running, there may be discrepancies. For example, compiler optimization may cause the compiled code to not match the source code view. In this case, the programmer may need the ability to see the exact instructions that implement the source code (Rosenberg). Disassembly is the process whereby machine code is disassembled into assembly language, and the representation is available for the programmer to review. How might VFP interact with other debuggers? Since the Windows API has functions designed to give feedback to applications and VFP can make use of the API, we can send information from a VFP application to the VC++ debugger. Hsia gave this example. 1.

On a machine with VC++ 6 or VC .NET, start VFP.

2.

Then, using VC .NET as an example, start Microsoft Visual Studio .NET.

3.

Select Tools | Debug Processes within the VC .NET IDE.

4.

Find the VFP process in the Available Processes list, and click on it.

5.

Click the Attach… button.

6.

Click Close in the “Attach to Process” window (the defaults are fine).

7.

Switch to the VFP task and create a program with the following code:

DECLARE integer OutputDebugString IN WIN32api string FOR I = 1 TO 10 OutputDebugString("Here I am "+ TRANSFORM(i)+CHR(13)+CHR(10)) ENDFOR

Now, with both windows visible, watch the “Output Window” of VC++ as you run the VFP program that you just created. The values in the FOR loop will be echoed to the VC++ output window. Next, try breaking the process (in the VC++ Processes window in VS .NET) and you should then see an option to view the disassembled source code. Unlike the VC++ debugger, the VFP debugger does not run as a separate process, and thus it has an impact on the program being debugged. A simple example is when a particular control doesn’t seem to refresh its values. One might set a breakpoint, intending to step through the code to determine where the problem is, and find, much to one’s chagrin, that the control refreshes just fine. When the debugger is activated at the breakpoint, events fire. The active control loses focus, the form loses focus, the form deactivates, and then the form’s paint event fires. Sometimes that’s just enough to alter the application’s behavior while we are debugging it. Another area where application-level debuggers can impact the target application is performance. For example, simply running the Coverage Profiler to find bottlenecks means that the target application will run more slowly overall. For that reason, the times reported by the

110

Debugging Visual FoxPro Applications

Coverage Profiler should be viewed as relative. If the absolute execution times are important to you, then you should leave the Coverage Profiler off and use benchmark code that tracks the starting time before the code executes (using SECONDS()), and the ending time when it completes. I discuss the interaction of the VFP debugger with target applications in Chapter 4 (“Visual FoxPro Debugging Tools”). In those few situations where the debugger negatively impacts running the target application, there are alternatives to the debugger. All told, however, the impact of the various ways in which the debugger interferes with the target is not significant enough to obviate its use. For our purpose, we can note that the VFP debugger is an integrated, application-level debugger. It doesn’t provide a disassembly view of our code, nor does it allow steps to be retraced. It’s different from a kernel debugger, for example, and yet it serves the same purpose all debuggers, ultimately, serve: It provides programmers multiple views into the operations of a system so that it can be examined and evaluated. The ability to accomplish this is dependent upon the debugger having information available about the source when the compiled or interpreted code is running so that it can map source to the running code, store information about register state, and so on. The exploitation of this connection between machine and source views to build debuggers that did more than simply dump register values was a significant step forward in the evolution of programming languages and has helped programmers keep pace with the increasing complexity of applications.

Index 111

Index Activate, 45 Activate debugger window, 57 ActiveX controls, 90 Ambiguous objects, 77 Analysis, 32 Anomalous behavior, 22 ASSERT, 38, 63 Auto Completion, 66 Automation servers, 96 Behavior, 22 Beta testing, 13 Black box testing, 12 Breakpoints, 52 Bug, 2 Bug type, 26 Building automation servers, 97 Call stack, 40 Call stack options, 37 Callback, 100 Cancel, 56 Chasing Bugs in the Electronic Village, 10 Class not registered error, 92 Code base versions, 18 Code Complete, 7, 12 Code reviews, 12 COM error reporting, 96 Comparison/assignment, 75 Configuration files, 54 Container, 92 Convert VBA to FoxPro for OLE Automation, 97 Coverage logging, 57 Coverage mode, 59 Custom error messages, 14 Cut and paste, 46 Data performance, 85 Deactivate, 45 Deactivate debugger window, 57 Debug frame, 37 Debug information, 41 Debug options, 35 Debug output window, 38 Debugging, 4 Debugging Applications: The Bugslayer's guide, DEBUGOUT, 38, 63

Decision, 32 DECLARE, 99 DECLARE DLL call caused an exception, 100 Defects, 2 Description, 23 Design phase, 6 Design-related bugs, 70 Diagnostics, 15 Documentation, 18 Documentation defect, 2 Dump member function, 15 Educated guess, 28 Enterprise solutions, 89 Environment, 36 Environmental variables, 82 Environmental views, 65 Error handling, 64 Evangelize, 19 Event tracking, 61 Event tracking results, 38 Expected behavior, 24 Experimentation, 30 Faults, 14 Fenton, Norman E., 7 Fonts and colors, 35 Fowler, Martin, 6 FoxPro frame, 36 Function design, reconsider, 47 General protection faults, 100 Gibbs, W. Wayt, 6 Glass box testing, 12 Gleick, James, 10 Goals, 7 How Debuggers Work, 105, 108 HRESULT, 96, 98 Hsieh, Yuan, 2 Hypothesis, 28 Implementation, 1 Implicit variable declaration, 73 Inquiry, 23 Integration testing, 12 Integrating Design & Automated Test Generation Investigation, 4 Issues, 2 Jeffries, Ron, 4

112

Debugging Visual FoxPro Applications

Kirda, Engin, 6 Kranzlmuller, Dieter, 14 List Members, 66 Locals window, 47 Lockscreen, 32 Logical bugs, 80 Looking, 28 Macro expansion, 79 Maintenance bugs, 87 Malicious compliance, 7 Mastering the Requirements Process, 70 McConnell, Steve, 7, 12 Measure twice, cut once, 17 MESSAGEBOX, 63 Minimal features list, 11 MSDN Library, 89, 98 Multiple occurrences, 16 Multiple RETURN statements, 75 Neil, Martin, 7 Observation, 22 Observed behavior, 24 OLE controls, 90 ON ERROR, 41 ON KEY LABEL, 36 One thing at a time, 14, 32 Optional parameters, 76 Output window options, 38 Pass count, 54 Passing variables by reference or value, 81 Prediction, 30 Priority, 26 Profile mode, 59 Programming errors, 2 Quick Info, 66 Repeated reliability, 23 Reproducibility, 106 Requirements gathering phase, 6 Requirements-related bugs, 69 Resume, 56 Reuse, 10 Risk assessment, 16 Risk management, 16 Robbins, John, 2, 22 Robertson, James, 70

Robertson, Suzanne, 70 Rosenberg, Jonathan B., 105, 108 Run To Cursor, 56 Rushmore optimization, 66 Science of Debugging, The, 2, 78 Scientific method, 21 SET ASSERTS, 32 SET CLASSLIB, 32 SET COVERAGE TO, 32 SET DELETED, 31 SET EVENTLIST TO, 63 SET EVENTTRACKING, 61 SET EXACT, 31 SET NEAR, 31 SET PATH, 32 SET STEP, 32 SET VIEW TO, 66 Smoke test, 11 Software metrics, 7 Software's Chronic Crisis, 6 Source code control, 18 Stakeholders, 1 STEP = ON, 37 Step Out, 56 Step Over, 56 Stepping, 56 Steps to reproduce, 23 Strategy, 21 Strong defense, 19 SUSPEND, 32, 63 Suspend conditon, 41 Syntax bugs, 78 Syntax errors, 79 SYS(3054), 66 System bugs, 86 System testing, 13 Tactics, 21 Task List, 13 Tells, Matt, 2 Template, 23, 27 Timer events, 35 Toolbar, debugger, 57 Trace window, 41 Trace window options, 37 TRBETWEEN, 37 Trigger, 4 TYPE(), 53 Typographic errors, 80

Index 113 UML Distilled, 6 Unit testing, 11 1 Unproductive debugging, 9 Variant variables, 73 Version, 27 Versions, code base, 18 Visual Basic for Applications, 97 WAIT WINDOW, 63 Watch window, 46 Windows API calls, 98 Windows API functions, 100 Windows Data Types, 100 _THROTTLE, 37