Friday, May 05, 2006

Debugging Tips

As I mentioned in my post about GLGDW, I missed the Best Practices for Debugging session. Here are a couple of tips I was going to mention. Sorry if someone else mentioned them; I was too busy setting up Rick's system to do my presentation to listen.

1. Write debugging-friendly code.

I used to write code like this:

llReturn = SomeFunction() and SomeOtherFunction() and YetAnotherFunction()
I don't do that for two reasons now: it's harder to understand than:
llReturn = SomeFunction()
llReturn = llReturn and SomeOtherFunction()
llReturn = llReturn and YetAnotherFunction()

But more importantly, it's harder to debug. If you step through the code, execution goes into SomeFunction. If you decide you don't need to trace inside that function and want to jump to the next one, you'd think Step Out would do it. Unfortunately, that steps out all the way back to the next line following the llReturn statement. So, the only ways to trace YetAnotherFunction are to go all the way through SomeFunction and SomeOtherFunction or specifically add a SET STEP ON (or set a breakpoint) for YetAnotherFunction.

Note that I don't typically write code like:

llReturn = SomeFunction()
if llReturn
llReturn = SomeOtherFunction()
if llReturn
llReturn = YetAnotherFunction()
endif
endif

That seems (to me) harder to read than the first example.

Here's another example of debugging-unfriendly code I used to write:

SomeVariable = left(SomeFunction(), 5) + substr(SomeOtherFunction(), 2, 1)

The reason that's not friendly is that you can't see what SomeFunction and SomeOtherFunction return unless you actually trace their code. Instead, I now use code like:

SomeVariable1 = SomeFunction()
SomeVariable2 = SomeOtherFunction()
SomeVariable = left(SomeVariable1, 5) + substr(SomeVariable2, 2, 1)

That way, I can trace this code and see what the functions return without having to trace the functions. If something doesn't look right, I can always Set Next Statement back to one of the statements calling a function and Step Into the function to see what went wrong.

2. Instrument Your Application

I, Rod Paddock, Lisa Slater Nicholls, and others have written articles on instrumenting your applications. The idea is to sprinkle calls to a logging object throughout your application. If logging is turned off, nothing happens. If logging is turned on (for example, oLogger.lPerformLogging is .T.), some message is written to some location (a text file, a table, the Windows Event log, etc.) indicating what the application is doing.

I've found this extremely valuable in tracking down problems. While error logs are great, they only give you a snapshot of how things were at the time the error occurred. Sometimes, you need to know how you got there. Also, sometimes the problem the user is experiencing doesn't cause an error but incorrect behavior. By perusing a diagnostic log, you can see all of the steps (the ones you've instrumented, anyway) that lead to the behavior. Yes, you can use SET COVERAGE in your application, but that generates a ton of information, likely a lot more than you need unless you have a really ugly problem that you have no clue about the cause.

Lisa's article is the most recent one I've read, so it's a good starting place. Rod's article is in the September 1997 issue of FoxTalk and mine is in the October 2003 issue of FoxTalk (both articles require you to be subscribers).

6 comments:

Anonymous said...

Wow, this is a great post!. Can I translate to spanish for the Spanish Community (PortalFox.com)?
Of course, your credits and link back will be present.

Doug Hennig said...

Yes, Esparta, feel free to translate any of my blog entries.

Anonymous said...

Good tips. I've been doing the logging since VFP6. Yes, it's your "trail of breadcrumbs" that helps when the errorlog is not enough. Also, it proves a point sometimes--specifically in my case, the Q/A guy said he was testing the software and found a couple issues that he'd get with me later on---but he was lying, and just stalling/delaying in doing his job. I was able to prove that the guy hadn't been in the system in 3 weeks!! WORTH EVERY MINUTE OF DESIGN TIME. Just like security cameras that came into play in a court trial. Perfect smoking gun evidence!!

Anonymous said...

When posting data for a invoice for instance i use
lnRetVal = iif(lnRetVal=0, AFunction(), lnRetVal)
lnRetVal = iif(lnRetVal=0,BFunction(), lnRetVal)

Its easier to read then the
If lnRetVal = 0
lnRetVal = AFunction()
if lnRetVal = 0
... code, but its still hard to understand.
Does anyone have a better way?

Fernando D. Bozzo said...

Hi Doug:

Sorry, but I don't totally agree in the firstone. For me is more simple this:

llReturn = SomeFunction() and SomeOtherFunction() and YetAnotherFunction()

Why? Well... for the same reason that I prefer to buy:
grapes and oranges and bananas

instead of:
buy grapes and buy (last buy and oranges) and buy (last buy and bananas).

It's more intuitive to read, but yes, must have more carefully when debugging.

Regards,

Fernando D. Bozzo
Madrid/España

Fernando D. Bozzo said...

Hi Doug:

Sorry, but I don't totally agree in the firstone. For me is more simple this:

llReturn = SomeFunction() and SomeOtherFunction() and YetAnotherFunction()

Why? Well... for the same reason that I prefer to buy:
grapes and oranges and bananas

instead of:
buy grapes and buy (last buy and oranges) and buy (last buy and bananas).

It's more intuitive to read, but yes, must have more carefully when debugging.

Regards,

Fernando D. Bozzo
Madrid/España