Contents

2.11 COORDINATES package

This chapter began by looking at a poor specification for coordinate transformation package. It is fitting to end the section by applying all we've learned to make it better. Figure 14 shows an improved version of the package specification.

Most of the improvements will show up in the package body, so there isn't too much difference between Figure 14 and Figure 1. The comments telling us that linear distances are measured in feet and angles are in degrees have been eliminated because they say the same things that the component declarations say. (Ironically some code quality metric tools might tell us that the original version is better than the improved version because the original version has more comments.) Replacing the comments with dimensioned data types may not make much difference to human readers, but it makes a big difference to Ada because she can read data types, and she can't read comments. This lets her check dimensional consistency. On a large program she is more likely to find errors than a human would.

2.11.1 Comments

Adding comments to an Ada program can counter-productive, so it is worth while to talk about how to comment an Ada program. Many of you have been taught that the more comments a program has, the better it is. That just isn't true. For example, if your program includes the statement X := A + B; and you comment that line with the phrase "-- Add A to B to get X" you have made the program the program worse, not better.

What's wrong with adding a comment like that? Two things are wrong. First, you have repeated information. Whenever you do that, you create an opportunity for contradiction and confusion. Suppose the program doesn't work properly because a third term, C, has to be added to X. Someone discovers the error and changes the code, but forgets to change the comment. Then when another maintenance programmer looks at the code to solve another problem, he will get confused because the comment contradicts the code. He may figure the comment is correct, and take C out of the equation.

Second, too many frivolous comments can clutter your program so much that you can't easily find the important comments. Once a bad comment is in a program it enjoys all the respect and status of any other comment in the program. There is no easy way to tell good comments from bad comments. I've occasionally used a text editor to remove all the comments from someone else's program because there were so many frivolous comments I couldn't easily find the executable lines of code. In so doing, I probably lost some valuable information, but that information was already lost in the clutter.

Since it is practically impossible to remove bad comments from a program, the only solution to the problem is to not insert them in the first place. Every organization seems to have some internal standard for a header block of comments. These header blocks include things such as the module name, a brief description of what it does, who wrote it, when the last revision was made, and so on. I have my own format which you have seen in the listings, but I don't have any burden for a particular header format. Header blocks aren't a problem. They generally contain important information, and since they are at the beginning of the program they can easily be skipped if they don't. Use whatever style header you like. The comments sprinkled through the listing are the ones that are likely to cause problems.

The nature of the Ada language makes many comments unnecessary. For example, it is important to know which modules depend on each other. In some languages it might be appropriate to include comments telling about these dependencies. This doesn't make sense in Ada because the dependencies are shown by context clauses (i.e. with LIBRARY_UNIT;) and separate clauses (separate(Main_Program)). Ada requires these clauses to be at the beginning of every compilation unit, so you don't have to search through the code to find them. If you refrain from excessive USE clauses, then dot notation will make dependencies obvious in the executable code. For example, if your program is calling the procedure Normalize from the package LIBRARY_UNIT, your statement will be LIBRARY_UNIT.Normalize(X); and there is no need to comment that Normalize is found in LIBRARY_UNIT.

One thing Ada doesn't do is force you to include information about source file names. This makes it possible for you to write a body stub like, procedure Accumulate is separate; which doesn't tell the name of the file containing the source code for Accumulate. Ada doesn't care what the name of the source file is. Some Ada implementations may record that information in the library as a courtesy to you, so you can write automatic recompilation tools, but it isn't required by the language. Even if the information is in the library, you shouldn't force the maintenance programmer to run a library utility to find it. Always follow a separate clause with a comment like -- File ACCUM.ADA so people will know where to find the source code.

Ada package specifications provide a top level view of what services the package provides without a hint at how it works. The comments should be consistent with this policy. Comments describing the implementation of subprograms have no place in a package specification. I like to let subprogram declarations serve as headings for a paragraph or two describing what the subprogram does. Sometimes I include samples of how the subprogram should be used. If it can raise an exception it is important to include comments telling that the subprogram may raise that exception, and under what circumstances that might happen. When a package declares user-defined exceptions, I always include comments below the exception declaration that tell what subprograms can raise the exception. I try not to repeat what conditions cause that exception to be raised because that information should be in the comments describing the subprogram. (If many subprograms can raise the same exception, I sometimes save space by describing the conditions that cause the exception to be raised under the exception, and let the subprogram specifications refer to the comments in the exception handler.)

Unlike package specifications, Ada package bodies shouldn't include descriptions of what the subprograms do. At best, those comments would exactly duplicate the comments in the package specification. At worst they would contradict those comments and confuse the reader. Most often they would have no effect because a rational person wouldn't think to look in a package body for comments describing the purpose of a subprogram. Package bodies should contain comments describing how the subprograms work. These comments shouldn't describe what you are doing because that should be obvious from the code. If you find you need to comment a line like P := GI - E; you should rewrite it as PROFIT := GROSS_INCOME - EXPENSES; to eliminate the need for the comment. Never write a comment that could just as well be expressed as executable code.

Comments should describe why you are doing something. For example, if the package body says for PORT in 1..MAX_PORTS loop ... the comment should not say, "-- Do the following statements for every input port". Instead, just before the loop there should be a comment saying, "--We need to check all the input ports to see if any new messages have been received." This tells the reader why the loop is there and what it is trying to accomplish, rather than the obvious fact it is a loop.

