Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

catch replacing useful errorInfo

137 views
Skip to first unread message

TronyQ

unread,
Aug 29, 2007, 12:29:31 PM8/29/07
to
Group,
I was wondering if there was a way to not lose the error stack trace
when using the catch command. For example:

proc read_data {args} {
if {[catch {gen_data} msg]} { error $msg }
}

proc gen_data {args} {
gen_data:sub
}

proc gen_data:sub {args} {
incr i "oops"
}

If you run "gen_data", the errorInfo variable has the following
contents:
expected integer but got "oops"
while executing
"incr i "oops""
(procedure "gen_data:sub" line 3)
invoked from within
"gen_data:sub"
(procedure "gen_data" line 2)
invoked from within
"gen_data"

If you run "read_data", the errorInfo variable has the following
contents:
expected integer but got "oops"
while executing
"error $msg"
(procedure "read_data" line 3)
invoked from within
"read_data"


The "gen_data" errorInfo has much more debug information that can
point me to my mistake. However, in order to fail gracefully, I like
to use a lot of catch statements and, thus, often lose a lot of useful
debug information.

Is there a different way to accomplish what I want to do but still
keep the more verbose errorInfo in tact?

Thanks,
JG

Gerald W. Lester

unread,
Aug 29, 2007, 12:41:27 PM8/29/07
to
Read the man/help page for both error and return.


--
+--------------------------------+---------------------------------------+
| Gerald W. Lester |
|"The man who fights for his ideals is the man who is alive." - Cervantes|
+------------------------------------------------------------------------+

Donald G Porter

unread,
Aug 29, 2007, 12:44:20 PM8/29/07
to
TronyQ wrote:
> Group,
> I was wondering if there was a way to not lose the error stack trace
> when using the catch command. For example:
>
> proc read_data {args} {
> if {[catch {gen_data} msg]} { error $msg }
> }

The best answer requires Tcl 8.5:

package require Tcl 8.5
proc read_data {args} {
if {[catch {gen_data} msg options]} {
return -options $options $msg
}
}

If you need to support Tcl 8.4 (or earlier):

package require Tcl 8.0


proc read_data {args} {
if {[catch {gen_data} msg]} {

error $msg $::errorInfo $::errorCode
}
}

DGP

Bryan Oakley

unread,
Aug 29, 2007, 1:17:20 PM8/29/07
to
Donald G Porter wrote:

> If you need to support Tcl 8.4 (or earlier):
>
> package require Tcl 8.0
> proc read_data {args} {
> if {[catch {gen_data} msg]} {
> error $msg $::errorInfo $::errorCode
> }
> }

Interesting. You advocate "error .." over "return -code error ...". Is
there a technical reason or just a personal preference?


--
Bryan Oakley
http://www.tclscripting.com

Donald G Porter

unread,
Aug 29, 2007, 1:57:39 PM8/29/07
to
Bryan Oakley wrote:
> Donald G Porter wrote:
>
>> If you need to support Tcl 8.4 (or earlier):
>>
>> package require Tcl 8.0
>> proc read_data {args} {
>> if {[catch {gen_data} msg]} {
>> error $msg $::errorInfo $::errorCode
>> }
>> }
>
> Interesting. You advocate "error .." over "return -code error ...". Is
> there a technical reason or just a personal preference?

In this case my key reason was greater similarity to what the
OP was already doing.

Usually for me, the choice between [return -code error] vs [error]
should rest on whether the intent is to report back to the caller
that the arguments it passed you are unacceptable, or whether it's
really something internal to your own code that's going wrong. In
this instance, that choice also leads to [error].

Note that there's an assumption underlying this, that the OP really
has something more sophisticated in mind than the example he posted,
because for that example the best answer is to just not [catch]
in the first place.

DGP

tom.rmadilo

unread,
Aug 29, 2007, 5:52:02 PM8/29/07
to
I use catch for the following reasons:

1. if a shared resource is used and released (like a file lock), an
error may abort release.

Hmm, that is about it, example:

proc ::datastore::restore { store } {

if {[exists $store]} {

set LockID [lock $store]
if {[catch {
set dataFile "[storeDirectory $store]/data"
# dataFile exists only if variables have been saved.
if {[file isfile $dataFile]} {
namespace eval ::datastore::store::$store [list source $dataFile]
} else {
namespace eval ::datastore::store::$store { }
}
set _RESULT 0
} err ]} {
unlock $store $LockID
closeStore $store
error $err "::datastore::restore failed"
}
unlock $store $LockID
closeStore $store
return
}

}

Notice that if caught, the unlock command is executed and store
closed, then the error is called again, with some additional
information. This can still be used by higher up procs if you decide
to catch.

It is important to remember a few things:

