Maximizing reuse has always been an important goal of software development. It's better to re-use existing code and
design than to expend the cost of creating something new, testing it, and releasing it for the first time with the risk
of hidden problems that all new software has. Languages, particularly object-oriented ones, have been developed to make
reuse easier. But a language alone isn't enough to provide cost effective reuse. The bulk of reusable software comes
from skilled developers and architects who are able to identify and leverage reuse opportunities.
There are three perspectives to look at when reusing software: code (implementation), design, and framework or
architecture. Architects should look to reuse significant application frameworks such as layers that can be applied to
many different types of applications. Developers should look to designs and patterns that can be reused to produce
desired behavior or robust structures. They should also look at how to reduce the amount of code that needs to be
written by leveraging stable components and code that has been proven in production environments.
Identifying Reuse Opportunities
The best way to enable a team to find opportunities for reuse is to exercise excellent design and coding practices.
It's difficult to find code and design that can be reused when dealing with large classes, classes that don't have a
clearly defined focus, or classes with relationships that are difficult to understand. Classes should be small, easy to
understand, and highly cohesive to make it easier to identify reuse opportunities. Any functionality that can be
reasonably separated into another class should be. Another way of saying this is that any concept that could be applied
to more than one type of class should be its own class.
For example, if some calculations are added to an existing class, it may make sense to then refactor those calculations
into a new helper class. Those calculations can then be re-used in any number of other classes without the burden
of having to know about the functionality of the original class.
The simplest but least efficient way to identify reuse opportunities is to "smell" similar code. A developer may recall
doing something similar to what they're designing or implementing now. Once the previous implementation has been
discovered or recalled it can be reused. Developers will always find reuse opportunities this way. But the unstructured
nature of it won't maximize the potential areas for reuse.
Collaboration is a good technique for identifying reuse opportunities. It provides a structure where identifying reuse
- instead of writing code - is the goal of the exercise. And the more brains that are looking for reuse opportunities,
the more likely it is that they'll be found. A brainstorming or review meeting that focuses on identifying reuse
opportunities would be useful to support this.
Patterns are good ways to find reuse opportunities in designs and frameworks. See Concept: Pattern for more information.
Analyzing behavior is another good way to identify potential areas for reuse. Analyze how classes need to collaborate
in order to deliver some specific functionality such as a requirement or feature. This collaboration can be documented
in sequence (behavior) and class (structure) diagrams and can be reused in similar circumstances.
Refactoring should always be considered when reusing code. Code (or design) is often
not originally written for re-use, or reusable code may not be a perfect fit for a new situation.
Reuse Techniques
Reuse can be performed differently depending on the capabilities of the implementation environment. The simplest
technique is to copy the code from one place to another. This isn't advisable because it's not really reuse. Multiple
copies of source code are difficult to maintain and can eventually diverge from each other. Reuse is about using the
same code to perform similar tasks as a way to increase quality and reduce overhead.
Some languages, such as C++, support templates. Templates, sometimes referred to as parameterized code, are a precursor
to patterns. Templates are code with parameters that are applied just when the code's needed, at compile time. The C++
Standard Template Library (STL) is one example. It provides many types of reusable containers (lists, sets, safe
arrays, etc) that don't have some of the drawbacks of inheritance. Templates such as these are also useful as mix-in
classes in languages like C++ that support multiple inheritance. Because mix-ins are implemented as templates, they
allow for a type of multiple inheritance without the baggage.
Inheritance and Aggregation
Inheritance (also known as generalization) is an easy way to implement polymorphism and has been used as the primary
mechanism for reuse in modern object-oriented languages. This is unfortunate, as inheritance imposes a rigid structure
on the software's design that is difficult to change. Any inheritance hierarchy that shares code from parents to
children will have problems when it grows to be three or more levels deep. Too many exceptions occur to maintain a pure
"is-a" relationship between parents and children, where children are always considered to have all the properties and
behaviors of the parents. Inheritance should be used to share definitions (interfaces), not implementations. Years of
difficulties with inheriting implementations have made this practice a primary object-oriented design principle.
Whenever inheritance is used, it is best to have only the last child class (leaf node) of the inheritance hierarchy be
instantiated. All parent classes should be abstract. This is because a class that tries to be both reusable and
concrete - to provide reusable and specific behavior at the same time - almost always fails to fulfill either goal.
This is a dimension of cohesiveness. One thing that makes a class cohesive is that it's dedicated to reuse or dedicated
to a specific implementation, but not both.
Aggregation is a technique that collects or aggregates functionality into larger elements of functionality. It provides
a structure that's far more flexible and reusable than inheritance. It's better to reuse implementation and design by
aggregating small pieces of functionality together rather than trying to inherit the functionality from a parent.
You may also find reuse opportunities by reviewing interfaces. If interfaces describe similar behavior it may be
possible to eliminate one of the interfaces, have just one implementation realize both interfaces, or refactor the
interfaces to put redundant content in a new, simpler interface.
Finding Reusable Code
There are many sources of reusable code beyond what the developers are writing for a specific project. Other places
from which to harvest code include the following:
-
Internal (corporate) code libraries
-
Third party libraries
-
Built-in language libraries
-
Code samples from tutorials, examples, books, etc.
-
Local code guru or knowledgeable colleague
-
Existing system code
-
Open source products (be sure to follow any licensing agreements)
Also, many tools that generate code will generate comprehensive code based on minimal specification. For example, a
design tool might generate the member variable plus a get and a set operation when the designer specifies an attribute.
Other more sophisticated tools with knowledge of a specific framework can generate voluminous code to ensure that a
class conforms to the framework. An example of this would be a tool that generates significant additional code when a
class is marked as a Java entity bean. This sort of consistent transformation from a specification (the design) to an
implementation (the code) could be considered a form of code reuse as well.
Don't Reuse Everything
Reuse makes code and design cheap to use but expensive to build. It requires experience and thoughtful consideration to
create an implementation or design that's abstract enough for others to re-use, but concrete enough to be truly useful.
Reusable code must also be maintained. Many organizations have difficulty assigning responsibility for maintaining
reusable code if they don't have a group dedicated to reuse.
It's usually not a good idea to create code or designs for reuse unless you know it's going to be reused. It's better
to refactor software to be more reusable after it's discovered that they can be reused. One rule of thumb is to write
for reuse only when you know you'll use it at least 3 times. Otherwise the cost of building and maintaining that part
of the software will not be recovered by reduced overhead in other areas of development.
|