Programming With Style 

Contents of Chapter 3

  1. Construction Tools

3 Construction Tools

Thousands of years ago people wielded tools as a means of survival. Today, tool use remains an integral part of life. In software engineering, scores of tools have been developed and applied to all phases of program production. A good construction tool is any application or piece of code that can be used with minimal effort or modification to accomplish a task and save time. This chapter will focus on utilities available to assist with program construction and management. However, before beginning that endeavour, we should stop and consider some of the merits of developing your own tools.

Intelligent programmers build tools today to save time tomorrow. Tool building has become an essential element of programming. For example, professional software development organizations often produce their own tools for internal use and support. These tools can be as simple as scripts or batch files to automate repetitive tasks, or they may be customized project-specific applications rivalling many commercially available products. Even if you are only writing code for yourself, applying these ideas on a smaller scale can be a valuable asset.

Aside from utilities, modular libraries of routines may also be considered tools. A code library should contain application-specific functions used in more than one program. Clearly, code reusability through generalized, independent routines is an effective way to improve your productivity. When working in a team, consider establishing standard libraries of common routines rather than having each member reinvent the wheel.

Code libraries are an excellent (but not the only) way to increase productivity. As mentioned, this chapter will focus on the large number of tools available to help you program with style. The utilities discussed in the remaining sections of this chapter have been organized into general categories. You will notice that this chapter does not end with a summary. The reason for this is that the chapter itself serves as a summary. That is, since each of the utilities mentioned is complicated enough to have its own manual, no attempt will be made to explain how to use each one specifically. Rather, the purpose of this survey is to provide reasons why you might consider using a certain tool for a specific job. We will begin by looking at how to use compiler options to your advantage.

3.1 Compilers

For obvious reasons, compilers are the most critical tool for software construction. Smart programmers learn how to use the features of their compiler to best advantage. If possible, it is advisable to set warning sensitivity (pickiness) to its highest level. Doing so will force the compiler to flag any problems, no matter how minor, within your code. Remember, even if trivial warning messages are displayed, it is better to knowledgeably ignore them than to not know about possible problems at all! Overall, by curing even the most trivial warnings, you are ensured a higher quality end product.

Also, for advanced debugging purposes, it is a good idea to instruct the compiler to include debug information in each test executable. Of course, setting the debug flag is not needed for the final program release. In a team environment everyone should use the same compiler settings; otherwise, when you try to combine code compiled differently you are likely to get a deluge of compiler messages and an integration nightmare. Keep these ideas in mind when choosing and using your compiler.

3.2 Debuggers

Symbolic debuggers, like "gdb", are helpful because they let programmers step through code line by line, inspect and change the values of data objects, and always interpret code the same way that the computer does. The ability to step through a piece of code and watch it work is enormously beneficial and can serve as a learning tool to understand exactly how code executes.

As mentioned in the previous chapter, it is always a good idea to have someone else test your code. If this is not possible, a good debugger may provide a favourable substitute because it does not have the blind spots that the original programmer might. Aside from the original purpose of diagnosing errors already detected by testing, many debuggers also allow you to toggle between high-level code and assembler listings, watch registers and the stack to see how arguments are passed, examine the type of optimizations performed by the compiler, and to keep a log of when specific statements are executed.

Without question, using a debugger is more elegant than scattering messages like: "I'm here!" throughout a program. However, it is wrong to assume that a debugger is a substitute for good thinking. By the same token, clear thinking is not always a substitute for a modern debugger either. A combination of the two is required to maximize code quality.

3.3 Editors

What could be more important to actual code entry than a dependable text editor? Programmers in industry spend up to 40% of their time editing code; therefore, a customizable programming editor is a worthwhile investment. Some of the features to look for in an editor are: multiple file editing with search and replace, ability to compile code and report errors from within the editor, interactive help for the language being used, brace matching, templates for common language constructs, colour syntax highlighting, smart indenting, etc. Some editors, such as "emacs", can be set up to automatically generate program constructs in an configurable way. For new code, these features make it easier to follow coding standards. Please realize that your editor does not need to have all of these features, just the characteristics that you deem most valuable to the way that you write code.

3.4 Language checkers

The purpose of a language checker is to supplement the syntax checking done by a compiler. Modern compilers are getting better and better at detecting subtle code portability problems, and semantic uses of a language which the programmer may not have intended. For this reason, the need for language checkers has been declining in recent years. Most language checkers are geared toward C because it tends to be more free form than other languages. The most well known language checker, available on most systems supporting C, is called "lint". Lint is especially good at detecting portability problems, so if code portability is a major concern you should use lint.

