Best Practices

Best Practices

See Also

Tcl Style Guide
'--' in Tcl
Brace your expr-essions
Tcl style discussion

Description

The idea behind best practices is often to provide examples of code that show what one should do in the best (or perhaps better thought of as worst) case scenarios. These examples would show proper error handling, consideration for variable naming, name space selection, package creation, etc.

By providing these examples, one is in effect saying "I consider this code to be worthy of emulating in even the most critical applications".

Should one code in this style all the time? I think many (most?) would state that the level of attention spent to error handling should be compared to the level of criticalness of the code. If one is writing a one time program to select some random lottery numbers, and then the code will be thrown away, then I suspect that the error handling could be less than code to be used to monitor the control hardware for a deep sea oil drilling rig...

RS: Also, Tcl's default error handling is far better than your choice of SIGSEGV or "Bus Error" that you may get from an unguarded C program. Why bother catching an [open] if the standard error behavior (file not found) is just a clear message on stdout, or even in a Tk popup?

DL: Two reasons: 1) It is often important to explain why the app was trying to open a file, particularly if it's a temp file with a completely meaningless name. So simply reporting "couldn't open /tmp/.exp38" isn't helpful. 2) Sometimes the underlying diagnostic is misleading. So I generally use an error handler that reports my best guess as to the problem along with the diagnostic that was returned by Tcl.

DKF: Indeed; it is always important to make sure that any errors reported to the user are done so in a way that is relevant to them, even if it makes life a bit harder for yourself. A trick I use when writing code that uses assertions is to mark the error codes specially with some kind of errorCode setting that, when it hits my custom bgerror handler, triggers the sending off of a bug report directly to me. Like that, things that I genuinely do not expect to see get thrown in my face and other problems (like trying to open unwritable files for writing) get relegated to the black pit of oblivion that they so rightly deserve. Not all errors are created equal...

RS: Sure, gentlemen, I agree to both of your points. It's just for most code that I write, I am the only user... and I'm used to Tcl's standard error messages. Chimpanzee-proof software is of course very more demanding, but that's the real Best Practice...

LV: I'd offer another reason for writing specialized code. It may be that generating an error msg is NOT the correct action. For example, if the user types the wrong file name, having the application stop with a message about 'file not found' isn't the most friendly solution - instead, catch it, and ask the user to try again...

Always brace expressions

You should always surround expressions with {braces}, including expressions supplied to expr, for, if, and while commands. Braced expressions can be compiled by the byte-code compiler, making your scripts faster, and they avoid the problems associated with double substitution. There is some discussion of alternative styles in A Question of Style, but the best practice is still to brace all expressions.

RS: ...except if operators are to substituted (see expr page, Summing a list, Additional math functions).

Use list to construct commands

Use a single proc handling the callback instead of multiple commands in bindings and callbacks

You should create a proc for use in callbacks and bindings. It is faster, because procs get the benefit of byte compiling. It is safer, because you avoid going Quoting hell in your code. It improves readability and maintainability of code.

The handler is also essentially a hook, if you want it to be. Bindings and callbacks can always be customized or even dynamically altered, but a handler procedure is a cleaner and more obvious place to do this. Since multiple bindings and callbacks can use the same handler, alterations can be done in fewer places this way and still be consistent over the program.

No New Syntax

Tcl was designed for creating domain-specific languages. Tcl provides the syntax so that the DSL designer can focus on the grammar. Creating a new syntax for a DSL would defeat the purpose of Tcl. Whether it's a convention for naming variables, routines, or resources, a new scripting language, or a new textual data format, there is no need to invent new syntax. From using a slave interp to parse configuration files to deep lists for self-describing data structures, Tcl syntax has already got the syntax covered. Tcl unifies the syntax of code and data, and a good code base leverages this as much as possible. Read my lips: No new syntax!

A Callback is a Command Prefix or Script, not the Name of a Command

If a callback is just the name of a command, the user of the interface is more limited than necessary. This example from tclrmq illustrates how not to do it:

if {$connectedCB ne {}} {
    $connectedCB [self]
}

Rather, $connectedCB could be a command prefix:

if {$connectedCB ne {}} {
    {*}$connectedCB [self]
}

or a script:

if {$connectedCB ne {}} {
    eval $connectedCB [list [self]]
}

If a variable is used as Boolean, use true and false instead 1 and 0

Or, use yes and no. The if procedure works perfectly with if false {...} and if true {...}. This way, not only the var names, but also the values tell me what you intended when I read your code.

MG Is this one really a 'best practice', or just personal preference? Personally, I think if 1 is a much better choice than if yes - with the former, it's clearly what's intended, whereas I'd wonder at a glance whether the second was a typo of if $yes - variables called "true" or "yes" are pretty common, whereas it's very rare to see a var named "1" being used. (And '1 is true and 0 is false' is a pretty standard and basic concept across a hell of a lot of languages, too, so is pretty clear to most people, I think.)

