Welcome to our captivating session on refactoring, a powerful tool for tidying up code, much like organizing a messy toy box or finding a faster route to school.
Each line of code is as essential as a brick in a building; clumsy code may result in an unstable structure. Today, we'll focus on enhancing the readability, maintainability, and performance of our code through refactoring.
Let's briefly revisit a few key concepts:
- Code Smells: Indicators that our code needs refactoring, akin to clutter calling for cleanup.
- Refactoring Techniques: We've familiarized ourselves with
Extract Method
,Rename Method
, andSubstitute Algorithm
techniques in earlier lessons. - OOP in Refactoring: We've learned how to leverage Object-Oriented Programming principles to enhance our code's structure.
- Code Decoupling and Modularization: Methods to make code easier to manage by minimizing dependencies.
We'll use these concepts as guiding stars as we traverse the cosmos of refactoring.
We'll start by rewriting a complex game score computation function. Let's look at it:
Kotlin1class Game { 2 companion object { 3 fun computeScore(player: Player, monsters: List<Int>): Int { 4 var score = 0 5 for (monster in monsters) { 6 if (player.power > monster) { 7 score += player.power - monster 8 } else { 9 score -= player.power - monster 10 } 11 } 12 return score 13 } 14 } 15}
This code uses an algorithm to adjust the score based on the player's and the monsters' power. The parts player.power > monster
and player.power - monster
recur in this function, indicating room for refactoring. We'll apply the Extract Method
and Rename Method
to untangle this:
- We'll extract the scoring logic into a separate function,
scoreChange
. - We'll rename the original function to
computeGameScore
.
With these adjustments, our improved code might look something like this:
Kotlin1// New function to calculate score changes. 2class Game { 3 companion object { 4 private fun scoreChange(power: Int, monster: Int): Int { 5 return if (power > monster) { 6 power - monster 7 } else { 8 monster - power 9 } 10 } 11 12 // Refactored function to calculate the game score. 13 fun computeGameScore(player: Player, monsters: List<Int>): Int { 14 var score = 0 15 for (monster in monsters) { 16 score += scoreChange(player.power, monster) 17 } 18 return score 19 } 20 } 21}
This refactoring has simplified the function and made it easier to modify in the future.
Let's consider another example where the game has multiple types of monsters. Each monster type behaves differently when encountered by a player.
Kotlin1class Game { 2 companion object { 3 fun monsterReaction(monsterType: String, player: Player) { 4 when (monsterType) { 5 "ghost" -> { 6 if (player.power > 5) { 7 println("The ghost flees in terror!") 8 } else { 9 println("The ghost grumbles and attacks!") 10 } 11 } 12 "goblin" -> { 13 if (player.power > 3) { 14 println("The goblin groans and retreats!") 15 } else { 16 println("The goblin hacks with its sword!") 17 } 18 } 19 } 20 } 21 } 22}
This scenario could also benefit from refactoring using OOP and Code Decoupling:
- First, we'll introduce a class
Monster
with an open methodreaction
that could be overridden by each type of monster. - Then, we'll create child classes
Ghost
andGoblin
that inherit fromMonster
and implement their ownreaction
methods.
Under the revised structure, our game code would look like this:
Kotlin1abstract class Monster { 2 abstract fun reaction(player: Player) 3} 4 5class Ghost : Monster() { 6 override fun reaction(player: Player) { 7 if (player.power > 5) { 8 println("The ghost flees in terror!") 9 } else { 10 println("The ghost grumbles and attacks!") 11 } 12 } 13} 14 15class Goblin : Monster() { 16 override fun reaction(player: Player) { 17 if (player.power > 3) { 18 println("The goblin groans and retreats!") 19 } else { 20 println("The goblin hacks with its sword!") 21 } 22 } 23} 24 25// Game class where List of Monsters is managed 26class Game { 27 companion object { 28 @JvmStatic 29 fun main(args: Array<String>) { 30 val player = Player(4) // This is just an example instantiation. 31 val monsters: List<Monster> = listOf(Ghost(), Goblin(), Ghost(), Goblin()) 32 for (monster in monsters) { 33 monster.reaction(player) 34 } 35 } 36 } 37}
Now, our code dealing with multiple monsters is easier to manage and can be extended to accommodate more types of monsters.
Phew! We've done an excellent job working through two practical problems, enhancing our refactoring skills, and learning how to identify code smells and apply refactoring techniques.
The more you practice, the better you'll become at spotting code that could benefit from refactoring. Brace yourself for more practice tasks, and remember, always keep your code lean and efficient!