You get a bonus - 1 coin for daily activity. Now you have 1 coin

6.3. The Macro Process of Design

Lecture



Overview

The macro process serves as a controlling framework for the micro process. The macro process prescribes a set of measurable deliverables and activities that allow the development team to assess risk, make timely adjustments to the micro process, and focus on collective analysis and design. The macro process is the activity of the entire team on a scale of weeks to months.

Many elements of the macro process belong to the practice of software project management itself, and are therefore carried out the same way for object-oriented systems as for any others. Among them are configuration management, quality assurance, walkthroughs, and documentation. In the next chapter we shall examine these practical matters in the context of object-oriented design. This chapter concentrates on describing what is specific to the object-oriented approach — or, in Parnas's phrase, on how we fake a rational design process in order to produce an object-oriented system.

The macro process is primarily the concern of the development team's technical manager, whose goals differ somewhat from those of the individual developer. Both are interested in the quality of the final software product and in meeting the customer's requirements [Well, of course, not all of them, but most. Unfortunately, some managers are more interested in building their empire than in building the software product. Add to this the earlier remark about analysts and designers. I think Dante could find a suitable place for them]. However, the end user cares little whether parameterized classes or polymorphism were used correctly in the design; the customer is far more concerned with schedule, quality, completeness, and correct operation of the program. That is why the macro process is centered on managing risk and identifying the overall architecture — two controllable components that are decisive for the schedule, completeness, and quality of the project.

The macro process largely preserves the traditional phases of analysis and design, and the process is reasonably ordered. As shown in Figure 6-2, the macro process typically comprises the following activities:

  • Establishing the core requirements for the software product (conceptualization).
  • Developing a model of the system's desired behavior (analysis).
  • Creating an architecture for the implementation (design).
  • Iteratively carrying out the implementation (evolution).
  • Managing the product's evolution during use (maintenance).

6.3. The Macro Process of Design

Figure 6-2. The macro process.

For every nontrivial software development effort, the macro process continues even after the system has been built and deployed. This is especially evident in organizations that specialize in creating families of programs, into which substantial capital investment is often made.

The underlying philosophy of the macro process is incremental development. As Wonk defines it, "in incremental development, the system is built step by step, with each new version containing the functionality of the previous one plus new functions" [14]. This approach fits the object-oriented paradigm exceptionally well and offers many opportunities for managing risk. As Gilb puts it: "Incremental delivery of programs to the customer was invented to warn us in advance of trouble ahead" [15].

Let us now examine each activity in the macro process in detail. Naturally, one of the marks of a mature development organization is knowing when these rules should be bypassed, which we shall note separately in our review.

Conceptualization

Purpose. Conceptualization must establish the core requirements for the system. For every fundamentally new piece of software, or even for a new application of an existing system, there comes a moment when the idea of a new application takes hold in the mind of a developer, architect, analyst, or end user.

This might be a new business venture, an additional item in a product line, or, say, a new function in an existing software system. The goal of conceptualization is not to define the idea completely, but to form a view of it and test it mentally.

Products. The primary products of conceptualization are prototypes of the system. Certainly, every substantially new software system needs some rough prototype, even one thrown together "quick and dirty." Such prototypes are incomplete by their very nature and are worked out only in outline. Nevertheless, interesting prototypes should be kept (even ones that were rejected), because in this way the organization maintains a corporate memory of the original conception and preserves the link to the initial assumptions. During design, this archive provides invaluable material for experimentation, to which analysts and architects can return when they want to try out new ideas.

Obviously, for grand applications (of national or international significance), building prototypes alone may turn out to be a major undertaking. After all, it is far better to run into difficulties during implementation, discovering that some assumptions about the system's functionality, efficiency, size, or complexity are wrong, than to dismiss a forward-looking solution. Such neglect may threaten financial or social catastrophe.

Let us emphasize: prototypes are good, but they should be thrown away. They must not be allowed to evolve directly into the finished system unless there are sufficiently serious grounds for it. A tight schedule is not a valid reason: optimizing short-term development while ignoring the software owner's subsequent costs is a classic example of false economy.

