The long way through Software Craftsmanship

Refactoring: split loop + loop/map equivalence

Feb 6, 2019 - 2 minute read - Comments - refactoringsplit-loopsplit-loop-refactoringloop-map-equivalencecodekotlinmultiparadigmfunctional-programmingfpobject-oriented-programmingobject-orientedoooop

We can combine the “Split loop” refactoring technique plus the “Loop / Map equivalence” when refactoring.

Given that it does not matter in which order we apply these operations, we can say that they have the associative property

Chart of the operations

Take the case of a loop doing two things. From there:

  • we can replace the loop for a map (including the two actions into a single map body)
  • we can split the loop into two loops

Take the case of two loops, doing one thing each. From there:

  • we can replace a loop for a map
  • we can replace both loops for maps

Take a map doing two things. From there:

  • we can split the map, into two maps, doing one thing each.

(We’re not specifying the inverse operations)

State diagram of the operations

Example

We want to print all substrings of a given string:

(State1: This uses a single loop, performing two actions)

fun printAllSubstrings(string: String){
  for (i in 0.rangeTo(string.length)) {
    val substring = string.substring(i)
    println(substring)
  }
}

From State1, we replace the loop for a map:

(State2: This uses a map, with two operations)

fun printAllSubstrings(string: String) {
    0.rangeTo(string.length).map {
        val substring = string.substring(it)
        println(substring)
    }
}

From State1, Refactor to split the loop:

(State3: This uses two loops, performing one action each)

fun printAllSubstrings(string: String){
  val substrings = mutableListOf<String>()
  for (i in 0.rangeTo(string.length)) {
    val substring = string.substring(i)
    substrings.add(substring)
  }
  for (substring in substrings) {
    println(substring)
  }
}

Collapse the first loop into a map:

(This uses a map and a loop)

fun printAllSubstrings(string: String) {
  for (substring in 0.rangeTo(string.length)
                     .map { string.substring(it) }) {
    println(substring)
  }
}

Extract the method to give it a name:

(This uses a map, a loop, and a method to explain the logic)

fun printAllSubstrings(string: String) {
  for (substring in allSubstrings(string)) {
    println(substring)
  }
}

private fun allSubstrings(string: String) = 
  0.rangeTo(string.length)
    .map { string.substring(it) }

Alternatively, use the method, with a map:

(This uses an explaining method, with a map)

fun printAllSubstrings(string: String) {
  allSubstrings(string)
    .map { println(it) }
}

private fun allSubstrings(string: String) = 
  0.rangeTo(string.length)
    .map { string.substring(it) }

Alternatively, from State3, we can also replace the loop for two maps:

(State4: This uses two maps)

fun printAllSubstrings(string: String){
  0.rangeTo(string.length)
    .map { string.substring(it) }
    .map { println(it) }
}

Self-Study in February 2019 Self-Study in March 2019