Refactor: Inline-Adjust-Extract

Sometimes, we feel that something we extracted isn’t as good as it could be. The Inline-Adjust-Extract refactoring pattern can help fix that.

It has three steps:

  1. Inline: put the extracted code back into context
  2. Adjust: if needed, tweak the code to move closer the code that belongs together
  3. Extract: pull the adjusted code back out

In a sense, this pattern is the opposite of the Isolate-Improve-Inline pattern. That tactic removes code from its context so you can work on either the code or the context without the distraction of the other.

Inline-Adjust-Extract goes the other way: it moves code into a context so you can better see how it’s intended to be used, then extracts to restore encapsulation.

Example

This case shows the pattern applied to methods that were extracted and moved; you can also use it on a class, a local method, or a variable.

Context: We previously extracted code from DatabaseView and moved it over to DatabaseSelectionBridge. (It’s a bridge in the sense that it connects two different user interface toolkits.) But nothing uses the databaseName.get; it’s required since we have the setter. The lines are really trying to do one thing: configure the DatabaseSelection.

// Here's the calling site
struct DatabaseView: View {
  :
  database = SqlDatabase(currentDatabase)
  bridge.databaseName = currentDatabase
  bridge.makeTableSelections(database)
  :
}

// Here's the code we had extracted
class DatabaseSelectionBridge: ObservableObject {
  @Published var selection = DatabaseSelection()

  var databaseName: String {
    get {
      selection.databaseName
    }

    set {
      selection.databaseName = newValue
    }
  }

  func makeTableSelections(_ database: SqlDatabase) {
    selection.tableSelections = database
      .tableMetadata
      .map { TableSelection(metadata: $0) }
  }
  // etc.
}
  1. Inline – inline databaseName and makeTableSelections() from DatabaseSelectionBridge.
struct DatabaseView: View {
  :
  database = SqlDatabase(currentDatabase)

  bridge.selection.databaseName = currentDatabase

  bridge.selection.tableSelections = database
    .tableMetadata
    .map { TableSelection(metadata: $0) }

  :
}

2. Adjust – the inlined code depends on both currentDatabase and database, but they’re related. In particular, we can get the currentDatabase name back from the database.

struct DatabaseView: View {
  :
  database = SqlDatabase(currentDatabase)

  bridge.selection.databaseName = database.name

  bridge.selection.tableSelections = database
    .tableMetadata
    .map { TableSelection(metadata: $0) }

  :
}

3. Extract – we’ll extract and move both blocks into a single new method. We only need to pass in database.

struct DatabaseView: View {
  :
  database = SqlDatabase(currentDatabase)

  bridge.select(database)

  :
}


class DatabaseSelectionBridge: ObservableObject {
  @Published var selection = DatabaseSelection()

  func select(_ database: SqlDatabase) {
    selection.databaseName = database.name

    selection.tableSelections = database
      .tableMetadata
      .map {
        TableSelection(metadata: $0)
      }
  }

  :
}

On the bridge class, we can delete the now-unused databaseName and makeTableSelections().

Result: Inline-Adjust-Extract has made the call site more clear, and the called code is better too.

Conclusion

Inline-Adjust-Extract and Isolate-Improve-Inline are examples of two refactoring “molecules”, built from the standard refactoring steps of “Extract” and “Inline”. You can either pull code back into context when that helps you understand it better, or isolate it when that helps. Both approaches deserve a place in your toolkit.

References

“Isolate-Improve-Inline: The 3-I Refactoring Tactic”, by Bill Wake. https://www.industriallogic.com/blog/isolate-improve-inline-refactoring-tactic/