Vous êtes sur la page 1sur 10

http://www.javaworld.com/javaworld/jw-02-1998/jw-02-techniques.html?

page=1

Introduction to "Design Techniques"


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

At last year's JavaOne conference, I attended a session in which the speaker talked about Sun's plan for
the Java virtual machine (JVM). In this talk, the speaker stated that Sun planned, among other things, to
clear up current performance bottlenecks in its virtual machine, such as the slowness of synchronized
methods and the performance costs of garbage collection. The speaker stated Sun's goal: With the
improvements to the JVM, programmers would not need to think about avoiding virtual machine
bottlenecks when they designed their programs; they would only need to think of creating "good object-
oriented, thread-safe designs."

The speaker did not, however, elaborate on what actually constitutes a good object-oriented,
thread-safe design. That is the aim of this new column. Through the articles of the Design
Techniques column, I hope to answer the question: What is a good Java program design, and
how do you create one?

The column's focus

My focus in this column will be to provide practical design techniques that you can put to use in
your everyday programming tasks. I'll assume you are familiar with the Java language and APIs.
I plan to discuss techniques, ideas, and guidelines that will help you use the language and APIs
in your real-world programs.

To give you an idea of what to expect in this column, here is a list of the kinds of topics I plan to
write about:

 Ways to improve the design of your objects


 Building class hierarchies
 What are interfaces for?
 What is the point of polymorphism?
 Choosing between composition and inheritance
 Designing for thread safety
 Designing for thread cooperation
 The Model/Controller/View architecture used by the JFC classes
 Design patterns
Much of the material that has already been written about software design can be applied to Java.
There are many full-featured design methodologies and thick textbooks that describe them. In
this column I won't promote one methodology over another. Nor will I promote a new
methodology of my own invention. Rather, I will draw on and combine insights that I've gained
from several existing methodologies and found to be useful in my own programming practice.

The approach to design that I will recommend in these articles arises out of my experiences over
the years in the cubicle: designing new software, enhancing old software, maintaining software
written by others, maintaining software written by myself, working with various languages,
tools, computers, and other programmable machines. My design philosophy will be very
"cubicle-oriented": based on, and geared toward, real-world commercial programming.

This month: Process described, "design" defined

In this initial article of the Design Techniques column, I will provide a detailed account of the
concept of software design based on my own experience as a developer. In the remainder of this
article, I'll discuss the process of software development and explain what I mean by the term
"design."

The software development process

In my experience, the process of software development tends to be rather chaotic. Team


members come and go, requirements change, schedules change, entire projects get canceled,
entire companies go out of business, and so on. The programmer's job is to successfully navigate
this chaos and in the end produce a "quality" product in a "timely" manner.

Besides being chaotic, the software development process also tends to be rather iterative. As a
software product is developed, it continuously evolves based on feedback from many parties.
This iterative process works from release to release (each release is one iteration) and within the
development cycle of a single release. From release to release, for example, the feedback of
customers with the current version indicates which bug-fixes and enhancements are most
important to make in the next version. Within the development cycle of a single release, the
vision of the end target is continuously adjusted by forces within the company as development
progresses.

Despite the chaos and iteration, however, I have found that most development teams try to
enforce some structure on their development efforts. For the purposes of this column, I'll loosely
divide the software development process of a single release cycle into these four phases:

1. Specification
2. Design
3. Implementation
4. Integration and test
With these four phases I intend to capture a structure that I have observed in most software
development projects. Because each company is different, each team is different, and each
project is different, these four phases form only a rough outline of a typical development cycle.
In practice, some phases may be skipped or may happen in a different order. And because the
iterative nature of software development tends to bubble up through any imposed structure, these
phases may to some extent overlap or bleed into one another.

When I talk about design in the Design Techniques column, I am talking about the activities that
take place during step two of the above list. To give you a better idea of what I mean by each
phase, I describe each individually in the next four sections.

Phase 1: Specifying the problem domain

The specification phase of a software project involves bringing together all the concerned parties
to discuss and define the end product of the software development process. During specification,
you define "the vision" -- the target you will aim at for the remainder of the project. The
deliverable that should come out of the specification phase is a written document that defines the
requirements of the software system.

The requirements specification is much like a contract. It is a contract between all the concerned
parties, but most importantly from the developer's perspective, it is a contract between the
developer and whatever party desires the end product in the first place: perhaps a client, a
customer, management, or the marketing department. When a specification is agreed to in
spoken terms but is not written down, it is basically an oral contract. Although an oral contract is
legally binding, in many cases not having something written down is a recipe for trouble.
Different people tend to have different recollections of oral agreements, especially when it comes
to details. A disagreement on details is even more likely if the details were never discussed as
part of the oral agreement in the first place, which is a common feature of oral contracts.

