User
Interface Software Tools Course
A Tcl style guide.
General
These are some general rules that actually apply to any programming language,
not just Tcl.
-
Put a comment just before every function. The comment should describe
what the function does.
-
Put a comment just before each global variable declaration. (What
it means to declare global variables in Tcl is covered later).
-
Keep functions short. If it is more than a page, rethink.
-
Keep the number of formal arguments to a function to a minimum.
I don't have a hard number here, but when the argument count gets above
5 or 6 I get worried.
-
Build for reuse.
-
Comment any code that is tricky or necessarily unclear. Fix
such code when possible.
-
Use long variable and function names. Don't worry about the
performance hit associated with these; with the compiler this no longer
matters.
Formatting
Luckily, Tcl doesn't offer very much leeway in where braces are put. So
hopefully this will be a lot shorter than the corresponding guide for C.
-
Indent two levels deep. On a continued line, indent 2 levels past the previous
line.
-
Put opening brace on the same line as the appropriate command (eg "if").
Put closing brace on its own line. Format "else" like this:
if {foo} then {
} else {
}
When formatting an expression use extra parentheses to make things clearer,
or to make them line up better. Make continued expressions line up with
the start of the expression on the previous line. For instance:
X if {[complicated expression 1]
&& [complicated expression 2]} then {
}
+ if {[complicated expression 1]
&& [complicated expression 2]} then {
}
legend:
X: dont write
+: do write
Braces around the formal args to a procedure are optional when there is
only one argument:
+ proc foo anArg { ... }
+ proc foo {anArg} { ... }
If there is more than one formal argument, always use braces and not quotes:
X proc foo "a1 a2" { ... }
+ proc foo {a1 a2} { ... }
Always fully brace the condition part of an "if". That way the compiler
will be able to compile the condition. (If the condition isn't braced,
then its expansion must be run through the expression interpreter)
if {[foo]} then { ... }
Similarly, always fully brace the expression argument to "expr", "while",
and "for".
If you have to split a widget-creation command, don't split it between
an option and the value:
X menubutton .foo -text \
Zardoz
+ menubutton .foo \
-text Zardoz
Comments
Comments are one of the trickier parts of Tcl. Unlike most other languages,
the comment leader, ``#'', only introduces a comment at command-execution
time. This has caused untold confusion (including a bug in the scripts
that come with Tcl itself).
-
Always line comments up with the code.
-
Comments shouldn't appear in column 0 unless they are not in any proc body.
-
Comments should never appear to the right of code.
-
Don't worry about the performance penalty associated with comments; the
compiler will render this irrelevant.
-
You can't put a comment inside a switch:
X switch {
# This is a bug
}
As much as possible, avoid continued comments. A continued comment occurs
when there is a trailing backslash on a comment line. Rather than commenting
out a continued line with a single ``#'', put a comment leader on every
line. The only time a continued comment should be used is when doing the
infamous ``#!'' hack.
Quoting
Another confusing aspect of Tcl is commonly referred to as Quoting Hell.
But as with other kinds of Hell, you only end up there if you try.
-
Use Tcl's quoting (eg, ``list'') and dequoting (eg, ``lindex'', ``eval'')
functions. Never do quoting by hand.
-
Use of the built-in quoting and dequoting functions is especially important
when debugging. It is too easy to get confused by the value you are looking
at.
-
If an event binding (or other callback) gets too nasty, use ``format''.
Likewise for regular expressions.
-
When building an event binding using ``format'', remember to double all
``%''s used to extract event-specific information.
-
If a callback gets too nasty even for format, introduce a new proc internal
to the module.
-
If you're processing user data in a catch or uplevel, it needs an extra
level of quoting:
X uplevel \#0 write_state $filename
+ uplevel \#0 write_state [list $filename]
Try to brace the command argument to uplevel and catch when possible:
+ if {[catch {some $command}]} then {...
Clarity
-
Never define a proc inside a proc.
-
Always put ``global'' and ``upvar'' declarations at the beginning
of a proc.
-
Always use the noise words "then" and "else". This makes it easier
to read large if statements. When the compiler comes, there won't be any
performance penalty associated with these.
-
Always use the `--' separator option in switch, regexp, etc.
-
Avoid the use of ``rename''. When necessary, use it in constrained
or idiomatic ways. For instance, if you implement your own widgets in pure
Tcl/Tk, you should encapsulate the (necessary) use of rename in a proc.
If you need to run hooks before and after arbitrary procs, then write an
advice package instead of using raw rename throughout the code.
-
Declare all global variables. You can do this by introducing a set
of functions, ``defvar'', ``defconst'', and ``defarray''. (Make sure these
functions can be run any number of times with the same arguments without
causing harm).
-
Never upvar a name to itself; this is a bug:
X upvar $name name
+ upvar $name local_name
The Compiler
Tcl 8.0 includes a reimplementation of the internals so that Tcl scripts
are now byte-compiled and run by a bytecode interpreter. This promises
to give a large performance boost. It also has some implications for Tcl
style.
-
Always put ``global'' and ``upvar'' declarations at the beginning of a
proc. Supposedly the compiler works better when you do this.
-
Ignore the performance penalty associated with noise words such as ``else''.
-
Ignore the performance penalty associated with comments.
-
Ignore the performance penalty associated with long variable names.
-
Use the autoloader. With the compiler, a proc is slow to scan the first
time, and then fast every time afterwards. So it makes sense to only load
procedures on demand.
Tk
By far the most important guideline for using Tk are User
interface Design rules. Tk specific rules are:
-
Never use the "." window in anything but the most trivial application.
Instead, always run "wm withdraw .'' at application startup. Unlike
all other window names, "." ends in a period. Use of this window can result
in special cases littered throughout your code.
-
Don't hard-code window paths. Instead, pass window names as arguments
to procs.
-
If you have to generate window names based on some sort of user input,
write a proc to sanitize the names beforehand. Eliminate all non-alphanumeric
characters, and ensure that the first character is not upper case.
-
A useful trick with canvases is to put a binding on <Configure> events
which resets the ``-scrollregion'' based on the result of ``$canvas bbox
all''.
-
When you write a convenience proc to create a bunch of widgets at once,
always make the first argument a window name. The proc should create
this window, and it should return this same name.
-
However, the <Destroy> binding on a widget should always be available
for use by the caller. So always put necessary actions on subwidgets of
a hierarchy. Create a new subwidget if necessary.
This is bad:
proc foo {name} {
frame $name
bind $name {...}
}
This is better:
proc foo {name} {
frame $name
frame $name.tmp
bind $name.tmp {...}
}
Typically there will be plenty of internal widgets to choose from; you'll
almost never have to create a scratch widget.
If you must add a binding to a Toplevel (<Destroy> and <Configure>
bindings are the most common), add it to a new tag that you attach to the
toplevel. Otherwise the binding will be run for subwidgets as well; probably
not what you want. Usually a proc is provided to handle the bindtags automatically.
Take the time to make sure that keyboard traversal works correctly. Traversal
should be in the same order that you read.
Take the time to make sure that window resizing works correctly.
It is best to try to structure parts of your application as so-called ``megawidgets''.
When writing a dialog, write in an async style. Use configurable
callbacks to pass back data. Do not write in a synchronous style (even
though this is what Tk does with tk_getOpenFile). You can easily turn an
async dialog into a synchronous one by use of vwait. But there
is no easy way to go in the other direction.
Naming conventions
Variables and procedures:
myProc or MyProc
countNum or CountNum
Variables holding Tcl code (intended for eval) ends with Script:
logScript
Variables holding parts of Tcl commands (intended for eval) ends
with Cmd:
myCmd