1. Catch makes it very difficult, or impossible, to track down
errors.
2. If the code within a catch does not compile, you get the previous
contents of errorInfo, which can lead you to think the error is
somewhere else.
3. Catch makes it more difficult to reuse code.
4. Catch is not an error reporting mechanism (again, catch destroys
error information).
5. Catch is not a program branching facility (like if).

Errors occur because of something not being done correctly prior to
the error, or because of the current state of the system. These are
exceptional conditions which are related to poor programming or bad
data. In any case, you need as much information as possible to
identify the cause. If you could identify the cause ahead of time, you
wouldn't need catch.

You could also use return -code return and return -code error in the
above code. These seem to be more consistent in application.

Donald G Porter

unread,
Aug 29, 2007, 6:21:07 PM8/29/07
to
tom.rmadilo wrote:
> I use catch for the following reasons:
> 1. if a shared resource is used and released (like a file lock), an
> error may abort release.

Yes, that's something "more sophisticated" as I guessed.

> proc ::datastore::restore { store } {
>
> if {[exists $store]} {
>
> set LockID [lock $store]
> if {[catch {
> set dataFile "[storeDirectory $store]/data"
> # dataFile exists only if variables have been saved.
> if {[file isfile $dataFile]} {
> namespace eval ::datastore::store::$store [list source $dataFile]
> } else {
> namespace eval ::datastore::store::$store { }
> }
> set _RESULT 0
> } err ]} {
> unlock $store $LockID
> closeStore $store
> error $err "::datastore::restore failed"

Here is where you need to apply the lessons from earlier in the thread.
You want this:

error $err $::errorInfo $::errorCode

I suppose, if you really insist on it, you could use:

error "::datastore::restore failed: $err" $::errorInfo $::errorCode

But that sort of context information is really best left to ::errorInfo,
where you will find it is already present. The guidance I use is to let
the error message report what happened, and leave it to ::errorInfo to
report where it happened.

> }
> unlock $store $LockID
> closeStore $store
> return
> }
>
> }
>
> Notice that if caught, the unlock command is executed and store
> closed, then the error is called again, with some additional
> information. This can still be used by higher up procs if you decide
> to catch.

If you want "higher up procs" to have a clear signal about the
error that has occurred, the right tool is ::errorCode. Parsing
error messages or $::errorInfo is the wrong idea. Those things are
for people to read. $::errorCode is for programs to read/write.

> It is important to remember a few things:
>
> 1. Catch makes it very difficult, or impossible, to track down
> errors.
> 2. If the code within a catch does not compile, you get the previous
> contents of errorInfo, which can lead you to think the error is
> somewhere else.
> 3. Catch makes it more difficult to reuse code.
> 4. Catch is not an error reporting mechanism (again, catch destroys
> error information).
> 5. Catch is not a program branching facility (like if).

These claims are either mostly or entirely false if you just use all
the features of the commands [catch], [return], and [error] to their
full advantage.

DGP

tom.rmadilo

unread,
Aug 29, 2007, 8:32:59 PM8/29/07
to
On Aug 29, 3:21 pm, Donald G Porter <d...@nist.gov> wrote:

> Here is where you need to apply the lessons from earlier in the thread.
> You want this:
>
> error $err $::errorInfo $::errorCode
>
> I suppose, if you really insist on it, you could use:
>
> error "::datastore::restore failed: $err" $::errorInfo $::errorCode
>
> But that sort of context information is really best left to ::errorInfo,
> where you will find it is already present. The guidance I use is to let
> the error message report what happened, and leave it to ::errorInfo to
> report where it happened.
>

Yes you are right, if you do a catch, you should somehow log the
current value of errorInfo. You really need to also log where the
catch takes place, because the location of the catch is not part of
the errorInfo. Catch destroys this information, in this specific
sense.


> > It is important to remember a few things:
>
> > 1. Catch makes it very difficult, or impossible, to track down
> > errors.
> > 2. If the code within a catch does not compile, you get the previous
> > contents of errorInfo, which can lead you to think the error is
> > somewhere else.
> > 3. Catch makes it more difficult to reuse code.
> > 4. Catch is not an error reporting mechanism (again, catch destroys
> > error information).
> > 5. Catch is not a program branching facility (like if).
>
> These claims are either mostly or entirely false if you just use all
> the features of the commands [catch], [return], and [error] to their
> full advantage.

I'm not claiming that catch can't be used, but lets look at each of
these one by one, and not gloss over these statements as applying as a
group everywhere.

1. The given example for this thread shows that catch does not help
you track down errors. You can work around most issues, assuming you
know how to do it. Does that make it easy or difficult?