When all the parties involved get together and try to write down the requirements of a software
project, it forces an exploration of the problem domain. The problem domain is the end product
described in a human (not computer programming) language. The same end product expressed in
a computer language is the solution domain. In the course of exploring the problem domain,
many ambiguous details can be identified and discussed, and disagreements can be resolved right
from the beginning.

A good specification gives you a well-defined target to aim for as you develop. But it doesn't
guarantee that the target won't move. Some adjustments in the vision of the end product are
almost inevitable during the design and implementation phases; however, a good specification
can help reduce the magnitude of such adjustments. Skipping the specification phase, or not
covering the details sufficiently, can lead to the same kind of misunderstanding between parties
that can occur with an oral contract. Thus, having a good specification first helps advance the
subsequent design and implementation phases to a successful conclusion.
Phase 2: Designing the solution domain

Once you have a written specification that everyone involved agrees to, you are ready for what I
call the design phase -- the process of planning, and in some way documenting, the architecture
of your solution domain. I include many activities under the name "design," including:

Defining the system:

1. Partitioning the system into individual programs (and documenting it)


2. Defining and documenting the interfaces between the individual programs
3. Deciding on and documenting third-party libraries (Java packages) your Java programs will use
4. Deciding on and documenting new libraries (Java packages) you will build that multiple
components of your system will share

Building user interface prototypes:

5. Building user interface prototypes for those system components that have any user interface

Doing object-oriented design:

6. Designing and documenting class hierarchies


7. Designing and documenting the individual classes and interfaces

Defining the system


As a first step in the design phase, you must partition your system into its component parts. For
example, you may require several processes at various places on a network. You may have some
applets and some applications. Some components of the system may be destined to be written in
Java and others not. If you want to use JDBC, you may need to select a third-party JDBC library
that will enable you to access the database of your choice. All these decisions must be made
before you can begin any object-oriented designs of the individual programs in the system.

As you define the system, you will likely want to document your work in one or more technical
specifications. Documentation allows you to communicate the design to other interested parties
in the organization and to get their feedback. You can pass out the specification, call a design
review meeting, then present the system design at the meeting. The group can discuss your
design and hopefully find any problems and make suggestions. Getting feedback -- and making
adjustments to your system design as a result of the feedback -- is an example of iteration in the
process of software development.
Building user interface prototypes
Building a user interface prototype is often a valuable activity during the design phase. Once the
user interface prototype is completed, the parties that agreed to the specification can gather
together again to review the preview version. Having a prototype gives the parties another
chance to visualize and discuss the end target. By requiring everyone who agreed to the
specification to review and sign off on a user interface prototype, you help ensure that all parties
have compatible expectations for the end product. With the visual tools available today for the
development of Java-based user interfaces, developing a user interface prototype can be very
fast, and the end result is a framework of Java code that you can then endow with functionality
during the implementation phase.

Note that the process of demonstrating a user interface prototype is a prime example of the
iterative nature of the development process. When the interested parties (who have all agreed on
a written specification) actually see user interface prototypes, they often have new ideas, or a
better understanding, or a more detailed understanding -- in other words, a clearer vision -- of the
end product. During the demonstration, some adjustments may be made to the specification. By
this time, however, hopefully the adjustments will be minor.

Doing an object-oriented design


As you design a Java program, you must think in terms of all the programming technologies
offered by the Java language, including multithreading, garbage collection, structured error-
handling, and object orientation. Yet, because the dominant architectural characteristic of the
Java programming language is object orientation, a Java program design phase is fundamentally
a process of object-oriented design.

Doing an object-oriented design involves creating inheritance hierarchies and designing the
fields and methods of individual classes and interfaces. Three basic categories of classes that you
will come up with in a design are:

1. User-interface classes
2. Problem domain classes
3. Data management classes

User interface classes are those that compose the program's user interface, such as classes that
represent windows and dialogs. Problem domain classes are those that represent objects that you
identified in the problem domain. For example, if your problem domain involved elevators, you
might have an Elevator class in your solution domain. Data management classes are those that
you create to manage objects or data. Neither user interface classes nor data management classes
have corresponding objects in the problem domain.
Phase 3: Implementation

Implementation is coding. Writing for loops, if statements, catch clauses, variables, and
comments; compiling; unit testing; bug fixing -- that's implementation: the stark act of
programming.

Phase 4: Integration and test

During the integration and test phase, the members of the project team, each tasked with building
a specific part of the whole, meet and try to get all the pieces of the software system to work
together. During this phase the team members find out how well the interfaces between the
individual system components were defined and communicated during the system partitioning
phase. The coding that takes place during this phase should primarily be bug fixing.

Documentation of software designs

There are many approaches to software design. Formal methodologies attempt to guide you
through the process of transforming a problem domain into a solution domain. In designing Java
programs, you may choose to use a formal methodology, to combine several formal
methodologies, or to forgo formal methodology and design by the seat of your pants. But no
matter how you attack the design phase of your software project, you should in some way
document your design.

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.