Refactoring

SIS

July 2024

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 should 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 it easier to find and fix bugs
  • 🚀 Makes it possible to develop faster

When Should You Refactor?

When should you edit your writing?

All the time! 🧐

Bad Smells: Signs That a Refactoring is Necessary

🤪 Cryptic names for variables, etc. Rename to have clearer names
😵 Long function / script body Break into smaller units
🤯 Duplicated code Extract units and share
🤔 Learn a better algorithm Replace algorithm

Some Useful 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

How to Refactor

  • Use version control (e.g. git)
  • Run unit tests
  • Use tools (if available)
  • 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")
N <- 100
numberSequence <- numeric(N)
symbolSequence <- character(N)
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 price(self):
        """price is base price - quantity 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.
 class ShoppingCartEntry:
     def __init__(self, item_price, quantity):
         self.item_price = item_price
         self.quantity = quantity

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

Extract Function

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

    def price(self):
        """price is base price - quantity 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)

Extract Function

  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 Function

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

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

Further Resources

Acknowledgements

  • Slides:
  • Based on slides by Franz-Josef Elmer