2. This one is difficult to give an example for, maybe this has been
fixed, but the idea is that the body of catch has to be compiled, and
this can fail. Maybe it takes multiple layers of catch for this to
show up, since I rarely use catch, I don't have any examples of this
in my code. So this could be mostly or entirely false (which would be
good).

3. Reusing code means that you have to handle everything a proc
returns. Everything already handles returning error information, so if
you replace that with expert error reporting, this is more difficult
to handle. The interface is no longer so simple. If my second claim
has any value, then layering calls to catch is an additional
complication. I'm not saying you shouldn't use catch, more that it is
not always obvious how best to use it, or if using it is advantageous.

4. Obviously catch does not report or propagate errors. If you use
catch, the errorInfo does not include the location of the catch
statement, you have to add that in some way. If you call error after a
catch, you will get the trail back to the original caller, but you
lose info in the catch environment. BTW, my example demonstrates that
you should always continue the error on up the line, but given the
tight application of catch, the err variable contains the cause (not
necessarily true if you wrap lots of code, so a definite general error
in style).

5. This is more difficult to explain. Obviously you can use catch like
this and try to disambiguate error codes. The more information you
have on failure modes, the more useful this programming style could
be. But.....as recognized, getting all this to work requires full use
of catch, error and return. So using catch is not bad style or bad
programming, it is a big tool which requires a lot of care to apply
correctly, and by correctly I only mean that the advantages (as
determined by the programmer) outweigh the time it takes to get it
right.

But my general guideline (for myself) is that errors are unanticipated
events. If you can anticipate the type of error, you can test for it
in advance. Unanticipated errors could leave the application in an
inconsistent state, remove or lock resources or partially execute a
series of operations which should be atomic. Error recovery should
first restore the application to a consistent state, beyond that you
can have lots of discussion and disagreement. So when I say that catch
is not a program branching facility, I mean that catch is on the
backend of normal program flow. If any of these branches of flow could
be handled prior to the catch, that is where they should (usually) go.
The number of conditions handled doesn't increase using catch, it just
moves to a point where recovery is more difficult.

Larry W. Virden

unread,
Aug 30, 2007, 5:46:44 AM8/30/07
to
On Aug 29, 8:32 pm, "tom.rmadilo" <tom.rmad...@gmail.com> wrote:

> Yes you are right, if you do a catch, you should somehow log the
> current value of errorInfo. You really need to also log where the
> catch takes place, because the location of the catch is not part of
> the errorInfo. Catch destroys this information, in this specific
> sense.

I wonder how much information in the default catch format really needs
to ripple back to a _user_ of a program . It would seem to me that the
information that catch generates is really intended more for a
programmer than a user of a program. Maybe a better way to do it would
be to log, into some file, the stack, as well as more detailed
information about the location , state, etc. into an error file. Then
the user could just be told to mail the contents of log abc to
bu...@yourprogram.sf.net or whatever, in the case of an unanticipated
type error. In the case of an anticipated problem (wrong file name
provided, bad password, etc.) the user could, instead, be given
information about corrective steps.

Cameron Laird

unread,
Aug 30, 2007, 9:23:27 AM8/30/07
to
In article <1188467204.1...@d55g2000hsg.googlegroups.com>,
Larry W. Virden <lvi...@gmail.com> wrote:
.
.
.

