Introduction to "Design Techniques"

A look at the role of design in the context of the overall software development process

1 2 3 Page 2
Page 2 of 3

Documentation helps you keep track of your own design and helps you communicate it to others. Communicating your design to other developers before you implement can give you valuable feedback on your design early on, while it is still relatively easy to make changes. It is usually easier to correct design flaws during the design phase than it is later, during the implementation or the integration and test phases. (Note that this process of getting feedback on your design, and making the appropriate adjustments, is yet another example of iteration in software development.)

Many design methodologies include graphical conventions for describing a design. CASE (Computer Aided Software Engineering) tools often allow you to document your design as you create it. With Java, however, you have one other option. Java offers a simple way to express a software design not requiring anything that isn't a standard part of any Java development environment.

Documenting a design in code

A simple approach to design documentation is to express your design in Java code that has the structure and organization, but not the functionality, of your end product. You define and name classes and interfaces, and describe them in comments. You define the name and type of each field, and describe them in comments also. You define the name, return type, number of parameters, and parameter types of each method, but you don't define the method body. Instead of a body, you write a comment that describes what the body of the method, once implemented, will do. The comment can be pseudo-code, regular text description, or both. If you take this approach, your design will remain relatively easy to change throughout the design process.

If you do this, then at the end of the design phase you will already have a lot of Java code. For each program for which you did a user interface prototype, you'll have a framework of Java code for that program -- one that has look but no functionality. (These are the user interface classes.) You will also have a framework of Java code for each class that you designed with fields, comments, and methods with empty bodies. (These are the problem domain and data management classes.)

Presenting a design with javadoc

The Java programming environment includes a tool, javadoc, that can help you document and communicate your design. In Java, there are three kinds of comments:

  1. A single //
  2. A matching pair of /* and */
  3. A matching pair of /** and */

// indicates that the rest of the line is a comment. /* and */ indicate that all characters between the initial /* and the terminating */ should be ignored by the compiler. /** and */ also comment out anything between them; however, the comments between /** and */ are picked up by the javadoc tool and placed into an HTML file it generates to document the code. Because of this, comments starting with /** are called documentation comments, or simply doc comments. javadoc parses .java files and generates several HTML files that describe classes, fields, methods, and that show doc comments.

Going through the entire system without implementing methods allows you to document your design in a way that helps you during implementation. At the start of implementation, you already have source code, and that source code already contains good comments. It is a good practice to make sure your classes compile as you design them even though they don't have method bodies.

Fluidity during design

An important goal to strive for during a software design phase is fluidity. What typically occurs is that you design one area, then as you go to design another area, you realize you need to go back and make adjustments in the first area. As a result, you will want to keep things fluid, or easily changeable. If you implement one section before continuing to the next, it is harder to go back and make changes to the areas already implemented. The more code you write, the greater your investment in a particular design, and the more financially and technically (and emotionally) difficult it is to go back and change it.

If there is a simple method and you implement it during the design phase, no alarms will sound. No one will expel you from the building (at least not for that reason). The main point of not implementing the methods during the design phase is to keep the design fluid until you've thought through the whole thing. To keep it easy to go back and make changes until you've gone through the entire solution domain.

Design monkeys on your back

In the real world, as you work to design software, you have several concerns to keep in mind -- several "monkeys on your back." Each monkey competes with the others for your attention, trying to convince you to take its particular concern to heart as you work. One large, heavy monkey hangs on your back with its arms around your neck and repeatedly yells, "You must meet the schedule!" Another monkey, this one perched on top of your head (as there is no more room on your back), beats its chest and cries, "You must accurately implement the specification!" Still another monkey jumps up and down on top of your monitor yelling, "Robustness, robustness, robustness!" Another keeps trying to scramble up your leg crying, "Don't forget about execution speed!" And every now and then, a small monkey peeks timidly at you from beneath the keyboard. When this happens, the other monkeys become silent. The little monkey slowly emerges from under the keyboard, stands up, looks you in the eye, and says, "You must make the code easy to read and easy to change." With this, all the other monkeys scream and jump onto the little monkey, forcing it back under the keyboard. With the little monkey out of sight, the other monkeys return to their positions and resume their activities.

As you sit there in your cubicle and work on your software, to which monkey should you listen? Alas, in most cases you must listen to all of them. To do a "good job," you will need to find a way to keep all these monkeys happy -- to strike a proper balance between these often conflicting concerns.

The first monkey: Meeting the schedule

In my experience, the most important way to keep management happy during a software development project is to meet the schedule. The schedule is critical, of course, because commercial software development is done for commercial reasons. It's a business.

Although there is often pressure to dive into implementation as soon as possible in an effort to finish a project faster, developing a well-thought-out specification and a thorough design from the start can help prevent unforeseen schedule catastrophes during the implementation phase. Having real specification and design phases before implementation gives you milestones that can help you manage your project schedule. In addition, the people for whom you are developing the end product (the people for whom schedule is the most important concern) will feel more comfortable if you give them intermediate milestones along the path to project completion. Early milestones can be the completion and sign-off of the specification, the completion and sign-off of the user interface prototype, and the completion and peer review of the design.

