Software Inventory Process
As new software components and features are contributed to the project
and existing ones evolve, it is important to maintain an up-to-date
inventory of components and their states. This document defines our
inventory process.
Goals
By maintaining an inventory, here is what we hope to achieve:
- Publish useful annotation and indexing on top of the
automatically-generated component lists produced by tools such as
Javadoc and Doxygen. This benefits developers trying to navigate such
a large project. Where possible, we would like to devise inline
annotation mechanisms so that the inventory report can be generated
automatically by running tools which process the codebase as input.
- Raise developer awareness of what already exists to encourage
controlled reuse. By controlled, we mean that some
components may not have been designed for reuse, or may have immature
public interfaces; by tracking component states, we can encourage
reuse where appropriate and discourage it where it will lead to
unmaintainable dependencies. In many cases, design pattern reuse may
be possible even where component reuse is not a good idea.
- Provide candidates for code inspection and design review
processes. Component states can be used as prioritization factors.
- Detect opportunities for standardization, refactoring, and
stabilization.
- Guide component owners in following best practices and managing the
code lifecycle.
Component Categories
Components come in a variety of shapes and sizes; they may be
individual classes, Java packages, C++ modules, or larger combinations
of these. In our current codebase, a number of abstract categories
can be imposed to explain the purpose of various components. Compound
components (e.g. many Java packages) may fall into more than one
category simultaneously. (GUI categories are left out since we don't
have any yet.)
| Category |
Description |
Examples |
|
API
|
An application program interface is a means of telling a system what to
do. API's come in many forms, including Java interfaces and their C++
equivalents, command-line interpreters, and parsable languages. Some
API's such as JDBC are defined by official third-party specifications;
others are public specifications defined by Eigenbase; still others
are internal to Eigenbase, meaning the "application" can be a
higher-level component in the system.
|
package java.sql; package net.sf.farrago.session
|
|
SPI
|
A service provider interface is a well-defined extension point in the
system. An SPI defines the interface and contract a plugin must
implement in order to be usable by the system. Optionally, an SPI may
include a set of abstract base classes which plugin implementations
are required or encouraged to subclass. The difference between an API
and an SPI is that an API is designed to be called from any component
in an unconstrained fashion, whereas an SPI is designed to be called
only from specific components being extended.
|
package java.nio.charset.spi; package net.sf.farrago.namespace
|
|
class library
|
A class library is a collaborating collection of components designed
for use in implementing larger systems. Some class libraries are also
designed for extensibility. Whereas an API focuses on action, a class
library merely provides a set of building blocks. However, an API may
be defined on top of a class library (for example, the class library
may provide the "nouns" referenced by the API's method "verbs"). A
special case of a class library is an object model: a
structural representation for some complex object, such as the parse
tree representing an SQL query.
|
STL; package org.eigenbase.sql
|
|
functional implementation
|
A functional implementation is an implementation of a specification or
interface (typically either an API or SPI) intended for production
use. A functional implementation is often self-contained and private
except for a factory method which instantiates it. Optionally, a
functional implementation may be designed for reuse (e.g. via
parameters, configuration files, or subclassing) and/or extensibility
(e.g. via SPI's).
|
package net.sf.farrago.jdbc.engine; package net.sf.farrago.db
|
|
reference implementation
|
A reference implementation is similar to a functional implementation,
but is intended mainly as an example of how to implement the interface,
rather than for production use. In some cases, a reference
implementation may be the embodiment of the specification ("as long as
your behavior conforms to the reference implementation, your
implementation is correct").
|
package net.sf.farrago.rng
|
|
mock implementation
|
A mock implementation is similar to a functional implementation, but is
intended for use in constructing test cases which need to exercise
components in isolation, where those components depend on the interface
being mocked. For example, to test an optimizer without coupling it to
the behavior of any ruleset's functional implementation, we create a
mock ruleset. Besides simple dummying, mocks can also be
parameterized and designed to record events, allowing them to be used
to probe the functioning and error paths of the component being
tested.
|
class org.eigenbase.test.MockCatalogReader
|
|
utility
|
A utility provides useful methods for manipulating objects or defines
isolated classes (as opposed to a class library, which defines a
collection of classes intended to be combined). A typical pattern in
the Eigenbase codebase is to define a non-instantiable class with
static methods all related to a particular purpose such as
manipulation of JMI objects. Besides classes and methods, utilities
may also be small programs invoked as part of the build or runtime.
|
package org.eigenbase.util; class net.sf.farrago.query.FennelRelUtil;
program class net.sf.farrago.catalog.codegen.ProxyGen
|
|
test harness
|
A test harness is a component designed to assist in testing other components.
|
class net.sf.farrago.test.concurrent.FarragoTestConcurrentCommandGenerator
|
|
test
|
A test case is a component which tests one or more other components; a
test suite is a collection of test cases.
|
class net.sf.farrago.test.FarragoServerTest
|
Component Maturity
Regardless of categorization, components grow, evolve, merge, split,
and die. To assist in tracking this lifecycle and managing its impact on
related components, we define the following maturity stages:
- vapor: The component exists only in a design doc or
someone's head.
- experimental: The component is still under development
and not ready for general use. Experimental components are typically
meaningful only to their creators, and so will usually not have any
dependent components (except possibly other experimental components).
- fluid: The component is working and may be part of a
larger system. However, it is either still undergoing rapid
development or has not been "cleaned up" enough to be able to predict
what kind of changes it will undergo in the future. If the component
has a public interface or extensibility points, they may not be
consistently or clearly defined. Dependencies from other components
are possible though discouraged, and are always on an "at your own
risk" basis, meaning changes to the fluid component will often involve
changes to dependent components as well, and these changes may take
place without consultation. Extreme programming methodology is
appropriate in this phase.
- congealing: The owner of the component has done enough
preparatory work that it is worth review by others, typically with the
intention of reaching the solid state. However, it is rare for
components to go from congealing to solid without at least minor
refactoring.
- refactorable: The component has been reviewed and
the action items from the review involved refactoring. Refactoring
may involve splitting the component up, refining or generalizing its
interface, merging with another component, internals cleanup,
deprecation, etc, and may involve several review/refactor iterations.
- solid: The component has been reviewed and refactored as
necessary, and all parties involved in the review have verified that
the results are worthy of being designated solid. If a solid
component is designed for reuse and/or extensibility, then creating
dependencies on it is encouraged. All dependency-breaking changes to
a solid component require consultation and review before proceeding.
The parties involved may also agree that after a particular release, a
solid component will revert to fluid status to allow major changes
to be made.
- frozen: The component's interface has been frozen and
cannot change except under exceptional circumstances. This status
will usually only be reached for interfaces which have become part of
an official specification. In some cases, implementations may be
frozen or near-frozen as well, at least temporarily, to assist
stabilization of other components.
- deprecated: The component is being phased out (ha ha) and
may be deleted in a future release. Dependencies should be removed as
soon as possible.
Documentation
Another aspect tracked in the inventory is a characterization of the
documentation available and what remains to be documented.
Documentation may be at the system-level, feature-level, or
component-level, so a number of different kinds of documentation may
be applicable to a particular component:
- code-level: Javadoc, inline comments, etc. (TODO: link to style guide)
- functional specification: where appropriate, this should be by
reference to an official specification or standard, with the focus on
Eigenbase-specific conformance levels, implementation-defined choices,
etc. For features which are unique to Eigenbase, a comprehensive
functional spec of quality equivalent to the output of a JSR should be
the goal. (TODO: template)
- design: an explanation of a component's implementation,
with reference to a functional spec if one exists.
(TODO: template)
- end-user: documentation on how to use a system or feature,
understandable by an external user. This is not currently a primary
focus for Eigenbase. However, a HOWTO, man page, or syntax guide is
worth the effort.
- developer: documentation on how to integrate, reuse or extend a
component. A HOWTO which walks through a self-contained example can
provide much more bang-per-buck than trying to write a comprehensive
SDK guide.
Component Quality
The inventory also tracks the tests associated with each component or
feature. Test categories are listed below (TODO: move to a separate
test process doc and link from here).
- Unit testing (component-level and feature-level)
- positive conformance
- negative conformance
- error path
- coverage and boundary value
- concurrency
- Integration testing
- checkin acceptance
- specific component combinations to be certified for release
- matrix (exhaustive)
- matrix (randomized)
- Extensibility testing
- mock SPI provider implementation for testing consumers
- harness for testing SPI providers in isolation
- Higher-order testing
- security
- recovery
- regression
- compatibility
- system
- application
- performance
- stress
Example
Putting it all together, here's an example of the kind of raw data
we'd like to see in the inventory (a variety of views and reports can be
generated from this):
| Subproject |
Component |
Description |
Categorization |
Maturity |
Documentation |
Tests |
| Farrago |
package net.sf.farrago.session |
Defines the Farrago session management interfaces. |
API (sessions), SPI (personalities) |
fluid |
javadoc, HOWTO for SPI; needs functional spec and developer docs |
integration only; needs API mocks, SPI mocks, API unit tests,
SPI harness |
| Fennel |
disruptivetech/calc |
Implements an SQL expression calculator |
API, SPI (extended instructions), functional implementation, class library
|
congealing |
functional spec, HOWTO for writing extended
instructions; need to update functional spec, write design doc, add
more developer docs |
unit, some integration; need SPI mocks and harness |
| ... |
... |
... |
... |
... |
... |
... |
Copyright (C) 2005-2005 The Eigenbase Project. All rights reserved. Contact: info@eigenbase.org
|