Lesson 5
Stepping into Refactoring Code in C#
Stepping into Refactoring Code

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.

Just as each line of code is as essential as a brick in a building, clumsy code may lead to an unstable structure. Today, we'll focus on enhancing the readability, maintainability, and performance of our code through refactoring.

Recapping Crucial Concepts

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, and Substitute 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.

Practice Problem 1: Taming a Complex Function

We'll start by rewriting a complex game score computation function. Let's look at it:

C#
1public class Game 2{ 3 public static int ComputeScore(Player player, List<int> monsters) 4 { 5 int score = 0; 6 foreach (int monster in monsters) 7 { 8 if (player.Power > monster) 9 { 10 score += player.Power - monster; 11 } 12 else 13 { 14 score -= player.Power - monster; 15 } 16 } 17 return score; 18 } 19}

This code uses an algorithm to adjust the score based on the player's and 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 method, ScoreChange.
  • We'll rename the original method to ComputeGameScore.

With these adjustments, our improved code might look something like this:

C#
1// New method to calculate score changes. 2public class Game 3{ 4 private static int ScoreChange(int power, int monster) 5 { 6 return power > monster ? power - monster : monster - power; 7 } 8 9 // Refactored method to calculate the game score. 10 public static int ComputeGameScore(Player player, List<int> monsters) 11 { 12 int score = 0; 13 foreach (int monster in monsters) 14 { 15 score += ScoreChange(player.Power, monster); 16 } 17 return score; 18 } 19}

This refactoring has simplified the method and made it easier to modify in the future.

Practice Problem 2: Refactoring with OOP and Code Decoupling

Let's consider another example where the game has multiple types of monsters. Each monster type behaves differently when encountered by a player.

C#
1public class Game 2{ 3 public static void MonsterReaction(string monsterType, Player player) 4 { 5 if (monsterType == "ghost") 6 { 7 if (player.Power > 5) 8 { 9 Console.WriteLine("The ghost flees in terror!"); 10 } 11 else 12 { 13 Console.WriteLine("The ghost grumbles and attacks!"); 14 } 15 } 16 else if (monsterType == "goblin") 17 { 18 if (player.Power > 3) 19 { 20 Console.WriteLine("The goblin groans and retreats!"); 21 } 22 else 23 { 24 Console.WriteLine("The goblin hacks with its sword!"); 25 } 26 } 27 // more monster types... 28 } 29}

This scenario could also benefit from refactoring using OOP and Code Decoupling:

  • First, we'll introduce a class Monster with a method Reaction that could be overridden by each type of monster.
  • Then, we'll create child classes Ghost and Goblin that inherit from Monster and implement their own Reaction methods.

Under the revised structure, our game code would look like this:

C#
1public abstract class Monster 2{ 3 public abstract void Reaction(Player player); 4} 5 6public class Ghost : Monster 7{ 8 public override void Reaction(Player player) 9 { 10 if (player.Power > 5) 11 { 12 Console.WriteLine("The ghost flees in terror!"); 13 } 14 else 15 { 16 Console.WriteLine("The ghost grumbles and attacks!"); 17 } 18 } 19} 20 21public class Goblin : Monster 22{ 23 public override void Reaction(Player player) 24 { 25 if (player.Power > 3) 26 { 27 Console.WriteLine("The goblin groans and retreats!"); 28 } 29 else 30 { 31 Console.WriteLine("The goblin hacks with its sword!"); 32 } 33 } 34} 35 36// Game class where List of Monsters is managed 37public class Game 38{ 39 public static void Main(string[] args) 40 { 41 Player player = new Player(4); // This is just an example instantiation. 42 List<Monster> monsters = new List<Monster> { new Ghost(), new Goblin(), new Ghost(), new Goblin() }; 43 foreach (Monster monster in monsters) 44 { 45 monster.Reaction(player); 46 } 47 } 48}

Now, our code dealing with multiple monsters is easier to manage and can be extended to accommodate more types of monsters.

Wrapping Up and Looking Ahead

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!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.