Once you have a design that includes all the classes populated with fields and methods without bodies, you have a good inventory of the work that remains. By making estimates for the time it will take to implement each method body, you should be able to come up with a reasonably accurate schedule for the implementation phase.

This is how a solid specification and design phase before implementation can help prevent major schedule slips during implementation: By the time you implement, you know what you are aiming for, and you know what you have to do to get there.

The second monkey: Correctly implementing the specification

To appease the second monkey, the most important thing is to do the work up front to figure out and communicate a clear, sufficiently detailed vision of the end target. If you don't know where you are going, you'll likely end up somewhere you never intended. To produce a product that matches and satisfies the customer's expectations, all those involved in developing the software must have a clear understanding of what they are supposed to produce.

The third monkey: Robustness

If you meet your milestones during the specification, design, and implementation phases but then require two more years to get the system to work reliably, you have a problem. Bugs are inevitable, but there is a threshold beyond which a product becomes unusable or at least less marketable. This threshold varies depending on the application. Software that helps commercial jetliners navigate likely has a higher robustness threshold than the browser software you are using to read this article. In all cases, however, there is some level of robustness that you must deliver.

In my experience, robustness has arisen out of good, solid designs and good coding practices. With the advent of Java, with its garbage collection and limitations on pointers, robustness became much easier to deliver. But even without memory problems, bad design and bad coding can still yield programs that lack robustness.

One coding technique I've found especially useful in achieving robustness is unit testing methods just after I implement them. After implementing a method, I walk through it with a debugger to make sure it is doing what I intended it to do.

The fourth monkey: Performance

Performance -- execution speed, resource usage, and so on -- is something you should usually keep in mind as you design and implement software. But often, programmers try to solve performance problems their programs don't actually have. The right approach typically is to keep performance in the back of your mind as you develop. In the front of your mind, keep "good object-oriented, thread safe" design. During integration and testing, if you discover you do indeed have a performance problem, that is the time to analyze and address it.

The fifth monkey: Flexibility

There is more than one path to correct implementation of a specification. Different teams of programmers can produce drastically different designs and implementations, all of which fulfill the requirements set forth by the specification. Some versions, however, may take longer to write and debug. As they execute, some versions may be slower than others. And over time, as the program evolves from release to release, some versions may turn out to be less flexible than others.

Flexibility, the ease with which a program can be changed, is important because source code usually evolves over time. As bugs are fixed and enhancements made for new releases, programmers must return to already-existing source code, understand it, and make changes to it.

When you write a program, you are communicating. Most obviously, you are communicating with a machine. You are telling a computer what you want it to do. But there is another, less obvious, form of communication that you perform when you write a program: communication with other programmers. Through your source code, you communicate your design to any programmers who, in the future, ever need to fix a bug, make an enhancement, or simply reuse your code. You are telling programmers how your code is supposed to work, how it should be used, and how best to go about changing it.

One of the most important aspects of program flexibility is source code readability. Readable source code is important because it helps future programmers (possibly including your future self) understand what you were trying to do. In many cases in the real world, a program's source code is the documentation. When others work on your source code, either to add new features or to fix bugs, they read your source code to understand what it does.

Conclusion

What constitutes a "good object-oriented, thread-safe" design? The answer depends on whether you are talking about object orientation or thread safety.

Thread safety primarily is about robustness (monkey number three). If you forget to synchronize methods that really need to be synchronized, you can end up with intermittent data corruption. Creating thread-safe designs involve understanding multithreading in general, understanding the multithreaded needs of your particular programs, and programming accordingly.

Good object-oriented design also plays a part in robustness. As I said earlier in this article, my experience has shown me that robustness arises out of good design and coding practices.

In addition, a good object-oriented design can play a role in keeping a project on schedule (monkey number one). As part of a solid overall software development process, a thorough design phase can help a team avoid unforeseen schedule slips. A thorough design gives the team a clear and detailed view of the work that will be required by the implementation phase.

The primary benefit, however, of a good object-oriented design is flexibility, the concern of monkey number five. A good object-oriented design can give you code that is easy to understand and easy to change. And the greater the flexibility of a body of code, the quicker (and cheaper) will be enhancements and bug fixes to that code.

The guidelines put forth in this column primarily will help you achieve program flexibility. When I talk about object design, building class hierarchies, interfaces, polymorphism, choosing composition vs. inheritance, and so on, my main focus will be to give insights that will help you make your programs easier to understand and easier to change.

A request for reader participation

Software design is subjective. Your idea of a well-designed program may be your colleague's maintenance nightmare. In light of this fact, I hope to make this column as interactive as possible.

1 2 3 Page 2
Page 2 of 3