wdb When used in an if-statement, the meaning of 1 and 0 are pretty clear. But if in your code, a variable is set to 1 or 0, I cannot see immediately if you need it for arithmetic or boolean operations without scanning the code for usage of this var name (hopefully the var name is not used more than once).

Moreover: The intention of any high-level programming language was: make it easy for the human author. Using 1 for true and 0 for false is a habit for a C programmer. But, it is a bad habit if there are better alternatives. -- The quality of some code is not only determined by the question: does it work? but also by: can I see what was the intent, and how to improve by my last idea of last friday night?

Third: If there is a variable named yes, I decide this code to be bad, because: the value yes is per defintionem a value to calculate with, but never a var name. If you do not agree with my opinion, try to imagine a variable named no which should be allowed as the yes above. See the problem? Should it contain 1 or 0 to trigger? Who does understand such name? Not me!

Lars H: Well, a perfectly legitimate use of yes as a variable name could arise in internationalization situations, where this variable might hold the human-readable text expressing this idea. As for yes vs. 1 as boolean true, I suspect Tcl's own preference of 1 has already made this the norm -- using several string representations for the same conceptual value in a single program is typically more trouble than it's worth.

RLH Aren't all 'best practices' a preference? Most are I think. The Perl side of the house has a book about 'best practices' and it is a wonderful book. Some things are hard to argue with but some things are definitely a preference.


Starting with Tcl 8.4, if you are testing two operands for equality use "eq" not "==" (unless you really want to compare numeric values)

It's faster (equivalent to a string equal call) and safer, as you may find surprising conversions when using "==" on strings that look like numbers. wdb: True spoken. Since 8.4, I avoid "==" (except in arithmetics, of course).

Command Name Collisions

PYK 2016-03-01: Avoid duplicating in any other namespace command names found in the global namespace. It turns out that it's frequently desirable (global command names reused as subcommand names abound in Tk and Tcllib and it would be bad practice not to use them as it would introduce inconsistency) to give namespace ensemble commands the same name as some command in the global namespace. For example,

my_nifty_doodad set ...

It's tempting to implement the ensemble command via a command in the ensemble's namespace called set, but that's the path to confusion and chaos (because locally, the command replaces the global command, which has to be qualified to be able to be invoked -- missing this can be a very hard-to-find error). Instead, consider some naming convention, perhaps set_, and then use a namespace ensemble map to expose it as set:

namespace eval my_nifty_doodad {
    namespace export *
    namespace ensemble create -map {set set_}

    proc set_ args {
        # ...
    }
}

Alan Smithee, 2017-11-07: If one uses TclOO, this isn't quite as much of a problem, since methods aren't quite as easily confused with procedures.

Resolving Command Names Vs Capturing the Namespace

PYK 2016-05-22: When I first started passing command names around in scripts, I did a lot of

if {![string match ::* $cmd]} {
    set cmd [uplevel [list namespace which $cmd]]
}

These days, I tend to capture the namespace and pass it around with the command name or prefix:

set cmdprefx [list [uplevel {namespace current}] $cmd]

or

set script [uplevel [list namespace code $cmd]]

or even

set cmdprefx [list apply [list {cmd args} {
    tailcall {*}$cmd {*}$args
} [uplevel {namespace current}]] $cmd]

This allows whatever finally evaluates the command to do it in the namespace of the original caller, allowing Tcl to resolve the command name at evaluation time rather than up-front, which seems more Tclish to me. Note the similarity with partial.

Ferreting out Existing Assumptions

PYK 2016-05-27: When changing the type of a value during development, take the time to inspect each occurrence of that value in the script and make sure the new type still works as expected. A case history: In the process of updating fileutil::magic::fileType, I decided to make a variable named qual, which previously held a string of letters, into a list:

set qual [list $qual $other]

Which lead to the error,

wrong # args: should be "S type offset comp val ?qual?"

It turned out that elsewhere, $qual was being substituted into a generated script:

append script "${indent}if \{\[$type $offset $comp [list $val] $qual\]\} \{"

Notice that $val is protected as a list, but $qual isn't, since previously it was always a string that contained no whitespace or other special characters, and therefore didn't need any escaping. The fix was to protect it:

append script "${indent}if \{\[$type $offset $comp [list $val] [list $qual]\]\} \{"

There is also something to say in this case about defensive programming . Although it wasn't necessary, it wouldn't have hurt, when the code was originally written, to guarantee via escaping that each value intended to be substituted as a single value would in fact be substituted as such, which would have also made the assumption explicit in the script. On the other hand, it could be argued that the entire body of the script made it clear how they were being substituted, and that wouldn't be wrong. Part of the art of programming is finding that fine line between not enough and too much detail. Another possibility would be to articulate the assumption in a nearby comment.