Guideline: Software Reuse
This guideline describes how to re-use software and design elements.
Relationships
Main Description

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.

More Information
Concepts
Guidelines