Uses Style

Table of contents:

2.2.1 Overview

The uses style of the module viewtype comes about when the depends-on relation is specialized to uses. An architect may employ this style to constrain the implementation of the architecture. This style tells developers what other modules must exist in order for their portion of the system to work correctly. This powerful style enables incremental development and the deployment of useful subsets of full systems.

2.2.2 Elements, Relations, and Properties

Table 2.2 summarizes the discussion of the characteristics of the uses style. The elements of this style are the modules as in the module viewtype. We define a specialization of the depends-on relation to be the uses relation, whereby one module requires the correct implementation of another module for its own correct functioning. This view makes explicit how the functionality is mapped to an implementation by showing relationships among the code-based elements: which elements use which other elements to achieve their functions.

2.2.3 What the Uses Style Is For and What It's Not For

This style is useful for planning incremental development, system extensions and subsets, debugging and testing, and gauging the effects of specific changes.

2.2.4 Notations for the Uses Style

Informal Notations

Informally, the uses relation is conveniently documented as a matrix, with the modules listed as rows and columns. A mark in element (x,y) indicates that module x uses module y. The finest-grained modules in the decomposition hierarchy should be the ones listed, as fine-grained information is needed to produce incremental subsets.

Table 2.2. Summary of the module uses style

Elements Module as defined by the module viewtype.
Relations The uses relation, which is a refined form of the depends-on relation. Module A uses module B if A depends on the presence of a correctly functioning B in order to satisfy its own requirements.
Properties of elements As defined by the module viewtype.
Properties of relations The uses relation may have a property that describes in more detail what kind of uses one module makes of another.
Topology The uses style has no topological constraints. However, if loops in the relation contain many elements, the ability of the architecture to be delivered in incremental subsets will be impaired.

The uses relation can also be documented as a two-column table, with using elements on the left and the elements they use listed on the right. Alternatively, informal graphical notations can show the relation by using the usual box-and-line diagram with a key. For defining subsets, a tabularthat is, nongraphicalnotation is preferred. It is easier to look up the detailed relations in a table than to find them in a diagram, which will rapidly grow too cluttered to be useful except in trivial cases.

UML

The uses style is easily represented in UML. The UML subsystem construct (see the graphic on page 64) can be used to represent modules; the uses relation is depicted as a dependency with the stereotype <>. In Figure 2.3(a), the User Interface module is an aggregate module with a uses dependency on the DataBase module. When a module is an aggregate, the decomposition requires that any uses relation involving the aggregate module be mapped to a submodule using that relation. In Figure 2.3(b), the User Interface module is decomposed into modules A, B, and C. At least one of the modules must depend on the DataBase module; otherwise, the decomposition is not consistent.

Figure 2.3. (a) The User Interface module is an aggregate module with a uses dependency on the DataBase module. We use UML Package notation to represent modules and the specialized form of depends-on arrow to indicate a uses relation. (b) Here is a variation of Figure 2.3(a) in which the User Interface module has been decomposed into modules A, B, and C. At least one of the modules must depend on the DataBase module or the decomposition would not be consistent.

graphics/02fig03.gif

The convention for showing interfaces explicitly and separate from elements that realize them can also be shown in a uses view. In Figure 2.4, the DataBase module has two interfaces, which are used by the User Interface and the Administrative System modules, respectively.

Figure 2.4. UML can be used to represent the uses view and show interfaces explicitly. Here, the DataBase module has two interfaces, which are used by the User Interface and the Administrative System modules, respectively. The lollipop notation for interfaces would also work well here.

graphics/02fig04.gif

2.2.5 Relation to Other Styles

The uses style also goes hand in hand with the layered style, with the allowed-to-use relation governing. An allowed-to-use relation usually comes first and contains coarse-grained directives defining the degrees of freedom for implementers. Once implementation choices have been made, the uses view emerges and governs the production of incremental subsets.

2.2.6 Example of the Uses Style

The following, taken from Appendix A, is a small excerpt of a uses view's primary presentation. The notation is textual, using the two-column format described earlier. Like most primary presentations, this one names only the elements; they are defined in the view's supporting documentation (not shown here).

  SDPS Element Uses This Element
Science Data Processing Segment
  Ingest Subsystem
  INGST CSCI ADSRV CSCI in the Interoperability Subsystem
STMGT CSCI in the Data Server Subsystem
SDSRV CSCI in the Data Server Subsystem
DCCI CSCI in the Communications Subsystem
[etc.] other CSCIs within the Ingest Subsystem
Data Server Subsystem
  DDIST CSCI MCI CSCI in the System Management Subsystem
DCCI CSCI in the Communications Subsystem
STMGT CSCI in the Data Server Subsystem
INGST CSCI in the Ingest Subsystem
[etc.] other CSCIs within the Data Server Subsystem
[etc.] other subsystems within the Science Data Processing Segment

COMING TO TERMS

Uses

Two of the module viewtype styles that we present in this bookthe uses style and the layered styleare based on one of the most underutilized relations in software engineering: uses. The uses relation is a special form of the depends-on relation. A unit of software P1 is said to use another unit P2 if P1's correctness depends on a correct implementation of P2 being present.

The uses relation resembles, but is decidedly not, the simple calls relation provided by most programming languages. Here's why.

  • A program P1 can use program P2 without calling it. P1 may assume, for example, that P2 has left a shared device in a usable state when it finished with it. Or P1 may expect P2 to leave a computed result that it needs in a shared variable. Or P1 may be a process that sleeps until P2 signals an event to awaken it.
  • A program P1 might call program P2 but not use it. If P2 is an exception handler that P1 calls when it detects an error, P1 will usually not care what P2 does. The following figure shows an example: P0 calls P1 to accomplish some work and depends on its result. (P0 thus uses P1.) Suppose that P0 calls P1 incorrectly. Part of P1's specification is that in case of error, it calls a program whose name is passed to it by its caller.[1] Once it calls that program, P1 has satisfied its specification. In this case, P1 calls but does not use P2. P0 and P2 may well reside in the same module, for P2 is likely privy to the same knowledge about what was intended as P0.

