[comp.object] A Closer Look At the Recursive/Parallel

eberard@ajpo.sei.cmu.edu (Edward Berard) (12/25/89)

			       PROLOGUE

In previous messages, I have frequently mentioned that a
Recursive/Parallel approach is the one which is most appropriate for
the object-oriented life-cycle. Since then, I have been asked for
references on, and the details of, a recursive/parallel life-cycle.
What I will attempt to do in this message is to provide you with a
better understanding of the recursive/parallel life-cycle.

In attempting to answer previous requests, I discovered that even
though this approach to the object-oriented life-cycle has been in
use, and evolving, for at least 8 years, no definitive article has
been written on the topic. To be sure, various aspects of the
recursive/parallel approach have been discussed in articles, seminars,
and net messages. Further, it has actually been used on quite a number
of actual development efforts.

Unfortunately, simple descriptions such as "analyze a little, design a
little, implement a little, and test a little," are often more
confusing than they are enlightening. In addition, there is a strong
tendency to confuse the recursive/parallel life-cycle with rapid
prototyping approaches (e.g., [Boehm, 1986]). This message is an
attempt to reduce, if not eliminate, this confusion.

			HISTORICAL BACKGROUND

	"Chaos is a friend of mine."

	-- Bob Dylan (quoted in _Newsweek_, December 9, 1985)

Years ago, the state-of-the-practice was to "write a bunch of code,
and then test and debug it into a product." Once all the code was
written, and "debugged," it was often necessary to document (the
so-called "design" of) the product. In short, the idea was to "code
first, and think later."

In an attempt to improve the quality of both the product and the
process, the concept of a software life-cycle with explicit analysis
and design phases emerged. The most prevalent life-cycle approach was
the "iterative waterfall," or "cascade" model. This approach required
that "all analysis" be completed and verified before design could
begin, and that "all design" be completed and verified before coding
could begin. While the waterfall life-cycle was generally an
improvement over the previous chaotic attempts to develop software, it
was not the final word.

When Grady Booch adopted the work of Russell J. Abbott ([Abbott,
1980]) to Ada software development (e.g., [Booch, 1981]), he referred
to the process as "object-oriented design," and demonstrated it using
several simple examples. Later, when people attempted to apply Booch's
method to medium-sized projects (e.g., up to, say, 30,000 lines of
code), they found that the actual development effort required repeated
applications of the approach. Specifically, in decomposing the
problem, they found that Booch's OOD could be re-applied to some (or
all) of the components uncovered via (object-oriented) decomposition.

Booch, and others, began referring to the OOD approach as "design a
little, code a little." This described a process wherein some
object-oriented decomposition (design) was accomplished, followed by
some coding. Then, depending on the complexity of a particular
component, it may have been possible to re-apply the OOD process to
that component. At my former company (EVB Software Engineering, Inc.),
we expanded this approach to include necessary testing, i.e., "design
a little, code a little, test a little."

Later, since the word "code" seemed to cause concern among some
managers (and customers), "code a little" was replaced by "implement a
little." In 1986, when we, and others began focusing on
object-oriented requirements analysis (OORA), the phrase "analyze a
little" was added.

Within the Ada community there has always been a significant effort to
use Ada (a coding language) as a design language (DL). (See, e.g.,
[Kerner, 1982], [Masters and Kuchinski, 1983], and [Hart, 1982].) This
effort encouraged the recursive/parallel approach since the "implement
a little" allowed for the use of the programming language as part of
the "design."

[In actuality, Ada has been used successfully for such things as a
programming/process design/description language (PDL), a hardware
design language (HDL), a requirements specification language (RSL),
and a system design language (SDL).]

While all of the above makes a nice story, it obscures the rigor
within the recursive/parallel life-cycle. We must now take a closer
look.

			    DECOMPOSITION

Many life-cycle approaches employ some form of decomposition. The
concept is simple enough:

	- select a piece of the problem (initially, the whole problem)

	- determine its components (using the mechanism of choice)

	- show how the components interact

	- repeat the previous steps on each of the components until
	  some completion criteria is met.

The most familiar example of this approach is classic functional
decomposition. (For a discussion of decomposition at a very low level
of abstraction (i.e., coding) see [Wirth, 1971].)