Activities. Conceptualization is by its very nature a creative activity, and consequently it should not be constrained by rigid development rules. Perhaps the most important thing for an organization is to create a structure that provides sufficient resources for new ideas to arise and be explored [If the organization does not do this itself, then individual developers will do it anyway, without asking the company they work for. That is how new software firms come into being. Their appearance is good for the industry as a whole, but not for the orphaned company itself]. New ideas can come from the most varied sources: end users, user groups, developers, analysts, designers, distributors, and so on. It is important for management to keep a record of these ideas, ranking them by priority and allocating limited resources so as to explore the most promising ones. Once a specific direction has been chosen for exploration, the typical order of subsequent activities is as follows:

  • Decide what goals are being pursued in the proof of concept, and what the criteria for a successful outcome are.
  • Assemble a suitable team to develop the prototype. Often it consists of a single member (who is the dreamer in question). The best thing the organizer can do to ease the team's efforts is to stay out of its way.
  • Evaluate the finished prototype and make a clear decision either to design the final product or to explore further. The decision to proceed with development of the final product must be made with due regard for the potential risks uncovered during the proof of concept.

There is nothing specifically object-oriented about conceptualization. Every programming paradigm must make provision for proof of concept. However, as is often the case, prototype development usually proceeds faster where a mature object-oriented environment is at hand.

Quite often concepts are tried out in one language (Smalltalk, for example), while development of the final product is carried out in another (say, C++).

Milestones and measures. It is important that clear criteria be established for evaluating the prototype. Work on a prototype is more often scheduled by time (meaning that the prototype must be finished by a certain date) than by requirements. This is not always a bad thing, since it artificially bounds the effort spent on the prototype and forestalls attempts to ship a conceptually premature product.

Senior managers can gauge the health of an organization by its attitude toward new ideas. Any organization that does not generate new ideas of its own is either already dead or close to it. The most prudent course of action in such a situation is to spin off independent units, or else to leave the business altogether. On the other hand, any organization awash in new ideas but unable to assign them sensible priorities is out of control. Such companies often waste substantial resources by jumping into product development too early, without exploring the risks. The most prudent thing here would be to formalize the production process and establish a proper transition from concept to product.

Analysis

Purpose. As Mellor puts it, "the purpose of analysis is to provide a description of the problem. The description must be complete, consistent, readable and reviewable by all interested parties, and testable against reality" [16]. In our terms, the purpose of analysis is to provide a model of the system's behavior.

It must be emphasized that analysis focuses not on form but on behavior. In this phase it is inappropriate to occupy oneself with class design, representation, or other tactical decisions. Analysis must explain what the system does, not how it does it. Any statement about the "how" made during analysis (contrary to this rule) may be regarded as useful only for demonstrating the system's behavior, not as a testable requirement on its design.

In this respect the goals of analysis and design are quite different. In analysis we seek a model of the world, identifying the classes and objects (their roles, responsibilities, and interactions) that form the vocabulary of the problem domain. In design we invent artificial actors that implement the behavior required by the analysis. In this sense, analysis is an activity that brings together the users and the developers of the system, uniting them through the writing of a common vocabulary of the problem domain.

Focusing on behavior, we set about determining the function points of the system. Function points, first described by Allan Albrecht, denote externally visible and verifiable elements of the system's behavior [17]. From the end user's point of view, a function point represents some elementary action of the system in response to some event [As Dreger notes, in information systems management theory a function point represents a distinct end-user business function [18]]. Function points often (but not always) denote a mapping of inputs onto outputs, and thus represent the transformations performed by the system. From the analyst's point of view, function points represent quanta of behavior. Indeed, function points are a measure of a system's complexity: the more of them there are, the more complex it is. In the analysis phase we convey the semantics of function points by means of scenarios.

Analysis never takes place in isolation. We do not aim for an exhaustive understanding of the system's behavior, and we would even assert that carrying out a complete analysis before beginning design is not only impossible but undesirable. The process of building a system raises questions about its behavior that in practice cannot be answered with any guarantee by analysis alone. It is enough to analyze all the primary elements of the system's behavior plus some number of secondary ones, added to ensure that no essential behavioral patterns have been missed.

