A Framework for Software Product Line Practice, Version 5.0
Component Development
One of the tasks of the software architect is to produce the list of components that will populate the architecture. This list gives the development, mining, and acquisition teams their marching orders for supplying the parts that the software system will comprise. The term component is about as generic as the term object; definitions for each term abound. Simply stated, components are the units of software that go together to form whole systems (products), as dictated by the software architecture of the products. Szyperski offers a more precise definition that applies well [Szyperski 2002a]:
-
A software component is a unit of composition with contractually specified interfaces and explicit context dependencies only. A software component can be deployed independently and is subject to composition by third parties.
By component development, we mean the production of components that implement specific functionality within the context of a software architecture. The functionality is encapsulated and packaged and then integrated with other components using an interconnection method.
Software components trace their heritage back to the subroutine, which was the first unit of software reuse. Programmers discovered they could invoke a previously written segment of code and have access to its functionality while being blissfully unconcerned with its implementation, development history, storage management, and so forth. Soon, few people had to worry about how to code, say, a numerically stable double-precision cosine algorithm. Besides saving time, this practice elevated our thinking: we could think "cosine" but not have to think about storage registers and overflowing multiplications. It also elevated our languages: sophisticated subroutines were indistinguishable from primitive, atomic statements in the programming language.
What we now call component-based software development flows in an unbroken line from these early beginnings. Modern components, the latest instantiation of which are Web-based services that populate service-oriented architectures, are much larger and much more sophisticated, carry us much higher into domain-specific application realms, and have more complex interaction mechanisms than subroutine invocation (mechanisms that are standardized). However, the concepts and the reasons why we embrace them remain the same. In the same way that early subroutines liberated the programmer from thinking about details, component-based (or service-oriented) software development shifts the emphasis from programming software to composing software systems. Implementation has given way to integration as the focus. At its foundation is the assumption that sufficient commonality exists in many large software systems to justify developing reusable components to exploit and satisfy that commonality. Today, we look for components that provide large collections of related functionality all at once (instead of a cosine routine, think of Mathematica; instead of a relational database, think of an all-encompassing business platform; instead of an address book, think of Customer Resource Management systems) and whose interconnections with each other are loose and flexible. If we have control over the decomposition into components and the interfaces of each, the granularity and interconnection is determined by our system's software architecture. If the components are built externally, their granularity and interfaces are imposed on us, and they affect our software architecture.
The "Component Development" practice area is concerned with in-house development and how to build the components so that the instructions given to us in the architecture are carried out. New development should occur only after carrying out the activities in the "Make/Buy/Mine/Commission Analysis" practice area. Components obtained from sources other than new, in-house development are covered under the "Using Externally Available Software," "Mining Existing Assets," and "Developing an Acquisition Strategy" practice areas.
Aspects Peculiar to Product Lines
For the purposes of product lines, components are the units of software that go together to form whole systems (products), as dictated by the product line architecture for the products and the product line as a whole. If we appeal to the Szyperski definition of components given above, "deployed independently" may simply mean installed into a product line's core asset base where they are made available for use in one or more products. "Third parties" are the product developers who assemble the component with others to create systems. The contractually specified interfaces are paramount, as they are in any software development paradigm with software architecture at its foundation.
The component development portion of a product line development effort focuses on providing the operational software that is needed by the products and that is to be developed in-house. Either the resultant components are included in the core asset base and hence used in multiple products in the product line or they are product-specific components. Components that are included in the core asset base must support the flexibility needed to satisfy the variation points specified in the product line architecture and/or the product line requirements. The needed functionality is defined in the context of the product line architecture. The architecture also defines where variation is needed.
The singular aspect of component development that is peculiar to product lines is providing the required variability in the developed components via the variation mechanisms described in the example practices for this practice area. These variation mechanisms must be chosen to adequately support the production strategy and production constraints (see Core Asset Development).
Application to Core Asset Development
If a developed component is to be a core component, it must have an associated attached process that explains how any built-in component-level variation can be exercised in order to produce an instantiated version for a particular product. Developed components and their related artifacts (interface specifications, attached processes for instantiating built-in variability, test support, and so on) constitute a major portion of the product line's core asset base. Hand in hand with the software architecture that mandated them into existence, the core components form the conceptual basis for building products. Consequently, component development, as described above, is a large portion of the activity on the core asset development side of product line operations.
Application to Product Development
If a developed component is not to be part of the core asset base, it is most likely specific to a particular product and therefore probably does not have much variability built into it. While the development task must obey the architecture as strictly as it must for core components, non-core development is likely to be simpler. Nevertheless, developers of non-core components would be wise to look for places where variability could be installed in the future, should the component in question ever turn out to be useful in a group of products. Components for a product are either (1) used directly from the core asset base, (2) used directly after binding the built-in variations, (3) used after modification or adaptation, or (4) developed anew. Since the first two cases are pro forma, we will discuss the last two.
Adapting components: Components that are being used in a context other than the one for which they were originally developed seldom fit their assigned roles exactly. There are techniques that can accommodate these differences. The Adapter Design pattern [Gamma 1995a], imposes an intermediary between two components. The adapter can compensate for mismatches in the number or types of parameters within a service signature, provide synchronization in a multithreaded interaction, and adjust for many other types of incompatibilities. Scripting languages can often be used to implement the adapter.
Another technique is to modify the component to fit its new environment. Doing so may be impossible if the source code is not available. And, even if it is possible, it is usually a bad idea. Cloning an existing component creates a new asset that must be managed and creates a dependency that cannot be expressed explicitly. It can vary independently of its parent component, making maintaining both pieces a difficult task. Object-oriented notations provide a semantic device to express this type of relationship by defining the dependent class in terms of an extension of the original class. Although similar devices do not exist at the component level, a new component may be implemented by deriving objects from those that implement the original component.
Developing new components: New development should occur only after a thorough search has been made of existing core assets. In some organizations, the product team may have to "contract" with a component development organization to build the needed component. If it is built by the product organization, product line standards should exist for creating the core assets that support the component.
Whether a product component is adapted or built from scratch, it should be reviewed ultimately for "promotion" to the core asset base (and, in fact, should be developed with that in mind). To help with that review, robustness analysis [Jacobson 1997a] can be applied to determine how flexible the product is with respect to future changes in requirements. By examining change cases (use cases that are not yet requirements), the team identifies system changes that are needed in order to support the new requirements and thereby provides a feedback loop to the component developers. Specifications for new components and modifications to existing ones are the outputs of this analysis.
Example Practices
The example practices in this practice area all deal with component-level variation mechanisms. Architectural variation mechanisms are addressed in the "Architecture Definition" practice area.
Variation mechanisms (1): Jacobson, Griss, and Jonsson discuss the mechanisms for supporting variability in components (see the following table) [Jacobson 1997a]. Each mechanism provides a different type of variability. The variation of functionality happens at different times depending on the type. Some of these variation types are included in the specification implicitly. For example, when a parameter is used, the specification includes the specific type of component mentioned in the contract or any component that is a specialization of that component. In the template instantiation example below, the parameter to the template is Container, which permits variation implicitly via the Inheritance pattern. The Container parameter can be replaced by any of its subclasses, such as Set or Bag.
One aspect of variability that is important in a product line effort is whether the variants must be identified at the time of product line architecture definition or can be discovered during the individual product's architectural phase. Inheritance allows for a variant to be created without the existing component having knowledge of the new variant. Likewise, template instantiation allows for the discovery of new parameter values after the template is designed; however, the new parameter must satisfy the assumptions of the template, which may not be stated explicitly in the interface of the formal parameter. In most cases, configuration further constrains the variation to a fixed set of attributes and a fixed set of values for each attribute.
|
Types of Variation [Jacobson 1997a] |
||
|
Mechanism |
Time of Specialization |
Type of Variability |
|
Inheritance |
At class definition time |
Specialization is done by modifying or adding to existing
definitions. |
|
Extension |
At requirements time |
One use of a system can be defined by adding to the definition
of another use. |
|
Uses |
At requirements time |
One use of a system can be defined by including the
functionality of another use. |
|
Configuration |
Previous to runtime |
A separate resource, such as file, is used to specialize the
component. |
|
Parameters |
At component implementation time |
A functional definition is written in terms of unbound elements
that are supplied when actual use is made of the definition. |
|
Template instantiation |
At component implementation time |
A type specification is written in terms of unbound elements
that are supplied when actual use is made of the specification.
|
|
Generation |
Before or during runtime |
A tool that produces definitions from user input.
|
Variation mechanisms (2): Anastasopoulos and Gacek expound on a somewhat different set of variation options that includes [Anastasopoulos 2000a]
- aggregation/delegation: an object-oriented technique in which the functionality of an object is extended by delegating the work it cannot normally perform to an object that can. The delegating object must have a repertoire of candidates (and their methods) and assumes a role resembling that of a service broker.
- inheritance: which assigns base functionality to a superclass and extended or specialized functionality to a subclass. Complex forms include dynamic and multiple inheritance, in addition to the more standard varieties.
- parameterization: as described above
- overloading: which means reusing a named functionality to operate on different data types. Overloading promotes code reuse but at the cost of understandability and code complexity.
- properties in the Delphi language: which are attributes of an object. Variability is achieved by modifying the attribute values or the actual set of attributes.
- dynamic class loading in Java: where classes are loaded into memory when needed. A product can query its context and that of its user to decide which classes to load at runtime.
- static libraries: which contain external functions that are linked to after compilation time. By changing the libraries, you can change the implementations of functions that have known names and signatures.
- dynamic link libraries: which give the flexibility of static libraries but defer the decision until runtime based on context and execution conditions
- conditional compilation: which puts multiple implementations of a module in the same file. One is chosen at compile time according to the appropriate preprocessor directives.
- frame technology: Frames are source files equipped with preprocessor-like directives that allow parent frames to copy and adapt child frames and form hierarchies. On top of each hierarchical assembly of frames lies a corresponding specification frame that collects code from the lower frames and provides the resulting ready-to-compile module.
- the ability of a program to manipulate data that represents information about itself or its execution environment or state. Reflective programs can adjust their behavior based on their context.
- aspect-oriented programming: which is described in the "Architecture Definition" practice area
- design patterns: which are extensible, object-oriented solution templates catalogued in various handbooks (for example, the work of Gamma and colleagues [Gamma 1995a]). We mentioned the Adapter Design pattern specifically as a variation mechanism earlier in this practice area.
Practice Risks
The overriding risk in component development is building unsuitable components for the software product line applications. Doing so results in poor product quality, the inability to field products quickly, low customer satisfaction, and low organizational morale. Unsuitable components can come about by
- an ill-conceived component development effort: A major risk in component development is doing it prematurely or doing it at all, when another option would have been better. For example, building a component before an architecture is in place can cause interface and integration problems. Building a component when a better option would be to buy, mine, or commission it will lead to increased cost and complexity. Building a component especially for a product instead of using an available core asset will lead to the disintegration of the product line.
- insufficient variability: Components not only must meet their behavioral and quality requirements (as imposed on them by the product line's software architecture) but also must be tailorable in preplanned ways to enable product developers to instantiate them quickly and reliably in the correct forms for specific products.
- too much variability: Building in too much variability can prevent the components from being understood well enough to be used effectively or can cause unforeseen errors when the variabilities conflict with each other.
- choosing the wrong variation mechanism(s) for the job: The wrong choice can result in components that cannot be tailored when necessary.
- poor-quality components: While any poor-quality components will set back a software development effort, those that are core asset components will undermine the entire product line. Product builders will lose confidence in the core asset builders, and pressure will mount to bypass them. Applying practices from the "Testing" practice area can ameliorate this risk.
Further Reading
[Jacobson 1997a] & [Anastasopoulos 2000a]
Together,
these two works provide a superb compendium of component-level variation
mechanisms that are available to a product line component developer.
[Szyperski 2002a]
Szyperski provides a comprehensive presentation on components that gives a
survey of component models and covers supporting topics such as domain analysis
and component frameworks.



