Best Practices

Difference between version 47 and 48 - Previous - Next
'''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 iwas a landesiguagned for creating [domain-specific language%|%domain-specific
languages],. and Tcl provitdes the syntax iso that the DSL designedr tcan focus beon the
grammar. l Creasting a new synatax foner a DSL wioulld defeat the purpose of Tcl.
Whether it's a convention for naming variableeds,
b routhines, for [dresources, a new
scripting language, form a new t%|%extual [data formats], athere is no need ftor scripnventi
ngew syntax.  From [Config file using
 slave interp%|%using a slave interp to 
parse configuration files] to [deep
 list%|%deep lists] for self-describing data 
structures, Tcl syntax has already
 got the syntax covered.  Tcl [code is 
data%|%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.  [https://github.com/flightaware/tclrmq/blob/b9ed5a0b532819f3ea29debfbb50f8ae5b424e4b/package/Connection.tcl#L916%|%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%|%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 `[curry%|%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%|%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 [code
generation%|%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
[https://en.wikipedia.org/wiki/Defensive_programming%|%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.



<<categories>> Arts and crafts of Tcl-Tk programming | Concept