Refactoring

Scientific IT Services ETHZ

July 2025

What Are Your Goals When Coding?

  • Compiled code communicates to the computer
  • Source code communicates to people

What is Refactoring?

The 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.

Martin Fowler, Refactoring (1999)

What is Refactoring?

  • Refactoring does not change the behavior of a program.
  • If the program is called twice (before and after a refactoring) with the same set of inputs, the resulting set of output values will be the same.
  • Refactoring supports software design and evolution by restructuring a program in the way that allows other changes to be made more easily.

William Opdyke‘s PhD Thesis (1992)

Extension-Refactoring Cycle

A little non-coding writing example:

Extend
The dog, which was long and thin and also very fast, ate.
Refactor
The long, thin, fast dog ate.
Extend
The long, thin, fast dog ate a biscuit.
Refactor
The greyhound dog ate a biscuit.

Why Refactoring?

  • Makes your code easier to read
  • Makes your code easier to modify
  • Makes your code easier to test
  • Makes it easier to find and fix bugs
  • Makes it possible to develop faster

When Should You Refactor?

When should you edit your writing?

Continuously but Purposefully

Code Smells: Signs that a refactoring is necessary

Cryptic names for variables ⇒ Rename to have clearer names
Long function or script ⇒ Break into smaller units (functions)
Long class with a lot of different functionality ⇒ Break into classes with single/fewer responsibilities
Duplicated code ⇒ Extract units (functions) and reuse
Magic numbers ⇒ Create named constants
Dead code ⇒ Delete (version control will keep it reachable)

Some Useful Refactorings

  • Rename Unit (Class, Method, Function, Variable)
  • Introduce Explaining Variable
  • Extract Method/Function
  • Extract Class
  • Substitute Algorithm/Data Structure
  • Remove Unused Code

How to Refactor

  • Use version control (e.g. git)
  • Run (unit) tests
  • Use tools (editor or LLM)
  • Make one change at a time
  • Iterate

A Comprehensive Example






x <- 0.3
symbols <- c("symbol 1", "symbol 2", "stuff", "more stuff", "lorem ipsum")
ns <- c()
ss <- c()

for (i in 1:100) {
  x <- 3.6 * x * (1 - x)
  ns <- c(ns, x)
  x <- 3.6 * x * (1 - x)
  ss <- c(ss, symbols[floor(x * 5) + 1]) 
}

Rename Variable






x <- 0.3
symbols <- c("symbol 1", "symbol 2", "stuff", "more stuff", "lorem ipsum")
numberSequence <- c()
symbolSequence <- c()

for (i in 1:100) {
  x <- 3.6 * x * (1 - x)
  numberSequence <- c(numberSequence, x)
  x <- 3.6 * x * (1 - x)
  symbolSequence <- c(symbolSequence, symbols[floor(x * 5) + 1]) 
}

Extract Function

logisticMap <- function(x) { 3.6 * x * (1 - x) }




x <- 0.3
symbols <- c("symbol 1", "symbol 2", "stuff", "more stuff", "lorem ipsum")
numberSequence <- c()
symbolSequence <- c()

for (i in 1:100) {
  x <- logisticMap(x)
  numberSequence <- c(numberSequence, x)
  x <- logisticMap(x)
  symbolSequence <- c(symbolSequence, symbols[floor(x * 5) + 1]) 
}

Extract Function

logisticMap <- function(x) { 3.6 * x * (1 - x) }
pickSymbol <- function(symbols, x) {
  symbols[floor(x * length(symbols)) + 1]
}

x <- 0.3
symbols <- c("symbol 1", "symbol 2", "stuff", "more stuff", "lorem ipsum")
numberSequence <- c()
symbolSequence <- c()

for (i in 1:100) {
  x <- logisticMap(x)
  numberSequence <- c(numberSequence, x)
  x <- logisticMap(x)
  symbolSequence <- c(symbolSequence, pickSymbol(symbols, x))
}

Substitute Algorithm

logisticMap <- function(x) { 3.6 * x * (1 - x) }
pickSymbol <- function(symbols, x) {
  symbols[floor(x * length(symbols)) + 1]
}

x <- 0.3
symbols <- c("symbol 1", "symbol 2", "stuff", "more stuff", "lorem ipsum")
numberSequence <- numeric(100)
symbolSequence <- character(100)

for (i in 1:100) {
  x <- logisticMap(x)
  numberSequence[i] <- x
  x <- logisticMap(x)
  symbolSequence[i] <- pickSymbol(symbols, x)
}

Introduce Explaining Variable

logisticMap <- function(x) { 3.6 * x * (1 - x) }
pickSymbol <- function(symbols, x) {
  symbols[floor(x * length(symbols)) + 1]
}

x <- 0.3
symbols <- c("symbol 1", "symbol 2", "stuff", "more stuff", "lorem ipsum")
numberSequence <- numeric(N)
symbolSequence <- character(N)
N <- 100
for (i in 1:N) {
  x <- logisticMap(x)
  numberSequence[i] <- x
  x <- logisticMap(x)
  symbolSequence[i] <- pickSymbol(symbols, x)
}

Basic Refactorings

  • Rename Unit (Class, Method, Function, Variable)
  • Introduce Explaining Variable
  • Extract Method/Function
  • Extract Class
  • Make Temporary Variable an Instance Variable
  • Substitute Algorithm/Data Structure

Rename Variable

