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:
- Inline: put the extracted code back into context
- Adjust: if needed, tweak the code to move closer the code that belongs together
- 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.
}
- Inline – inline
databaseName
andmakeTableSelections()
fromDatabaseSelectionBridge
.
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/