Ở bài trước chúng ta đã học được các khái niệm về các vấn đề thường gặp khi chúng ta code rồi. Bạn nào chưa xem có thể tham khảo tại đây
Hôm nay chúng ta sẽ đi phân tích các vấn đề thường gặp trong Long Method và cách giải quyết nhé.
Đầu tiên chúng ta sẽ liệt kê những Issue và sau đó chúng ta sẽ đi sâu vào cách giải quyết từng issue 1 nhé.
Issue thường gặp của Long method
- Method quá dài
- Có quá nhiều Local variable và Parametters
Cách giải quyết
1. Tách nhỏ thành những method con (Extract Method)
Khi method quá dài, chúng ta sẽ nghĩ đến ngay kỹ thuật đầu tiên này đó là kỹ thuật Extract Method
Problem: Phần print details cần phải có dòng comment để giải thích
void PrintOwing()
{
this.PrintBanner();
// Print details.
Console.WriteLine("name: " + this.name);
Console.WriteLine("amount: " + this.GetOutstanding());
}
Solution: Xóa dòng comment đó đi và thay vào đó bằng 1 method mới.
void PrintOwing()
{
this.PrintBanner();
this.PrintDetails();
}
void PrintDetails()
{
Console.WriteLine("name: " + this.name);
Console.WriteLine("amount: " + this.GetOutstanding());
}
2. Method có quá nhiều Local variable và Parameters
Ở đây chúng ta có 3 kỹ thuật để giải quyết bài toán này
- Replace Temp with Query (thay thế query/biểu thức tính toán với biến tạm thành method)
- Introduce Parameter Object
- Preserve Whole Object
2.1 Replace Temp with Query
Problem: Bạn dùng 1 biến tạm để lưu một biểu thức tính toán. Sau đó dùng biến này để xử lý
double CalculateTotal()
{
// problem here
double basePrice = quantity * itemPrice;
if (basePrice > 1000)
{
return basePrice * 0.95;
}
else
{
return basePrice * 0.98;
}
}
Resolved: Bạn move biểu thức tính toán gắn với biến tạm đó ra một method riêng. Trong method chính bạn gọi lại method vừa tách ra là được
double CalculateTotal()
{
if (BasePrice() > 1000)
{
return BasePrice() * 0.95;
}
else
{
return BasePrice() * 0.98;
}
}
// solution
double BasePrice()
{
return quantity * itemPrice;
}
2.2 Introduce Parameter Object
Problem: Những method bạn gọi có số lượng parametter giống nhau và nó lặp lại nhiều lần
class Customer{
void amountInvoiceIn(DateTime start, DateTime end);
void amountReceivedIn(DateTime start, DateTime end);
void amountOverdueIn(DateTime start, DateTime end);
}
Resolved: Hãy thay những param bị trùng lặp này thành 1 object để tái sử dụng
class Customer{
void amountInvoiceIn(DateRange date);
void amountReceivedIn(DateRange date);
void amountOverdueIn(DateRange date);
}
// solution
class DateRange{
public DateTime Start {get;set;}
public DateTime End {get;set;}
}
2.3 Truyền nguyên Object vào (Preserve Whole Object)
Problem: Bạn lấy giá trị từ một object ra và gán vào các biến tạm.
int low = daysTempRange.GetLow();
int high = daysTempRange.GetHigh();
bool withinPlan = plan.WithinRange(low, high);
Solution: Thay vì vậy bạn hãy truyền nguyên cả Object vào. code sẽ gọn hơn nhiều
bool withinPlan = plan.WithinRange(daysTempRange);
3. Thay thế Method bằng Object’s Method
Nếu 2 phương pháp trên không thể giải quyết, bạn hãy thử dùng phương pháp này nhé. Phương pháp này hơi khó một chút nên bạn cần phải nghiền ngẫm nhé.
Problem: Bạn có 1 method dài và những local variable trong đó đan xen vào nhau
public class Order
{
// ...
public double Price()
{
double primaryBasePrice;
double secondaryBasePrice;
double tertiaryBasePrice;
// Perform long computation.
}
}
Solution: Biến đổi những local variable đó trở thành những fields của một class. Từ đó bạn có thể tách long method đó ra thành những method nhỏ trong class vừa khởi tạo
public class Order
{
// ...
public double Price()
{
return new PriceCalculator(this).Compute();
}
}
public class PriceCalculator
{
private double primaryBasePrice;
private double secondaryBasePrice;
private double tertiaryBasePrice;
public PriceCalculator(Order order)
{
// Copy relevant information from the
// order object.
}
public double Compute()
{
// Perform long computation.
}
}
4. Xử lý câu điều kiện phức tạp và vòng lặp (Conditionals and Loops)
Những toán tử điều kiện và vòng lặp xuất hiện trong code. Báo hiệu rằng đoạn code đó có thể move ra một phương thức riêng.
Với toán tử điều kiện thì sử dụng kỹ thuật Decompose Conditional
Với vòng lặp ta có thể sử dụng kỹ thuật Extract Method
4.1 Decompose Conditional
Problem: Bạn có một phương thức xử lý điều kiện phức tạp (if-then/else hoặc là switch)
if (date < SUMMER_START || date > SUMMER_END)
{
charge = quantity * winterRate + winterServiceCharge;
}
else
{
charge = quantity * summerRate;
}
Solution:
Phần thứ 1, Chúng ta gỡ rối phần phức tạp của if trước bằng cách move ra một phương thức riêng đặt tên cho nó có ý nghĩa.
Phần thứ 2, ta thấy biến charge dược lấy giá trị từ các phép toán phức tạp, chúng ta tách phép toán phức tạp này ra 2 phương thức khác nhau.
if (isSummer(date))
{
charge = SummerCharge(quantity);
}
else
{
charge = WinterCharge(quantity);
}
4.2 Extract Method
Problem: Bạn có 1 đoạn code có thể nhóm lại với nhau
void printProperties(IList users)
{
for (int i = 0; i < users.size(); i++)
{
string result = "";
result += users.get(i).getName();
result += " ";
result += users.get(i).getAge();
Console.WriteLine(result);
// ...
}
}
Solution: Chuyển đoạn code trong vòng for ra một phương thức mới.
void printProperties(IList users)
{
foreach (User user in users)
{
Console.WriteLine(getProperties(user));
// ...
}
}
string getProperties(User user)
{
return user.getName() + " " + user.getAge();
}
Kết luận
- Method/function càng dài thì càng khó để đọc hiểu và maintance nó.
- Method dài còn ẩn chưa những góc khuất và dupplicate code
Class với những method ngắn gọn, dễ hiểu có phải là điều tuyệt vời đúng không nào. Hãy nhớ luôn luôn clean code bạn nhé.