I have added representation clauses to the type definitions in STANDARD_INTEGERS. There were times when I needed to interface with operating system data structures, and I needed to be sure that Integer_16 really was just sixteen bits.
I have abandoned the file naming convention I used to use. The old convention was based on initials. For example, the TRIG package specification was in file TS.ada, and the TRIG package body was in file TB.ada. Some of the file names (like ftbcgfp.ada for FORM_TERMINAL.Create. Get_Field.Protect_Field) were bizarre to say the least.
The last straw came when I wrote a magnetic tape interface package interface called TAPE. Naturally, I called its specification TS.ada, and the body was in TB.ada. I happened to copy these files into a directory that contained the TRIG files, and clobbered them.
I now use a file naming convention based on part numbers that I assign to reusable software components. For a complete description of the new convention, see the file pub/users/do_while/components/names.txt.
I have also abandoned the type naming convention (described in section 2.10) which uses abbreviations for real types and full names for integer types. It was more trouble than it was worth.
I've adopted a more structured format for the software documentation. For each software component, I write a four- part document.
Part 1 is the Programmer's Guide. It tells what the software component does, and how to use it. It is really an expanded description of the component's specification.
Part 2 contains Implementation Notes. It tells how the software component works, with special emphasis on why design decisions were made. If other approaches were considered and rejected, they are described and an explanation is given why they weren't used. (Sometimes the explanation is, "They all seemed to be pretty much the same, but I had to pick one, so I did.")
Part 3 is titled Suggested Improvements. When writing the first two parts, I often think of things I could have done better. I document them in Part 3, and usually implement them the next time I revise the component. Eventually, Part 3 just contains the word, "None."
Part 4 takes two different forms, depending on the situation. The hard-copy document contains complete source listings and all associated data files for the component. When I put the document on the Internet, however, I change Part 4 to the list of files that should be in Part 4. All the files referenced in Part 4 are in the same FTP directory, so you can create a hard-copy document by catenating the listed files to the end of the documentation file. (That's easier than un-catenating the files from the end of the document so you can compile them.)
My software components are grouped in families. There is always a document for part number "00" of the family, which is really an overview of all the components in the family. It briefly lists all the components in the family and tells, in a sentence or two, what they do. It ends with a suggested compilation order.
It is customary to encapsulate data types with their subprograms in a package. This is certainly a good thing to do for abstract (private and limited private) types. It creates a clean, complete abstraction that is easy to maintain.
But what is good for abstract types isn't necessarily good for other types. Consider the TRIG package. I defined the data types Deg and Rad in the TRIG package. I later discovered that there were many times when I wanted to declare objects of type Deg or Rad when I had no intention of doing any trigonometry at all. I just wanted to add some angles together. I had to "WITH TRIG" and "USE TRIG" to make the addition operation visible. This had the side effect of making all the TRIG subprograms visible, and possibly added a lot of "dead code" to the program.
I ran into a similar situation when I wrote a package called BASIC_GRAPHICS which defined the data types X_coordinates and Y_coordinates that defined locations on the screen. I often found myself USEing BASIC_GRAPHICS just to make the type definitions of the coordinates visible. It really seemed strange to me that the MENU package should have to be compiled in the context of a BASIC_GRAPHICS package that draws circles and polygons, and many other things that have nothing to do with displaying a menu (other than the fact that you have to tell the menu where to appear on the screen, which requires an x/y coordinate point).
I now separate visible type definitions from operations. That is, the ANGULAR_UNITS package (Physical Units part number PU08) defines the types Degrees and Radians. The new specification of TRIG (Ada in Action part number AA09) is compiled WITH ANGULAR_UNITS so it can USE Degrees and Radians. Other packages can be compiled WITH ANGULAR_UNITS to get just the data types and primitive operations, without unnecessary TRIG functions. Similarly, X_coordinates and Y_coordinates are now defined in BASIC_GRAPHICS_TYPES (Graphical Interfaces part GI03) rather than BASIC_GRAPHICS (part GI04).
I put too much unrelated stuff in ASCII_UTILITIES. If I had it to do over again, I would have made Fixed_Image, Float_Image, and Value three separate library procedures. (I would have called the Value function Real_Value instead.) I would have put Number_bases, Value, and Image in a package called DIGIT_CONVERSIONS. (If I had done that, there would have been no need for the discussion of qualified expressions in section 3.1.2.)
There isn't really any harm in putting all this unrelated stuff in ASCII_UTILITIES, but it doesn't set a very good example for readers.
I first ran into a need for finalization when I ported the VIRTUAL_TERMINAL to the Encore computer running the MPX- 32 operating system. I ran into it again when I ported the VIRTUAL_TERMINAL to UNIX. In both cases it was necessary to change some console interface parameters.
For example, in UNIX, I had to turn off the character echo and disable the canonical mode so I could get unfiltered characters from the keyboard as soon as keys were pressed. This was easily done by putting the appropriate instructions at the end of the VIRTUAL_TERMINAL package body, where it would be executed automatically at elaboration. The problem was that I had to remember to reset the console interface parameters when the program finished, or else the UNIX command line interface would not work!
I encouraged the Ada 9X team to include package finalization in Ada 95. If they had taken my suggestion, I could have written the VIRTUAL_TERMINAL package body this way.
The procedures Echo_Off and Canonical_Off would be called automatically during the elaboration of VIRTUAL_TERMINAL. When the package passed out of scope, package finalization would automatically call Echo_On and Canonical_On. The application programmer would not have to worry about them.
I lost that battle because package finalization is inadequate for some applications. Specifically, there are cases when finalization needs to be done on an object basis rather than a package basis. When an object goes out of scope, it needs to be finalized even though the package that defines the type is still in scope. Package finalization can be done by declaring a dummy object in the package body and associating finalization procedures with the dummy object. When the package goes out of scope, then the object goes out of scope, and finalization gets done. I don't think that's a very elegant solution, but I must admit that it is acceptable.
But the VIRTUAL_TERMINAL finalization problem needed an immediate solution. Two years ago I could not wait for whatever finalization process Ada 9X would eventually have (if any). I had to solve the problem with Ada 83, so I did.
Eventually, I concluded that any kind of implicit finalization could get me into trouble, so it is probably best to avoid it completely.
When I was developing some air-traffic control software, I discovered that multiple objects needed shared access to devices such as the keyboard, screen, and mouse. When a menu goes away, it doesn't need the mouse any more, so it tries to close the mouse. But there might be a control panel or a dialog box that still needs the mouse, so the mouse should stay open even though the menu tried to close it. Implicit finalization might close something that should remain open.
I finally decided that the approach that gives me the most flexibility is to have explicit Open and Close procedures for all shared resources. The package body contains a hidden reference counter that is initially set to 0. If Open is called when the reference counter is 0, it opens the device and sets the reference counter to 1. If Open is called when the reference counter is not 0, it simply increments the reference counter without opening the device. If Close is called when the reference counter is 1, then it closes the device and sets the reference counter to 0. If Close is called when the reference counter is greater than 1, it just decrements the reference counter without closing the device.
All components that use shared devices must follow the "kindergarten convention." That is, "If you opened it, close it!"
This approach gives me three options. I can let the main program open the keyboard, mouse, and screen at the beginning of the program, and then close them at the end; or I can let every subprogram that needs a resource open it when it needs it and close it when it is done with it; or I can do both. (I generally select the third option.)
Now that Ada 9X has become Ada 95, I've been asked if I will re-write Ada in Action with an emphasis on the new Ada 95 features. No, I won't in the foreseeable future.
I write about what I have learned from personal experience. I don't repeat fables I've read someplace else. There are no validated Ada 95 compilers yet, so I don't have any experience with them. GNAT, an evolving Ada 95 compiler has been out for a year or so, but I don't enjoy working on quicksand, so I haven't used it. I'm not even anxiously awaiting the arrival of a validated Ada 95 compiler because I don't have an immediate need for any of the new features. Eventually I will probably come across a real application where one of the new Ada 95 features will permit me to write a more elegant solution than Ada 83 did, but I haven't found one yet.
For example, in section 2.4.2 I said, "Although Ada always allows you to add new operations to a data type, she never lets you take away operations derived from a parent." That is no longer true. Ada 95 lets you make operations abstract, which effectively takes them away. So I could have created the dimensional units types by starting with integer or float types and taken away multiplication and division. But which is better? to have a specification that lists all the operations a type has; or to have a specification that lists all the operations a type doesn't have? I'd prefer to know what it can do, not what it can't. So I'm not about to go back and rewrite the dimensional data types and use abstract operations.
The most important thing I learned from following the progress of the Ada 9X project is that Ada had very little room for improvement. There were hundreds of suggested improvements to Ada, but the vast majority of them were rejected because Ada already did the right thing.
Most of the changes that were made were simply syntactic sugar. There were, in my opinion, only five significant changes.
The first significant change was the expansion of the character type from 7-bits to 8-bits, permitting the use of accented characters. There no doubt is a need for this given the popularity of Ada outside the United States.
The second change (really a group of changes) provided for more programmer control of real-time performance. Although nearly all of my work for the past 18 years relates to real-time data processing and display (with cycle times much less than 100 milliseconds), I have never experienced the problems that these changes were supposed to solve. Therefore, I don't feel qualified to comment on whether these changes have merit or not.
I campaigned vigorously against the third change, the hierarchical package structure. Although I agree that it is good to break huge, monolithic packages into smaller pieces, I fear the "child package" solution invites abuse.
Years ago I called the potential problem the "Howard Hughes effect." This was shortly after wealthy Mr. Hughes died without leaving a will, and several people claimed to be his children and therefore rightful heirs to his fortune. I tried, without success, to make Ada 95 require the parent package to contain a list of all legitimate children, thus limiting the child packages that could be written. I still believe this is necessary to avoid an abuse that results in loss of privacy. But the prevailing opinion was that specifying the names of children would make Ada 95 more restrictive than C++. That restriction would be perceived as a weakness which would become a marketing disadvantage.
If you write a package that declares a private type in the package specification, or a type hidden in the body, Ada 83 prevents me from writing any code that takes advantage of representation details (unless I instantiate the conspicuously evil UNCHECKED_CONVERSION function). This gives you the freedom to change the representation without any possibility of messing up my code. That's what privacy is all about.
Ada 95, however, allows me to claim that my package is a child of yours, which lets me see your private parts (whether you want me to or not). My child package can take advantage of your representation details without using UNCHECKED_CONVERSION. You can declare things to be private, but it means nothing. I can now invade your privacy at will.
The argument against my objection is that if the private type changes and the parent is recompiled, then the child packages become obsolete and have to be recompiled. During that recompilation Ada will catch any inconsistencies.
My counter argument is that I can (and do) achieve the exact same effect in Ada 83. I declare private things that I intend to use in multiple packages in a package with a name that begins with "PRIVATE_".
For example, I have a family of network interface components that includes packages called NETWORK_CLIENT, NETWORK_SERVER, NETWORK_TRANSMITTER, and NETWORK_RECEIVER. (The CLIENT/SERVER pair does point-to-point communications, the TRANSMITTER/RECEIVER pair does one-to-many broadcasts.) The package specifications present the application programmer with simple, virtual abstractions of interfaces to other parts of the program running on other computers. The package bodies hide the actual interface, which may be shared memory, remote procedure calls, serial RS-232, parallel DR11-W, GPIB, ethernet connections, or something else.
The package bodies I commonly use on the Sun and SGI computers depend on interfaces to UNIX services (connect, bind, accept, send_to, etc.). I declare the implementation- specific ethernet interfaces in a package called PRIVATE_NETWORK_DETAILS. The specification of this package and the accompanying documentation loudly proclaim that no application program should ever be compiled WITH PRIVATE_NETWORK_DETAILS. My development standards treat the PRIVATE_NETWORK_DETAILS package like UNCHECKED_CONVERSION and UNCHECKED_DEALLOCATION. Using any of them is considered dangerous, and requires special dispensation.
But suppose some evil person does use the forbidden PRIVATE_NETWORK_DETAILS, and I make changes to it, then Ada 83 will make those illegitimate dependents obsolete as soon as I recompile PRIVATE_NETWORK_DETAILS. The effect is exactly the same as in Ada 95 if someone falsely claims to be a child. So, in my opinion, Ada 95 gives me nothing that I didn't already have in Ada 83, and takes away my right to privacy.
I have mixed feelings about the fourth change, which added C++ style object-oriented features. People seem to be having great difficulty coming up with practical examples of how the features can be used. This suggests to me that they solve interesting academic problems, but do not have much practical utility. I used to think that these features were necessary for dynamically created user interface windows. But for the last two years I have been writing an air- traffic control program that lets the user view the data using any combination of twelve different display windows, which can be created and modified in real time. It has been very easy to do in Ada 83, and I don't think Ada 95 would make it any easier.
I do think the object-oriented features are important to Ada because of the "rhythm rail effect." In the 1960's, I used to teach guitar at a music store that sold Hammond organs. The organs sold in the other music store in town featured glorified metronomes called rhythm rails. (They were the fore-runners of drum machines.) Hammond didn't offer any organs with rhythm rails. Customers would buy inferior organs from the other store because the rhythm rail seemed like such a neat gadget. After a few weeks, they never used the rhythm rail. It was too hard to play along with it. But by then, they had already bought the organ. Hammond eventually added rhythm rails to their organs, because they found they could not compete with the junk organs unless they had them, too.
Object-oriented features are today's rhythm rails. Nobody will buy a language without them. Lots of people are playing with Ada 95 prototypes just to get experience with the new object-oriented features. The novelty will wear off soon, but before it does, people will have learned about Ada's other, more useful features. After playing with Ada 95 for a while, they won't be afraid of her any more, and will appreciate her many other virtues. So, the new object- oriented features are important because they have made Ada more interesting and novel, and are getting potential customers into the store. Many people will adopt Ada because of them, but I don't expect them to be used very much in actual programs.
The fifth important change introduced in Ada 95 doesn't get much publicity, but it may be the most important. Ada 95 allows subunits to have the same simple name, as long as the expanded name is unique. I think this will be very useful in big projects, where it is likely that several subunits written by different people will have common names (like Get and Put). You won't be frustrated because you can't call the subunit what you want to call it because someone else has already used that name in another part of the program that isn't even visible. It is a feature you will use without knowing that you are using it, unless you later try to compile your program on an Ada 83 compiler, but it will make programming much easier.
I'm still looking for better ways to tell if I am on schedule, and how much time projects will take, than the method described in section 4.6.22.3. I have a novel approach, and I'm still collecting data, but I think I'm on the right track, so I'll tell you about it. It is becoming an integral part of my software development process.
After the usual thrashing around, the requirements specification gets signed off. From the requirements specification I write the user's guide and one or more interface specifications. (The number of interface specifications depends on the number of external devices the program interacts with.)
Generally the users are surprised and disappointed with the user's guide. I show them how it does exactly what they approved in the requirements specification. They say that they didn't know that was what the precise legal language in the requirement specification meant. So, I rewrite the user's guide to satisfy them. (A similar process usually happens with the interface specifications, too. For convenience I won't explicitly refer to the interface specifications any more; but whenever I talk about the user's guide, understand that I mean the user's guide and interface specifications.)
From this point on, the user's guide drives the design. It is the de facto requirements document. I do not waste time revising the requirements specification. The requirements specification's only purpose was to lead to the initial user's guide. The requirements specification has served its purpose and is shelved. (Fortunately I'm not in a situation where the requirements specification is a binding legal document describing the delivered software.)
I change all the characters in the user's guide to italics. Text written in italics describes things that the prototype does not do yet. As the prototype evolves, I change the text describing the implemented features back to normal font. When the program is complete, the entire user's guide will be in normal font.
I store a copy of the user's guide in Microsoft's Rich Text Format (RTF). I then run a simple program that scans the file, counting the total number of characters and the number of characters that are italic. It computes the percentage complete by dividing the number of characters that are not italic by the total number of characters in the document.
I code and test the prototype for three months. At the end of the three months (regardless of the progress that has been made), I freeze the design and fully document the prototype. I revise the user's guide based on experience with the prototype, and change the sections of the user's guide that have been implemented in the prototype from italic to normal characters. I store the revised user's guide in RTF format and let my program count the characters and compute percentage of completion.
In general, the number of non-italic characters increases with each revision. The number of total characters also increases because experience with the prototype leads to new requirements.
I do not yet have enough data to know what my normal rate of completion is. I don't expect it to be linear (and it hasn't been). It will be years before I have completed enough projects to compute meaningful statistics. If you work in a large company where dozens of projects are going on at once, you may be able to take similar results and come up with results sooner.
I have noticed some interesting correlations in some of the measurements I have taken so far. Scatter diagrams of number of bytes of source code as a function of number of lines of source code fall very close to a line with a slope of 30 to 1. The number of bytes of executable code per line of source code is generally 60 to 1. That is, 38,000 lines of source code generally results in roughly 1.14 Mbytes of source code and 2.28 Mbytes of executable code, with optimization turned off. These numbers are easy to measure. Do you get similar results?
I'm also looking at bytes of documentation per byte of executable code, number of bytes of source code per non- italic character in the user's guide, and so on. I'm measuring hours spent and dollars spent, so I can compute lines of code per hour and dollars per line of code (or dollars per byte of executable code).
Some ratios change depending on the phase of the project. For example, at the beginning of the project progress is measured in lines of code produced per day. Near the end of the project, when redundant routines are being consolidated in subroutines or generics, and more efficient algorithms are found, progress should be measured in lines of code REMOVED per day. Near the end of a project, a decrease in the ratio of lines of code per day is good.
It will probably be several more years before I have enough data to know what it all means. It is not a good idea to make any vast changes based on half-vast data. So, for now, I'm just collecting the data. I suggest you do the same, and see if you discover anything interesting.
The most important thing I've learned about predicting software cost and schedule is this: You can't predict how much your next project will cost, and how long it will take, unless you can tell how much your last project cost, and how long it took. If it cost $150 per line of code on your last project, it will probably cost $150 per line of code on your next project. If you produced an average of 3 lines of code per person per day on your last project, you will probably produce 3 lines of code per person on your next project. So, take good data on your current project, and try to reconstruct data from your past projects, and you will have a better idea of how to predict progress on your next project.
John Wiley & Sons did very little editing on the first edition, but there were two paragraphs dealing with design methodologies that they strongly urged me to take out. They felt I was overly critical of MIL-STD-2167A, and that my rejection of a standard worthy of sainthood damaged my credibility. All the experts said MIL-STD-2167A was the one true way to develop software, so I would look like a fool to criticize it.
I was just ahead of my time. Now 2167 has largely been repudiated. I wish that I had fought to keep these two paragraphs in the first edition.
Now that I don't have an editor to restrain me, I can make the following predictions.
C and C++ have established such a presence that they will be around as long as there are computers running UNIX. There will always be significant software development in C++, but I don't think C++ will retain its current popularity. It is based on C and a questionable object- oriented model, and that will be its downfall. Maintenance of large C++ programs will be very difficult. C++ has been around long enough that we are starting to hear the first of what will be a long series of spectacular failures. I expect the computer magazines to publish more articles critical of C++ in the next few years, and then C++ will take its place along side FORTRAN and COBOL as a heavily used, but seldom discussed, language.
I REALLY LIKE the Software Engineering Institute's software Capability Maturity Model. There is a lot of good advice in it. An organization that is following the CMM processes and is using Ada will be a strong competitor.
On June 6, 1982, I took a three-day course called "Developing Software with Ada" taught by George Cherry. The government had previously tried to force me to use a horrible language called ATLAS, and it appeared that they were about to try to make me use Ada. I took the Ada course to find out all of Ada's weaknesses so I could convince my managers not to make me use Ada. By the second day of the class, I was in love with Ada.
As it turned out, I had trouble getting the government to let me use Ada. It was 1989 before I could even get my working group to buy a compiler, and I wasn't permitted to officially write Ada code until 1993. (Before that, I wrote and tested algorithms in Ada, and translated them to FORTRAN.)
But I made a personal commitment to Ada in 1982, and cut my teeth on a remarkable unvalidated Ada compiler called Maranatha A (which ran on a CP/M computer with 56 Kbytes of memory) written by David Norris. I later bought an IBM PC AT clone and got several PC Ada compilers. I expected to ride Ada's popularity all the way to the top, as the whole world embraced Ada.
Well, Ada didn't achieve the success she deserved. Some of the first compilers were pretty bad, and they gave her a bad reputation. The DoD Mandate was largely ignored, and only inspired hate and resistance. (Programmers said, "If Ada were any good, the government wouldn't be forcing it down our throats.") The acceptance of Ada has been much less than I expected.
Despite my disappointment in Ada's market share, I don't regret choosing Ada. Certainly "C in Action" would have sold far more copies than Ada in Action did, but my goal wasn't to make the big bucks. I guess I'm just an artist at heart. My goal is to be the best, not the richest.
Ada has helped me be the best that I can be. I've been far more productive using Ada than I could have if I used any other language. My Ada code is easier to understand, more reliable, and easier to maintain than my FORTRAN or assembly language code is. But beyond that, Ada has helped me to help others be the best they can be. That's what is most important to me.
So, I will continue to use Ada.