Code Refactoring

Mở đầu

Một lần làm xong dự án thì thấy tự dưng phía khách hàng thuê hẳn một "cao nhân" không biết từ đâu về viết lại code cho cả dự án.Kỳ lạ là tên này không hề hiểu nghiệp vụ dự án, cũng chưa từng làm với framework của dự án nhưng vẫn viết lại đống code của dự án ầm ầm.Hỏi ra mới biết là làm refactoring code, vậy code refactoring là gì, làm xong có lợi ích gì và làm như thế nào? Bài viết này sẽ giúp các bạn trả lời những câu hỏi ở trên.

Refactoring Code là gì

Trên trang HaNoi Scrum có định nghĩa ngắn gọn như sau.

 Refactoring là thay đổi ở cấu trúc bên trong mà không làm thay đổi hành vi với bên ngoài của hệ thống.

Refactoring có thể thực hiện ở nhiều mức độ: Hệ thống -> Chức năng -> File/Class -> Method/Functions. Tùy theo những mức độ này thì "cấu trúc bên trong" "hành vi bên ngoài" "hệ thống" sẽ được hiểu khác nhau.Ví dụ khi refactoring 1 class thì cấu trúc bên trong là properties, method của class đó hành vi bên ngoài là các nhiệm vụ mà class đó thực hiện.Như vậy refactoring khi đó là viết lại properties, method sao cho không làm thay đổi các nhiệm vụ của class đó.Đối với mình thì

Refactoring là viết lại source code một cách khoa học hơn mà vẫn giữ được tính đúng đắn và giá trị về chức năng của source code đó.

Tại sao phải refactoring code

Refactoring không hề làm hệ thống chạy nhanh hơn, bảo mật hơn tuy nhiên nó sẽ giúp source code dễ tiếp cận, dễ đọc, dễ hiểu từ đó giúp ích rất nhiều cho quá trình bảo trì, mở rộng hệ thống.

Khi nào thì thực hiện refactoring

Bất cứ khi nào bạn muốn đoạn code của mình "tốt hơn" thì đều có thể thực hiện refactoring. Tuy nhiên một số giai đoạn dưới đây được cho là thích hợp hơn để làm refactoring.

Khi thêm chức năng mới vào source cũ

đây là thời điểm bạn phải đọc lại source cũ để hiểu và thêm vào 1 phần mới, có thể phần mới này sẽ ảnh hưởng cả đến những phần source cũ thì đây là thời điểm thích hợp để refactoring.

Khi tiến hành review code

Khi những người có kinh nghiệm hơn review cho những người ít kinh nghiệm thì họ sẽ chỉ ra cách viết code khoa học hơn cho người ít kinh nghiệm .Từ đó người ít kinh nghiệm học hỏi và tự refactoring code của mình để nâng cao trình độ.

Khi cần handover lại

Có những mã code phức tạp và rối đến mức ngay cả người viết ra nó cũng cần thời gian để hiểu logic.Việc handover lại nguyên những source code như vậy gây khó khăn cho người mới do đó để người mới dễ dàng tiếp cận hợn thì đây cũng là một thời điểm thích hợp để refactoring code.

Source code như thế nào thì cần refactoring và làm khi nào thì xong?

Để biết được source code có cần refactoring hay không người ta đưa ra một số tiêu chí gọi là "Bad code smells". Các bạn có thể tham khảo ở đây.Từ những smells sẽ có những kỹ thuật phù hợp để refactoring code, tùy theo mức độ áp dụng các kỹ thuật này code sẽ được tối ưu đến một mức độ nhất định.Có rất nhiều kỹ thuật để refactoring tuy nhiên nếu áp dụng tất cả thì sẽ rất tốn effort nên thông thường người ta chỉ áp dụng một số kỹ thuật thông dụng.

Một số Smell và kỹ thuật refactoring

vì có nhiều kỹ thuật refactoring khác nhau và khá dài nên trong phạm vi bài này mình chỉ trình bày những phần liên quan đến Bloaters (thu gọn, làm gọn).

Smell: Method, functions quá dài

Nguyên nhân: Việc viết code update liên tục vào 1 Function, methods khiến cho lượng code trong methods ngày càng lớn. Mục đích của method có khi không còn giống như lúc đầu nữa nên việc đọc lại ngày càng khó khăn và tốn thời gian hơn. Extract method: phương pháp này tách 1 phần methods lớn ra thành những methods nhỏ hơn làm những nhiệm vụ riêng biệt.

