Rules for Component Builders



May 01, 1999
URL:http://www.drdobbs.com/rules-for-component-builders/184415684

May 1999: Features: Rules for Component Builders

Building components that work requires taking a good look at the most basic component-based development issue: quality.

It’s no wonder components hold the promise of a new beginning. More people are seeing components as the only way for the software industry to get its act together and move to the next level of sophistication. The electronics industry is always there to remind us—with every new announcement instantly turning last semester’s hot product into old hat—how the reuse of standard components can multiply the powers of invention throughout an entire industry.

But in spite of all the excitement and the lure of ubiquitous CORBA, COM, and JavaBeans components, few people have realized what’s at stake in turning software development into a component-based industry. We may be seeing the light at the end of the tunnel, but how do we know it’s not the headlights of a train rushing our way?

The train has a name: quality. I’m flabbergasted to see that in people’s enthusiasm for component-based development (CBD)—including CIOs apparently willing to stake their operation’s future on components—they overlook the most basic CBD issue: quality. What hasn’t registered in our collective psyche is that in a component-based application, the quality of the whole—what we deliver to our customers, and what they will judge us on—is the quality of the weakest link in the chain. Yet the industry has devoted little attention to this issue of component quality so far. It seems we are just hoping for the best and assuming that, somehow, everything will turn out right in the end.

It won’t. Unless we put component quality at the center of our concerns, components will come back to haunt us. Try telling an angry customer that it’s the fault of a JavaBean you found on the Internet.

The Challenge of Components

In invoking the electronics industry to promise component paradise, it’s tempting to ignore the key to success in hardware components: our colleagues in the electronics field have only made such incredible component-based progress over the past 30 years because of an unrelenting concern for quality—in all steps of the process. They achieve quality ahead of time through rigorous design techniques, and later through continuous quality assurance throughout design, implementation, and testing. We are still far from this in the software field. This is the challenge of producing not just components, but quality components, and guaranteeing that quality.

What Makes a Good Component?

The experience of building and selling components is sobering. Quality componentware is top-quality software—and then some. The key qualities are:

Careful specification. A component must work in precisely defined circumstances and, in those circumstances, produce a precisely defined result.

Correctness. The component must always work properly in cases covered by its specification.

Robustness. The component must never crash or produce wrong results.

Ease of identification. An application developer who sees the description of a set of components must be able to decide within a few minutes whether or not a particular component is interesting for the application. This is key to component success: if people have a hard time identifying and choosing components, they will be tempted to bypass component libraries and redo things themselves.

Ease of learning. If you tentatively decide to use a component, you should be able to learn how to use it quickly; if you use it repetitively, or use a set of related components, you should be able to master the basics quickly and not have to go back to the manual continuously.

Wide-spectrum coverage. A component must be easy to learn for a novice (as per the previous requirement), but must also meet the sophisticated needs of expert users, so they can extend and deepen their use of the component as their experience grows.

Consistency. Successful CBD involves component libraries. Beyond elementary uses, the quality of individual components is no longer sufficient: the library should also exhibit a high-quality design. Consistency between the various components of the library is crucial. What good is a component if its conventions—how it expects and returns information, handles erroneous situations, affects the environment, and so on—are different from those of other components in the library?

Generality. This is one of the toughest issues facing a software developer who wants to become a component developer. Non-component software often relies on assumptions about its environment which its authors may not realize are there. When you turn your software into componentware and it gets used by people of different groups, corporate cultures, countries, and industries, things change. What was a successful program element in your environment may fail miserably when you’re trying to meet other people’s expectations. This is why you must fulfill the first of these requirements: precise specification. Thus, when your componentware gets sold to people from Vanuatu to Smolensk, all its relevant properties are deducible from the official specification.

These requirements come in addition to usual software quality factors: they must be reliable, extendible, efficient, and so on. But in the case of components, some standard quality requirements become even more stringent, and new ones appear that condition the success of components.

The Experience of Object-Oriented Libraries