Decomposition techniques are "top down" approaches. A top down
approach has the following characteristics:

	- It progresses from the general to the specific, and, by
	  induction, from "what" to "how"

	- At any point in the process we always have a complete
	  (although possibly limited in detail) description of the
	  system. (We often refer to the deliverable from a major step
	  in a top down approach as a "layer," where a layer is a
	  complete description of the system at a given uniform level
	  of abstraction.)

	- Items at higher levels of abstraction treat items at lower
	  levels of abstraction as "black boxes"

	- While items at higher levels of abstraction may be aware of
	  items at lower levels of abstraction, items at lower levels
	  of abstraction are unaware of items at higher levels of
	  abstraction.

Many people often confuse "top down" with "functional decomposition"
or "structured" approaches. While these approaches are indeed examples
of top down thinking, they are implementations of the concept, i.e.,
not definitions.

(For an interesting discussion of "layers of abstraction," see
[Dijkstra, 1968].)

It is possible to decompose a system in an object-oriented manner, e.g.:
 
	- View the problem as an object or system of objects

	- Identify its major parts in terms of interacting objects

	- Show how the component parts (objects) interact to provide
	  the characteristics of the composite object (or system of
	  objects)

	- Repeat the previous steps on each of the component objects
	  until some completion criteria is met.

We note that object-oriented decomposition differs from functional
decomposition primarily in the way information is localized, i.e.,
around objects versus around functions. Other examples of noticeable
differences include:

	- Functional decomposition components have "verb" names, while
	  object-oriented components have "noun" names.

	- Objects are typically more complete abstractions than are
	  functions.

			     COMPOSITION

Most conventional object-oriented programming (OOP) approaches are
compositional approaches, e.g.:

	- Survey the problem attempting to identify necessary
	  components

	- Select components from an existing "library of components"
	  and/or create new components as necessary

	- Assemble the components to form larger components

If the larger component satisfies the complete problem, then stop,
otherwise continue to assemble and merge components until the problem
is (or appears to be) solved. (Obviously, in OOP, the components will
be objects.)

Composition techniques are "bottom up" approaches. A bottom up
approach has the following characteristics:

	- It progresses from the specific to the general and, by
	  induction, from "how" to "what."

	- At any point in the process we usually do not have a
	  complete description of the entire system (with the possible
	  exception of the description of the original problem).

	- Most often, we are building "black boxes" -- not decomposing
	  them.

A bottom up process need not be chaotic, and is most appropriate for
rapid prototyping or research efforts. Further, a composition process
may be entirely adequate if the problem is small, well-understood, and
non-critical.

	       REUSABILITY AND COMPOSITIONAL TECHNIQUES

Reusability implies that we must be using composition techniques at
some point in the life-cycle. We may arrive at the composition process
in one of two ways:

	- We initially select reusable components and combine them to
	  solve a problem.

	- We decompose a problem to a point where we can easily and
	  accurately identify existing reusable components, and then
	  select these components.

[There are many issues relating to software reusability that I am not
going to discuss at this point. However, suffice it to say that while
an object-oriented approach facilitates software reuse, it does not
guarantee reusable software.]

	       AND NOW, THE RECURSIVE/PARALLEL PROCESS

In general, the recursive/parallel approach (a top down approach)
stipulates that we:

	- decompose a problem into highly-independent components

	- re-apply the process to each of these components to
	  decompose them further (if necessary) -- this is the
	  "recursive" part

	- accomplish this re-application of the process simultaneously
	  on each of the components -- this is the "parallel" part

	- we continue this process until some completion criteria is met

The process that we will be applying, in whole, or in part, to each of
the components is "analysis, followed by design, followed by
implementation, followed by testing."

For the recursive/parallel approach to be effective, the components
must be as highly-independent of each other as possible. Well-designed
objects tend to be much more independent of each other (in an overall
system) than are well-designed functions.

At this point, we need to make several clarifications:

	- Although a recursive/parallel approach may be used with
	  functions, it is recommended that, for best results, it be
	  used with objects.

	- We will emphasize reusability in our recursive/parallel
	  approach.

	- As opposed to a "pure" top down approach, we will also use
	  composition techniques with our recursive/parallel
	  life-cycle:

		- The larger the product (or component) the more "top
		  down" (and decompositional) will be our approach.

		- The smaller the product (or component) the more
		  likely it will be that we use compositional
		  techniques

Occasionally, when people first see "analyze a little," they think
that this implies a "sloppy" (or an inappropriate amount of) analysis.
This is not true. As with any other life-cycle approach, some decision
must be made at the beginning of the project as to which details must
be considered first, and which can be considered later. The
recursive/parallel life-cycle is no different. "Analyze a little"
means that one must identify the details which are appropriate to the
current level of abstraction. Further, the analyst must make sure that
details which are left for later, can truly be ignored until later.