A reasonably complete and formal analysis is needed first and foremost so that the project's progress can be traced. Traceability is needed to make the project measurable, so as to guarantee that not a single function point has been overlooked. Traceability is also the basis of risk management. In developing any nontrivial system, managers will face the need to make hard choices, either in allocating resources or in resolving some tactical problem. With the ability to trace the process from function points through to implementation, it is far easier to assess the impact of such problems on the architecture.

Products. DeChampeaux holds that the product of analysis should be a description of the system's purpose, accompanied by performance characteristics and a statement of the resources required [19]. In object-oriented design we obtain such descriptions by means of scenarios. Each scenario represents one function point. We use primary scenarios to illustrate key behavior, and secondary ones to describe behavior in exceptional situations.

As discussed in earlier chapters, we use the CRC card technique to storyboard scenarios, and then apply object diagrams to illustrate more precisely the semantics of each scenario. Such diagrams should show the interaction of objects that provides the system's functions, and the ordered process of that interaction, consisting of objects sending messages to one another. Besides object diagrams, one may bring in class diagrams (to show the existing associations among the classes of objects) and state transition diagrams (to show the life cycle of the most important objects).

Often these products of analysis are combined into a single formal document, which states the analysis requirements on the system's behavior, illustrating them with diagrams, and sets out such nonbehavioral aspects of the system as efficiency, reliability, security, and portability [20].

A by-product of analysis will be a risk assessment: the identification of danger areas that may affect the design process. Discovering the risks that exist at the beginning of the design process will ease possible architectural trade-offs in the later stages of development.

Activities. There are two principal activities associated with analysis: domain analysis and scenario planning.

As we described in Chapter 4, domain analysis must identify the classes and objects that inhabit the given problem domain. Before setting about the development of a new system, one usually studies systems that already exist. In this case we can profit from the experience of other projects in which similar decisions were made. The best outcome of domain analysis may be the conclusion that we need not design a new product at all, but should instead reuse or adapt an existing program.

Scenario planning is the central activity of analysis. Interestingly, on this point there seems to be a convergence of opinion among the other methodologists, especially Rubin and Goldberg, Adams, Wirfs-Brock, Coad, and Jacobson. The typical order in which it is carried out is as follows:

  • Identify the system's principal function points and, if possible, group functionally related behaviors. Consider creating a hierarchy of functions in which the higher functions derive from the lower ones.
  • For each set of function points of interest, storyboard a scenario, using the technique of behavior analysis and use cases described in Chapter 4 [A comprehensive analysis of this subject can be found in the works of Jacobson [22] and of Rubin and Goldberg [23]]. The CRC card technique is effective for brainstorming each scenario. Once the semantics of the scenarios become clear, they should be documented using object diagrams that illustrate the objects that initiate and provide the behavior, and their interaction while carrying out the scenario's actions. Attach a description of the events that occur while the scenario is carried out, and the order of the actions performed as a result. In addition, all assumptions, constraints, and performance measures must be listed for each scenario [21].
  • If necessary, produce secondary scenarios illustrating the system's behavior in exceptional situations.
  • For objects with a particularly important life cycle, describe state transition diagrams (build a finite state machine).
  • Look for recurring patterns in the scenarios and express them in terms of more abstract, generalized scenarios, or in terms of class diagrams showing the relationships among the key abstractions.
  • Update the data dictionary; add to it the new classes and objects identified for each scenario, together with a description of their roles and responsibilities.

As described in the next chapter, scenario planning is carried out by analysts in collaboration with domain experts and architects. A quality assurance person should additionally take part in scenario planning, since scenarios represent testable behavior. Involving quality assurance staff at the very beginning of the process helps to establish high quality standards from the outset. It is also effective to involve other members of the team, so as to give them a chance to take part in the design process and to speed up their understanding of the system's structure.

