GENERIC TCL STYLE GUIDE

@(#)tclstylg.htm 3.5 - 03/08/02

The Generic UWF Maintenance Process (GUMP) 1994, 1995, 1996, 1997, 1999, The University of West Florida (UWF). All rights reserved.

Permission is granted to reproduce and adapt this document provided credit is given to the University of West Florida. This documentation is provided "as is" and no warranty of fitness for any particular purpose is made or implied.

ABSTRACT

This document is the style guide for applications written using the Tool Command Language (Tcl) and Tool Kit (Tk). It outlines the basic conventions and standards for the appearance and structure of Tcl source code and comments used in supporting projects for the University of West Florida. Tcl source code shall conform to this style guide to the maximum extent possible. This will enhance the readability and understandability of the code for future maintenance efforts

TABLE OF CONTENTS

ABSTRACT
LIST OF FIGURES
1.0 INTRODUCTION
2.0 EXECUTABLE FILES
2.1 UNIX PLATFORMS
2.2 WINDOWS PLATFORMS
3.0 HOW TO ORGANIZE A CODE FILE
3.1 THE FILE HEADER
3.1.1 Title
3.1.2 Description
3.1.3 Author
3.1.4 Date
3.1.5 Revision String
3.1.6 Package Definition
3.2 PROCEDURE HEADERS
3.2.1 Title
3.2.2 Description
3.2.3 Arguments
3.2.4 Results
3.3 PROCEDURE DECLARATIONS
3.4 PARAMETER ORDER
3.4.1 Normal Order
3.4.2 Sub-command Arguments
3.4.3 Multi-procedure Arguments
3.5 PROCEDURE BODIES
3.6 MULTI-FILE PACKAGES
4.0 NAMING CONVENTIONS
4.1 GENERAL CONSIDERATIONS
4.1.1 Be Consistent
4.1.2 Use Names in Context
4.1.3 Make Names Reflect Function
4.1.4 Don't Be Too Generic
4.2 BASIC SYNTAX RULES
4.2.1 Variable Names
4.2.2 Multi-word Names
4.2.3 Reference Variables
4.2.4 Tcl Code Variables
4.2.5 Partial Tcl Command Variables
5.0 LOW-LEVEL CODING CONVENTIONS
5.1 INDENTATION
5.2 CURLY BRACES: { GO ON A SEPARATE LINE
5.3 COMMENTS
5.4 CONTINUATION LINES
5.5 ONE COMMAND PER LINE
5.6 PARENTHESIZE EXPRESSIONS
5.7 ALWAYS USE THE RETURN STATEMENT
5.8 SWITCH STATEMENTS
5.9 IF STATEMENTS
6.0 DOCUMENTING CODE
6.1 DOCUMENT THINGS WITH WIDE IMPACT
6.2 DON'T JUST REPEAT WHAT'S IN THE CODE
6.3 DOCUMENT EACH THING IN EXACTLY ONE PLACE
6.4 WRITE CLEAN CODE
6.5 DOCUMENT AS YOU GO
6.6 DOCUMENT TRICKY SITUATIONS
7.0 PACKAGES AND NAMESPACES
7.1 PACKAGE NAMES
7.2 VERSION NUMBERS
7.3 PACKAGE NAMESPACES
7.4 STRUCTURE
8.0 TESTING
8.1 BASICS
8.2 ORGANIZING TESTS
8.3 COVERAGE
8.4 FIXING BUGS
8.5 TRICKY FEATURES
8.6 TEST INDEPENDENCE
9.0 MISCELLANEOUS
9.1 PORTING ISSUES
9.2 CHANGES FILES
REVISION HISTORY
BIBLIOGRAPHY
GLOSSARY

LIST OF FIGURES

Figure 1. An Example of a File Header
Figure 2. An Example of a Procedure Header
Figure 3. An Example of Commenting Style

1.0 INTRODUCTION

This guide is for use by the CEN6015 Software Engineering Project class, UWF, while developing or maintaining Tcl code. It is intended to be a full scope Tcl guide and may address items not required for the class. It describes a set of conventions for writing code and the associated test scripts. There are three reasons for the conventions:

1. Conventions ensure certain important things get done; for example, every procedure must have documentation that describes each of its arguments and its result, and there must exist test scripts that exercise every procedure,

2. Conventions guarantee all of the Tcl and Tk code has a uniform style making it easier for us to use, read, and maintain each other's code, and

3. Conventions help avoid some common mistakes by prohibiting error-prone constructs such as building lists by hand instead of using the list building procedures.

This document is based heavily on the style guide by Ray Johnson of Sun Microsystems, and the Tcl/Tk Engineering Manual by John Ousterhout. The engineering manual specified the style of the C code used in the implementation of Tcl/Tk and many of its extensions. The engineering manual has been very valuable in the development of Tcl/Tk and is an important reason why Tcl is a relatively easy system to maintain.

Deciding any style standard involves making tradeoffs that are usually subjective. This standard was created as a consolidation of coding styles and standards from many sources. We don't claim that these conventions are the best possible ones, but the exact conventions don't really make that much difference. The most important thing is that we all do things the same way, with a goal of ease of readability and maintenance. Keeping in mind that every rule has exceptions, be willing to bend rules when necessary. Don't intentionally try to make exceptions.

Write your code so that it conforms to the conventions from the very start. For example, don't write comment-free code on the assumption that you'll go back and put the comments in later once the code is working. This simply won't happen. Regardless of how good your intentions are, when it comes time to go back and put in the comments you'll find that you have a dozen more important things to do; as the body of uncommented code builds up, it will be harder and harder to work up the energy to go back and fix it all. One of the fundamental rules of software is that its structure only gets worse over time; if you don't build it right to begin with, it will never get that way later.

The rest of this document consists of the following major parts:

2.0 EXECUTABLE FILES

An executable is a file, a collection of files, or some other collection of Tcl code and the necessary runtime environment. Often referred to as an application, an executable is simply what you run to start your program. The format and exact make up of an executable is platform-specific. At some point, however, a Tcl start-up script will be evaluated. It is the start-up script that will bootstrap any Tcl based application.

The role of the start-up script is to load any needed packages, set up any non-package specific state, and finally start the Tcl application by calling routines inside a Tcl package. If the start-up script is more than a few lines it should probably be a package itself.

There are several ways to create executable scripts. Each major platform usually has a unique way of creating an executable application. Here is a brief description of how these applications should be created on each platform:

2.1 UNIX PLATFORMS

The most common method for creating executable script on UNIX platforms is the #! mechanism with the executable permissions set for the file. On the UWF Sun machines, Tclsh and Wish are installed in /usr/local/bin as tclsh8.0 and wish8.0 with corresponding links to tclsh and wish.

The first line in an executable Tcl script file will be:

#!/usr/local/bin/tclsh

The first line in an executable Wish script file will be:

#!/usr/local/bin/wish

This form will ensure that you are always running with the currently installed version of Tcl or Wish.

2.2 WINDOWS PLATFORMS

On the Windows platforms you only need to end a file with the .tcl extension and the file will be run when the user double clicks on the file. This, of course, is assuming you have installed Tcl/Tk.

3.0 HOW TO ORGANIZE A CODE FILE

Each source code file should either contain an entire application or a set of related procedures that make up a package or another type of identifiable module, such as the implementation of the menus for your application, or a set of procedures to implement Hyper Text Transfer Protocol (HTTP) access. Before writing any code you should think carefully about what functions are to be provided and divide them into files in a logical way. The most manageable size for files is usually in the range of 500-2000 lines. If a file gets much larger than this, it will be hard to remember everything that the file does. If a file is much shorter than this, then you may end up with too many files in a directory, which is also hard to manage.

3.1 THE FILE HEADER

The first part of a code file is referred to as the header. It contains overall information that is relevant throughout the file. It consists of everything but the definitions of the file's procedures. The header typically has six parts, as shown in Figure 1:

3.1.1 Title

The name of the file.

3.1.2 Description

A brief description of the overall functions provided by the file.

3.1.3 Author

Who you are and who you work for.

3.1.4 Date

Date the initial development started.

3.1.5 Revision String

The contents of this string are managed automatically by some control system for the file, such as Source Code Control System (SCCS), as used in the example (figure 1). It identifies the file's current revision and date of last modification.

3.1.6 Package Definition

Optional. Also any require statements for other packages that this package depends on should be the first code in the file. Any global variables that are managed by this file should be declared at the top of the page. The name space definition should be next and the export list should be the first item in the namespace definition.

Structure your file header in exactly the order given above and follow the syntax of Figure 1 (below) as closely as possible.

#!/usr/local/bin/(tclsh or wish)

#*******************************************************************
#
# Title: specMenu.tcl
#
# Description:
#   This file implements the Tcl code for creating and
#   managing the menus in the SpecTcl application.
#
# Author: Joe Programmer, COP 5990 UWF
#
# Date: April 22, 1998
#
# SCCS:%W% %G%
#
#*******************************************************************

package require specTable
package provide specMenu 1.0
namespace eval specMenu \
{
  namespace export addMenu
  array set menuData {one two three}
  ...
}

Figure 1. An Example of a File Header

3.2 PROCEDURE HEADERS

After the header you will have one or more procedures. Each procedure will begin with a procedure header that gives overall documentation for the procedure, followed by the declaration and body for the procedure. See Figure 2 for an example. The header should contain everything that a caller of the procedure needs to know in order to use the procedure, and nothing else. It consists of four parts:

3.2.1 Title

The name of the procedure.

3.2.2 Description

A brief description of what the procedure does. This should not be a detailed description of how the procedure is implemented, but rather a high-level summary of its overall function. It is recommended that in some cases, such as callback procedures, describe the conditions under which the procedure is invoked and who calls the procedure.

3.2.3 Arguments

This portion of the header describes the arguments that the procedure expects. Each argument should get at least one line. The comment should describe the expected type and describe its function. Optional arguments should be pointed out and the default behavior of an unspecified argument should be mentioned. Comments for all of the arguments should line up on the same tab stop.

3.2.4 Results

The last part of the header describes the value returned by the procedure. The type and the intended use of the result should be described. This section should also mention any side effects that are worth noting.

Structure your procedure header in exactly the order given above and follow the syntax of Figure 2 as closely as possible.

#*******************************************************************
#
# Title: tcl::HistRedo
#
# Description:
#   Fetch the previous or specified event,
#   execute it, then replace the current history
#   item with that event.
#
# Arguments:
#   event  (optional) index of history item to redo.
#          Defaults to -1, meaning
#          the previous event.
#
# Results:
#   The result is that of the command being redone.
#   Also replaces the current history list item
#   with the one being redone.
#
#*******************************************************************

proc tcl::HistRedo {{event -1}} \
{
  ...
}

Figure 2. An Example of a Procedure Header

3.3 PROCEDURE DECLARATIONS

The procedure declaration should also follow exactly the syntax in Figure 2. Note that the procedure is defined outside the namespace command that defines the export list and namespace globals. The first line gives the proc keyword, the procedure name, and an argument list. If there are many arguments, they may spill onto additional lines (see Sections 5.1 and 5.3 for information about indentation).

3.4 PARAMETER ORDER

Procedure parameters may be divided into three categories. In parameters only pass information into the procedure (either directly or by pointing to information that the procedure reads). Out parameters point to things in the caller's memory that the procedure modifies such as the name of a variable the procedure will modify. In-out parameters do both. Below is a set of rules for deciding on the order of parameters to a procedure:

3.4.1 Normal Order

Parameters should normally appear in the following order: in, in/out, out, except where overridden by the rules below.

3.4.2 Sub-command Arguments

If an argument is actually a sub-command for the command then it should be the first argument of the command. For example:

proc graph::tree {subCmd args} \
{
switch $subCmd \
{
  add {eval add_node $args}
  draw {...

3.4.3 Multi-procedure Arguments

If there is a group of procedures, all of which operate on an argument of a particular type, such as a file path or widget path, that argument should be the first argument to each of the procedures (or after the sub-command argument).

3.5 PROCEDURE BODIES

The body of a procedure follows the declaration. See Section 5 for the coding conventions that govern procedure bodies. The curly braces enclosing the body should be on different lines, as shown in Figure 2, even if the body of the procedure is empty.

3.6 MULTI-FILE PACKAGES

Some packages may be too large to fit into one file. You may want to consider breaking the package into multiple independent packages. However; when that is not an option, you need to make one of the files the primary file. The primary file will include the complete export list and the definitions of all exported variables and procedures. The secondary files should only contain supporting routines to the primary file. It is important to construct your package in this manner or utilities like pkg_mkIndex will not work correctly. Finally, the header to the various files should make it clear which file is the primary file and which are supporting files (see Section 7 for more information on packages).

4.0 NAMING CONVENTIONS

Choosing names is one of the most important aspects of programming. Good names clarify the function of a program and reduce the need for other documentation. Poor names result in ambiguity, confusion, and error. This section gives some general principles to follow when choosing names and lists specific rules for name syntax, such as capitalization.

4.1 GENERAL CONSIDERATIONS

The ideal variable name is one that instantly conveys as much information as possible about the purpose of the variable it refers to. When choosing names, play devil's advocate with yourself to see if there are ways that a name might be misinterpreted or confused. Here are some things to consider:

4.1.1 Be Consistent

Use the same name to refer to the same thing everywhere. For example, within the code for handling standard bindings in Tk widgets, a standard name w is always used to refer to the window associated with the current event.

4.1.2 Use Names in Context

If someone sees the name out of context, will they realize what it stands for, or could they confuse it with something else? For example, the procedure name BuildStructure could get confused with some other part of the system. A name like BuildGraphNode both describes what part of the system it belongs to and what it is probably used for.

4.1.3 Make Names Reflect Function

Could this name be confused with some other name? For example, it's probably a mistake to have two variables str and string in the same procedure: it will be hard for anyone to remember which is which. Instead, change the names to reflect their functions. For example, if the strings are used as source and destination for a copy operation, name them src and dst.

4.1.4 Don't Be Too Generic

Is the name so generic that it doesn't convey any information? The variable str from the previous paragraph is an example of this; changing its name to src makes the name less generic and hence conveys more information.

4.2 BASIC SYNTAX RULES

Below are some specific rules governing the syntax of names. Please follow the rules exactly, as they make it possible to determine certain properties of a variable just from its name.

4.2.1 Variable Names

Primary, exported or, global names for both procedures and variables always start with an upper-case letter. Temporary or variables that are meant only for use with in the current package or namespace should start with a lower-case letter. We chose upper-case for the exported symbols because it separates locally defined variables and functions from variables and functions defined in libraries and external packages. For example:

# countNum is a private variable
set countNum 0

# The function AddWindow is public
proc AddWindow {} \
{
  ...

# NewWindow is a public interface in the spectcl namespace
proc spectcl::NewWindow {} \
{
  ...

4.2.2 Multi-word Names

In multi-word names, the first letter of each trailing word is capitalized. Do not use under scores or dashes as separators between the words of a name.

set numWindows 0

4.2.3 Reference Variables

Any variable whose value refers to another variable has a name that ends in Name. Further more, the name should also indicate what type of variable the name is referring to. These names are often used in arguments to procedures that are taking a name of a variable.

proc foo::Bar {arrayName} \
{
  upvar 1 $arrayName array
  ...
}

4.2.4 Tcl Code Variables

Variables that hold Tcl code that will be evaluated should have names ending in Script.

proc log::eval {logScript} \ {
  if {$Log::logOn} \
  {
    set result [catch {eval $logScript} msg]
    ...

4.2.5 Partial Tcl Command Variables

Variables that hold a partial Tcl command that must have additional arguments appended before being a valid script should have names ending in Cmd.

foreach scrollCmd $listScrollCmds \
{
  eval $scrollCmd $args
}

5.0 LOW-LEVEL CODING CONVENTIONS

This section describes several low-level syntactic rules for writing Tcl code. These rules help to ensure that all of the Tcl code looks the same, and they prohibit a few confusing coding constructs.

5.1 INDENTATION

Each level of indentation should be two spaces. There are ways to set 2-space indents in all of the most common editors. Be sure that your editor really uses two spaces for the indent, rather than just displaying tabs as two spaces wide. If you use the latter approach then the indents will appear eight spaces wide in other editors.

5.2 CURLY BRACES: { GO ON A SEPARATE LINE

For indented code, open curly braces can appear on lines by themselves in Tcl by continuing the previous line. Both the open and close curly braces are indented to the same level as the outer code. Start the new level of indention on the line after the open curly brace. However, you should always use curly braces rather than some other list generating mechanism that will work in the Tcl language. This will help make code more readable, will avoid unwanted side effects, and in many cases will generate faster code with the Tcl compiler. Control structures should always use curly braces, even if there is only one statement in the block. Thus you shouldn't write code like:

if {$tcl_platform(platform) == "unix"} return

but rather:

if {$tcl_platform(platform) == "unix"} \
{
  return
}

This approach makes code less dense, but it avoids potential mistakes like unwanted Tcl substitutions. It also makes it easier to set breakpoints in a debugger, since it guarantees that each statement is on a separate line and can be named individually.

5.3 COMMENTS

Comments that document code should occupy full lines, rather than being tacked onto the ends of lines containing code. The reason for this is that side-by-side comments are hard to see, particularly if neighboring statements are long enough to overlap the side-by-side comments. Also in Tcl, it is easy to place comments in a place that could cause errors. Comments have the structure shown in Figure 3, with a blank line above and below the comment. The leading blank line can be omitted if the comment is at the beginning of a block, as is the case in the second comment in Figure 3. Each comment should be indented to the same level as the surrounding code. Use proper English in comments: write complete sentences, capitalize the first word of each sentence, capitalize important words, and so on.

# If we are running on the Macintosh platform then
# we can assume that the sources are located in the
# resource fork of our application, and we do not
# need to search for them

if {$tcl_platform(platform) == "macintosh"} \
{
  return
}

foreach dir $dirList \
{
  # If the source succeeds then we are done.

  if {![catch {source [file join $dir file.tcl]}]} \
  {
    break
  }
}

Figure 3. An Example of Commenting Style

5.4 CONTINUATION LINES

You should use continuation lines to make sure that no single line exceeds 80 characters in length. Continuation lines should be indented at least 4 spaces so that they won't be confused with an immediately-following nested block. Pick clean places to break your lines for continuation, so that the continuation doesn't obscure the structure of the statement. For example, if a procedure call requires continuation lines, try to avoid situations where a single argument spans multiple lines. If the test for an if or while command spans lines, try to make each line have the same nesting level of parentheses and/or brackets if possible. Try to end each line being continued with an operator such as +, *, &&, or ||; this helps by implying the statement is not complete. Remember the \ escapes the following character and must be the last item on the line.

5.5 ONE COMMAND PER LINE

You should have only one Tcl command per line. Do not use the semi-colon character to place multiple commands on the same line. Having only one command per line makes the code easier to read and helps with debugging.

5.6 PARENTHESIZE EXPRESSIONS

Use parentheses around each subexpression in an expression to make it absolutely clear what is the evaluation order of the expression (a reader of your code should not need to remember Tcl's precedence rules). For example, don't type

if {$x > 22 && $y <= 47} ...
Instead, type this:
if {($x > 22) && ($y <= 47)} ...

5.7 ALWAYS USE THE RETURN STATEMENT

You should always explicitly use the return statement to return values from a Tcl procedure. By default Tcl will return the value of the last Tcl statement executed in a Tcl procedure as the return value of the procedure which often leads to confusion as to where the result is coming from. In addition, you should use a return statement with no argument for procedures whose results are ignored. Supplying this return will actually speed up your application with the new Tcl compiler. For example, don't write code like this:

proc foo {x y} \
{
  if {$x < 0} \
  {
    incr x
  }\
  else\
  {
    expr $x + $y
  }
}
But rather, type this:
proc foo {x y} \
{
  if {$x < 0} \
  {
    return [incr x]
  }\
  else \
  {
    return [expr $x + $y]
  }
}
For Tcl procedures that have no return value a single return statement with no arguments is placed at the end of the procedure.

5.8 SWITCH STATEMENTS

The switch statement should be formatted as below. Always use -- to end options. This avoids the string from being confused with an option. This can happen when the string is user-generated. If the body is a single statement allow it to be on the same line as the pattern.

switch -regexp -- $string \
{
  plus -
  add {DoAddFunct}

  subtract \
  {
    # Do subtract case
    ...
  }
  default {error "Function $string Not Found"}
}

5.9 IF STATEMENTS

Do not use the word then in an if statement, however, the else word should always be used as it imparts some semantic information and is more like the C language. Here is an example:

if {$x < 0} \
{
  ...
}\
elseif {$x == 0} \
{
  ...
} \
else \
{
  ...
}

6.0 DOCUMENTING CODE

The purpose of documentation is to save time and reduce errors. Documentation is typically used for two purposes. First, people will read the documentation to find out how to use your code. For example, they will read procedure headers to learn how to call the procedures. Ideally, people should have to learn as little as possible about your code in order to use it correctly. Second, people will read the documentation to find out how your code works internally, so they can fix bugs or add new features; again, good documentation will allow them to make their fixes or enhancements while learning the minimum possible about your code. More documentation isn't necessarily better: wading through pages of documentation may not be any easier than deciphering the code. Try to pick out the most important things that will help people to understand your code and focus on these in your documentation.

6.1 DOCUMENT THINGS WITH WIDE IMPACT

The most important things to document are those that affect many different pieces of a program. Thus it is essential that every procedure interface, every structure declaration, and every global variable be documented clearly. If you haven't documented one of these things it will be necessary to look at all the uses of the thing to figure out how it's supposed to work: this will be time-consuming and error-prone.

On the other hand, things with only local impact may not need much documentation. For example, in short procedures, you don't need comments explaining the local variables. If the overall function of the procedure has been explained, and if there isn't much code in the procedure, and if the variables have meaningful names, then it will be easy to figure out how they are used. On the other hand, for long procedures with many variables, document the key variables. Similarly, when writing short procedures, it may not be necessary to have any comments in the procedure's code: if the procedure header provides enough information to figure out what is going on. For long procedures, place a comment block before each major piece of the procedure to clarify the overall flow through the procedure.

6.2 DON'T JUST REPEAT WHAT'S IN THE CODE

The most common mistake seen in documentation (besides it not being there at all) is that it repeats what is already obvious from the code, such as this trivial (but exasperatingly common) example:

# Increment i.

incr i
Documentation should provide higher-level information about the overall function of the code, helping readers to understand what a complex collection of statements really means. For example, the comment:
# Probe into the array to see if the symbol exists
is likely to be much more helpful than
# Loop through every array index, get the third
# value of the list in the content to determine
# if it has the symbol we are looking for. Set
# the result to the symbol if we find it.
Everything in this second comment is probably obvious from the code that follows it. Another thing to consider in your comments is word choice. Use different words in the comments than the words that appear in variable or procedure names. For example, the comment:
# Title: SwapPanels
#
# Description:
#   Swap the panels
# ...
is not very useful. Everything in the comment is already obvious from the procedure's name. Here is a much more useful comment:
# Title: SwapPanels
#
# Description:
#   Unmap the current UI panel from the parent frame
#   and replace it with the newly specified frame.
#   Make sure that the new panel fits into the old
#   frame and resize if needed.
# ...
This comment tells why you might want to use the procedure, in addition to what it does, which makes the comment much more useful.

6.3 DOCUMENT EACH THING IN EXACTLY ONE PLACE

Systems evolve over time. If something is documented in several places, it will be hard to keep the documentation up to date as the system changes. Instead, try to document each major design decision in exactly one place, as near as possible to the code that implements the design decision. The principal documentation for each procedure goes in the procedure header. There's no need to repeat this information again in the body of the procedure (but you might have additional comments in the procedure body to fill in details not described in the procedure header). If a user's manual needs details about how a procedure works, it should refer back to the source code. Source code will always be more current than any user's manual. The focus of a user's manual should be on how to use a procedure and not how the procedure works.

The other side of this coin is that every major design decision needs to be documented at least once. If a design decision is used in many places, it may be hard to pick a central place to document it. Try to find a data structure or key procedure where you can place the main body of comments; then reference this body in the other places where the decision is used. If all else fails, add a block of comments to the header page of one of the files implementing the decision.

6.4 WRITE CLEAN CODE

The best way to produce a well-documented system is to write clean and simple code. This way there won't be much to document. If code is clean, it means that there are a few simple ideas that explain its operation; all you have to do is to document those key ideas. When writing code, ask yourself if there is a simple concept behind the code. If not, perhaps you should rethink the code. If it takes a lot of documentation to explain a piece of code, it may be sign that you haven't found a clean solution to the problem.

6.5 DOCUMENT AS YOU GO

It is extremely important to write the documentation as you write the code. It's very tempting to put off the documentation until the end; after all, the code will change, so why waste time writing documentation now when you'll have to change it later? The problem is that the end never comes; there is always more code to write. Also, the more undocumented code that you accumulate, the harder it is to work up the energy to document it. So, you just write more undocumented code. Many people start a project fully intending to go back at the end and write all the documentation, but seldom does anyone actually do it. If you do the documentation as you go, it won't add much to your coding time and you won't have to worry about doing it later. Also, the best time to document code is when the key ideas are fresh in your mind, which is when you're first writing the code. One way to do this is to write all of the headers for a group of procedures before filling in any of the code. This way you can think about the overall structure and how the pieces fit together before getting bogged down in the details of individual procedures.

6.6 DOCUMENT TRICKY SITUATIONS

If code is non-obvious, meaning that its structure and correctness depend on information that won't be obvious to someone reading it for the first time be sure to document the non-obvious information. One good indicator of a tricky situation is a bug. If you discover a subtle property of your program while fixing a bug, be sure to add a comment explaining the problem and its solution. Of course, it's even better if you can fix the bug in a way that eliminates the subtle behavior, but this isn't always possible.

7.0 PACKAGES AND NAMESPACES

Tcl applications consist of collections of packages. Each package provides code to implement a related set of features. For example, Tcl itself is a package, as is Tk; these packages happen to be implemented in both C and Tcl. Other packages are implemented completely in Tcl such as the http package included in the Tcl distribution. Packages are the units in which code is developed and distributed: a single package is typically developed by a single person or group and distributed as a unit. It is possible to combine many independently-developed packages into a single application; packages should be designed with this in mind. The notion of namespaces was created to help make this easier. Namespaces help to hide private aspects of packages and avoid name collisions. A package will generally export one public namespace including all states and routines that are associated with the package. A package should not contain any global variables or global procedures. Side effects when loading a package should be avoided. This guide focuses on packages written entirely in Tcl. For a discussion of packages built in C, or C and Tcl see the Tcl/Tk Engineering Manual.

7.1 PACKAGE NAMES

Each package should have a unique name. The name of the package is used to identify the package. It is also used as the name of the namespace that the package exports. It is best to have a simple one word name in all lower-case like http. Multi-word names are acceptable. Additional words should just be concatenated with the first word but start with a capital letter as in specMenu.

Coming up with a unique name for your package requires a collaborative component. For internal projects this is an easy task and can usually be decided among the management or principal engineers in your organization. For packages you wish to publish you should make an effort to make sure that an existing package isn't already using the same name you are. This can often be done by checking the comp.lang.tcl newsgroup or the standard Tcl ftp sites. It is also suggested (but not required) that you register your name on the NIST Identifier Collaboration Service (NICS). It is located at: http://pitch.nist.gov/nics

7.2 VERSION NUMBERS

Each package has a two-part version number such as 7.4. The first number (7) is called the major version number and the second (4) is called the minor version number. The version number changes with each public release of the package. If a new release contains only bug fixes, new features, and other upwardly compatible changes, so that code and scripts that worked with the old version will also work with the new version, then the minor version number increments and the major version number stays the same (e.g., from 7.4 to 7.5). If the new release contains substantial incompatibilities, so that existing code and scripts will have to be modified to run with the new version, then the major version number increments and the minor version number resets to zero (e.g., from 7.4 to 8.0).

7.3 PACKAGE NAMESPACES

As of version 8.0, Tcl supports namespaces to hide the internal structure of a package. This helps avoid name collisions and provides a simpler way to manage packages. All packages written for Tcl 8.0 or newer should use namespaces. The name of the name space should be the same as the package name.

7.4 STRUCTURE

There are a couple of ways to deploy a package of Tcl commands:

8.0 TESTING

One of the environments where Tcl works best is for testing. While Tcl has traditionally been used for testing C code it is equally as good at testing other Tcl code. Whenever you write new code you should write Tcl test scripts to go with that code and save the tests in files so that they can be re-run later. Writing test scripts isn't as tedious as it may sound. If you're developing your code carefully you're already doing a lot of testing; all you need to do is type your test cases into a script file where they can be reused, rather than typing them interactively where they vanish after they are run.

8.1 BASICS

Tests should be organized into script files, where each file contains a collection of related tests. Individual tests should be based on the procedure test, just like in the Tcl and Tk test suites. Here are two examples:

test expr-3.1 {floating-point operators} \
{
  expr 2.3*.6
} 1.38
test expr-3.2 {floating-point operators} {unixOnly} \
{
  list [catch {expr 2.3/0} msg] $msg
} {1 {divide by zero}}
test is a procedure defined in a script file named defs, which is sourced by each test file. test takes four or five arguments: a test identifier, a string describing the test, an optional argument describing the conditions under which this test should run, a test script, and the expected result of the script. test evaluates the script and checks to be sure that it produces the expected result. If not, it prints a message like the following:
==== expr-3.1 floating-point operators
==== Contents of test case:

   expr 2.3*.6

==== Result was:
1.39
---- Result should have been:
1.38
---- expr-3.1 FAILED

To run a set of tests, you start up the application and source a test file. If all goes well no messages appear; if errors are detected, a message is printed for each error.

The test identifier, such as expr-3.1, is printed when errors occur. It can be used to search a test script to locate the source for a failed test. The first part of the identifier, such as expr, should be the same as the name of the test file, except that the test file should have a .test extension, such as expr.test. The two numbers allow you to divide your tests into groups. The tests in a particular group (e.g., all the expr-3.n tests) relate to a single sub-feature, such as a single procedure. The tests should appear in the test file in the same order as their numbers.

The test name, such as floating-point operators, is printed when errors occur. It provides human-readable information about the general nature of the test.

Before writing tests, look over some of the test files for Tcl and Tk to see how they are structured. You may also want to look at the README files in the Tcl and Tk test directories to learn about additional features that provide more verbose output or restrict the set of tests that are run.

8.2 ORGANIZING TESTS

Organize your tests to match the code being tested. The best way to do this is to have one test file for each source code file, with the name of the test file derived from the name of the source file in an obvious way (e.g. http.test contains tests for the code in http.tcl). Within the test file, have one group of tests for each procedure (for example, all the http-3.n tests in http.test are for the procedure http::geturl). The order of the tests within a group should be the same as the order of the code within the procedure. This approach makes it easy to find the tests for a particular piece of code and add new tests as the code changes.

The Tcl test suite was written a long time ago and uses a different style where there is one file for each Tcl command or group of related commands, and the tests are grouped within the file by sub-command or features. In this approach the relationship between tests and particular pieces of code is much less obvious, so it is harder to maintain the tests as the code evolves. This approach is not recommended for new tests.

8.3 COVERAGE

When writing tests, you should attempt to exercise every line of source code at least once. There will occasionally be code that you can't exercise, such as code that exits the application, but situations like this are rare. You may find it hard to exercise some pieces of code because existing Tcl commands don't provide fine enough control to generate all the possible execution paths. In situations like this, write one or more new Tcl commands just for testing purposes. It's much better to test a facility directly then to rely on some side effect for testing that may change over time. Use a similar approach in your own code, where you have an extra file with additional commands for testing.

It's not sufficient just to make sure each line of code is executed by your tests. In addition, your tests must discriminate between code that executes correctly and code that isn't correct. For example, write tests to make sure that the then and else branches of each if statement are taken under the correct conditions. For a loop, run different tests to make the loop execute zero times, one time, and two or more times. If a piece of code removes an element from a list, try cases where the element to be removed is the first element, last element, only element, and neither first element nor last. Try to find all the places where different pieces of code interact in unusual ways, and exercise the different possible interactions.

8.4 FIXING BUGS

Whenever you find a bug in your code it means that the test suite wasn't complete. As part of fixing the bug, you should add new tests that detect the presence of the bug. Writing the tests after you've located the bug but before you fix it is recommended. That way you can verify that the bug happens before you implement the fix and the bug doesn't happen afterwards, so you'll know you've really fixed something. Use bugs to refine your testing approach: think about what you might be able to do differently when you write tests in the future to keep bugs like this one from going undetected.

8.5 TRICKY FEATURES

Use tests as a way of illustrating the need for tricky code. If a piece of code has an unusual structure, and particularly if the code is hard to explain, try to write additional tests that will fail if the code is implemented in the obvious manner instead of using the tricky approach. This way, if someone comes along later, doesn't understand the documentation for the code, decides the complex structure is unnecessary, and changes the code back to the simple (but incorrect) form, the test will fail and the person will be able to use the test to understand why the code needs to be the way it is. Illustrative tests are not a substitute for good documentation, but they provide a useful addition.

8.6 TEST INDEPENDENCE

Try to make tests independent of each other, so that each test can be understood in isolation. For example, one test shouldn't depend on commands executed in a previous test. This is important because the test suite allows tests to be run selectively: if the tests depend on each other, then false errors will be reported when someone runs a few of the tests without the others.

For convenience, you may execute a few statements in the test file to set up a test configuration and then run several tests based on that configuration. If you do this, put the setup code outside the calls to the test procedure so it will always run even if the individual tests aren't run. Keep a very simple structure consisting of setup followed by a group of tests. Don't perform some setup, run a few tests, modify the setup slightly, run a few more tests, modify the setup again, and so on. If you do this, it will be hard for people to figure out what the setup is at any given point and when they add tests later they are likely to break the setup.

9.0 MISCELLANEOUS

9.1 PORTING ISSUES

Writing portable scripts in Tcl is actually quite easy as Tcl itself is quite portable. However, issues do arise that may require writing platform specific code. To conditionalize your code in this manner you should use the tcl_platform array to determine platform specific differences. You should avoid the use of the env variable unless you have already determined the platform you are running on via the tcl_platform array.

As Tcl/Tk has become more cross platform capable, commands have been added that aid in making your code more portable. The most common porting mistakes result from assumptions about file names and locations. To avoid such mistakes always use the file join command and list commands so that you will handle different file separation characters or spaces in file names. In Tk, you should always use provided high level dialog boxes instead or creating your own. The font and menu commands have also been revamped to make writing cross-platform code easier.

9.2 CHANGES FILES

When a package consists of a single file, the changes to that file are tracked by the revision control system for the file.

When a package consists of multiple files, the package should contain a file named changes that keeps a log of all significant changes made to the package. The changes file provides a way for users to find out what's new in each new release, what bugs have been fixed, and what compatibility problems might be introduced by the new release. The changes file should be in reverse chronological order. Just add short blurbs to the top of the file each time you make a change. Here is a sample from the Tk changes file:

5/26/94 (feature removed) Removed support for "fill" justify mode from Tk_GetJustify and from the TK_CONFIG_JUSTIFY configuration option. None of the built-in widgets ever supported this mode anyway. (SS)
*** POTENTIAL INCOMPATIBILITY ***

5/20/94 (new feature) Added "bell" command to ring the display's bell. (JO)

5/19/94 (bug fix) Canvases didn't generate proper Postscript for stippled text. (RJ)

The entries in the changes file can be relatively terse; once someone finds a change that is relevant, they can always go to the code to find out more about it. Be sure to highlight changes that cause compatibility problems, so people can scan the changes file quickly to locate the incompatibilities. Also be sure to add your initials to the entry so that people scanning the log will know who made a particular change.

REVISION HISTORY


February 2001
Beth Yockey
Minor corrections per GUMP CR

July 1999
Tracie McCoy
Reordered Revision History to reflect Style Guide

March 1999
Joel Cook
Generic Style Revisions

May 1998
Duane Braford
Preliminary Document

BIBLIOGRAPHY

GANS96

Ganssle, J. G. "A Modest Software Standard", Embedded Systems Programming,
March, 1996, http://www.embedded.com

JOHN97

Johnson, Raymond, Tcl Style Guide, Sun Microsystems, Aug. 1997

MICR96

MicroSol Corp., "Software Development and Standards Manual",
Columbia, MD: MicroSol Corp., 1996

WIEG95

Wiegers, K. "Improving Quality with Software Inspections."
Software Development, Apr. 1995

GLOSSARY

GUMP
Generic UWF Maintenance Process

HTTP
Hyper Text Transfer Protocol

NICS
NIST Identifier Collaboration Service

SCCS
Source Code Control System

Tcl
Tool Command Language

Tk
Tool Kit

UWF
University of West Florida