Chapter 3
Refactorings
The main object of this
research is the refactoring of existing software. This chapter gives some
definitions of used terms. It explores the ideas and the ways how code changes
can be done faster and easier, and how they can be automated using special
kinds of components (wizards/transformations).
3.1 Principles in
refactorings
Refactoring is a fast and safe way to transform an existing program.
·
Fast, because it automatically changes program code and saves time
spent typing and fixing new bugs.
·
Safe, because it guarantees the preservation of program behavior.
3.1.1 Definition of refactoring
The main purpose of
refactoring is to make existing software more flexible, easier to extend,
better structured and more understandable. A good definition for refactoring
was made by Ralph Johnson [Johnson95]:
„Refactoring is a change
made to the internal structure of software to make it easier to understand and
cheaper to modify without changing its observable behavior“
The purpose of this research
is to explore refactorings which can be done or supported by a transformation
tool. We would call it: „a change made automatically to the internal
structure of software...“ In this paper
we will use the term: „transformation“ as the implementation of a particular
refactoring type.
3.1.2 Properties
of refactoring
The main property of a
refactoring is the preservation of the program behavior. It doesn't matter if
refactoring is used to make a program more flexible, or easier to extend or
just cleans up the code - the program functionality does not change. This property
makes refactorings a good and „safe“ way to transform the software.(see section
4.1.4)
The basic parts of a refactoring description are very well introduced
by Martin Fowler
in his book [Fowler99] „Refactorings“:
- Name (used to identify a particular
refactoring)
- Description(what it does and when to
use it)
- Motivation(describes why it is good to use it)
- Mechanics(step by step description of
the refactoring process)
Examples(simple
example for this kind of refactoring)
The name of the refactoring
should introduce what this refactoring does (like “Create Class“), and the
refactoring-description in some cases could be better done as an UML-diagram
(start- and end- state).
3.2 Use of refactorings
Software development is an
expensive process. This fact motivates reuse and evolution of existing software
via refactoring.
3.2.1 Refactorings for reuse
The example described in section 2.2 is a good demonstration for the
reuse of software:
- The program was
restructured and was made more flexible, before it was extended.
- Some new functionality was
added to the program. (e.g. new engine for a car)
- Some program modules were
removed (e.g. old engine)
William Opdyke refers in his thesis „Refactoring object-oriented
frameworks“
[Opdyke92] to four important aspects for the reuse of software:
- Finding a reusable
component
- Understanding the
component
- Modifying a
component
- Composing the
components together
He describes these as
complex mental tasks which „do not happen by accident“. The refactoring-example
above is not very difficult to understand, nevertheless one has to plan these
transformations, to find what can be reused and how to extend the program. This
task could prove to be very difficult, because one has to understand the code
of the existing program!
3.2.2 Refactorings during
the implementation
Sometimes programmers
refactor their own source code during its development. By adding new
functionality, for example, the programmer realizes this would be much easier
if the code would be structured differently. The programmer could also find
duplicate code while implementing his program and may decide to extract it into
a new class.
Often a good design idea for
a particular problem comes after the programmer has already implemented a
„strange“ solution. Also, a „last minute request“ can make your well-designed
implementation useless!
In the above examples the
programmer starts to refactor his program. Usually with a „copy-and-paste“
operation! How can a refactoring-tool help the developer transform his program?
- A tool can give an overview of an existing solution. Information
about a design-pattern
could bring the programmer to a new design idea.
- A tool can check the conditions(or show the information about the
conditions) for a
code-change. This will help to
avoid errors and helps in choosing the right solution
- A tool can save time spent typing(and finding new syntax errors!) by
performing those
changes automatically.
Example: We have an
interface with several implementations. A new functionality has
to be added to this interface. This method will be used
only by one implementation.
Problem: By adding a new
method to the interface this method must be supported by all
implementations
of the interface.
Different
Solutions:
- Derive this interface to a new extended interface with one
implementation(so far)
- Create an abstract class with default implementation of new method
and let this class
be super-class for all implementations of interface
- Extend all implementations with this method.
Benefit: The developer
can use a refactoring-tool to create and test a chosen solution.
Another interesting idea
could be the integration of a refactoring-tool
in a UML-tool such as Rational Rose or STP. UML tools can use
refactoring-features for „reverse engineering“. Example: If a class has been
moved in a „Class-diagram“ of the UML-tool this tool can use refactoring „Move
Class“ to update the source code.
3.2.3 Refactorings for
maintenance
Very similar to software
implementation and reuse is software maintenance. Refactoring can also be used
for the maintenance of software.
Example: Restructuring
in the new version of a program can be done automatically by a refactoring tool
without changing the behavior of the whole program. Refactoring can be used
to „clean up“ the code and can make the
program easier to understand which may then help you to find the bugs.
3.3 Transformation
A transformation is an implementation of a concrete refactoring. A
transformation is a process which:
1) takes the current program source code as input (e.g. DPT gets
symbols from parser)
2) requests and gets parameters from user (DPT - user interface)
3) generates code changes(DPT - code generators)
(Figure 3.1 transformation)
3.3.1 Scheme for
transformations
The scheme of a transformation
contains:
·
Description
of the transformation
A verbal description should
include start and end states of the transformation. Information about program
changes, preconditions, parameters should be available. Motivation, examples,
mechanics of the transformation can be added to the description
(DPT-shows description as
help-information).
·
Preconditions
which should be accomplished
There are two kinds of
preconditions:
1) Necessary conditions:
Transformation can’t be
completed if condition is not accomplished.
Example: Transformation „New class“ : Class name must
not exist.
2) Minor conditions: The
compilancy with minor conditions is necessary to preserve program behavior(i.e.
transformation can be completed, but program behavior
will change if the condition is not accomplished)
Example: Transformation „Copy method“ : If method is abstract then
target class must
be abstract, too (see section 4.1.4).
·
Parameters of the transformation
There are two kinds of parameters:
1)
Fixed parameters: Required parameters.
Example: Transformation „Create method “ Parameter = “Name of the
method“
2) Variable list of optional
parameters:
Example: Transformation „Create method “ Parameter = “List of
parameters“
·
Output of the transformation
Output can be used to construct a composite-transformation(see section
4.4).
Output contains the type of
entity and entity identifier
Example: Transformation =
„Create class“
Output-type
= “class“
Output-Identifier
= “com.ui.NewClass“
3.3.2 Transformation properties
There are two kinds of transformations:
1) Single transformation (one
step transformation). Example: “Move Method“
2) Multiple transformation
which could be the multiple(simultaneous) execution of a single transformation.
(Example: “Move all public methods“) or a composition of more than one
single transformation (see section 4.1.5)
Other important property is
the safety of the transformation. The transformation is „safe“, if it does not
change behavior of the program. Only a „safe“ transformation implements a
refactoring (see section 4.1.4). One of the most important tasks for the
transformation is checking the conditions to preserve the behavior of the
program. It would be nice if a transformation could check all conditions. This
feature depends on the implementation of the transformation-tool. One of the
most difficult problems is finding all references of a program entity (see
section 4.1.4).
If a transformation tool is included in the development suite, then
it can use the compiler information to resolve all references to a given
symbol. In this case the user does not need to check the preconditions.
If a transformation tool is
a small stand-alone program(like DPT) with only parsers for given languages, it
does not and probably should not resolve all references to a entity. In this
case some of the preconditions should be checked by the user.
Example: Transformation = “Delete method“ Condition = “Method is unreferenced“
After the transformation the user can run his compiler and will
get „undefined symbol found“ if there
were some hidden references to this method. (see also section 4.2.2)
3.4 Tools for refactoring and wizards
There are very few tools to
support refactorings. (Example: „Refactoring Browser“ for Smalltalk).
Some of the software development tools offer a few basic refactoring
-transformations (referred as „Code-Wizards“).
Examples: „Implement
interface“-wizard (JBuilder) or „New
Class“ wizard (MSD).
The term „Wizard“ comes from
software development environments like MSD/Jbuilder.
Examples: LayoutWizard,
ClassWizard, DeploymentWizard, DocWizard,...
A wizard is basically a
creational code generator which generates particular program-changes. Many
wizards are designed to allow the user to control(e.g. parameter setting) the
execution process: they usually contain a GUI-Interface for interaction with the
user.
The wizard could also
guarantee the preserving behavior of the program. In this case a wizard is a
refactoring-transformation. There are very few wizards to support refactorings
for strongly typed languages like C/C++ or Java.
Often wizards offer a very intuitive GUI with navigational setting of the parameters and simple action handling. Also some wizards do not require the knowledge of the whole program-code bundle and external dependencies to complete a simple task. All of those wizard-features could be used for design of a refactoring-tool.