void printPet() {
  printBackground();

  //print details
  System.out.println("name: " + name);
  System.out.println("legs: " + getNumOfLeg());
}

sau khi refactoring

void printPet() {
  printBackground();
  printDetails(getNumOfLeg());
}

void printDetails(int legs) {
  System.out.println("name: " + name);
  System.out.println("legs: " + legs);
}

Preserve Whole Object: thay vì lấy giá trị từ object vào param sau đó truyền param vào method khác gây dư thừa param và khó quản lý các giá trị param khi có thay đổi thì hãy sử dụng trực tiếp object đó ngay trong methods cần dùng.

int start = valueRange().getStart();
int end = valueRange().getEnd();
boolean withinPlan = plan.withinRange(start, end);

sau khi refactoring

boolean withinPlan = plan.withinRange(valueRange());

Replacing Temp with Query:trong code có sử dụng những tính toán vào param tạm thời và sau đó sử dụng param đó cho những xử lý tiếp theo.

double CalculateTotal()
{
  double basePrice = quantity * itemPrice;

  if (basePrice > 1000)
  {
    return basePrice * 0.95;
  }
  else
  {
    return basePrice * 0.98;
  }
}

Sau khi refactoring

double CalculateTotal()
{
  if (BasePrice() > 1000)
  {
    return BasePrice() * 0.95;
  }
  else
  {
    return BasePrice() * 0.98;
  }
}
double BasePrice()
{
  return quantity * itemPrice;
}

Smell: quá nhiều parameters trong method, functions

có quá nhiều param trong method sẽ dễ gây nhầm lẫn, khó nhớ, khó hiểu.Có thể áp dụng 1 trong các phương phap dưới đây để xử lý tình trạng này. Thay param bằng giá trị của method call:

 int basePrice = quantity * itemPrice;
double seasonDiscount = store.GetSeasonalDiscount();
double fees = store.GetFees();
double finalPrice = GiscountedPrice(basePrice, seasonDiscount, fees);

Sau khi refactoring

int basePrice = quantity * itemPrice;
double finalPrice = GiscountedPrice(basePrice, store);

passing Whole Object: thay vì đưa các giá trị riêng lẻ thì có thể gộp các giá trị đó vào 1 object nếu nó có liên quan đến nhau.

int weight = calculator(numberOfpet,legs,head,body);

sau khi refactoring

...
class pet{
int legs;
int head;
int body;
}
....

int weight = calculator(numberOfpet,pet);

Smell: Class quá lớn

Class có quá nhiều properties, methods trở nên khó hiểu, khó thay đổi. Chia nhỏ: cân nhắc xem 1 phần trong class lớn có thể chia thành những class nhỏ hơn được hay không từ đó tách phần code này ra để làm thành 1 class con.

Class house{
int table;
int freeze;
int keyboard;
int monitor;
....
}

sau khi refactor

Class Computer{
int keyboard;
int monitor;
...
}
Class house{
int table;
int freeze;
Computer computer;
....
}

Dùng chung parent class or implements interface: Nhiều class có những phần dùng chung thì nên thu gọn lại.

 Class bicycle{
 int wheel;
 int body;
 bool isRunning(){
 ...
 }
 person getRider(){
 ...
 }
 }
 Class Car{
 int wheel;
 int body;
 int energyLevel;
  bool isRunning(){
 ...
 }
 person getDriver(){
 ...
 }
 int getEnergy(){
 ....
 }
 }

ta có thể sử dụng interface hoặc tạo một parrent class như sau

  interface moveThings{
   int wheel;
   int body;
   bool isRunning(){
   }
  }
  class bicycle implements moveThings{
  ....
  }
  class Car implements moveThings{
  ...
  }

Kết luận

Đối với các dự án dù lớn nhỏ thì việc refactoring code là cần thiết.Refactoring code đảm bảo tính dễ đọc, dễ hiểu, dễ mở rộng và bảo trì cho hệ thống.Ai cũng muốn đọc đống code sáng sủa chứ chẳng ai muốn động vào một dây xích chó phải không nào =)). Việc refactoring code cũng cần thiết phải được test lại để đảm bảo tính đúng đắn của hệ thống.