Software Refactoring

Michael L. Collard, Ph.D.

Department of Computer Science, The University of Akron

Source-Code Activities

  • Fix a bug (Debugging)
  • Add a new feature (Development)
  • Understand what some part of the code is doing (Comprehension)
  • Change for a new API (Adaptive Maintenance)

Levels of Software Changes

  • High-Level Software Changes
  • Add features to a system
  • E.g., Support a new output format by adding a class
  • Intermediate-Level Software Changes
  • Change design
  • E.g., Move a method from one class to another
  • Low-Level Software Changes
  • Change individual statements
  • E.g., Convert a deeply nested if to separate if statements

Mathematics: fac.tor

One of two or more quantities that divides a given quantity without a remainder, e.g., 2 and 3 are factors of 6; a and b are factors of ab

  • Multiple equivalent forms:
  • 2 × 3 = 6 and 1 × 6 = 6
  • x^2 - 4 = (x - 2)(x + 2)
  • The best form depends on the problem to solve

Mathematics: fac.tor.ing

To determine or indicate explicitly the factors of

  • Multiple equivalent forms:
  • 2 × 3 = 6 and 1 × 6 = 6
  • x^2 - 4 = (x - 2)(x + 2)
  • The best form depends on the problem to solve

Software Engineering: fac.tor

The individual items that combined form a complete software system

  • Identifiers
  • Contents of methods
  • Contents of classes
  • Relationship of a class to other classes
  • Many different ways to factor a software system

Software Engineering: fac.tor.ing

Determining the items, at design time, that make up a software system

  • Identifiers
  • Contents of methods
  • Contents of classes
  • Relationship of a class to other classes
  • Many different ways to factor a software system

Restructuring: [Opdyke'92]

A program restructuring operation to support the design, evolution, and reuse of object-oriented frameworks that preserve the behavioural aspects of the program

Refactoring: [Fowler99]

Process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure

  • Does not change the behavior of the program
  • Type of software transformation
  • Source-to-source, remain inside the same language, e.g., Java to Java, C++ to C++
  • Originally designed for object-oriented languages, but can also apply to other programming paradigms, e.g., functions, SQL, and other DSL (Domain Specific Languages)

Testing Requirements

Process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure

  • Testing must be in place to make sure that the behavior of the software does not change
  • Requires unit testing
  • No refactoring (or changes of any kind) before unit testing is in place
  • During refactoring, the current, expected behavior of the software must not change

Legacy Code

  • Often applied to old systems with "old" technology (e.g., COBOL, Java)
  • There is a great deal of software behavior that is:
  • Not stated (or clearly stated) in written policies
  • No code documentation
  • And even no test suite
  • Any code that does not have a unit-testing suite is considered legacy code

Refactoring and Process

Process of changing a software system in such a way that it does not alter the external behavior of the code yet improves its internal structure

  • Not the same as "cleaning up code" (which may cause changes to the behavior of the program)
  • Cleaning up code is not an effective strategy
  • Changes to a small context (e.g., individual method) or the entire program
  • A key element of most workflows and processes in agile
  • Viewpoint is that design is part of an iterative process

Refactoring: Rename Method

Refactoring: Rename Method Step 1

  • Copy the declaration/definition of the old method to the new method
  • Compile and build to verify no name conflicts
  • You can also commit at this point

Refactoring: Rename Method Step 2

  • Change the body of the old method to call the new method
  • Compile and build
  • Be sure to commit

Refactoring: Rename Method Step 3

  • Find all calls to the old method, and replace them with calls to the new method
  • Compile and build after each change
  • Be sure to commit at least once

Refactoring: Rename Method Step 4

  • Remove the old method declaration and definition (if not a public interface)
  • Compile and build
  • Commit

Refactoring: Rename Method Timeline

  • 1) Copy the declaration/definition of the old method to the new method
  • 2) Change the body of the old method to call the new method
  • 3) Find all calls to the old method, and replace them with calls to the new method
  • 4) Remove the old method declaration and definition (if not a public interface)

Refactoring Examples

  • Introduce Explaining Variable
  • Rename Method
  • Move Method
  • Pull-Up Method
  • Change Value to Reference
  • Remove Parameter
  • Extract Hierarchy

Refactoring: Split Temporary Variable

Refactoring: Pull-Up Method

  • Methods with identical results in subclasses
  • Move them to the superclass

Refactoring: Replace Inheritance with Delegation (UML)

  • A subclass uses only part of a superclasses interface or does not want to inherit data
  • Create a field for the superclass, adjust methods to delegate to the superclass, and remove the subclassing

Refactoring: Replace Inheritance with Delegation (Code)

Catalog

  • Collected by Fowler
  • Refactoring entry composed of:
  • Name
  • Summary
  • Motivation
  • Mechanics
  • Examples
  • Recent edition examples are in Javascript

Categories

  • Composing Methods
  • Organizing Data
  • Moving Features Between Objects
  • Simplifying Conditional Expressions
  • Simplifying Method Calls
  • Dealing with Generalization
  • Big Refactorings

Composing Methods: Extract Method

Composing Methods: Inline Method

Replace Nested Conditional with Guard Clauses

Inline Temp

Replace Temp with Query

Why Refactor?

  • Design Preservation
  • Comprehension
  • Debugging
  • Faster Programming

Why?

Design Preservation

  • Code changes often lead to a loss of the original design
  • Loss of design is cumulative:
  • Difficulty comprehending design →
    Difficulty preserving design →
    Design decays more rapidly →
    Difficulty comprehending design
  • Refactoring improves the design of existing code

Why?

Comprehension

  • Developers are most concerned with getting the program to work, not about future developers
  • Refactoring makes existing code more readable
  • Increases comprehension of existing code, leading to higher levels of code comprehension
  • Often applied in stages

Why?

Debugging

  • Greater program comprehension leads to easier debugging
  • Increased readability leads to the discovery of possible errors
  • Developers can put back into the code the understanding gained during debugging

Why?

Faster Programming

  • A counterintuitive argument made by Fowler
  • Good design is essential for rapid development
  • A poor design allows for quick progress but soon slows the process down
  • Spend time debugging
  • Changes take longer as you understand the system and find duplicate code
  • Supported by Lehman's laws

When?

During activities such as …

  • adding functionality
  • comprehension of an existing program
  • preparation for additional functionality
  • debugging
  • Code Review
  • preparation for suggestions to other programmers
  • looking for ideas to improve