Milestones and measures. We successfully complete this phase when we have refined and signed-off scenarios for all the fundamental kinds of system behavior. By signed-off we mean that the products of analysis have been reviewed by experts, end users, analysts, and architects; by fundamental we mean the kinds of behavior that are central to the given application. Again, we do not expect a complete analysis — it is enough to consider only the principal behaviors and a few secondary ones.

The degree of excellence of an analysis will be measured, in particular, by its completeness and simplicity. A good analysis identifies all the primary scenarios and, as a rule, the most important secondary ones. A sound analysis also includes a review of all the strategically important scenarios, since this helps to instill a shared vision of the system throughout the development team. Finally, one should look for behavioral patterns that would yield the simplest possible class structure and would account for everything the various scenarios have in common.

Another important component of analysis is risk assessment, which will facilitate future strategic and tactical trade-offs.

Design

Purpose. The purpose of design is to create an architecture for the evolving implementation and to establish the common tactical policies that the various elements of the system must use. We begin the design process as soon as some acceptable model of the system's behavior appears. It is important not to begin design before analysis is complete. It is equally important to avoid dragging design out in an attempt to obtain an ideal — and hence unattainable — analysis model [Such a situation is usually classified as analysis paralysis].

Products. There are two principal products of design: a description of the architecture and the establishment of common tactical policies.

We can describe the architecture by constructing diagrams or by creating successive architectural releases of the system. As described in earlier chapters, the architecture of an object-oriented system expresses the structure of the classes and objects within it, and so class diagrams and object diagrams can be used to show its strategic organization. In describing the architecture it is important to demonstrate visually the grouping of classes into class categories (for the logical architecture) and the grouping of modules into subsystems (for the physical architecture). Such diagrams may be circulated as part of a formal document describing the architecture, which should be available to all members of the team for review and for the introduction of corrections as the architecture evolves.

We use architectural releases of the system as a tangible demonstration of the architecture's structure. An architectural release is, as it were, a vertical slice through the architecture, conveying the most important (but not the complete) semantics of the essential categories and subsystems. An architectural release of the system must be a working program, which makes it possible to measure, study, and evaluate the architecture. As we shall see in the next section, architectural releases are the basis of the system's evolution.

Common tactical policies are the localized mechanisms that appear throughout the system. They include such design aspects as the principles of error detection and error handling, memory management, data storage and representation, and approaches to control. It is important to describe these policies explicitly, so as not to force developers to devise individual solutions to common problems and thereby wreck our strategic architecture.

We describe common policies in scenarios and in working releases of each mechanism.

Activities. There are three activities associated with design: architectural planning, tactical design, and release planning.

In architectural planning we occupy ourselves with the vertical and horizontal partitioning of the system. It encompasses logical decomposition, which consists in grouping classes, and physical decomposition, which consists in dividing the system into modules and assigning tasks to processors. The typical order of activities is as follows:

  • Consider the grouping of the function points (found during analysis) and distribute them among the layers and partitions of the architecture. Functions that build upon one another must fall into different layers; functions that collaborate with one another to provide the system's required behavior at a given level of abstraction must fall into partitions of the system that represent services at that level.
  • Validate the architecture by creating working releases that partially satisfy the semantics of several of the most important scenarios provided by analysis.
  • Assess the strengths and weaknesses of the architecture. Determine the risk of change for each key architectural interface, so that resources can be allocated in advance as the system evolves.

Architectural planning is focused on creating a framework for the system at the very start of the life cycle, and then developing it incrementally.

Tactical design consists in making decisions about a set of common policies. As described earlier in this chapter, poor tactical design can destroy even a very well thought out architecture. We can reduce this risk by explicitly singling out the tactical policies and resolving to adhere to them firmly. The typical order of activities is as follows:

  • Enumerate all the cases in which common shared policies must be followed. Some of them will prove to be fundamental and independent of the problem domain — for example, memory management, error handling, and so on. Others will be specific to the given domain and will contain the idioms and mechanisms peculiar to it, such as the principles of controlling real-time systems, or of handling transactions and databases in information systems.
  • For each policy, compose a scenario describing its semantics. Then express it in the form of an executable prototype, which can be refined and supported by tools.
  • Document each principle and circulate the resulting documents, so as to ensure a shared architectural vision.