>programmer than a user of a program. Maybe a better way to do it would
>be to log, into some file, the stack, as well as more detailed
>information about the location , state, etc. into an error file. Then
>the user could just be told to mail the contents of log abc to
>bu...@yourprogram.sf.net or whatever, in the case of an unanticipated
>type error. In the case of an anticipated problem (wrong file name
.
.
.
You mean your programs don't already "call home" when they
encounter an exception?

More soberly, yes, Larry: I've been threatening to write
a book on the subject of error-handling, and one of its
themes would be that, as you describe, developers have a
responsibility to code for many of the anticipated condi-
tions that otherwise manifest as "errors".

Donald G Porter

unread,
Aug 30, 2007, 10:35:09 AM8/30/07
to
tom.rmadilo wrote:
>> error $err $::errorInfo $::errorCode

> Yes you are right, if you do a catch, you should somehow log the


> current value of errorInfo. You really need to also log where the
> catch takes place, because the location of the catch is not part of
> the errorInfo. Catch destroys this information, in this specific
> sense.

Either you're just plain wrong, or I don't understand what you're
saying. Let's look at the corrected example again:

% proc err {} {incr foo oops! ;# error here}
% proc errCaller {} err
% proc demo {} {
if {[catch errCaller msg] == 1} {


error $msg $::errorInfo $::errorCode
}
}

% demo
expected integer but got "oops!"
% set errorInfo
expected integer but got "oops!"
(reading increment)
invoked from within
"incr foo oops! "
(procedure "err" line 1)
invoked from within
"err"
(procedure "errCaller" line 1)
invoked from within
"errCaller"
(procedure "demo" line 2)
invoked from within
"demo"

Now, in the context of this example, can you please point to
what information was "destroyed". It appears to me that
all procs on the call stack at the time of the original error
appear in the $::errorInfo, providing complete data on the
context of the error.

Maybe more later if we get this misunderstanding cleared up.

DGP

tom.rmadilo

unread,
Aug 30, 2007, 10:43:35 AM8/30/07
to
On Aug 30, 6:23 am, cla...@lairds.us (Cameron Laird) wrote:
> More soberly, yes, Larry: I've been threatening to write
> a book on the subject of error-handling, and one of its
> themes would be that, as you describe, developers have a
> responsibility to code for many of the anticipated condi-
> tions that otherwise manifest as "errors".

And if you handle before an error actually occurs, then catch can be
reduced to the purpose of protecting shared resources from exceptional
conditions. The point about errors is that the program is already not
doing the intended job, it is best to stop at that point and let
everyone know. An error log going to stderr can record the gory
details, but the user needs to know at least the fact that the
application has had an application error, different from an error
caused by the user due to bad data or some other easily corrected
condition.

Catching exceptional errors is also one of the last things to be done.
The reason is that during development you need to get as much error
tracing information as possible and also get very comfortable with how
you are going to use catch for your benefit and for the user's
benefit. It is also possible that you will cover up a bug in a package
you are just using, so delay use until code matures a little.

Alan Anderson

unread,
Aug 30, 2007, 6:38:47 PM8/30/07
to
"tom.rmadilo" <tom.r...@gmail.com> wrote:

> And if you handle before an error actually occurs, then catch can be
> reduced to the purpose of protecting shared resources from exceptional
> conditions.

How can you handle an error before it happens? I also don't understand
why handling exceptional conditions for shared resources would be any
different from non-shared ones.

> The point about errors is that the program is already not
> doing the intended job, it is best to stop at that point and let
> everyone know.

Not so. Sometimes the job involves attempting something that has a
significant chance of encountering an error, such as making a connection
to a network service to get a status report. A failure to connect can
be a valid result. The problem could be temporary, and is not fatal to
the program, which can try again later.

> An error log going to stderr can record the gory
> details, but the user needs to know at least the fact that the
> application has had an application error, different from an error
> caused by the user due to bad data or some other easily corrected
> condition.

What do you mean by "application error"? That sounds like something
very different from what I usually see [catch] used for.

> Catching exceptional errors is also one of the last things to be done.

Are you saying that it usually ends up that way, or that it *should* be
that way? My most robust programs come about when I take the time to
practice paranoid programming and write code to catch errors early in
the development cycle.

> The reason is that during development you need to get as much error
> tracing information as possible and also get very comfortable with how
> you are going to use catch for your benefit and for the user's
> benefit. It is also possible that you will cover up a bug in a package
> you are just using, so delay use until code matures a little.

What you're saying is totally at odds to how I think of it. It's so
different that I have to wonder if your concept of an error is not the
same as mine. Or maybe you just have a mistaken idea of what [catch]
does and how to use it.

tom.rmadilo

unread,
Aug 31, 2007, 2:38:57 AM8/31/07
to
On Aug 30, 3:38 pm, Alan Anderson <arand...@insightbb.com> wrote:

> "tom.rmadilo" <tom.rmad...@gmail.com> wrote:
> > And if you handle before an error actually occurs, then catch can be
> > reduced to the purpose of protecting shared resources from exceptional
> > conditions.
>
> How can you handle an error before it happens? I also don't understand
> why handling exceptional conditions for shared resources would be any
> different from non-shared ones.
>

Below I give an example, but basically as the developer you know, or
will know, the requirements of the following code. Maybe you are
taking an integer from the user. But the user could supply a non-
integer. If you check for this prior to using it in code which expects
an integer, this is handling the error as a client error, before it
becomes an application error. Catch is not needed, the client can get
a useful message, and there is no need to think about undoing a half
done job.

Shared resources are very different from non-shared. If a thread opens
a temporary file to write some data, it usually doesn't matter if the
process aborts, or the thread aborts, because another thread doing a
similar task will open another temporary file, completely unaffected
by any other temp file. However, if you use a file lock to provide
shared access to a file, you have to release the lock after you are
finished. Errors here could end up skipping the code which releases
the lock. Now the next thread is stuck, maybe even a restarted
application will fail until the lock is removed. This is just basic
coverage of shared resources, more robust applications could have
additional points for recovery.

> > The point about errors is that the program is already not
> > doing the intended job, it is best to stop at that point and let
> > everyone know.
>
> Not so. Sometimes the job involves attempting something that has a
> significant chance of encountering an error, such as making a connection
> to a network service to get a status report. A failure to connect can
> be a valid result. The problem could be temporary, and is not fatal to
> the program, which can try again later.

Maybe you are using the wrong programming model for this, you
shouldn't be required to handle errors for expected conditions. It's
not like network programming is a new thing. But you seem to be
looking for a point of disagreement, how much code is involved in
making a network connection? I've been pretty explicit that catch
should enclose the smallest amount of code possible, not that it
shouldn't be used. I've also said that the developer is the final
decision maker.

> > An error log going to stderr can record the gory
> > details, but the user needs to know at least the fact that the
> > application has had an application error, different from an error
> > caused by the user due to bad data or some other easily corrected
> > condition.
>
> What do you mean by "application error"? That sounds like something
> very different from what I usually see [catch] used for.
>

An application error is due to the operation of the application
itself, not due to client errors.
The original example for this thread was:

catch {incr i "oops"}

This is an application (programming) error. But

catch {incr i $num}

Could also lead to an application error. Did we check that $num is an
integer? If not, we have an application error, which can be converted
to a client error by checking if $num is an integer. Then we can
remove the catch.


> > Catching exceptional errors is also one of the last things to be done.
>
> Are you saying that it usually ends up that way, or that it *should* be
> that way? My most robust programs come about when I take the time to
> practice paranoid programming and write code to catch errors early in
> the development cycle.
>

Errors happen at runtime, not during the development cycle. Error
catching code is put in last because 'catch' covers up the source of
an error. If you are developing, running developing code inside a
catch, how can you locate the error? You have to distinguish between
coding errors, data input errors (client) and actual errors which mean
that the code cannot function as intended. If you don't distinguish
these general causes of an error, good luck.

> > The reason is that during development you need to get as much error
> > tracing information as possible and also get very comfortable with how
> > you are going to use catch for your benefit and for the user's
> > benefit. It is also possible that you will cover up a bug in a package
> > you are just using, so delay use until code matures a little.
>
> What you're saying is totally at odds to how I think of it. It's so
> different that I have to wonder if your concept of an error is not the
> same as mine. Or maybe you just have a mistaken idea of what [catch]
> does and how to use it.

I'm not trying to persuade anyone. An error is something which
triggers catch to return something other than TCL_OK (0). I have
provided an example and complete explanation of how and when I use
catch. Why not provide an example of your own so I know how you use it?

tom.rmadilo

unread,
Aug 31, 2007, 2:57:14 AM8/31/07
to

I can't see how this differs from what I said: errorInfo does not
contain the location of the catch, but you didn't look at the value of
errorInfo after the catch, only after demo. Your code does exactly
what I suggested you should do, which is to propagate the error, or
log errorInfo plus the location.

If you run the modified demo:

proc demo {} {
if {[catch errCaller msg] == 1} {

global errorInfo
puts $errorInfo


error $msg $::errorInfo $::errorCode
}
}

% demo
expected integer but got "oops!"

(reading increment)
invoked from within
"incr foo oops! "
(procedure "err" line 1)
invoked from within
"err"
(procedure "errCaller" line 1)
invoked from within
"errCaller"

And as you can see, errorInfo did not contain the caller, but after
your new call to error, it does:

% set errorInfo
expected integer but got "oops!"
(reading increment)
invoked from within
"incr foo oops! "
(procedure "err" line 1)
invoked from within
"err"
(procedure "errCaller" line 1)
invoked from within
"errCaller"
(procedure "demo" line 2)
invoked from within
"demo"


Maybe it is easier to say it this way: if you catch an error, throw an
error. Maybe not 100% applicable, but all the examples on this thread
do exactly that. If you don't want to throw an error, figure out how
to avoid the need to catch one.

Larry W. Virden

unread,
Aug 31, 2007, 7:35:03 AM8/31/07
to
On Aug 30, 9:23 am, cla...@lairds.us (Cameron Laird) wrote:
> In article <1188467204.146993.275...@d55g2000hsg.googlegroups.com>,

> Larry W. Virden <lvir...@gmail.com> wrote:
> >the user could just be told to mail the contents of log abc to
> >b...@yourprogram.sf.net or whatever, in the case of an unanticipated

> >type error. In the case of an anticipated problem (wrong file name
>
> .
> .
> .
> You mean your programs don't already "call home" when they
> encounter an exception?

I've seen programs do this, and I'm torn about the practice. Programs
which send information are more frequently being considered bad
practice because you don't know what is being sent to whom...


Donald G Porter

unread,
Aug 31, 2007, 10:04:46 AM8/31/07
to

Donald G Porter <d...@nist.gov> wrote:
>> Let's look at the corrected example again: ...

tom.rmadilo wrote:
> I can't see how this differs from what I said...

OK, violent agreement then. :)

...

> Maybe it is easier to say it this way: if you catch an error, throw an
> error. Maybe not 100% applicable, but all the examples on this thread
> do exactly that. If you don't want to throw an error, figure out how
> to avoid the need to catch one.

I can agree on all that as good general advice.

DGP

Donald G Porter

unread,
Aug 31, 2007, 10:45:27 AM8/31/07
to

Scrolling back to pick up a couple other points.

tom.rmadilo wrote:
> 2. This one is difficult to give an example for, maybe this has been
> fixed, but the idea is that the body of catch has to be compiled, and
> this can fail.

The addition of bytecode compiling in 8.0 was a big step, and it did
take some time to shake *all* the bugs out (some might say we're still
shaking). My guess is that you ran into something ugly like Tcl Bug
705406, and that's made you shy about using [catch]. That bug was a
bad one, but it's fixed now.

> 5. ... getting all this to work requires full use


> of catch, error and return. So using catch is not bad style or bad
> programming, it is a big tool which requires a lot of care to apply
> correctly,

Developments in Tcl 8.5 should have improved this a fair bit. Take
a look at the updated documentation and TIP 90 for some examples.

http://tmml.sourceforge.net/doc/tcl/return.html
http://tmml.sourceforge.net/doc/tcl/catch.html
http://tip.tcl.tk/90

DGP

tom.rmadilo

unread,
Aug 31, 2007, 12:11:22 PM8/31/07
to
On Aug 31, 7:04 am, Donald G Porter <d...@nist.gov> wrote:
> Donald G Porter <d...@nist.gov> wrote:
>
> >> Let's look at the corrected example again: ...
> tom.rmadilo wrote:
> > I can't see how this differs from what I said...
>
> OK, violent agreement then. :)
>

I hate to admit it, but yes!

> > Maybe it is easier to say it this way: if you catch an error, throw an
> > error. Maybe not 100% applicable, but all the examples on this thread
> > do exactly that. If you don't want to throw an error, figure out how
> > to avoid the need to catch one.
>
> I can agree on all that as good general advice.

So now time for counter-examples! I don't want you to get the
impression that I don't violate my own advice, but this goes to more
or less supporting the point that using catch is difficult, in the
sense that you can't necessarily look at a chunk of code and determine
good/bad/okay.


In this example, the pattern is supplied by the user. Before accepting
it, it needs to be minimally tested. Does the regexp compile? If not
an error will be thrown. So I test compile it, using a catch. On
failure, I return a message to the client (actually the application
writer, not the end user) indicating failure, and it aborts the
script:
....
pattern {
if {[catch {regexp $Restrictions(pattern) abc} errorMsg]} {
return -code error "failed pattern test on $tns $typeName
$errorMsg"
}
}
whitespace {
if {[lsearch -exact {preserve replace collapse}
$Restrictions(whitespace)] == -1} {
return -code error "whitespace must be one of preserve,
replace or collapse"
}
}
.....

Second example uses catch to continue loading an application. Why?
Shouldn't the application abort on error? Actually the application is
a shared resource. If a buggy package is added to the system, should
all functionality be lost? Continued loading allows you to figure out
if the error is harmless:

proc ::tws::util::package::loadPackages { packageList } {

foreach package $packageList {

if {[catch {
procs $package
} err ]} {
global errorInfo
log Error $errorInfo [list PACKAGE "$package"]
}
}
....

Third example allows silent failure of a documentation proc. Is this
good practice? Maybe not for finished code. But I'm not sure what
would lead to failure just yet. Since documentation is not the primary
function of the application, for now just catch the error. This type
of error reporting is for the application developer and isn't
triggered by any user input. It is also possible that repeated logging
of the error will do more harm than good. Difficult call:

while {1} {
# First check that we have a real web service
if {![<ws>namespace exists $tclNamespace]
|| [<ws>namespace isFrozen $tclNamespace]
} {
break
}
# Only document certain components
# Note: eventually get search list from the web service
if {[lsearch -exact {type element operation} $what] == -1} {
break
} else {
set what ${what}s
}

catch {::wsdl::doc::document doc $what $xmlAlias $name $docString}

break
}

In a 50k line application, those are the only uses of catch. Two of
them appear on the surface to violate the general rule of catch and
throw, but 50k lines follow the advice of figuring out how to avoid

tom.rmadilo

unread,
Aug 31, 2007, 12:41:23 PM8/31/07
to
On Aug 31, 7:45 am, Donald G Porter <d...@nist.gov> wrote:
> Scrolling back to pick up a couple other points.
>
> tom.rmadilo wrote:
> > 2. This one is difficult to give an example for, maybe this has been
> > fixed, but the idea is that the body of catch has to be compiled, and
> > this can fail.
>
> The addition of bytecode compiling in 8.0 was a big step, and it did
> take some time to shake *all* the bugs out (some might say we're still
> shaking). My guess is that you ran into something ugly like Tcl Bug
> 705406, and that's made you shy about using [catch]. That bug was a
> bad one, but it's fixed now.
>
I think this explains why I couldn't seem to reproduce this behavior.
I remember it, but wasn't able to force it to happen in an example.
Very nasty, good it is gone.

> > 5. ... getting all this to work requires full use
> > of catch, error and return. So using catch is not bad style or bad
> > programming, it is a big tool which requires a lot of care to apply
> > correctly,
>
> Developments in Tcl 8.5 should have improved this a fair bit. Take
> a look at the updated documentation and TIP 90 for some examples.
>

> http://tip.tcl.tk/90

This is simply amazing! My original example accomplishes the same
thing in 8.4, but is much more ugly and prone to bad programming
style. It appears that these new features will allow the caller to
handle or not any exceptional results, so the proc interface remains
clean.

proc doSomething {} {
set resource [allocate]
catch {
# Arbitrarily long script of operations
} result options
deallocate $resource
return -options $options $result
}

Obviously none of my comments or examples in this thread apply to 8.5.


Darren New

unread,
Aug 31, 2007, 5:46:23 PM8/31/07
to
Larry W. Virden wrote:
> I've seen programs do this, and I'm torn about the practice. Programs
> which send information are more frequently being considered bad
> practice because you don't know what is being sent to whom...

It depends on who is running the code, really. All my programs phone
home, because they're all servers running on my own machines. :-)

--
Darren New / San Diego, CA, USA (PST)
Remember the good old days, when we
used to complain about cryptography
being export-restricted?

Donal K. Fellows

unread,
Aug 31, 2007, 8:19:50 PM8/31/07
to
tom.rmadilo wrote:
> Obviously none of my comments or examples in this thread apply to 8.5.

Well... I'd have loved to have put the [try...] syntax in, but the hard
bit is what to catch and how to specify it. The problem is that the Tcl
core is not very good about using errorCode for internal problems, and
is inclined to be inconsistent about the human-readable parts of error
messages. It's a mess, and so a lot of work to clean up.

Donal.

Cameron Laird

unread,
Sep 2, 2007, 11:30:00 AM9/2/07
to
In article <5jqlfu...@mid.individual.net>,

Donald G Porter <d...@nist.gov> wrote:
.
.

.
>> Maybe it is easier to say it this way: if you catch an error, throw an
>> error. Maybe not 100% applicable, but all the examples on this thread
>> do exactly that. If you don't want to throw an error, figure out how
>> to avoid the need to catch one.
>
>I can agree on all that as good general advice.
.
.
.
I like other advice you've given in this thread better, Don.

Part of the reason is that I don't understand what the "Maybe it is
easier ..." paragraph *means*. If it's seriously advocating that
it's improper ever to write

if [catch $script result] {
$action_which_includes_no_error
}

I know I definitely don't agree.

Slightly subtler is the "... figure out how to avoid the need to
catch ..." Developers *do* need to think about how to handle
exceptional situations, and not just code to happy inputs. The
correct mechanisms aren't so obvious, though, I think.

Here's a model for what I suspect many people have in mind:

if [file exists $fn] { (*)
set fp [open $fn]
} else {
complain_about $fn
}

I think there's a general presumption that this is entirely
superior to

if [catch {open $fn} fp] { (**)
complain_about $fn
}

I'm sympathetic to the instinct that (*) is more polite.
It's certainly the way I've tried to code during many of
my years.

(**)'s succinctnes is appealing; I'm a strong believer that
brief code is generally more maintainable-verifiable-...
Python elite programmers generally advocate, more than with
any other major development community I know, that it's
proper to trust exceptions, and it's at least worth the time
to consider their arguments.

Most potently, though, I can make the case that (**) is
objectively more correct, in a well-defined sense: (*)
can misbehave in case of a race (if someone messes with
the $fn inode between [file] and [open]), or permission
conflict, or resource exhaustion (if there are no file
handles left).

I'm leaving aside for now clean-up of side-effects--memory
leaks, for example, which notoriously congregate around
error mishandlings.

My conclusions:
* I suspect we're still at too primitive a state
in understanding exceptions to justify dogmatism;
but
* there are plenty of specific situations where a
strategy of try-to-do-what-I-need-and-catch-the-
exceptions-carefully is provably superior to
avoid-catch-at-all-costs.

tom.rmadilo

unread,
Sep 3, 2007, 11:49:33 AM9/3/07
to
On Sep 2, 8:30 am, cla...@lairds.us (Cameron Laird) wrote:
> My conclusions:
> * I suspect we're still at too primitive a state
> in understanding exceptions to justify dogmatism;
> but
> * there are plenty of specific situations where a
> strategy of try-to-do-what-I-need-and-catch-the-
> exceptions-carefully is provably superior to
> avoid-catch-at-all-costs.

I'm wondering how else to say it, but that is a good point: there can
be no dogmatism with catch, that is the complexity of this command,
maybe that is the dogmatism. If you extract a few lines of code and
try to argue one way or another good or bad with catch, this is
usually way too narrow of a view.

I think I also made the point that if you catch an error and don't
correctly rethrow it, you are likely to loose information about the
error. If the point is to eventually handle the exception, you have to
first understand what is generating them.

Also, catch is not a cure for race conditions, nor a way to recover
from or even detect them. Checking for file existence and other types
of checks have other purposes, like permission to read/write to the
file or if the file is the right type. Race conditions could always
intrude into this space. In my first example, I use a lock, unseen in
this code is the fact that the file key is tied to a specific shared
condition/mutex. Once it gets the lock, at the very least no other
thread of that application will be messing around with the file in
question. This has nothing to do with catch. Two threads could write
to a file at the same time and no error would be thrown, catch would
not trigger in this case, but the application would be broken. Catch
is used to ensure that the condition/mutex variable is returned,
otherwise a larger problem would result: that file and any other
sharing the variable would not be accessible until the application was
restarted.

Here is the example again:

proc ::datastore::restore { store } {

if {[exists $store]} {

set LockID [lock $store]
if {[catch {
set dataFile "[storeDirectory $store]/data"
# dataFile exists only if variables have been saved.
if {[file isfile $dataFile]} {
namespace eval ::datastore::store::$store [list source
$dataFile]
} else {
namespace eval ::datastore::store::$store { }
}
set _RESULT 0
} err ]} {
unlock $store $LockID
closeStore $store
error $err "::datastore::restore failed"
}

unlock $store $LockID
closeStore $store
return
}

}

Note that I do a [file isFile] check simply to figure out what to do,
but the check is inside the catch, not outside, a comment explains the
reason for the check. This example exactly tracks your (**) code. Your
comment is correct: don't presume that the file check and file open
comprise an atomic operation, they don't. There is no way in Tcl to
guarantee that [open], or any file operation will not cause an error,
or even prevent a particular type of error. Any check beforehand can
only test for things which will _always_ lead to an error, for
instance if there was a filename length limitation, you could do this
check beforehand.

Donald G Porter

unread,
Sep 4, 2007, 7:09:37 PM9/4/07
to

>>> Maybe it is easier to say it this way: if you catch an error, throw an
>>> error. Maybe not 100% applicable, but all the examples on this thread
>>> do exactly that. If you don't want to throw an error, figure out how
>>> to avoid the need to catch one.

Donald G Porter <d...@nist.gov> wrote:
>> I can agree on all that as good general advice.

Cameron Laird wrote:
> I like other advice you've given in this thread better, Don.
>
> Part of the reason is that I don't understand what the "Maybe it is
> easier ..." paragraph *means*. If it's seriously advocating that
> it's improper ever to write
>
> if [catch $script result] {
> $action_which_includes_no_error
> }
>
> I know I definitely don't agree.

No, I wouldn't say that.

I would say that it is a rare situation when code like this:

catch {
...
}

is a good idea.

A [catch] alone, without any post-evaluation handling of the result
is an extremely crude tool, suitable only really for hacks or other
situations where you just can't afford the time/effort to do any better.
This coding style is just as likely to mask your (programming) errors
as anything.

Better is to followup your [catch] with some kind of processing that
reacts to the error/exception in a suitable way, and preserves your
ability to find things like typos in the script. That's what I was
thinking when I agreed with the spirit of "if you catch an error,
throw an error". Perhaps better said "don't leave your [catch] results
unhandled".

Also along these lines, I agree with the sentiment that you should
have a [catch] in your code *for some reason*. Either to handle
some error condition you expect to occur sometimes, or to contain
the reach of evaluating some untrusted code. Just throwing more and
more [catch]es into a script until it "works" is the hackery to avoid.

DGP

Cameron Laird

unread,
Sep 5, 2007, 11:20:29 AM9/5/07
to
In article <5k66thF...@mid.individual.net>,

Donald G Porter <d...@nist.gov> wrote:
.
.
.

>the reach of evaluating some untrusted code. Just throwing more and
>more [catch]es into a script until it "works" is the hackery to avoid.
>
>DGP

Now I can join in with the violent agreement.

0 new messages