Wes is vice president and cofounder of Cimulus. He can be contacted at [email protected] cimulus.com.
At Cimulus, teams of developers design, write, and support custom software in C/C++, Delphi, or any other language the customer asks for. Our primary product is source code, delivered incrementally to customers. Our reputation depends not just on the functionality of our products, but also on the quality and legibility of our source code. As a result, we've refined our development process to produce well-structured, clean source code. In this article, I'll present our company's development process, along with notes on specific tools that we use and how we use them.
Accepted projects, usually 32-bit MFC applications, begin life on the conference room whiteboard as a team of programmers designs the internals of the system. The whiteboard works well because its information is supposed to be temporary, so people feel free to brainstorm. To help discussions focus on design intent rather than diagram syntax, we use a shorthand version of Booch called "Blackboard Booch" and focus on class, interaction, and state diagrams (annotated to show screens). We also often use traditional dataflow diagrams (annotated to denote threads) and pseudocode.
Our first deliverable is a formal, written version of this design for customer review. Unfortunately, we've found no single tool that can capture all of the design information. As a result, we use a variety of different programs to develop different parts of the formal design document.
We create Booch diagrams with the shareware Object Domain (http://www.object-domain.com/) that is both fast and easy to use. A single document contains several charts with a document-central list of classes so that changes to a class on one diagram are reflected in all other diagrams that use the class. (There are even some features, via an embedded Tcl interpreter, for reading existing code and generating new code, but we don't use them.) The downside of Object Domain is that its diagrams cannot easily be embedded within a larger document that is delivered to customers.
We construct most other diagrams using Micrografx's ABC Flowcharter (http:// www.micrografx.com/). ABC Flowcharter provides less structure than Object Domain, but allows standard diagrams, such as dataflows, to be annotated with critical design information. Its Snap Graphics module is good for showing flows between screens by placing screen captured bitmaps and connecting them with a variety of arrows and explanatory text. As a general rule, we've found it best to create an ABC Flowcharter diagram as an embedded object within a word processor instead of as a stand-alone file since most of these graphics are part of a proposal or specification.
The design also includes a project stub; the initial coding and exact screen layouts are very good at pointing out overlooked areas of the design. To build screen layouts, we use the test mode of a resource editor, then edit the result in a paint program. These screen captures are essential for communicating the final design.
The project stub includes header (.h) and source (.cpp) files for all of the classes anticipated in the project. The stubbed functions may not have any code, but they do have all required parameters and comments regarding their intent. We use Genitor's Object Construction Suite (http://www .genitor.com/) to build well-commented class stubs. Genitor's Class Editor provides an MDI child window for each class, each containing a tree of items belonging to the class. The tree holds a class description, #include statements, member variables, member function source code, and liberal comments for each item -- right down to the individual function parameter. Genitor is used outside of your IDE to build objects, which it then turns into commented source and header files. It can even generate a single help file that has details from several classes, invaluable as an online reference manual.
We can't conclude the design process without taking the required team dynamics into account. Placing the stubbed project files under source control (we use Microsoft's Visual Source Safe; http://www .microsoft.com/) often brings these issues to the forefront. We ask ourselves "when a developer checks out this file, what other files are probably going to be needed?" Situations that would require two developers to compete for a file must be dealt with. As a scheduling issue, this affects the amount of concurrent work that can be done on a system. This typically results in deliverables that contain many disparate features. As a design issue, new classes are introduced to move the code out of frequently used classes (such as the mouse handling code in a view class) and to break compilation dependencies.
The design process generates a document for the client, a set of documents for developers, and stubbed project. Armed with this information, a detailed work breakdown is made. This breakdown is detailed to get an accurate schedule. We break down work into tasks that are between one hour and two days. If a task will take longer than two days, there is a chance that the design did not address the internal details sufficiently and should be revisited.
There are a variety of tools that support project scheduling, ranging from dedicated project-management tools to generic tools such as spreadsheets. We use a spreadsheet with some simple formulas to ensure that every task has been assigned (just sum the total task time and compare with the sum of the times allocated to people). We just hide some rows of the spreadsheet to create a simple but accurate document for the client.
Once the work breakdown is completed, a lead programmer is selected and each task is assigned to a specific person. This schedule is posted in a common area and team members check off their responsibilities as they get completed.
At the start of the work for each milestone, we bring together the programming team and prepare a "micro-schedule" for the upcoming deliverable. This is a one to three hour meeting where the insights from the previous milestones are applied to the overall design producing a detailed short-term, low-level design, typically with significant amounts of pseudocode. This practice ensures that programmers are aware of the big picture at each step and creates a plan with more detail than can be economically designed initially.
Micro-scheduling doesn't involve many tools. We gather around the whiteboard again and bring the latest design documents. Often, these documents are revised as a result, so computerized tools must readily accommodate revisions.
Eventually, we have to start coding the project. We use specific coding conventions so customers won't be able to tell which developer wrote the code. Conventions put creativity in more useful places.
We use a couple of different tools during development. For instance, we use Microsoft Visual C++ for most of our work. It supports syntax-based highlighting (which I find essential), but its online documentation is very slow. (Over 15 seconds to find the documentation for Clist::GetHeadPosition on a 180-MHz PentiumPro with all documentation on the hard drive!)
We also use Genitor for coding. It allows drag-n-drop function creation, syntax highlighted code editing, and works with a number of compilers and platforms. Using such a tool, programmers can concentrate on higher level items and let the tool's code generator worry about exact syntax and such things as ensuring that appropriate const words are in both the .H and .CPP files.
To find the code bugs before someone else does, we use a combination of Gimpel's PC-Lint (http://www.gimpel.com/) and code reviews. PC-Lint rummages through the code and checks it against thousands of possible error types. This can produce much white noise, but PC-Lint lets you have a set of common message suppression flags that represent your policy of lint-acceptable code. Table 1 lists our policy. On most projects, we mandate that the final shipped code must pass PC-Lint with no warnings or errors. In fact, PC-Lint has more than paid for itself in the time savings of tracking down bugs due to uninitialized pointers. On the downside, PC-Lint can take a long time to do its work, so we had to create an automated program that would lint modified code at night and distribute the results to web pages on the corporate intranet.
Code reviews are another excellent way of improving programs. Simply trying to explain the inner workings of a module can raise many questions about boundary conditions, initial values, and the variety of tiny assumptions that had to be made to code the module. Your best tools for such reviews are training and books such as Code Complete, by Steve McConnell (Microsoft Press, 1993).
The easiest way to find bugs in your code is to let someone else test it. This can be harder than you think, as it requires that you either build and distribute a minirelease of the product or that other programmers are able to build your source code on their machines. The most common method we use is to have another team member build the code on their machine, which is also a good way of making sure we've checked all items into Source Safe. You can make these builds easier by setting the Working Folder option in Source Safe to the name of the project, thus matching the directory name with the project name. This is often essential, as some files may contain hard-coded paths and must be placed into the same location on each developer's machine.
When the best written program crashes during testing, you'll need a tool for finding out where it went wrong. Under NT, just-in-time debugging can be a big help. However, 16-bit Windows (yes, there's still a 16-bit market) gives us no such luxury. We've found that by keeping MAP files for each 16-bit release, we can effectively use the WinSpctr utility from Inprise (formerly Borland; see http://www.inprise.com/). WinSpctr produces a log file when a GPF occurs, similar to Dr. Watson. However, we've found that WinSpctr gives better information and its log files have enough data to quickly (less than five minutes) reconstruct an accurate call stack, even when using a MAP file from a Microsoft 1.5 compiler! The downside is that Inprise refuses our request to ship this utility with our code, so you'll only be able to use it if you've bought Inprise products and then only during your internal testing.
When a program doesn't crash -- but still doesn't give the correct results -- it's time to get out the debugger that comes integrated with your compiler. Such debuggers are absolutely essential in today's complex programming environment. To get the best use from our debuggers, we:
- Use assertions to enforce and document your assumptions.
- Place the body of if() statements on a separate line than the condition test; this lets you set a breakpoint in the body, effectively breaking only when the condition is met.
- Pay attention to the call stack at each breakpoint in order to ensure that the calling patterns you expected are being followed.
- Set up a number of expressions that must be watched by the debugger, often choosing pointers when tracking a GPF.
- Enable run-time type information and watch the type of object referenced by your pointers.
Sometimes a program will only malfunction when used by a customer. In such cases, you need to resort to more traditional tools. These include TRACE statements and a printf function that writes to a log file (mine is called log_prtf; see Example 1). However, if you're looking for something even better, check out the shared memory features of the Win32 API. This lets a running program write status information to shared memory and have a separate application perform real-time display and logging of the status data. Shared memory is much faster than disk I/O and only people experiencing a problem need to run (or even be aware of) the data display application. You can even take this strategy to an extreme, building a generic data display application and adding "software test ports" to your application just like the IC builders do.
If you're a manager of a development team, it is important that you keep track of the team's progress. This can be made even more difficult if you are both a developer and manager. My best tool for staying informed is management by walking around. A few minutes with developers tells volumes about the problems and successes they've had. This also proves to be an effective way of communicating effective solutions found by one developer to all others.
Similarly, team members need to stay informed of what others are doing. Your process is the best way of getting this done. Posting the schedule in a central area causes water cooler-style interaction and can motivate developers as they see others getting work done. The process of producing a micro-schedule causes all team members to be aware of all program areas, which is especially good for those developers creating common code. Also, the peer testing process used during debugging keeps the concept of quality on every developer's mind.
There is no doubt that effective team management involves effective knowledge transfer. This information should be retained to benefit the maintenance team. Creating a knowledgebase can be as simple as defining a directory structure on a shared network drive, adding simple text documents with information acquired as the software is developed, and using grep to search for information later. This same base framework can be expanded by building a web-style search page on your intranet and replacing grep with a more powerful search tool.
Delivering the Goods
As a custom software developer, your primary product is source code. Your customers want this source code delivered in a variety of manners -- ZIP file, FTP, e-mail, floppy disk, or even printouts. Sadly, the available tools for shipping software products do not take such things into account. For instance, many of our clients expect incremental source code deliveries, since they must integrate our code into their version-control systems. This required that we write a custom program that would work with Visual Source Safe's COM interfaces to extract only the files that have changed since the last delivery.
If your customers are really computer savvy, a ZIP file is acceptable packaging. However, we have recently begun to ship our code using the Wise Installation System (http://www.glbs.com/). It gives us a professional image during the delivery and has been well received by our clients for ease of use. Once set up for the first delivery, which takes about one hour, creating a new set of installation disks only takes about 20 minutes. It has a useful feature that will grab all files in a directory tree (handy for the hundreds of source code files that sometimes must be shipped), although it does not have any way to restrict the result to only .cpp and .h files.
Finally, remember that when delivering custom code, you are likely to be the last programmer to see the code before it gets to the customer. Do not rely on your clients finding comments in your code that instruct them on changes that must be made. Use reasonable icons, cursors, colors, and graphics initially, instead of relying on a client's art department. We use a variety of freeware icon collections, as well as RomTech's (http://www.romt.com/) Icons CD-ROM, with more than 8000 icons.
Tools both support and mandate software development processes. Whether the tool is a high-tech code generation program or a simple highlighter, the tools we programmers use matter.
Copyright © 1998, Dr. Dobb's Journal