重構
目的:筆者最近在開發的時候,透過這一篇將常用的重構方法彙整起來,並記錄以便參考。
常使用快捷鍵
Ctrl + N:Go To Everything 定位到任何,非常强大。
Ctrl + Shift + N:Go To File 定位到文件。
Ctrl + F12:Go To File Member 在目前文件尋找。
一、Extract Method
用途:將一段程式碼抽離出來一個方法。
//前
class MyClass
{
public void Main()
{
string id ="test";
string name = "eddie";
// alt + enter -> extract method
Console.WriteLine("Hello World! id=" + id + " name=" + name );
}
}
//後
class MyClass
{
public void Main()
{
string id ="test";
string name = "eddie";
// alt + enter -> extract method
PrintInfo(id, name);
}
private static void PrintInfo(string id, string name)
{
Console.WriteLine("Hello World! id=" + id + " name=" + name);
}
}
二、InLine Method
用途:去掉一個方法,將調用的該方法程式替換成該方法內容。
//前
class MyClass
{
public void Main()
{
string id = "test";
string name = "eddie";
// alt + enter -> extract method
//在 PrintInfo的P前面選擇Ctrl + Shift + R 在選擇Inline Method
PrintInfo(id, name);
}
private static void PrintInfo(string id, string name)
{
Console.WriteLine("Hello World! id=" + id + " name=" + name);
}
}
//後
class MyClass
{
public void Main()
{
string id = "test";
string name = "eddie";
// alt + enter -> extract method
//在 PrintInfo的P前面選擇Ctrl + Shift + R 在選擇Inline Method
Console.WriteLine("Hello World! id=" + id + " name=" + name);
}
}
三:InLine Variable
用途:將所有對某方法返回變數使用,替換該方法直接呼叫。
註記:若該方法執行時間很長,很耗資源,謹慎使用。
//前
class MyClass
{
public void Main()
{
int qty = 5;
double amount = 0.0;
//在price p 前面ctrl+shift+R 選Inline Variable
double price = GetPrice();
if (price > 1000)
{
amount = price * qty;
}
else
{
amount = qty * 1000;
}
}
private double GetPrice()
{
return 100000.0;
}
}
//後
public void Main()
{
int qty = 5;
double amount = 0.0;
//在price p 前面ctrl+shift+R 選Inline Variable
if (GetPrice() > 1000)
{
amount = GetPrice() * qty;
}
else
{
amount = qty * 1000;
}
}
private double GetPrice()
{
return 100000.0;
}
四:Replace Temp With Query
用途:將一個表達式抽離到一個查詢方法中將所有該表達式結果變數到引用,替換成這個查詢方法,這樣做好處利於重複使用。
//前
class MyClass
{
public double basePrice = 1000.0;
public void Main()
{
int qty = 5;
double price = 0.0;
double amount = 0.0;
var product = new Product();
product.Name = "Product 1";
product.Price = 10000.0;
//作法一
//Step1 Extract Method 將If Else 提煉成一個Method 叫做GetPrice
//Step2 Inline Temp 將price變數移除
//作法二
//Step1 在if判斷轉換運算子
//Step2 Extract Method 將運算子提取Method 叫做GetPrice
//Step3 Inline Temp 將price變數移除
if (product.Price > basePrice)
{
price = product.Price;
}
else
{
price = basePrice;
}
amount = price * qty;
}
}
class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
class MyClass
{
public double basePrice = 1000.0;
public void Main()
{
int qty = 5;
double amount = 0.0;
var product = new Product();
product.Name = "Product 1";
product.Price = 10000.0;
//作法一
//Step1 Extract Method 將If Else 提煉成一個Method 叫做GetPrice
//Step2 Inline Temp 將price變數移除
//作法二
//Step1 在if判斷轉換運算子
//Step2 Extract Method 將運算子提取Method 叫做GetPrice
//Step3 Inline Temp 將price變數移除
amount = GetPrice(product) * qty;
}
private double GetPrice(Product product)
{
return product.Price > basePrice ? product.Price : basePrice;
}
五、Introduce Explaining Variable
用途:將一個複雜表達式的結果賦予一個有意義的變數,以提升程式碼的可讀性。
//前
public class Product
{
public decimal CalculateTotalPrice(int quantity, decimal unitPrice)
{
//Step1 1.2m 區塊 CTRL+Shirt+R使用Intrduce Variable
//Step2 提取出來部分取名有意義的變數,叫做Tax
return quantity * unitPrice * 1.2m; // 20% tax
}
}
public decimal CalculateTotalPrice(int quantity, decimal unitPrice)
{
var tax = 1.2m;
return quantity * unitPrice * tax; // 20% tax
}
六、Split Temporary Variable
用途:當程式中連續對一個變數多次給予給予值,並且每次給予的值含義不同時候,該針對變數的值分解成多個解釋變數的給予值,以提高程式的可讀性。
//前
public class LegacyShoppingCart
{
public double CalculateTotalAmount(double itemPrice, int quantity)
{
double temp = itemPrice * quantity;
Console.WriteLine($"Total Amount: {temp}");
temp = temp * 1.1;
Console.WriteLine($"Total Amount after tax: {temp}");
return temp;
}
}
//後
public class LegacyShoppingCart
{
public double CalculateTotalAmount(double itemPrice, int quantity)
{
//Step2 :Extract method - method Name: CalculateBaseAmount
//Step6 :ReName: baseAmount
double baseAmount = CalculateBaseAmount(itemPrice, quantity);
Console.WriteLine($"Total Amount: {baseAmount}");
//Step1 :Extract method - method Name: CalculateTotalAmountWithTax
//Step3 :InLine variable 在temp游標前
//Step4 :Indroduce variable - CalculateTotalAmountWithTax(temp)
//Step5 :ReName:totalAmount
var totalAmount = CalculateTotalAmountWithTax(baseAmount);
Console.WriteLine($"Total Amount after tax: {totalAmount}");
return totalAmount;
}
private static double CalculateBaseAmount(double itemPrice, int quantity)
{
return itemPrice * quantity;
}
private static double CalculateTotalAmountWithTax(double temp)
{
return temp * 1.1;
}
}
七:Remove Assignments to Parameters
用途:程式試圖修改傳遞進來參數時,先將參數定義一個變數,然後對參數的修改,改寫成該變數的修改,以降低函數的副作用,原始變數沒有被修改。
class LegacyMemberRegistration
{
public void RegisterMember(string username, string password)
{
// Validate the input
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
Console.WriteLine("Invalid username or password");
return;
}
ProcessRegistration(username, password);
//Step1 Encrypt-> Indroduce variable -> ReName: encryptedUsername
username = Encrypt(username);
//Step2 HashPassword-> Indroduce variable -> ReName: hashedPassword
password = HashPassword(password);
//Step3 update username -> encryptedUsername
//Step4 update password -> hashedPassword
Console.WriteLine($"Registered member with username: {username}, hashed password: {password}");
}
private void ProcessRegistration(string username, string password)
{
Console.WriteLine($"Processing registration for {username}");
}
private string Encrypt(string data)
{
Console.WriteLine($"Encrypting data: {data}");
return "EncryptedData";
}
private string HashPassword(string password)
{
Console.WriteLine($"Hashing password: {password}");
return "HashedPassword";
}
}
class LegacyMemberRegistration
{
public void RegisterMember(string username, string password)
{
// Validate the input
if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
{
Console.WriteLine("Invalid username or password");
return;
}
ProcessRegistration(username, password);
//Step1 Encrypt-> Indroduce variable -> ReName: encryptedUsername
var encryptedUsername = Encrypt(username);
//Step2 HashPassword-> Indroduce variable -> ReName: hashedPassword
var hashedPassword = HashPassword(password);
//Step3 update username -> encryptedUsername
//Step4 update password -> hashedPassword
Console.WriteLine($"Registered member with username: {encryptedUsername}, hashed password: {hashedPassword}");
}
private void ProcessRegistration(string username, string password)
{
Console.WriteLine($"Processing registration for {username}");
}
private string Encrypt(string data)
{
Console.WriteLine($"Encrypting data: {data}");
return "EncryptedData";
}
private string HashPassword(string password)
{
Console.WriteLine($"Hashing password: {password}");
return "HashedPassword";
}
}
八:Replace Method with method Object
用途:當一個函數包含了大量變數時候,將這些變數抽取出來變成一個方法物件,該函數主體變成方法物件中的一個方法,使原本函數替換成方法物件,提高可維護性與重用性。
using System;
public class LegacyCalculator
{
public void PerformCalculation(double operand1, double operand2, string operation)
{
// 複雜的計算邏輯
double result = 0.0;
switch (operation)
{
case "+":
result = operand1 + operand2;
break;
case "-":
result = operand1 - operand2;
break;
case "*":
result = operand1 * operand2;
break;
case "/":
result = operand1 / operand2;
break;
default:
Console.WriteLine("Invalid operation");
return;
}
// 其他邏輯...
Console.WriteLine($"Result of {operand1} {operation} {operand2} = {result}");
}
}
class Program
{
static void Main()
{
// 使用遺留程式碼
LegacyCalculator calculator = new LegacyCalculator();
calculator.PerformCalculation(5, 3, "+");
calculator.PerformCalculation(8, 2, "-");
}
}
步驟一:建立 CalculatorMethodObject類別
public class CalculatorMethodObject
{
private double operand1;
private double operand2;
private string operation;
public CalculatorMethodObject(double operand1, double operand2, string operation)
{
this.operand1 = operand1;
this.operand2 = operand2;
this.operation = operation;
}
public void PerformCalculation()
{
// 原本的計算邏輯
double result = 0.0;
switch (operation)
{
case "+":
result = operand1 + operand2;
break;
case "-":
result = operand1 - operand2;
break;
case "*":
result = operand1 * operand2;
break;
case "/":
result = operand1 / operand2;
break;
default:
Console.WriteLine("Invalid operation");
return;
}
// 其他邏輯...
Console.WriteLine($"Result of {operand1} {operation} {operand2} = {result}");
}
}
步驟二:修改原始類別
public class LegacyCalculator
{
public void PerformCalculation(double operand1, double operand2, string operation)
{
// 使用 Method Object 進行計算
CalculatorMethodObject methodObject = new CalculatorMethodObject(operand1, operand2, operation);
methodObject.PerformCalculation();
}
}
class Program
{
static void Main()
{
// 使用遺留程式碼
LegacyCalculator calculator = new LegacyCalculator();
calculator.PerformCalculation(5, 3, "+");
calculator.PerformCalculation(8, 2, "-");
}
}
九、Substitute Algorithm
用途:將方法中的演算法替換成另一個程式更加簡潔的演算法,而不改演算法的結果。
備註:這兩個方法是內部算法不一樣,外部結果一致
//前
static string FindPerson(List<string> person)
{
foreach (var name in person)
{
if (name == "Don")
return "Don";
else if (name == "John")
return "John";
else if (name == "Kent")
return "Kent";
}
return "";
}
static void Main()
{
List<string> personList = new List<string> { "John", "Jane", "Kent" };
string result = FindPerson(personList);
Console.WriteLine("Result: " + result);
}
//後
static string FindPerson(List<string> person)
{
List<string> perList = new List<string> { "Don", "John", "Kent" };
foreach (var name in person)
{
if (perList.Contains(name))
return name;
}
return "";
}
static void Main()
{
List<string> personList = new List<string> { "John", "Jane", "Kent" };
string result = FindPerson(personList);
Console.WriteLine("Result: " + result);
}
元哥的筆記