The good news is that we are not entering virgin territory here. For many years, developers of object-oriented software environments, in such languages as Smalltalk, C++, and Eiffel, have tried to develop quality components. I’ve described some principles of quality library design in Reusable Software (Prentice Hall, 1994), where I applied them to the design of the EiffelBase library—a public-domain repository of classes organized around a systematic classification of the fundamental structures of computing, from lists and arrays to sorting and searching (see http://eiffel.com/products/base/).

It’s remarkable how these principles still apply to the newer form of components, such as COM components. There seems to be a widespread view that these components are new, but that’s not true: the difference between an object-oriented class and, say, a COM binary component, is one of granularity, not nature. Even the difference of granularity can fade out these days, with more object-oriented components turning into frameworks, like EiffelBase or the C++ Standard Template Library, and COM developers turning their attention away from large-grained components (Microsoft Word used to be the standard example) to smaller ones.

In the next few sections, I’ll summarize some principles that have proved to be productive for both object-oriented and binary components in the COM world. The Eiffel libraries that straddle both worlds, such as EiffelCOM (see http://eiffel.com/products/com/), are an example of this combination. These quality components include a mix of high-level design rules and more mundane aspects of style, some aspects that non-component development may dismiss as “cosmetic.”

The discussion only covers a few examples from the set of principles my colleagues and I have developed and applied over the years; the books mentioned later in the article provide more.

Design by Contract

The first principle, Design by Contract, provides a direct answer to the precise specification requirement. Design by Contract associates a set of logical assertions with every software element. These assertions define the element’s contract and consist of:

Preconditions: input conditions for individual operations

Postconditions: output conditions for individual operations

Invariants: global consistency conditions both assumed and maintained by every operation.

The applications are far-ranging: writing correct components from the start; providing a rich set of debugging, testing, and quality assurance mechanisms; automatic documentation; exception handling; and project management (contracts help a manager control team communications, and should be taken into account by management standards such as CMM and ISO 9001). In addition, Design by Contract lets developers control the power of inheritance and polymorphism, and manages to preserve the quality of an architecture when a variety of maintainers make changes to it.

For components, Design by Contract is not a cute addition but essential to success. What hardware engineer would even dream of selecting, say, an amplifier chip without a specification of the precondition (such as acceptable range of input voltages), postcondition (such as ratio of output to input voltage) and invariant (such as temperature range)? It’s time the Interface Definition Language (IDL) of such tools as CORBA and COM start including such contract specifications. Before that happens, component developers can draw the lesson themselves by systematically applying Design by Contract principles to the development, quality assurance, and documentation of their products.

Naming

One lesson my colleagues and I learned early in the development of Eiffel libraries is that some issues viewed as cosmetic in non-component application development take on a critical role in component development. Naming is one of them. A general rule in non-component development is that you should choose reasonably meaningful names. (Well, even that isn’t universal, as illustrated by the “Hungarian notation,” but most people take it for granted.) In CBD, this is not enough anymore. The consistency principles imply that the names must not only be clear but also uniform.

In the first iteration of EiffelBase, class STACK had operations push (x), pop, top for an element x; class ARRAY had enter (x, i) and entry (i) for an integer i; class HASH_TABLE had add (x, k) and value (k) for a key k. Using such well-accepted names, each well-adapted to each kind of structure, emphasized specificity rather than generality. This practice is not a problem with a few library classes, but with hundreds of components it doesn’t work with the “ease of learning” requirement: it amounts to requiring the library user to learn a specific programming language for every new kind of structure. In each case the effort isn’t much, but compounded over the practice of large-scale component reuse it can defeat the best intentions. As a result of these observations, we went to a set of completely systematic naming conventions, which I detailed in Reusable Software:

The basic replacement or addition operation is always called put (replacing push, enter, add, and so on).

The basic access operation is always called item.

The basic removal operation is always called remove or prune.

We chose names very carefully for consistency. For example, we tend not to use delete because you often need a query that asks “Is it possible to remove an element?” Choosing delete for the removal operation would lead, for consistency, to deletable for the query; but then if s.deletable then... carries the wrong connotation. You are not asking “Can s be deleted?” but “Is it possible to delete elements from the structure s?” This would be a trifle in non-component development, but the small probability of confusion becomes serious when compounded by the number of novice users for a successful component library. Choosing the names prune and prunable solves the question, since s.prunable carries the right connotation. This is a typical example of how cosmetic issues can become serious in CBD.

Command-Query Separation

The Command-Query Separation principle goes against programming techniques that are so deeply ingrained in today’s practices that most people don’t think twice before applying them. In programming with C, C++, Java, and similar languages, it is common to use a function that performs an action and returns a result. I always avoid this in non-component programming, because it makes it hard to reason with programs the way we reason with mathematical formulae. When you see “i + i” and “2 x i”, you can assume they mean the same thing. Unfortunately, if “i” is a function call and the function may produce side effects, a whole set of assumptions we’re used to breaks down.

In CBD, this desirable style becomes a strong requirement. To trust what the component will do for you, you want a clear separation between commands, which can change the state of one or more objects. You also want queries, which return information about the state of an object, but don’t change that state. This is the Command-Query Separation principle: asking a question shouldn’t change the answer.

Note that the principle may appear drastic at first; for example, it disallows things like getint, a function that both reads an integer and returns its value, so that the next time you call getint, you will get a different answer—asking the question changes the answer. Yet I find this principle essential if you want precisely defined components whose behavior you can understand and predict.

Option-Operand Separation

Like the Command-Query Separation principle, the Option-Operand Separation principle is inconsistent with dominant practices. It states that an operation’s arguments should only include operands, with no options. In CBD, operand and option are defined as follows:

An operand is a necessary argument because it describes one of the values or objects on which the operation works. For example, if you are printing a document, the document is an operand; the operation can’t do anything significant without it.

An option describes a mode of operation, or some auxiliary information. For example, the printer on which you will print the document is an option.

Options have two characteristic properties: you can define a default value, applicable if the option is not explicitly specified; and options will typically come and go from the software engineering perspective.

Excluding options from the arguments to a command means ease of learning, wide-spectrum coverage, and evolution. For ease of learning, it’s crucial to let novice users determine quickly whether or not the component is applicable to their needs. If many options are available as arguments, most will be irrelevant to these initial needs, and as a result it will take too long to master the component. For wide-spectrum coverage, keeping options separate from operand arguments lets the component address the sophisticated needs of the expert user without bothering the beginner. For evolution, it’s crucial to let the component developer add and change options. If there are arguments to the component’s operations, this will imply changing all existing client applications, which would be unacceptable.

When you apply the Option-Operand Separation principle, you limit explicit arguments to operands, and specify options through some other means; for example, other operations whose only purpose is to set applicable options until contrary notice. For unspecified options, the component will use default value.

The “Trusted Components” Project

The most striking observation about components is how shaky our whole industry is. We can’t even rely on the basic tools we use: operating system, compiler, and so on. The next time you hear the obligatory comparison between software “engineering” and other forms of engineering, don’t forget the fundamental difference: we don’t have physical laws at the basis of what we do. In electronics, everything relies on a few well-accepted scientific principles, such as Ohm’s laws and Maxwell’s equations. This is not so in software. We can have all the maturity models we like, but they won’t make up for the lack of a rigorous foundation.

Component technology partially addresses this issue. The Trusted Components project, initiated by Interactive Software Engineering and Monash University but intended as a cooperative effort for any interested company, is an attempt to develop a set of rigorously qualified components that everyone can trust. The components can start from simple ones (even a fully trustable BIT class would be a first!) to sophisticated application-specific components. More information is available at http://trusted-components.org, which also reproduces “Trusted Components for the Software Industry” (IEEE Computing, May 1998), which I co-wrote with Christine Mingins and Heinz Schmidt. You can also visit the Trusted Components public discussion group at http://talkitover.com/trusted.

How do we build trust? There is no single answer, but rather a combination of mutually reinforcing strategies:

Design by Contract to specify, document, and test the components.

Extensive testing strategies.

Public scrutiny. The open source software movement has yielded good quality software even though that hasn’t been its primary focus. Hopefully, by having critical contributions from many different people, we can achieve high-quality software.

Proofs. Proof technology is still too clumsy to apply to application development, but in some cases, especially components, it’s worth it. Tools such as Abrial’s B (see Jean-Raymond Abrial’s The B Book, Cambridge University Press, 1997) are showing the way. If it’s possible to prove the correctness of the software driving the Paris metro system (one of B’s most publicized successes), it should be possible to prove the correctness of reusable software components.

All the experience we can gain not just theorizing about components but producing industrial-quality components and subjecting them to the test of actual project use.

The trusted components project is an example of what component builders should be doing. I hope that you will find this project exciting and will share in making it a success.

Casual vs. Systematic Choice of Names from EiffelBase

Original Names (Each specific to the concept covered by its class)

CLASS Basic operation to access an element Basic operation to replace or add an element Basic operation to remove an element
ARRAY Entry Enter N/A
HASH_TABLE Value Insert Delete
STACK Top Push Pop
QUEUE Oldest Add Remove_Oldest
Current Names (Systematic names, de-emphasizing specificity and emphasizing commonality)
CLASS Basic operation to access an element Basic operation to add an element Basic operation to replace or remove an element
ARRAY Entry Enter N/A
HASH_TABLE Item Put Remove
STACK Item Put Remove
QUEUE Item Put Remove

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.