3.5 Librarians

Librarians help to create and maintain object-code libraries. An object-code library is a collection of separately compiled or assembled object files combined into a single file (usually with a .lib or .a extension). Libraries provide a convenient way to organize commonly used routines and modules, for easy use and distribution. A program that calls library routines is linked with the library to produce an executable file by the linker. As will be explained in the next section (3.6), only necessary routines, not all library routines, are linked into the executable file.

3.6 Linkers

Linkers combine object modules, generated by a compiler, and library files, created by a librarian, to produce an executable file. Most linkers only include the code from object files or libraries which is necessary for the executable to run. For example, if you had a library of 100 mathematical functions, and only one was actually called in the executable, the linker would only include the routine that was needed. Intelligent linking helps to eliminate superfluous code from the final executable program.

3.7 Pretty printers

Code beautifiers reformat a program according to a consistent standard. Most pretty printers allow for customization of nearly all elements of style. Some programmers are under the misconception that people should program in their own unique style and run code through a program like "cb" or "indent", to make it presentable before they give the code to someone else. If nothing else, this handbook has made it clear that style is not only about code layout, but involves many other factors. For example, code beautifiers cannot add comments, create meaningful identifier names, or correct obscure language usage. The only rational use of beautifiers is to bring the many diverse styles of old code up to a common standard today.

3.8 Profilers

Execution profilers measure the complexity of code. The most simple profilers indicate complexity through quantitative time measurements. Other more elaborate profilers can produce a map showing where the bottlenecks in the code are located. Like debuggers, profilers can be beneficial because, by studying the execution of code through a profiler, programmers are in a better position to uncover hidden errors in program logic. Even if you do not detect a problem through the profiler, by reading the output you can assure yourself that the program spends a reasonable amount of time in each area of code. Aside from giving insight into how a program works, profilers help to locate areas on which to concentrate code-tuning efforts should the programmer decide optimization is truly necessary.

3.9 Source code managers

Programs such as "sccs" and "rcs" allow you to efficiently manage project construction. These version control systems provide an effective way to monitor the modifications made to source files. This can be especially helpful when more than one programmer needs access to the same code. A history file is maintained to track any changes.

3.10 Make utilities

The "make" utility is used to recompile only those modules that have been changed since the last time it was used. In order to operate make, you must provide it with a description of your project in a file called a "makefile". This section is not a tutorial on how to program a makefile. Rather, it merely explains the proper layout of each section in a makefile to maximize readability and simplify maintenance. Before getting into makefile design, a few points regarding appropriate source file organization should be mentioned.

To begin with, to effectively use make, it is helpful to put all of the files for one program in a unique directory with its own makefile. If you are working on two or more programs use separate directories. This will remove the difficulty of separating the files for different programs and improve project manageability. If several programs share common functions or files, consider incorporating the shared items in a library and placing it in a special subdirectory accessible to all. With your files well organized, we can concentrate specifically on makefile design.

A well designed makefile is essential to optimise project compilation; therefore, it is unfortunate that many students have difficulty producing well organized and readable makefiles. As with other source files in a project, makefiles should adhere to certain standards. In general, they should include six major sections: heading comments, macro defines, major targets, minor/other targets, special compilation rules, and dependencies. Each of these sections will be dealt with in turn.

The remainder of this section will demonstrate each of the six makefile components for the simple and well-known, "Hello World!" application in C. Although this sample is very simple, it clearly illustrates the layout of the major elements. This example is developed slowly with text and example code fragments interspersed. The final makefile can be visualised by appending all of the example makefile code fragments together and ignoring the explanatory text.

1] Heading Comments: These comments are the first thing that a programmer will see when studying the makefile. As with any program source file, the heading comment should at least explain what the makefile does, how to use it, and information regarding the author and date of writing as below:

############################################################

#

# Makefile for managing the simple program "hworld"

#

# Written by: Vikram Rao January 1, 199x

#

 

Macro definitions are often used to allow for compile-time configuration of program construction. This will be explained in more detail later. For now, remember that any configurable information should be listed and explained in the header comment:

# Set variable SYSTEM to the appropriate value for your O/S

#

# SYSTEM=-DOS If DOS Based Compiler

# SYSTEM=-SCO If SCO UNIX

# SYSTEM=-SYSV If AT&T System V UNIX

 