Software releases lay the foundations of the system's architectural evolution. Based on the function points and risk assessments obtained in the analysis phase, releases are issued with ever broader functional capabilities and, in the end, reach the requirements imposed on the final system. The typical order of activities is as follows:

  • Order the scenarios obtained from the analysis from the principal ones to the secondary ones. The prioritization of scenarios is best worked out together with the domain expert, the analyst, the architect, and the quality assurance person.
  • Distribute the function points among the releases so that the last release in the series represents the resulting system.
  • Determine the goals and the schedule of the releases so as to allow time for development and to synchronize the releases with other activities, such as the development of documentation and field testing.
  • Begin task planning, taking into account the project's critical areas and the resources allocated to producing each release.

A natural by-product of release planning is a plan that defines the work schedule, the team's tasks, and the risk assessment.

Milestones and measures. We successfully complete this phase when we have an architecture that has been validated and approved, having passed through prototyping and formal reviews. In addition, all the important tactical policies and the plan of successive releases must be approved.

The principal mark of excellence is simplicity. A good architecture has the characteristics of an organized complex system (see Chapter 1).

The main benefits of this activity are the early detection of architectural mistakes and the adoption of common policies that make it possible to obtain a simpler architecture.

Evolution

Purpose. The purpose of evolution is to grow and change the implementation, refining it successively, so as ultimately to produce a finished system.

The evolution of an architecture consists to a large degree in the attempt to satisfy several mutually exclusive requirements on time, memory, and so on — one always constrains another. For example, if the computer's weight is critical (as in the design of space systems), then the weight of an individual memory chip must be taken into account. In turn, the amount of memory permitted by the weight limit constrains the size of the program that can be loaded. Relax any constraint, and an alternative solution becomes possible; tighten the constraint, and some solutions fall away. Evolution in the implementation of a software project helps determine which constraints are essential and which may be disregarded better than any monolithic set of practices can. For this reason, evolutionary development is focused first of all on functionality and only then on local efficiency. Usually, at the start of design we know far too little to foresee where the system's performance bottleneck will be. By analyzing the behavior of each new release, using histograms and similar techniques, the development team will in time be better able to understand how to tune the system.

Thus, evolution is precisely what the process of developing a program is. As Andert writes, design "is a time of innovation, refinement, and unrestricted freedom to change the code in order to achieve one's goals. Production is a controlled, methodical process of raising the quality of the product to the appropriate level" [24].

Page-Jones lists a number of advantages of such incremental development:

  • "Feedback from users is provided when it is most needed, useful, and meaningful.
  • Users receive several draft versions of the system, which smooths the transition from the old system to the new one.
  • It is less likely that the project will have its funding cut if it suddenly falls behind schedule.
  • The system's main interfaces are tested first and most often.
  • Testing resources are distributed more evenly.
  • Implementers can see the first results of the system's operation sooner, which boosts their morale.
  • If the schedule is tight, then coding and debugging can begin before design is complete."

Products. The principal product of evolution is a series of executable releases representing iterative refinements of the initial architectural model. As a secondary product one should recognize the identification of behavior that is used to explore alternative approaches and to further analyze the dark corners of the system.

Working releases are issued according to the schedule laid out at the beginning of planning. For a modest-sized project requiring 12-18 months of development from start to finish, this might mean a release every two or three months. For more complex projects requiring greater development effort, a release might be issued every six months or less often. A less frequent schedule is suspect, since it does not force developers to complete their micro processes properly and may conceal areas of danger.

Who is a working release of the program made for? Early in the development process, the main working releases are handed by the developers to the quality assurance staff, who test them against the scenarios drawn up during analysis and accumulate information about the completeness, correctness, and robustness of the release's operation. This early accumulation of data helps in identifying quality problems that will be addressed in subsequent releases. Later, working releases are handed to end (alpha and beta) users in some controlled manner. "Controlled" means that the developers carefully verify the requirements for each release and determine the aspects that they wish to test and evaluate.