def increment_depth(request):
    temp = request["depth"]
    temp = temp + 1
    request["depth"] = temp
    return temp
  1. Decide on a new name.
  2. Check that name isn‘t used in scope.
  3. Find all references to the name and replace it with the new name.

Rename Variable

def increment_depth(request):
    temp = request["depth"]
    temp = temp + 1
    request["depth"] = temp
    return temp
def increment_depth(request):
    depth = request["depth"]
    depth = depth + 1
    request["depth"] = depth
    return depth

Introducing Explaining Variable

class ShoppingCartEntry:
    def __init__(self, item_price, quantity):
        self.item_price = item_price
        self.quantity = quantity
    
    def calculate_price(self):
        # price is base price - discount + shipping
        return self.quantity * self.item_price - \
            max(0, self.quantity - 500) * self.item_price * 0.05 + \
            min(self.quantity * self.item_price * 0.1, 100.0)
  1. Introduce a variable.
  2. Set the value to the result of part of a complex expression.
  3. Replace that part of the expression with the variable.

Introducing Explaining Variable

class ShoppingCartEntry:
    def __init__(self, item_price, quantity):
        self.item_price = item_price
        self.quantity = quantity
    
    def calculate_price(self):
        # price is base price - discount + shipping
        return self.quantity * self.item_price \
            - max(0, self.quantity - 500) * self.item_price * 0.05 \
            + min(self.quantity * self.item_price * 0.1, 100.0)
class ShoppingCartEntry:
    def __init__(self, item_price, quantity):
        self.item_price = item_price
        self.quantity = quantity

    def calculate_price(self):
        # price is base price - discount + shipping
        base_price = self.quantity * self.item_price
        return base_price \
            - max(0, self.quantity - 500) * self.item_price * 0.05 \
            + min(base_price * 0.1, 100.0)

Introducing Explaining Variable

class ShoppingCartEntry:
    def __init__(self, item_price, quantity):
        self.item_price = item_price
        self.quantity = quantity

    def calculate_price(self):
        # price is base price - discount + shipping
        base_price = self.quantity * self.item_price
        return base_price \
            - max(0, self.quantity - 500) * self.item_price * 0.05 \
            + min(base_price * 0.1, 100.0)
class ShoppingCartEntry:
    def __init__(self, item_price, quantity):
        self.item_price = item_price
        self.quantity = quantity
        self.base_price = self.quantity * self.item_price

    def calculate_price(self):
        # price is base price - discount + shipping
        return self.base_price \
            - max(0, self.quantity - 500) * self.item_price * 0.05 \
            + min(self.base_price * 0.1, 100.0)

Extract Function

class ShoppingCartEntry:
    def __init__(self, item_price, quantity):
        self.item_price = item_price
        self.quantity = quantity
        self.base_price = self.quantity * self.item_price

    def calculate_price(self):
        # price is base price - discount + shipping
        return self.base_price - \
            max(0, self.quantity - 500) * self.item_price * 0.05 
            + min(self.base_price * 0.1, 100.0)
  1. Pick a good name.
  2. Create a new function by copy existing code.
  3. Bring over local variables or provide them as parameters.
  4. Replace extracted code with a call to the function.

Extract Functions

class ShoppingCartEntry:
  def __init__(self, item_price, quantity):
    self.item_price = item_price
    self.quantity = quantity
    self.base_price = self.quantity * self.item_price

  def calculate_price(self):
    # price = base price - discount + shipping
    return self.base_price - \
      max(0, self.quantity - 500) \
      * self.item_price * 0.05 \
      + min(self.base_price * 0.1, 100.0)





class ShoppingCartEntry:
  def __init__(self, item_price, quantity):
    self.item_price = item_price
    self.quantity = quantity
    self.base_price = self.quantity * self.item_price

  def calculate_price(self):
    return self.base_price - self.calculate_discount() \
          + self.calculate_shipping_cost()

  def calculate_discount(self):
    return max(0, self.quantity - 500) * self.item_price * 0.05 

  def calculate_shipping_cost(self):
    return min(self.base_price * 0.1, 100.0)

Extract Function

def calculate_discount(quantity, item_price):
  discount_rate = 0.05
  minimum_quantity = 500
  return max(0, quantity - minimum_quantity) \
         * item_price * discount_rate 


def calculate_shipping_cost(base_price):
  shipping_cost_rate = 0.1
  minimum_shipping_cost = 100
  return min(base_price * shipping_cost_rate, minimum_shipping_cost)




class ShoppingCartEntry:
  def __init__(self, item_price, quantity):
    self.item_price = item_price
    self.quantity = quantity

    self.base_price = self.quantity * self.item_price

  def calculate_price(self):
    return base_price - calculate_discount(self.quantity, self.item_price) \
           + calculate_shipping_cost(self.base_price)
  



Extract Function

class ShoppingCartEntry:
    def __init__(self, item_price, quantity):
        self.item_price = item_price
        self.quantity = quantity
    
    def calculate_price(self):
        # price is base price - discount + shipping
        return self.quantity * self.item_price - \
            max(0, self.quantity - 500) * self.item_price * 0.05 + \
            min(self.quantity * self.item_price * 0.1, 100.0)



class ShoppingCartEntry:
  def __init__(self, item_price, quantity):
    self.item_price = item_price
    self.quantity = quantity

    self.base_price = self.quantity * self.item_price

  def calculate_price(self):
    return base_price - calculate_discount(self.quantity, self.item_price) \
           + calculate_shipping_cost(self.base_price)
  


Further Resources