We do not necessarily have to perform an entire "analyze a little,
design a little, implement a little, test a little" process with each
new component. For example, if the size of the component is "small,"
we may not need to perform analysis or design. We may recognize a
component as being one which is already in our reuse library, or is a
relatively simple modification of a pre-existing component. In this
case, we may simply reuse (or slightly modify) the pre-existing
component.

	  THE SIZE AND CRITICAL NATURE OF A SOFTWARE PRODUCT

If a software product (or component) is small enough (e.g., < 2,000
lines of code), then all that may be needed is "implement a little,
test a little." However, size alone is not a determining factor.

Even if a software product (or component) is relatively small (e.g. <
2,000 lines of code), it may be a highly-critical application, e.g.,
the kernel of an operating system or an embedded flight control
application in a commercial aircraft. For critical applications, some
serious analysis and/or design is often mandatory, i.e., we will be
required to "analyze a little, design a little, implement a little,
and test a little" even if the product is "only a few hundred lines of
code."

	    ACCOMPLISHING THE RECURSIVE/PARALLEL APPROACH

It is useful to establish some general guidelines for what we mean by
each part of "analyze a little, design a little, implement a little,
and test a little."

The "analyze a little" step requires that we:

	- Understand the requirements for the product (or component)

	- Propose a "high level" solution for these requirements which
	  involves identification of the major components (or
	  subcomponents)

	- Demonstrate that the proposed solution meets the "client's"
	  needs

The "design a little" step requires that we:

	- Precisely define the interfaces to the components (or
	  subcomponents)

	- Make decisions about how each component (or subcomponent)
	  will be implemented in the selected programming language.

	- Identify an necessary additional program units

	- Describe any necessary programming language relationships,
	  e.g., nesting and dependency

The "implement a little" step requires that we:

	- Implement the programming language _interfaces_ for each of
	  the components (or subcomponents) [Note that this substep,
	  and the next one, allow one to use a programming language in
	  the form of a design language.]

	- Implement the algorithm which describes the interactions
	  among the components (or subcomponents)

	- Implement the internals of components which will not be
	  further decomposed.

[Note: It is usually either in the "design a little" step or the
"implement a little" step that we may identify (select) a
previously-existing component for the implementation. There are many
issues which must be addressed here. However, we will sidestep them
for the moment.]

The "test a little" step requires that we:

	- Compile any code produced (or selected) as a result of the
	  "implement a little" step. This should check things like
	  syntax, some semantics, and some interfaces.

	- Carry out any dynamic testing (machine-executable testing)
	  which is possible

	- Perform any necessary (or required) static testing

Of course, regardless of where we are in the recursive/parallel
life-cycle, we must take care to ensure the quality of the product.
Software quality assurance (SQA) is a continually on-going effort.

    "ANALYSIS OBJECTS," "DESIGN OBJECTS," "INTERFACE OBJECTS," AND
			 "COMMUTING OBJECTS"

Object-Oriented Requirements Analysis (OORA) stops at the ("user")
interface to a (potential collection of) system(s) of objects. It is
the job of Object-Oriented Design (OOD) to precisely define the
interface for each system of objects, and, if the system of objects is
small enough, to define the internal structure (architecture) of each
system of objects.

It is very likely that some objects were identified during analysis
which will not become code software. We refer to these objects as
"analysis objects." [Note: These objects may already be in the form of
code software.]

It is also very probable that we will identify objects during design
which were not addressed in analysis. We refer to these objects as
"design objects." Most of these objects will indeed become code
software.

One, or more, objects may lie on the "user" interface to the system of
objects, i.e., taken in combination, they are the (user) interface for
the system of objects. We refer to these objects as "interface
objects." [Note: A "user" may be another piece of software, a piece of
hardware, or even a human being.]

There will be objects which were mentioned in analysis, _and_ are
needed for the proper behavior of the system of objects. In effect,
these objects are inputs to the system of objects, outputs from the
system of objects, or both. We refer to these as "commuting objects."

			   A SIMPLE EXAMPLE

Let us now consider s simple example. We have been tasked with the
creation of a simple "electronic message system" (EMS). What are some
example scenarios for the object-oriented development of such a
system?

One scenario might be to say that:

	- The EMS is composed of:

		- a Post Office,

		- a message carrier,

		- and other EMS objects ...

	- The Post Office is in turn composed of:

		- a postal clerk,

		- a collection of mailboxes,

		- and other post office objects ...

	- One of the components of a collection of mailboxes is an
	  individual mailbox.

	- One of the components of an individual mailbox would be and
	  individual message.

	- The components of a message could be:

		- the sender's name,

		- the sender's address,

		- the receiver's name,

		- the receiver's address,

		- the postmark (i.e., the date and time the message
		  was sent),

		- and the actual text of the message.