The specifics of the micro process imply that, out of numerous internal releases, developers ship only a few executable versions outside. Internal releases represent a kind of process of continuous integration of the system and conclude each cycle of the micro process.

It is implicitly understood that the system's documentation evolves along with the architectural releases. So as not to treat documentation as a primary occupation, it is best to obtain it as a natural, semi-automatically generated by-product of the evolutionary process.

Activities. Evolution is associated with two activities: the micro process and change management.

The work carried out between releases represents the development process in condensed form: it is precisely one cycle of the micro process. We begin with an analysis of the requirements for the next release, move on to designing the architecture, and explore the classes and objects needed to implement that design. The typical order of activities is as follows:

  • Determine the function points that will go into the new release, and the areas of highest risk, especially those that were already identified during the evolution of the previous release.
  • Distribute the tasks for the release among the members of the team and begin a new micro process. Control the micro process by reviewing the design, and check the state of affairs at important intermediate milestones at intervals of a few days to two weeks.
  • When it becomes necessary to understand the semantics of some required system behavior, assign developers to prototype that behavior. Clearly establish the purpose of each prototype and define the criteria for completion. Once it is finished, decide how to incorporate the results of the prototyping into this or subsequent releases.
  • Conclude the micro process with integration and the next working release.

After each release, the dates and requirements in the master release plan should be reviewed. As a rule, these are minor adjustments of dates or the shifting of functionality from one release to another.

Change management is necessary precisely because of the strategy of iterative development. It is always tempting to make disorderly changes to the class hierarchy, to class protocols, or to mechanisms, but this undermines the strategic architecture and leads to developers becoming confused in their own code.

In practice, the following kinds of changes are to be expected as a system evolves:

  • Adding a new class or a new interaction between classes.
  • Changing the implementation of a class.
  • Changing the representation of a class.
  • Reorganizing the class structure.
  • Changing the interface of a class.

Each kind of change has its own cause and cost.

A designer introduces new classes when new abstractions have been discovered or new mechanisms have become necessary. The cost of carrying out such changes is usually insignificant for development management. If a new class is added, one must consider where it will fall within the existing class structure. When a new class interaction is introduced, a minimal amount of domain analysis must be performed to make sure that it really does satisfy one of the interaction patterns.

Changing an implementation is also inexpensive. Usually, in object-oriented development the interface of a class is created first, and then its implementation is written (that is, the code of the member functions). Provided only that the interface is reasonably stable, one can choose any internal representation for the class and carry out the implementation of its methods. The implementation of an individual method may be changed later (usually to fix an error or improve efficiency). One can adjust a method's implementation so as to take advantage of new methods defined in an existing or newly introduced superclass. In any case, changing a method's implementation is comparatively inexpensive, especially if it was properly encapsulated.

In similar fashion one might change the representation of a class (in C++, the protected and private members of a class). This is usually done to obtain more efficient (in terms of memory or speed) instances of the class. If the class's representation is encapsulated, which is possible in Smalltalk, C++, CLOS, and Ada, then a change in the representation will not break the logic by which client objects interact with instances of the class (provided, of course, that the new representation delivers the class's expected behavior). On the other hand, if the class's representation is not encapsulated, which is also possible in any language, then a change in the class's representation is extremely dangerous, since clients may depend on it. This is especially true in the case of subclasses: changing the representation of a superclass will cause changes to the representation of all its subclasses. In any event, changing the representation of a class has its cost: the class's interface and implementation must be recompiled, and the same must be done for all of its clients, for the clients of those clients, and so on.

Reorganizing a system's class structure occurs fairly often, though less often than the other kinds of change mentioned. As Stefik and Bobrow note, "Programmers often create new classes and reorganize existing ones when they see a convenient opportunity to factor their programs" [26]. Changes to the class structure usually take the form of changing inheritance relationships, adding new abstract classes, and moving responsibilities and the implementation of common methods up to higher-level classes in the class hierarchy. In practice, a system's class structure is reorganized especially often at the beginning, and then stabilizes once the developers better understand the interaction of the key abstractions. Reorganizing the class structure is encouraged in the early stages of design, because it may result in a more concise program. However, reorganizing the class structure does not come for free. Usually, changing the position of a class high in the hierarchy renders the definitions of all the classes beneath it obsolete and requires their recompilation (and hence the recompilation of all the classes that depend on them, and so on).

