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