Debugging
Screams, intermittent yells and bangs, kid's and dogs cowering in the
corner. What is going on? Have I gone nuts? Have I finally cracked and
lost my mind?
No, it's just me trying to debug my latest program. Go on laugh, but I'll
guarantee we've all done it, and we'll all do it again.
Debugging can be a real pain. "But it worked just now!"
"Why did it crash?" "The coding looks perfect. I've checked
it line by line 23 times." "There must be something wrong with
my computer."
This tutorial will start off by showing you some of the more common
AutoLisp bugs that I've come across, and then show you a couple of things
to assist you in your debugging efforts. Let's start with some common
bugs.
The simplest and most common is yourself. I don't know if you are aware
of the fact that most programming errors occur between the seat and the
keyboard. Yes, sad but true. So, how do we fix you up? A couple of tips :
- Don't program or debug when you're tired. Walk away from it. Have a
break, go shopping. You'll be amazed at how a break from a program
clears the mind.
- Very rarely will it be a computer or system bug. 99.99999 times out
of a hundred, it's YOU.
- The computer or any other sinister source will not change your
program coding or the variable values therein. YOU DO.
The second type of bug is the syntax bug. A syntax bug is where
the command is in error. For example when you've miss-spelt an AutoLisp
function or command. e.g. (setq ang1 (angl pt1 pt2))
This is normally quite easy to spot and rectify as AutoLisp will not
recognise the command and your program will stop.
Another VERY common syntax bug is naming variables after an AutoLisp
function or command. The most common? Angle and T ( angle and t ).
I must have seen these two used as variables a thousand times. Length (length)
is another very common one.
The third type of bug is the logical bug. Now this little
beastie has numerous faces and comes in all shapes and forms.
The simplest form is when you pass incorrect information to a function.
(setq ang (angle pt1 pt2))
But what happens if pt1 is nil. Crash!!
Another good example is trying to pass string values to a function that
expects numbers. (and vice-a-versa.)
Again, Crash!! Check your variable values and data types.
To check if a variable is a list, you can use the (type) command,
which returns a variable type. But since a list is not a string you must
test it :
( if ( = ( eval v ) LIST) . . .
You could also use (listp) to see whether a variable is a list :
( if ( = T ( listp a ) ) . . .
(listp) returns T if it is a list and nil if it isn't.
To test whether a variable is a number, use the same test as earlier
but test for REAL or INT. You can also use (numberp), which works
the same as (listp) on lists. If the variable is a real number or
an integer, it returns T. If not, it returns nil.
To test for a positive or negative number use (minusp). This
returns T if a number is negative and nil if it is positive.
Oh, and before I forget, another good example of wrong data types is
confusing radians with degrees. AutoLisp functions use radians, AutoCAD
commands expect degrees.
Loops are another area that seem extremely susceptible to attracting
bugs. Have a look at this :
(while flag
(+ cntr 1)
(program statements here)
( if ( = cntr 5)
(setq flag
nil)
);if
);while
What is supposed to happen here is that the program will
loop until flag is set to nil. cntr is supposed to be
incremented by one at each loop. But, as you can see from this coding, (at
least I hope you can see), cntr will never reach the value of 5. It
will always be equal to 1. The correct coding should be :
(setq cntr (+ cntr 1))
System variables can also create untold problems, especially Snaps.
Switch them On only when you need them and Off once finished or you'll
find yourself snapping to some weird and wonderful parts of your drawing.
And please remember the golden rule. "Reset the system back to the
way you found it." Before changing system variables, save their
existing values then reset them back at the end of your program. And don't
forget to to include these settings in your error traps so that if
something untoward does happen, your error trap will reset them back to
their original values.
Now let's have a quick look at some DCL coding bugs.
Have a look at the first one :
(action_tile "cancel" "(done_dialog) (setq bryt 1)
(exit)")
I was told that every time the user clicked the
"Cancel" button, AutoCAD would freeze. Any
idea why? The second example has a similar
error :
(action_tile
"accept"
"(progn
(setq rimel (get_tile \"ebrim\"))
(setq maxel (get_tile \"ebmax\"))
(done_dialog) (setq
flag
T) (cutt))"
)
The answer to both examples is that they
were trying to call a function
(exit) and (cutt) from within a dialog.
In other words, the dialog was still open when they called
the function.
An easy error to make and one to look out for.
Please everybody, be careful out there!
Now we'll look at a couple of handy ideas for making your debugging a
little be easier.
When writing an AutoLisp routine it's quite handy to
have the program stop if it encounters an error. It's also nice to be able
to view the variables whilst the program is running to ensure that they
contain the correct values.
In Visual Lisp and Visual Basic you can insert "breakpoints"
into your coding that do just that and use the Watch window to check on
their values. If you are not using Visual Lisp or Visual Basic this
can still easily be accomplished. Place the following statement within
your coding where you would like your program to stop :
(setq bp (getstring))
This statement will stop your program and only continue
once you hit the space bar.
To print the variables, put a (princ) statement to print the
variable before the break point.
(princ variablename)
(setq bp (getstring))
Do remember to remove your print statements and
breakpoints on completion of debugging.
You could if you wish turn this little trick into a sub
function. This would be quite handy if you have a large program and can
foresee a lot of debugging. Have a look at this small AutoLisp routine
that changes an objects colour to "Bylayer."
;CODING BEGINS HERE
(defun c:clay ()
;clear the loop control variables
(setq i 0 n 0)
;prompt the user
(prompt "\n Select entities to analyze ")
;get the selection set
(setq sel (ssget))
;get the number of objects
(setq n (sslength sel))
;start the loop
(repeat n
;get the entity name
(setq entity (ssname sel i))
;now get the entity list
(setq name (entget entity))
;retrieve the layer name
(setq layern (cdr (assoc 8 name)))
;get the layer data
(setq layerinf (tblsearch "LAYER" layern))
;extract the default layer colour
(setq layercol (cdr (assoc 62 layerinf)))
(bpt)
(setq bp (getstring))
;construct an append the new list
(setq name (append name (list (cons 62 layercol))))
;update the entity
(entmod name)
;update the screen
(entupd entity)
;increment the counter
(setq i (1+ i))
;loop
);repeat
(bpt)
(setq bp (getstring))
(princ)
);defun
(defun bpt ()
(princ i)
(princ "\n")
(princ n)
(princ "\n")
(princ sel)
(princ "\n")
(princ layern)
(princ "\n")
(princ layercol)
(princ "\n")
(princ i)
);defun
(princ)
;CODING END HERE
I included a sub routine that simply stops the program and lists the
values of selected variables at two places, one within the loop and one at
the end of the program. This way you can easily track the value of the
variables whilst your program is in progress. You could even include this
type of breakpoint function within your error trap whilst debugging.
Another good trick is to always make your variables
"global" at the beginning of a program. If you make them
"local", they may have no value at the end of the program and
you won't be able to see what was in them. The same as for the
breakpoints, remember to declare your variables as "local" after
debugging.
Leaving your variables "global" can though,
create it's own problems whilst debugging. You run various programs that
use the same variable names and suddenly you find your variables
"tripping" all over themselves. This can be difficult to find as
sometimes your program runs perfectly well and the next minute Crash!!
Look out for this one.
I also found this little tit bit at AcadX,
which I'm sure they won't mind me sharing with you.
Prior to A2K, AutoLisp would dump a bug trace to the command line when an
error was encountered.
While developers went out of their way to suppress this behavior in a
released product, it was a handy tool during the debugging cycle. To get
that same behavior in A2K, replace the standard error handler with this
one:
(defun errdump (s)
(vl-bt)
(princ)
)
Oh, and one more thing! Please remember the power of
your eyes. Watch the screen as your program runs. You can pick up a lot of
clues from watching what happens to entities whilst your program runs.
Well, that's it for debugging. I hope this helped you
and didn't leave you even more frustrated than before.
Remember, test, test, test and test.
If you would like to know how "NOT" to write
your AutoLisp coding,
then check this out. |