Given the previously (partially) described electronic message system
(EMS):

	- The first pass at OORA would:

		- identify the post office, message carrier, and
		  "other objects" as being components of the EMS,

		- describe the environment in which the EMS will be
		  used, and

		- describe the conceptual interfaces of the EMS, post
		  office, message carrier, and "other objects."

	- The first pass at OOD would:

		- detail the precise programming language interfaces
		  for the EMS, post office, message carrier, and
		  "other objects."

		- create the programming language algorithm for the
		  internal structure of the EMS at the top level,
		  i.e., how the EMS will manipulate the post office,
		  message carrier, and "other objects."

	- Assuming that the message carrier is fairly simple, the
	  first pass at OOP would create the entirety of the source
	  code for the message carrier (and any other very simple
	  objects at that level).

	- Assuming a certain level of complexity, one of the second
	  passes at OORA would identify the collection of mailboxes,
	  postal clerk, and "other post office objects" as being
	  components of the post office.

	- The pass at OOD for the post office would:

		- detail the precise programming language interfaces
		  for the collection of mailboxes, the postal clerk,
		  and the "other post office objects."

		- create the programming language algorithm for the
		  internal structure of the post office at the top
		  level, i.e., how the post office will manipulate the
		  collection of mailboxes, the postal clerk, and the
		  "other post office objects."

	- Assuming that the postal clerk is a fairly simple object
	  with life, OOP would create the entirety of the source code
	  for the postal clerk.

Returning to the top level of abstraction (i.e., the EMS):

	- The post office, message carrier, and "other objects" would
	  be the "analysis objects" for the EMS.

	- With respect to the EMS, the collection of mailboxes, the
	  postal clerk, and the "other post office objects" are
	  "design objects."

	- The post office, message carrier, and "other objects" are
	  the "interface objects" with respect to the EMS and the rest
	  of the EMS system.

	- Message objects are "commuting objects" with respect to the
	  top-level EMS and the post office.

 WHEN TO USE OORA, WHEN NOT TO USE OORA, AND WHEN TO STOP USING OORA

Often, OOD and OOP are all that is necessary for a product. However
there are times when OORA is justified, e.g.:

	- When the overall size of the project is very large, e.g.,
	  significantly larger than, say, 2,000 lines of code.

	- When the software product being developed is critically
	  important.

	- When the customer, or management, dictates that a
	  conventional, waterfall life-cycle must be followed -- even
	  though this is inappropriate for an object-oriented
	  approach.

Given a recursive/parallel approach, a large (or critical) project,
and the fact that OORA was used at least at the start of the effort, a
software engineer, would want to know when to stop using OORA on the
project. The following are situations where one can consider not using
OORA:

	- If the component (or subcomponent) is small (e.g., <2,000
	  lines of code)

	- If the component is slightly larger than, say 2,000 lines of
	  code, and the component is non-critical

	- If the component (or subcomponent) is recognized as a
	  pre-existing library component

	- If the component (or subcomponent) is a relatively simple
	  variation on a pre-existing library component

	    CONSISTENCY IN THE OBJECT-ORIENTED LIFE-CYCLE

Object-oriented thinking is more consistent than traditional
structured thinking. This both simplifies and complicates matters,
i.e.:

	- Good News: Since they are more consistent in thinking, the
	  "gap" between OORA and OOD is very narrow, when compared to
	  the "gap" between structured analysis and structured design.

	- Bad News: This high level of conceptual integrity makes it
	  harder to distinguish between OOD and OORA.

		   DIFFERENCES BETWEEN OOD AND OORA

Even though I mentioned some of the differences between OORA and OOD
in another message, it might be useful to repeat them here:

	- Although they share many graphical techniques in common,
	  there are differences. For example, OOD uses program unit
	  graphics, whereas OORA does not.

	- Programming language issues are much more prevalent in OOD
	  than in OORA.  Client visibility is higher in OORA than in
	  OOD.

	- Not all objects identified during OORA will become code
	  software, whereas many (if not all) of the objects
	  identified during OOD will become code software.

	- OORA does not identify all of the objects in a system.
	  Additional objects will be introduced during the OOD
	  process.

	- The larger the overall project, the higher the ratio of
	  design objects to analysis objects.

			      CONCLUSION