When executing make, it is possible to specify a standard target on the command line. As with macro definitions, a list of standard targets should be specified in the heading comment such that the user knows which targets are valid and what they do.

# The valid targets are:

#

# all [default] - create "hworld" executable

# lint (on UNIX) - run hworld through program checker

# clean - remove any scrap object files

#

############################################################

 

The targets used above are only a few of the commonly employed ones. Some other conventional targets include: "debug" to compile the program with the debug flag enabled; "print" to send the sources to a printer; "srcs" to check the source code out from a software control system such as SCCS.

2] Macro Definitions: Recall, in the header comment, we listed three configuration options for the SYSTEM macro. When providing a set of options, it is useful to list every valid definition and comment out all but the selected one. It is a well established convention that all macro names be in upper case only. For example:

# MACRO DEFINITIONS ########################################

# Set variable SYSTEM to the appropriate value for your O/S

SYSTEM=-DOS # For DOS Based Compiler

#SYSTEM=-SCO # If SCO UNIX

#SYSTEM=-SYSV # If AT&T System V UNIX

 

Macro defines allow the programmer to specify simple text macros for a variety of items; such as, source files to compile, compiler name, and compilation flags. Macros make it easy to globally modify a makefile by simply changing one definition. Many macros are predefined, so check your manual. Each macro should be preceded by a blank line and a short comment that explains the macro, as follows:

# The standard C compiler.

CC = cc

# Compile with debug information enabled.

CFLAGS = -g $(SYSTEM)

# The source file for our program.

SOURCE = hworld.c

# The object file for our program.

OBJECT = hworld.o

3] Major Targets: Up to this point we have simply been defining things. Now it is time to actually instruct make to do something. The third section of a well organized makefile should contain rules for all of the major targets listed in the comment header. That is:

# MAJOR TARGETS ############################################

all: hworld

lint:

lint hworld.c

clean:

rm -f hworld.o

4] Other/Minor Targets: Minor targets are the intermediate steps required for the building of major targets. This makefile is very simple, and only has one minor target. This is commonly the largest part of a makefile when dealing with more substantial projects. As illustrated above, the major (default) target "all" depends on the minor target "hworld", shown below:

# MINOR TARGETS ############################################

hworld: $(OBJECT)

$(CC) $(CFLAGS) -o hworld $(OBJECT)

5] Special Rules: Make knows how to deal with most standard compilers, but when using a special compiler, it may be necessary to define a unique transformation rule for it. For example, perhaps an application calls for yacc, a parser generator, to convert a series of *.y to *.c files. Or, maybe it is necessary to run all C source files through a special pre-processor. Again, for our simple program, no special rules are required. Still, it is considerate to indicate this explicitly with a comment:

# SPECIAL RULES ############################################

# NO Special Rules.

6] Dependencies: This section shows the association between each binary file and the various source files it depends on. Notice that since this is the last section in the makefile, we clearly indicate the end of the file.

# DEPENDENCIES #############################################

hworld.o: hworld.c hworld.h

# End Of Makefile ##########################################

The code above would inform make that hworld.o is created from hworld.c and hworld.h. The dependencies section is the weakest link in the makefile, because if a source file is missing or frequently out of date, most make utilities will be unable to complete the build successfully. More recently, advanced make utilities have automated dependency checking thus eliminating the need for the dependencies section entirely.

Again, the purpose of this subsection was only to illustrate the correct usage of the six conventional parts of a well designed makefile. Any experienced programmer will tell you that learning to write a properly organized makefile is well worth the effort. If you would like to learn more about this topic, there are many excellent references available.[Oram and Talbott 92]

Please realize that even if makefile facilities are not available to you, creating simple batch or script language programs can be invaluable for managing project construction. Since compiling code can be very mechanical and repetitive, why not let the computer handle most of it for you? In short, take advantage of any and all tools provided by the environment on which you are working.

3.11 Search/Replace programs

Search tools such as "grep" make it easy to search multiple source files for exact or similar strings, as well as complex string patterns (regular expressions). Other tools like "awk", "perl", and "sed", are useful to locate and change strings across multiple files, for example, if you decide to give a global identifer a clearer name.

Finally, cross-reference utilities, like "cflow" or "cscope", have been classified here as a search tool. Cross-referencers allow programmers to search multiple source files and record variables, routines, and all the places that they are used under one listing. Using these tools and the others mentioned in this chapter, guarantee improved programmer productivity and overall program excellence.