Building components that work requires taking a good look at the most basic component-based development issue: quality.
Its 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 uswith every new announcement instantly turning last semesters hot product into old hathow 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 whats 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 its not the headlights of a train rushing our way?
The train has a name: quality. Im flabbergasted to see that in peoples enthusiasm for component-based development (CBD)including CIOs apparently willing to stake their operations future on componentsthey overlook the most basic CBD issue: quality. What hasnt registered in our collective psyche is that in a component-based application, the quality of the wholewhat we deliver to our customers, and what they will judge us onis 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 wont. Unless we put component quality at the center of our concerns, components will come back to haunt us. Try telling an angry customer that its the fault of a JavaBean you found on the Internet.
The Challenge of Components
In invoking the electronics industry to promise component paradise, its 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 qualityin 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 softwareand 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 conventionshow it expects and returns information, handles erroneous situations, affects the environment, and so onare 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 youre trying to meet other peoples 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. Ive described some principles of quality library design in Reusable Software (Prentice Hall, 1994), where I applied them to the design of the EiffelBase librarya 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/).
Its 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 thats 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, Ill 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 elements 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)? Its 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.
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 isnt 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 doesnt 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 isnt 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.
The Command-Query Separation principle goes against programming techniques that are so deeply ingrained in todays practices that most people dont 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 were 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 dont change that state. This is the Command-Query Separation principle: asking a question shouldnt 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 answerasking the question changes the answer. Yet I find this principle essential if you want precisely defined components whose behavior you can understand and predict.
Like the Command-Query Separation principle, the Option-Operand Separation principle is inconsistent with dominant practices. It states that an operations 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 cant 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, its 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, its crucial to let the component developer add and change options. If there are arguments to the components 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 cant 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, dont forget the fundamental difference: we dont have physical laws at the basis of what we do. In electronics, everything relies on a few well-accepted scientific principles, such as Ohms laws and Maxwells equations. This is not so in software. We can have all the maturity models we like, but they wont 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 hasnt 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, its worth it. Tools such as Abrials B (see Jean-Raymond Abrials The B Book, Cambridge University Press, 1997) are showing the way. If its possible to prove the correctness of the software driving the Paris metro system (one of Bs 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.
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|
|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|