Good programmers always indent if statements and control loops to show structure. They recognize how white space can improve readability. Blank lines can also do the same thing. If a group of three to ten lines cooperate to do a particular operation, use blank lines to separate those statements from the surrounding code. It has the same effect as breaking an essay into paragraphs. When you have isolated a few statements into a paragraph, it usually is appropriate to begin that paragraph with a comment to serve as a topic sentence. This not only helps the maintenance programmer understand your program, it helps you write better code because you will occasionally discover that one of the statements in the paragraph doesn't fit with the topic sentence. This is usually an indication that it belongs someplace else.

Ada will check everything in your program except the comments. That means you have to pay particular attention to them yourself. Be sure that you don't have a lot of useless comments cluttering up your code. Don't use a comment to repeat something that is expressed in Ada statements. Wisely use comments to point to important information, like the name of the file containing a subunit or the names of all the subprograms that can raise a user-defined exception. Use comments near a subprogram declaration to tell what that subprogram does. Use comments inside a subprogram body to describe why it is doing what it is doing. Use comments and white space inside a subprogram body to break it into logical processing steps. If you need comments inside a subprogram body to explain how it works, you need to ask yourself why that isn't obvious from the code, and perhaps rewrite the code to make the comment unnecessary.

2.11.2 Other Improvements in COORDINATES

The derivation of type Feet forces the distances to be expressed in 32-bit integers (or longer integers with constraints limiting them to 32-bit range). The original version only told us that distances were expressed as integers, but did not fix their length. It used different lengths on different systems. The improved version is more consistent.

The real differences show up in the package bodies. Figure 15 shows the poor version. It works, but it has some problems. Most of the problems have to do with transportability. There is the previously mentioned problem of undefined integer size, and also a reliance on a package found only in the Meridian Ada compiler library. Fortunately Figure 15 has used dot notation rather than a USE clause so a text editor can search for MATH_LIB to find all the potentially nonportable statements.

The original package is cluttered with conversion constants (RADIANS_TO_DEGREES and DEGREES_TO_RADIANS). These are places where errors are likely to creep into the code. It also includes logic to figure out which quadrant to use. At best, this logic was copied out of one of several other application programs that needed a four quadrant arctangent. At worst it was written from scratch and took several iterations to get all the bugs out. In either case it took some duplication of effort.

Figure 15 has some nasty surprises in it. First, it blows up for points more than 8.78 miles from the origin. Although 32 bits can almost represent the number of feet in a round trip to the moon, 32 bits can only represent 46,340 feet squared. The declaration R_SQUARED : integer; is inadequate even on 32-bit machines. Second, the four quadrant logic returns an angle of 270 degrees when NORTH and EAST are both zero, instead of raising an INVALID_ARGUMENT exception.

The improved version in Figure 16 is much shorter and cleaner. It uses 32-bit integers no matter what computer it is running on, and it is directly transportable to any system that has the TRIG package on it. It is shorter because the four quadrant arctangent is in the TRIG package (where it belongs). The reuse of TRIG.Atan saves time and improves reliability because the four quadrant logic does not need to be developed and tested again.

In a large program there might be several times when distances are multiplied to compute an area. Then I would write a function that multiplies two distances in Feet and returns a floating-point type Sq_ft. I would also write a function that takes the square root of an object of type Sq_ft and returns Feet. In this little example, distances are only squared once, so it isn't worth the trouble to write a special function. Instead I move into the dimensionless number domain to calculate the square root of the sum of the squares. Since Ada can't check these statements for me, I bracket them with comments to draw my attention to them.

The final version of the COORDINATES package gives us a library unit that defines all the data types the application program is likely to need. It defines Feet for linear distances, Rectangular_points for Cartesian coordinates, and Polar_points for polar coordinates. (A realistic version of this package would use three dimensional coordinates and include more dimensional data types, too.)

This package specification should be given careful consideration, because most of the other modules in the application program will depend upon this package for type definitions. If you change COORDINATES you will have to recompile every module that depends on it. That's a two edged sword. You won't want to add data types to the package because you cut yourself every time you do, suffering the pain of recompilation with every change. But the sword also protects you because it tells you which modules need to be reexamined in light of your recent change in type definitions.

2.11.3 USE Clauses

I avoid USE clauses whenever practical. Special emphasis should be placed on the word "practical" because it was intentionally chosen instead of "possible." It is always possible to avoid a USE clause, but I feel there are times when it isn't practical.

I like to avoid USE clauses because I like to remind myself which package defined the type or subprogram. It shows me exactly were the dependencies are. My general rule is, don't use the USE.

There are exceptions to the rule. Some packages, like TEXT_IO, are so common that I don't need to be reminded about them. A statement like TEXT_IO.new_line; is no more informative than new_line;, so I commonly use TEXT_IO;. But, if I have added a line like TEXT_IO.put_line("Section 1 entered"); for diagnostic purposes, then I retain the dot notation to make it easy to take the diagnostic line out.

Another package that is so common it doesn't need dot notation is STANDARD_INTEGERS. There is real motivation to use STANDARD_INTEGERS; because it makes arithmetic visible. That is, it is legal to say X := Y + Z; if X, Y, and Z are all STANDARD_INTEGERS.Integer_32. If you don't use a USE clause, you have to say X := STANDARD_INTEGERS."+"(Y,Z);. I think that distracts from the program logic, especially if it is part of a complex equation. True, you could use renaming declarations to rename all the arithmetic functions, but that is longer and no clearer than this USE clause and comment:

use STANDARD_INTEGERS; -- makes 32 bit arithmetic operators visible
Contents | Next ...