Since this is only a net message, and not a book (;-)), I have left a
great deal of deal out. However, what I have presented should give you
a "flavor" of the recursive/parallel life-cycle.

Once again, thanks for listening.

				-- Ed Berard
				   Berard Software Engineering, Inc.
				   18620 Mateney Road
				   Germantown, Maryland 20874
				   Phone: (301) 353-9652
				   FAX:   (301) 353-9272

			     BIBLIOGRAPHY

[Abbott, 1980]. R.J. Abbott, "Report on Teaching Ada," Technical
Report SAI-81-313-WA, Science Applications, Inc., McClean, Virginia,
1980.

[Abbott, 1983]. R.J. Abbott, "Program Design by Informal English
Descriptions," Communications of the ACM, Vol. 26, No. 11, November
1983, pp. 882 - 894.

[Boehm, 1986]. B. W. Boehm, "A Spiral Model of Development and
Enhancement," Software Engineering Notes, Vol. 11, No. 4, August,
1986.

[Booch, 1981]. G. Booch, "Describing Software Design in Ada," SIGPLAN
Notices, Vol. 16, No. 9, September 1981, pp. 42 - 47.

[Dijkstra, 1968]. E.W. Dijkstra, "Structure of the
'THE'-Multiprogramming System," Communications of the ACM, Vol. 11,
No. 5, May 1968, pp. 341 - 346.

[Hart, 1982].  H. Hart, "Ada for Design: An Approach for Transitioning
Industry Software Developers," Ada Letters, Vol. II, No. 1,
July/August 1982, pp. 50 - 57.

[Kerner, 1982].  J. S. Kerner, "Should PDL/Ada Be Compilable?," Ada
Letters, Vol. II, No. 2, September/October 1982, pp. 49 - 50.

[Masters and Kuchinski, 1983].  M. W. Masters and M. J. Kuchinski,
"Software Design Prototyping Using Ada," Ada Letters, Vol. II, No. 4,
January/February 1983, pp. 68 - 75.

[Wirth, 1971].  N. Wirth, "Program Development by Stepwise
Refinement," Communications of the ACM, Vol. 14, No. 4, April 1971.
(Reprinted in Communications of the ACM, Vol. 26, No. 1, January 1983,
pp. 70 - 73).

nick@lfcs.ed.ac.uk (Nick Rothwell) (01/04/90)

In article <16836@isvax.isl.melco.co.jp>, eberard@ajpo (Edward Berard) writes:
>What I will attempt to do in this message is to provide you with a
>better understanding of the recursive/parallel life-cycle.
>
>Unfortunately, simple descriptions such as "analyze a little, design a
>little, implement a little, and test a little," are often more
>confusing than they are enlightening. In addition, there is a strong
>tendency to confuse the recursive/parallel life-cycle with rapid
>prototyping approaches (e.g., [Boehm, 1986]). This message is an
>attempt to reduce, if not eliminate, this confusion.

Why is your "recursive/parallel" life-cycle idea tied to
object-oriented languages? I use the same approach when programming in
ML, except that (i) my "objects" don't usually have state, and (ii)
there's no inheritance mechanism. I think that everything else you
mention in your article pretty much carries through.

		Nick.
Nick Rothwell,	Laboratory for Foundations of Computer Science, Edinburgh.
		nick@lfcs.ed.ac.uk    <Atlantic Ocean>!mcvax!ukc!lfcs!nick
~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~ ~~
  "...all these moments... will be lost in time... like tears in rain."

eberard@ajpo.sei.cmu.edu (Edward Berard) (01/05/90)

In article <1487@castle.ed.ac.uk>, nick@lfcs.ed.ac.uk (Nick Rothwell) writes:
> Why is your "recursive/parallel" life-cycle idea tied to
> object-oriented languages? I use the same approach when programming in
> ML, except that (i) my "objects" don't usually have state, and (ii)
> there's no inheritance mechanism. I think that everything else you
> mention in your article pretty much carries through.

	1. The recursive/parallel life-cycle is _not_ tied exclusively
	   to object-oriented programming languages. It did originate
	   chiefly out of attempts to apply object-oriented thinking
	   to life-cycle phases other than the coding phase. However,
	   I would not make the claim that it is only appropriate for
	   "purely object-oriented approaches."

	2. Like anything else in the technical arena, I am sure that
	   the approach has been used in some form by a good number of
	   people, who called it something else. All I did was attempt
	   to describe the process.

Thank you for your input. I would be interested in hearing from others
on the recursive/parallel life-cycle.

				-- Ed Berard
				   (301) 353-9652