One further important kind of change that must be resorted to as a system evolves is changing the interface of a class. A developer usually changes a class's interface in order to add some new aspect, to satisfy the semantics of some new role for the class's objects, or to add a new operation that was always part of the abstraction but had not previously been exported, and is now needed by some client object. In practice, using the heuristics for building classes that we discussed in Chapter 3 (especially the requirement of a primitive, sufficient, and complete interface) reduces the likelihood of such changes. However, our experience is never final. We shall never define a nontrivial class in such a way that its interface turns out right the first time.

Rarely, but it does happen, an existing method is removed; this is usually done only in order to improve the encapsulation of an abstraction. More often we add a new method or override a method already declared in some superclass. In all three cases this change is expensive, because logically it affects all clients, requiring at a minimum that they be recompiled. Fortunately, these last kinds of change, the addition and overriding of methods, are upwardly compatible. In fact, you will find that most of the interface changes made to given classes as a system evolves are upwardly compatible. This makes it possible to apply such sophisticated technologies as incremental compilation to reduce the impact of these changes. Incremental compilation allows us to recompile only individual declarations and statements rather than whole modules; that is, the recompilation of most clients can be avoided.

Why is recompilation such a nuisance? For small systems there is no problem here: recompiling the whole system takes a few minutes. For large systems, however, it is quite another matter. Recompiling a program of hundreds of thousands of lines may take up to half a day of machine time. Imagine that you needed to make a change to the software of a ship's computer system. How will you tell the captain that he cannot put to sea because you are still compiling? In some cases the cost of recompilation is so high that developers have to forgo making certain changes that represent perfectly reasonable improvements. Recompilation poses a particular problem for object-oriented languages, since inheritance introduces additional compilation dependencies [27]. For strongly typed object-oriented programming languages the cost of recompilation may be even higher; in these languages compilation time has been sacrificed for safety.

All the changes discussed so far are comparatively easy: the greatest risk is carried by substantial changes to the architecture, which can wreck an entire project. Often such changes are made by all-too-brilliant engineers who have too many good ideas [28].

Milestones and measures. We successfully complete the implementation phase when the releases grow into a finished product. The first measure of quality, then, will be the extent to which we have managed to implement the function points allocated to the intermediate releases, and how closely the schedule drawn up when planning them is being kept.

The two other principal measures of quality are the rate of defect discovery and the measure of volatility of the key architectural interfaces and tactical policies.

Roughly speaking, the rate of defect discovery is a measure of how quickly new defects are found [29]. By investing in quality assurance at the beginning of development, we can obtain quantitative quality measures for each release, which the team's managers will be able to use to determine areas of risk and to refresh the development team. After each release there should be a spike in defect discovery. A flat value for this measure usually indicates that defects are not being found, while an excessively high value indicates that the architecture has not yet stabilized, or that the new elements have been designed or implemented incorrectly. These measures are used in refining the goals of the next release.

The measure of volatility of an architectural interface or of tactical policies is a principal indicator of the architecture's stability [30]. Local changes are likely throughout the whole process of evolution, but if inheritance structures or the boundaries between class categories or subsystems are constantly being rebuilt, this is a sign of unresolved problems in the architecture, which must be taken into account as an area of risk when planning the next release.

Maintenance

Purpose. Maintenance is the activity of managing the evolution of a product during its use. It largely continues the preceding phases, except that it introduces fewer architectural innovations. Instead, more localized changes are made, arising as new requirements are taken into account and old defects are corrected.

Lehman and Belady made several indisputable observations while examining the process by which an already deployed software system "matures":

  • "A program in use must be continually changed; otherwise it will become less and less useful (the law of continuing change).
  • As an evolving program is changed, its structure becomes more complex unless active efforts are made to avoid this (the law of increasing complexity)" [31].

