Updated 2014-08-25 01:58:38 by RLE

One of the more frustrating experiences that I have endured while developing applications in Tcl is that of trying to find bugs in code that consists of many procedure modules which call each other. A single statement error, such as an invalid expression, causes an error message that tells one nothing about the probable location of the error. I am then forced to read each line of the code in the possible execution path to try to locate the statement that actually caused the error.

Of course, there are tools to help, such as the Tkcon console, the Tcl debugger, and the use of the bg_error command and the unknown command. Since I use a programming style that consists of developing a large number of 5 to 10 line procedures, which I then build into an application, I am able to use the following artifice to tell me immediately where an error occurs:

!!The call Procedure

The call procedure is a simple one with the following lines of code:
      proc call { what args } {
           if { [catch { eval $what $args } result] } {
                 puts stderr "Procedure \"$what $args\" failed because : $result"
                 }
           return $result
           }

This procedure will trap the error message from any procedure call and print out the name of the procedure, its arguments, and the reason for the failure. If there is no failure, the result returned from the called procedure is returned without comment.

Implementation

I write a procedure using a style such as:
      proc Myproc { a b c } {
           set x [call proca $a]
           set y [call procb $x $b $c]
           return [call procc $y]
           }

Every time I invoke a procedure, I use a statement of the form:
      call Myproc arg arg ...

as in, for example,
      set result [call Myproc $a $b $c]

Now, if Myproc, or one of the functions that it calls, fails, then I get the name of the failed procedure, its arguments at the time of failure, and the error message. It is usually a lot faster to debug the specific failed procedure than it is to re-check a large chunk of code.

Of course, some programmers still believe that the way to write procedures is to have one proc with thousands of lines of code. If you are of this school, this method is quite useless.

For the Critics

Indeed, this method adds overhead to your script. It does, in my experience, considerably reduce development and debugging time, however, and I believe that any loss of efficiency in execution is of secondary importance, considering that everyone is buying 3.0 Ghz boxes these days.

And yes, I am a one time FORTRAN programmer, so the syntaxtic style comes naturally!

RHS 10June2004 Why not just wrap the main call of your code in a catch and print out any errors, ie:
 if {[catch {
     myproc with some args
 } retval]} {
     puts "Error: $retval\n$::errorInfo"
 }

If you have that at the toplevel of your code, then any errors deeper in your code will propogate out and you'll get the stack trace.

Admittedly, I'm not a fan of exec, due to the speed cost (much of the code I work on is highly speed dependant)... and the overhead of a second call on top of that doesn't help either.

jcw - I think the point is that Tcl's error traceback is hard to read. Another option to consider these days is "trace add execution <name> enter <script>" (and leave), as of Tcl 8.4, I think. These could be added wholesale for debugging and omitted in production code.

It would help CL's understanding of this page to have a concrete example of a standard Tcl traceback, and call's improved rendering. As things stand now, I'm unpersuaded. Perhaps that merely testifies to my familiarity with Tcl error tosses.

sbron 12June2004 The call procedure will not work for "any procedure call". There are at least two problems:

  1. Procedures that get passed a variable name. They may try to access a variable by that name in the local scope of the call procedure, which will not give the intented result.
  2. The double evaluation of what. call {hello world} would actually try to run the procedure hello.

These problems can easily be avoided by changing the call procedure into the following:
 proc call {args} {
     if {[catch {uplevel 1 $args} result]} {
         puts stderr "Procedure \"$args\" failed because : $result"
     }
     return $result
 }