graphics/02infig03.gif

So "uses" is not "calls" or "invokes." Likewise, "uses" is different from other depends-on relations, such as includes or inherits from. The includes relation deals with compilation dependencies but need not influence runtime correctness. The inherits-from relation is also usually a preruntime dependency not necessarily related to uses.

The uses relation imparts a powerful capability to a development team: It enables the building of small subsets of a total system. Early in the project, this allows incremental development, a development paradigm that allows early prototyping, early integration, and early testing. At every step along the way, the system carries out part of its total functionality, even if far from everything, and does it correctly. Fred Brooks (1995) writes about the "electrifying effect" on team morale that is caused by seeing the system first succeed at doing something. Absent incremental development, nothing works until everything works, and we are reduced to the somewhat eschewed waterfall model of development. Subsets of the total system are also useful beyond development. They provide a safe fallback in the event of slipped schedules: It is much better for the project manager to offer the customer a working subset of the system at delivery time rather than apologies and promises. And a subset of the total system can often be sold and marketed as a downscaled product in its own right.

Here's how it works. Choose a program that is to be in a subset; call it P1. In order for P1 to work correctly in this subset, correct implementations of the programs it uses must also be present. So include them in the subset. For them to work correctly, their used programs must also be present, and so forth. The subset consists of the transitive closure of P1's uses.[2] Conceptually, you pluck P1 out from the uses graph and then see what programs come dangling beneath it. There's your subset.

The most well-behaved uses relation forms a hierarchy: a tree structure. Subsets are then defined by snipping off subtrees. But architecture is seldom that simple, and the uses relation most often forms a nontree graph. Loops in the relationthat is, for example, where P1 uses P2, P2 uses P3, and P3 uses P1are the enemy of simple subsets. A large uses loop necessitates bringing in a large number of programsevery member of the loopinto any subset joined by any member. "Bringing in a program" means, of course, that it must be implemented, debugged, integrated, and tested. But the point of incremental development is that you'd like to bring in a small number of programs to each new increment, and you'd like to be able to choose which ones you bring in and not have them choose themselves.

A technique for breaking loops in the uses relation is called sandwiching. It works by finding a program in the loop whose functionality is a good candidate for splitting in half. Say that program P4 is in a uses loop. We break program P4 into two new programs, P4A and P4B. We implement them in such a way so that

  • They do not use each other directly.
  • The programs that used to use P4 now use only P4A.
  • P4A does not use any of the programs that the former P4 used, but P4B does.

This breaks the loop:

graphics/02infig04.gif

In the figure on the left, if any element is a member of a subset, they all must be. In the figure on the right, P4 has been divided into two parts that do not use each other directly. Now, including, say, P2 in a subset necessitates inclusion of only a small number of additional elements. If P4B is desired in a subset, the situation is as before. But we could use sandwiching again on, say, P6 to shorten the uses chain.

Besides managing subsets, the uses relation is also a useful tool for debugging and integration testing. If you discover a program that's producing incorrect results, the problem is going to be either in the program itself or in the programs that it uses. The uses relation lets you instantly narrow the list of suspects. In a similar way, you can employ the relation to help you gauge the effects of proposed changes. If a program's external behavior changes as the result of a planned modification, you can backtrack through the uses relation to see what other programs may be affected by that modification.

As originally defined by Parnas (1979), the uses relation was a relation among "programs" or other relatively fine-grained units of functionality. The modern analog would be methods of an object. Parnas wrote that the uses relation was "conveniently documented as a binary matrix," where element (i,j) is true if and only if program i uses program j.

As with many relations, there are shorthand forms for documenting uses; a complete enumeration of the ordered pairs is not necessary. For example, if a group of programs made up of module A use another group of programs made up of module B, we can say that module A uses module B. We can also say A uses B if some programs in A use some programs in B, as long as you don't mind bringing in all of module B to any subset joined by any program of module A. This makes sense if module B is already implemented and ready to go, is indivisiblemaybe it's a COTS productor if its functionality is so intertwined that B cannot be teased apart. The main point about size is that the more finely grained your uses relation, the more control you have over the subsets you can field and the debugging information you can infer.

[1] Or perhaps it calls a program whose name was bound by a parameter at system-generation time or a program whose name it looks up via a name server. Many schemes are possible.

[2] Of course, calls and other depends-on relations must be given their due. If a program in the subset calls, includes, or inherits from another program but doesn't use it, the compiler is still going to expect that program to be present. But if it isn't used, there need not be a correct implementation of it: a simple stub, possibly returning a pro forma result, will do just fine.

Software Architectures and Documentation

Part I. Software Architecture Viewtypes and Styles

The Module Viewtype

Styles of the Module Viewtype

The Component-and-Connector Viewtype

Styles of the Component-and-Connector Viewtype

The Allocation Viewtype and Styles

Part II. Software Architecture Documentation in Practice

Advanced Concepts

Documenting Software Interfaces

Documenting Behavior

Choosing the Views

Building the Documentation Package

Other Views and Beyond

Rationale, Background, and Design Constraints

References



Documenting Software Architectures(c) Views and Beyond
Documenting Software Architectures: Views and Beyond
ISBN: 0201703726
EAN: 2147483647
Year: 2005
Pages: 152

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net