Code Smells - Part 1 - Long Method
Bài đăng này đã không được cập nhật trong 4 năm
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...
All rights reserved