Fabricio Rocha - 08-Feb-2010 - Error treatment in programming always seems to be an underestimated topic, often untold by and to newbies, while it's a useful thing that might be naturally taught along with the basics in a programming language. Only after some two years of studying Tcl/Tk I was able to find some information about this subject and develop myself a very basic and limited idea of how applications can avoid being crashed by bugs or misuse, so I would like to discuss some error management techniques with the experienced folks, while building up a tutorial from this discussion (something highly useful by aspiring Tclers like me). And, please, treat the errors you find...
Peter Lewerin - 2013-12-07 - I've overhauled the page a bit, concentrating the content around actual error management (leaving the in-depth description of the commands to the commands' respective pages) and adding the new throw/try commands.
These commands cause an error (raise an exception) that must be handled by code somewhere else in the program: if the error isn't handled (see below), the program stops.
The return command has been along for a long time, but has only been able to raise exceptions since Tcl 8.5.
The basic invocation is return result, where result is passed to the caller of the command that return was called in as the, well, result (return value) of that command.
When used to raise an exception, the minimal invocation is return -code error -level 0 result, and in this case result is treated as an error message to be handled (displayed) by exception handling code. Instead of -code error you may write -code 1: it has the same meaning.
Bug alert: The -level 0 option isn't required by documentation, but omitting it will in practice (in Tcl 8.6.1 at least) cause serious problems when the exception is to be handled (see Investigating exceptions when I get around to finishing that page (PL)).
Bug alert: using the -errorinfo option will mess with the -errorstack value in the return options dictionary, at least in Tcl 8.6.1 (see Investigating exceptions when I get around to finishing that page (PL)).
Exception handling code typically gets passed a dictionary, the return options dictionary, (see below) when an exception is raised. The -code and -level options to return are stored in that dictionary. Other options, such as -errorinfo, -errorcode, -errorline, and -errorstack, may be added to the invocation of return and have their values stored in the return options dictionary. The option -options, with a dictionary as value, may be used to set the return options dictionary all at once.
See return and the man page for a more thorough description of the command.
The error command has been the basic exception-raising command since Tcl 8.4.
It can be invoked in three ways:
The invocation... | ...is functionally equivalent to: | |
---|---|---|
"Unary error" | error result | return -code error -level 0 result |
"Binary error" | error result info | return -code error -level 0 -errorinfo info result |
"Ternary error" | error result info code | return -code error -level 0 -errorinfo info -errorcode code result |
Bug alert: binary and ternary error will mess with the -errorstack value in the return options dictionary, at least in Tcl 8.6.1 (see Investigating exceptions when I get around to finishing that page (PL)).
See error and the man page for a more thorough description of the command.
The throw command was added in Tcl 8.6.
It is invoked like this:
throw code result
which is functionally equivalent to:
return -code error -level 0 -errorcode code result
See throw and the man page for a more thorough description of the command.
An exception that isn't handled by the program is eventually handled by a default handler which basically only does two things: 1) output an error message, and 2) stop the program. If you want to do something else, like for instance deal with the error somehow and go on, or give up and at least save important data, you need to define an exception handler.
An exception handler has two parts: the grabber and the dispatcher (not the usual terms, but I'm trying to avoid verbs like catch and trap, which are used in the commands already). The grabber executes a script or body and intercepts any exceptions raised during the execution, capturing state information about the exception. This state information can then be used to dispatch to the exact piece of code that the programmer has assigned to deal with the exception in question.
The catch command has been the basic exception-handling command since Tcl 8.4.
The catch command performs the grabbing part of exception handling, but does not dispatch. If fail is a command that raises an exception, this invocation:
catch { fail }
simply prevents the exception from stopping the program. The traditional invocation looks like this:
set returnCode [catch { cmd } result]
In this case, the variable result will either contain the return value of cmd, or the error message of an exception raised while executing cmd. This is disambiguated by the value in returnCode: if the value is 0 everything went well and result holds a return value. If an exception was raised, returnCode has the value 1.
(I'll be back soon -- PL)
Whenever a script invokes a command/procedure which is not defined anywhere, the Tcl interpreter triggers a built-in command called unknown. This command searches a definition of the procedure in other places than the interpreter's context, and if a procedure with the name is not found anywhere, unknown stops the script's processing and shows an error message in the console.
Like other Tcl built-in commands, ::unknown can be renamed and substituted by a procedure with the same name which can do other things before the default actions; and this is the way unknown is best used. Since Tcl 8.5, there is also the namespace unknown command, which allows the programmer to name a procedure which will be called when a command/procedure lookup fails in the scope of a specific namespace.
A reserved and global variable called errorCode is automatically created by the Tcl interpreter during the execution of a script for holding information about errors occurred in runtime, so its contents are changed everytime an error happens. errorCode is a variable-length list whose first element is a string which indicates the type of error which happened, and the following elements, if existant, are details about the errors which can be used by a procedure for error treatment. As of Tcl8.5, ::errorCode seems to be still underused by many of the core Tcl commands, and these are the possible values and structures that are generated by these commands and stored in ::errorCode, according to the official documentation [L1 ]:
Any procedure can set its own error values in ::errorCode by using the "advanced" options for the command return, as we will see below.
The catch command is the Tcl way for directly receiving the special values that return may have set in the case of an error. It runs a certain procedure/command under a second instance of the interpreter, which will not crash the application if something goes wrong. If the "catched" procedure ends abnormally -- i.e., its return -code is other than 0 --, catch will return exactly this code. Two variables names can optionally be passed to catch: the first one will receive the value passed by the called procedure's return command (which, in case of an error, is expected to be an explanation of the error) and the second one will hold a dict (which can be processed like a list of pairs) pretty similar to the contents of ::errorCode and to the extra options used in the return command, with the keys -errorcode, -errorinfo and -errorline.
The error command triggers the error-handling measures in the application and/or Tcl interpreter. Before Tcl 8.5 introduced some of the advanced options for the return command, error was the preferred way to intentionally signal an error.
The bgerror command is called when an uncaught error reaches the Tcl/Tk event loop; it gives the application the ability to handle the error in some appropriate way. In GUI applications, it's common to report the error the user and give them the ability to easily send the stack trace and any related information to the developer. In non-GUI applications, it's useful to log the stack trace and related information to a log file; then, the application can either keep running or shutdown, as appropriate.
The infrastructure provided by Tcl allows applications to use exception handling, in the traditional sense of "try to do this, and if something goes wrong tell me and I'll see what can I do". This contrasts to the approach of "errors prediction", which, for example, performs a series of tests on the data which will be passed to a command for checking its validity, before the operation is performed. Both techniques are not excludent, however. Tcl allows various approaches to errors management, with their pros and cons:
1) Always use the advanced return options when writing procedures which can cause or face errors, or which may give back an invalid result;
2) Always use catch for calling commands or your own procedures which can cause or face errors like described in 1;
3) Create a procedure to be called in the case that catch captures an error, for interpreting the error codes and, based on that, show error messages in friendly and standardized dialogs and perform operations which could minimize or solve the error.
Create a trace on ::errorCode, and a procedure to be called everytime it is modified, for interpreting the codes, display them, provide minimization measures, etc.
Any other? Please add what you do!
LV One useful thing that I sometimes use is creation of log files containing information intended to be useful in determining the state of the program during particular points. Sometimes, displaying information about the values of a number of variables is not as helpful as having that information written to a file - for instance, there are times when a GUI application might not have easy access to stderr for error traces. Writing information to a log file, which is available - and perhaps even emailable - to the programmer responsible is helpful.
Failure in files, channels and sockets operations?
Errors caused by invalid inputs. It is often useful to use a distinct error code (e.g., INVALID) for data validation errors, as it makes it possible for the application to distinguish between errors in the user's input and errors in the validation or execution code.
Syntax errors and programming bugs - They'd better be fixed. Sure, but....
LV Certainly they need to be fixed. However, if you hide the info from the user, how will the programmer know what the bug/error is? Unless you have a guaranteed method of getting said info to the programmer (and email doesn't count - the user MIGHT be working off line), then providing the user with sufficent information to a) know what the error is and b) know who to contact or what to do about the problem seems the best approach to me.
Fabricio Rocha - 12-Feb-2010 - One more reason for having a way to intercept and explain this kind of errors to common users is that it seems that any test suite or any test routine will not be able to find some errors that users are able to find. Of course it is not nice to show weaknesses to a final user, but this is something practically unavoidable in software. And in addition to the situations listed by LV, we can consider that, for an open source/free software, providing good information about an error is a way to c) allow a user with sufficient programming knowledge to fix the problem and possibly contribute to the software development.