12/08/2018, 15:24

Code Smells - Part 1 - Long Method

Joker: What? How can code "smell"?? Developer: Well it doesn't have a nose... but it definitely can stink! Definition Code smell hay bad code (dịch nôm na là code bốc mùi hay code xấu) trong lập trình, đề cập đến bất kỳ triệu chứng bất ổn nào bên trong mã nguồn của một chương trình, mà vì ...

Joker: What? How can code "smell"?? Developer: Well it doesn't have a nose... but it definitely can stink!

Definition

Code smell hay bad code (dịch nôm na là code bốc mùi hay code xấu) trong lập trình, đề cập đến bất kỳ triệu chứng bất ổn nào bên trong mã nguồn của một chương trình, mà vì nó có thể sẽ dẫn đến các vấn đề lớn hơn. Code smells không phải là bugs, về mặc kĩ thuật chúng không làm sai chức năng của ứng dụng. Thay vào đó, chúng là biểu hiện của sự yếu kém trong việc thiết kế và sẽ làm cho quá trình phát triển ứng dụng bị chậm lại hoặc tăng nguy cơ của bugs hoặc lỗi trong tương lai. (Theo wikipedia)

Bloaters

Bloaters là những đoạn code, phương thức và lớp đã tăng về kích thước LOC (Line of code) mà bản thân nó trở nên khó đọc, hiểu và bảo trì. Thông thường những điều này không xảy ra ngay lập tức, mà thay vào đó chúng tích lũy theo thời gian khi chương trình tiến hóa (và đặc biệt là khi không ai cố gắng loại bỏ chúng).

Trong bài viết này, mình xin nói về lỗi thường xuyên xảy ra nhất của các lập trình viên thuộc loại Bloater smell, đó là lỗi viết method quá dài - Long Method

Long Method

Signs and Symptoms (Dấu hiệu)

Method chứa quá nhiều dòng mã. Nói chung, bất kỳ method nào dài hơn 10 dòng nên làm cho bạn bắt đầu đặt câu hỏi "Tại sao?"

Reasons for the Problem (Nguyên nhân gây ra vấn đề)

Có nhiều thứ được thêm vào nhưng chả có thứ gì được lấy ra. Bởi vì viết code dễ hơn là đọc code, vì thế thứ smell này sẽ không được chú ý cho đến khi nó biến method của bạn trở thành một con quái vật to lớn, và xấu xí. Nói cách khác, thường developer sẽ cảm thấy khó để tạo một method mới hơn là thêm vài dòng code vào một method có sẵn. Kiểu như "Nó chỉ có 2 dòng code thôi!", hay là "sẽ chả có lợi ích gì nếu tạo thêm cả một method cho điều này"... Điều này có nghĩa là một dòng code khác và rồi một dòng code khác nữa được thêm vào method, cuối cùng sẽ sinh ra một mớ hỗn độn spaghetti code.

Treatment (Giải pháp)

Có một quy tắc chung, đó là nếu bạn cảm thấy cần comment trên một vài thứ bên trongmethod, hãy lấy đoạn mã này sang một method mới. Ngay cả là một dòng, có thể và nên được chia thành một method riêng biệt, nếu nó đòi hỏi phải giải thích. Và nếu method có một tên mô tả, không ai sẽ cần phải nhìn vào mã để xem nó làm gì.

1. Để giảm kích thước method, dùng extract method

Problem

void printOwing() {
  printBanner();

  //print details
  System.out.println("name: " + name);
  System.out.println("amount: " + getOutstanding());
}

Solution

void printOwing() {
  printBanner();
  printDetails(getOutstanding());
}
void printDetails(double outstanding) {
  System.out.println("name: " + name);
  System.out.println("amount: " + outstanding);
}

2. Nếu các biến local hoặc các tham số can thiệp vào việc extract method bạn có thể dùng các cách sau:

2.1. Replace Temp with Query - Di chuyển toàn bộ biểu thức sang một method riêng biệt và nhận kết quả trả về từ nó.

Problem

double calculateTotal() {
  double basePrice = quantity * itemPrice;
  if (basePrice > 1000) {
    return basePrice * 0.95;
  }
  else {
    return basePrice * 0.98;
  }
}

Solution

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

2.2. Introduce Parameter Object - Thay thế các parameters bằng một object

Problem:

class Custumer {
    double amountInvoicedIn(start : Date, end : Date) {}
    double amountReceivedIn(start : Date, end : Date) {}
    double amountOverdueIn(start : Date, end : Date) {}
    ...
}

Solution:

class Custumer {
    double amountInvoicedIn(date : DateRange) {}
    double amountReceivedIn(date : DateRange) {}
    double amountOverdueIn(date : DateRange) {}
    ...
}

2.3. Preserve Whole Object - truyền nguyên đối tượng như một parameter

Problem

int low = daysTempRange.getLow();
int high = daysTempRange.getHigh();
boolean withinPlan = plan.withinRange(low, high);

Solution

boolean withinPlan = plan.withinRange(daysTempRange);

3. Thay thế method bằng một method object

Tức là chuyển đổi method thành một class riêng biệt để các biến local trở thành các field của class. Sau đó, chia method thành các method khác trong cùng class.

Problem

class Order {
  //...
  public double price() {
    double primaryBasePrice;
    double secondaryBasePrice;
    double tertiaryBasePrice;
    // long computation.
    //...
  }
}

Solution

class Order {
  //...
  public double price() {
    return new PriceCalculator(this).compute();
  }
}

class PriceCalculator {
  private double primaryBasePrice;
  private double secondaryBasePrice;
  private double tertiaryBasePrice;
  
  public PriceCalculator(Order order) {
    // copy relevant information from order object.
    //...
  }
  
  public double compute() {
    // long computation.
    //...
  }
}

4. Những toán tử có điều kiện, và vòng lặp là những đầu mối tốt để code có thể chuyển sang một method mới.

Với các vòng lặp ta có thể dùng extract method, với điều kiện ta dùng Decompose Conditional

Problem

if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
  charge = quantity * winterRate + winterServiceCharge;
}
else {
  charge = quantity * summerRate;
}

Solution

if (notSummer(date)) {
  charge = winterCharge(quantity);
}
else {
  charge = summerCharge(quantity);
}

Payoff

Method hoặc function càng dài, nó sẽ trở nên khó hiểu và khó bảo trì. Hơn nữa, method dài sẽ trở thành nơi trú ẩn hoàn hảo cho những đoạn duplicated code không mong muốn.

Performance (Hiệu năng)

Liệu sự gia tăng số lượng các method có làm ảnh hưởng đến hiệu suất, như nhiều người khẳng định? Trong hầu hết các trường hợp, tác động của nó là không không đáng kể, thậm chí là không đáng lo ngại.

Thêm vào đó, bây giờ khi code của bạn đã trở nên trong sáng, rõ ràng và dễ hiểu, bạn có nhiều khả năng tìm ra các methods thực sự hiệu quả để tái cơ cấu code (restructuring code) và thu được hiệu suất thực sự nếu cần.

Summary

Hi vọng với bài viết này sẽ giúp các bạn có cái nhìn khái quát về Code Smells, đồng thời tự nhủ bản thân từng bước loại bỏ những long method, làm cho chất lượng method được cải thiện, giúp các team member và maintainer dễ dàng hơn trong việc hiểu và bảo trì code.

Tham khảo: https://en.wikipedia.org/wiki/Code_smell

https://sourcemaking.com/refactoring/smells/long-method

https://viblo.asia/Do.Minh.Hai/posts/PaLkDYldvlX

TO BE CONTINUE...

0