We distinguish the notion of preservation of a software system from its maintenance. In maintenance, developers make continual improvements to an existing system; maintenance is usually carried out by a group of people different from the development group. Preservation, by contrast, is based on committing additional resources to keeping an obsolete system going (one that often has a poorly designed architecture and is therefore hard to understand and modify). And so a business decision must be made: if the cost of owning the software product is higher than the cost of developing a new system, then the most humane course of action is to leave the old system alone or to put an end to it.

Products. Since maintenance is in a certain sense a continuation of the system's evolution, its products are similar to those we sought in the preceding phases. In addition to them, maintenance also involves managing a list of new tasks. Besides those requirements that for one reason or another were not addressed, it is likely that soon after a working system is released, developers and end users will exchange a multitude of wishes and suggestions that they would like to see embodied in subsequent versions of the system. Note that once more users have worked with the system, new defects and unexpected ways of using it will come to light that the quality assurance staff could not have foreseen [Users display prodigies of ingenuity in using a system in the most unusual ways]. The discovered defects and the new requirements are entered on the list and will be taken into account when planning new releases, according to their priority.

Activities. Maintenance differs somewhat from the evolution of a system. If the original architecture was successful, adding new functions and changing existing behavior happen naturally.

Besides the usual activities of evolution, maintenance requires determining the priorities of the tasks gathered in the list of comments and suggestions. The typical order of activities is as follows:

  • Prioritize the proposals for major changes and the defect reports connected with system-wide problems, and estimate the cost of the rework.
  • Draw up a list of these changes and treat them as function points in the further evolution.
  • If resources permit, schedule less intensive, more localized improvements into the next release.
  • Proceed to develop the next evolutionary release of the program.

Milestones and measures. The milestones of maintenance are the continued production of evolving releases and the elimination of defects.

We consider that we are engaged in maintaining a system if the architecture withstands change; we shall know that we have entered the stage of preservation when the amount of resources required to achieve a desired improvement begins to grow sharply.

Summary

  • Successful projects are usually characterized by a clear vision of the architecture and a well-managed iterative life cycle.
  • An ideally rational design process is impossible, but it can be faked by combining the micro and macro processes of development.
  • The micro process of object-oriented development is driven by the stream of scenarios and the products of architectural analysis (the macro process); the micro process represents the daily activity of the development team.
  • The first step in the micro process involves identifying the classes and objects at a given level of abstraction; the principal activities are discovery and invention.
  • The second step of the micro process consists in identifying the semantics of classes and objects; the principal activities here are storyboarding scenarios, designing isolated classes, and looking for patterns.
  • The third step of the micro process is identifying the relationships among classes and objects; the principal activities are specifying associations, identifying collaborations, and refining associations.
  • The fourth step of the micro process involves implementing classes and objects; the principal activity is selecting data structures and algorithms.
  • The macro process of object-oriented development controls the micro process, defines measurable characteristics of the project, and helps to manage risk.
  • The first step of the macro process is conceptualization, which establishes the core requirements for the system; it serves as a proof of concept and, for the most part, should not be controlled, so as to allow unrestricted freedom of imagination.
  • The second step of the macro process is analysis. Its purpose is to obtain a model of the system's behavior. The principal activities at this stage are domain analysis and scenario planning.
  • The third step of the macro process is design. At this step the architecture for the implementation is created and common tactical policies are worked out; the principal activities are architectural planning, tactical design, and release planning.
  • The fourth step of the macro process is evolution, which successively brings the system closer to the desired result. The principal activities are applying the micro process and managing change.
  • The fifth step of the macro process is maintenance, that is, managing the evolution of the system during its use; the principal activities are similar to those of the preceding step, but to them is added work with the list of improvements and corrections.
created: 2020-12-19
updated: 2026-03-10
130



Was this answer useful?
Choose a quick rating so we can improve the next answer for you.
How satisfied are you?


Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Object Oriented Analysis and Design"

Terms: Object Oriented Analysis and Design