BỘ GIÁO DỤC VÀ ĐÀO TẠO
ĐẠI HỌC ĐÀ NẴNG
BÁO CÁO
LUẬN VĂN THẠC SĨ KỸ THUẬT
NGÀNH KHOA HỌC MÁY TÍNH
TÊN ĐỀ TÀI:
ỨNG DỤNG KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN ĐỂ
TRIỂN KHAI DÒ TÌM VÀ CẢI TIẾN CÁC ĐOẠN
MÃ XẤU TRONG CHƢƠNG TRÌNH C#
Họ tên HV : NHIÊU LẬP HÒA
Họ tên CBHD : TS.NGUYỄN THANH BÌNH
ĐÀ NẴNG, 11/2008
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
2
LỜI CAM ĐOAN
Tôi xin cam đoan nội dung luận văn "Ứng dụng kỹ thuật tái cấu trúc mã nguồn để
triển khai dò tìm và cải tiến các đọan mã xấu trong chƣơng trình C# ", dƣới sự hƣớng dẫn
của TS. Nguyễn Thanh Bình, là công trình do tôi trực tiếp nghiên cứu.
Tôi xin cam đoan các số liệu, kết quả nghiên cứu trong luận văn là trung thực và
chƣa từng đƣợc công bố trong bất cứ công trình nào trƣớc đây.
Tác giả
Nhiêu Lập Hòa
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
3
MỤC LỤC
LỜI CAM ĐOAN ................................................................................................................ 2
MỤC LỤC ........................................................................................................................... 3
DANH MỤC HÌNH ẢNH ................................................................................................... 5
MỞ ĐẦU ............................................................................................................................. 6
CHƢƠNG I: KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN (REFACTORING) .............. 7
I.1 ĐỊNH NGHĨA KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN ................................ 7
I.1.1 Ví dụ minh họa................................................................................................. 7
I.1.2 Định nghĩa kỹ thuật tái cấu trúc mã nguồn ................................................... 19
I.2 HIỆU QUẢ CỦA TÁI CẤU TRÚC MÃ NGUỒN ................................................ 20
I.2.1 Refactoring cải thiện thiết kế phần mềm ....................................................... 20
I.2.2 Refactoring làm mã nguồn phần mềm dễ hiểu .............................................. 20
I.2.3 Refactoring giúp phát hiện và hạn chế lỗi ..................................................... 21
I.2.4 Refactoring giúp đấy nhanh quá trình phát triển phần mềm ......................... 21
I.3 KHI NÀO THỰC HIỆN TÁI CẤU TRÚC MÃ NGUỒN ..................................... 22
I.3.1 Refactor khi thêm chức năng ......................................................................... 22
I.3.2 Refactor khi cần sửa lỗi ................................................................................ 22
I.3.3 Refactor khi thực hiện duyệt chƣơng trình ................................................... 23
I.4 CÁC KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN .............................................. 23
I.4.1 Danh mục các kỹ thuật tái cấu trúc mã nguồn ............................................... 23
I.5 NHẬN XÉT VÀ KẾT LUẬN ................................................................................ 26
CHƢƠNG II: LỖI CẤU TRÚC (BAD SMELLS) TRONG MÃ NGUỒN ................... 27
II.1 KHÁI NIỆM VỀ LỖI CẤU TRÚC (BAD SMELLS) ........................................ 27
II.2 LỖI CẤU TRÚC VÀ GIẢI PHÁP CẢI TIẾN ..................................................... 27
II.2.1 Duplicated Code - Trùng lặp mã ................................................................. 27
II.2.2 Long Method – Phƣơng thức phức tạp ......................................................... 28
II.2.3 Large Class – Qui mô lớp lớn ...................................................................... 30
II.2.4 Long Parameter List - Danh sách tham số quá dài ....................................... 31
II.2.5 Divergent Change – Cấu trúc lớp ít có tính khả biến .................................. 32
II.2.6 Shotgun Surgery – Lớp đƣợc thiết kế không hợp lý và bị phân rã ............ 32
II.2.7 Feature Envy – Phân bố phƣơng thức giữa các lớp không hợp lý .............. 33
II.2.8 Data Clumps – Gôm cụm dữ liệu ................................................................ 34
II.2.9 Primitive Obsession – Khả năng thể hiện dữ liệu của lớp bị hạn chế ......... 34
II.2.10 Switch Statements – Khối lệnh điều kiện rẽ hƣớng không hợp lý ........... 36
II.2.11 Lazy Class – Lớp đƣợc định nghĩa không cần thiết .................................. 38
II.2.12 Speculative Generality – Cấu trúc bị thiết kế dƣ thừa ............................... 38
II.2.13 Temporary Field – Lạm dụng thuộc tính tạm thời .................................... 39
II.2.14 Message Chains –Chuỗi phƣơng thức liên hoàn khó kiểm soát............... 39
II.2.15 Middle Man – Quan hệ ủy quyền không hợp lý/logic ............................... 39
II.2.16 Inapproprite Intimacy - Cấu trúc thành phần riêng không hợp lý ............ 41
II.2.17 Alternative Classes with Different Interfaces - Đặc tả lớp không rõ ràng 41
II.2.18 Incomplete Library Class – Sử dụng thƣ viện lớp chƣa đƣợc hòan chỉnh 41
II.2.19 Data Class – Lớp dữ liệu độc lập ............................................................. 42
II.2.20 Refused Bequest – Quan hệ kế thừa không hợp lý/logic ......................... 43
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
4
II.2.21 Comments – Chú thích không cần thiết .................................................... 43
II.3 NHẬN XÉT VÀ KẾT LUẬN .............................................................................. 44
CHƢƠNG III: NỀN TẢNG .NET VÀ NGÔN NGỮ LẬP TRÌNH C# ............................ 45
III.1 TỔNG QUAN VỀ NỀN TẢNG .NET .............................................................. 45
III.1.1 Định nghĩa .NET ........................................................................................ 45
III.1.2 Mục tiêu của .NET ..................................................................................... 45
III.1.3 Dịch vụ của .NET ....................................................................................... 45
III.1.4 Kiến trúc của .NET .................................................................................... 46
III.2 NGÔN NGỮ LẬP TRÌNH C# .......................................................................... 47
III.2.1 Tổng quan về ngôn ngữ lập trình C# ......................................................... 47
III.2.2 Đặc trƣng của các ngôn ngữ lập trình C# ................................................... 47
III.3 MÔI TRƢỜNG PHÁT TRIỂN ỨNG DỤNG VISUAL STUDIO .NET .......... 48
CHƢƠNG IV: ỨNG DỤNG KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN ĐỂ
DÒ TÌM VÀ CẢI TIẾN CÁC ĐOẠN MÃ XẤU TRONG CHƢƠNG TRÌNH C# ......... 49
IV.1 GIẢI PHÁP VÀ CÔNG CỤ HỖ TRỢ REFACTOR .......................................... 49
IV.1.1 Đặc tả giải pháp triển khai ......................................................................... 49
IV.1.2 Một số công cụ và tiện ích hỗ trợ việc dò tìm và cải tiến mã xấu ............. 50
IV.1.3 Thử nghiệm minh họa các công cụ hỗ trợ refactor trong VS.Net .............. 57
IV.1.4 Nhận xét và đánh giá .................................................................................. 80
IV.2 ỨNG DỤNG KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN ĐỂ DÒ TÌM VÀ
CẢI TIẾN CÁC ĐOẠN MÃ XẤU TRONG CHƢƠNG TRÌNH C#.......................... 81
IV.2.1 Thực hiện kỹ thuật tái cấu trúc mã nguồn trên chƣơng trình thực tế ......... 82
IV.2.2 Phân tích và đánh giá kết quả thực hiện .................................................... 94
IV.3 NHẬN XÉT VÀ KẾT LUẬN ............................................................................ 95
CHƢƠNG V: KẾT LUẬN ............................................................... 96
V.1 ĐÁNH GIÁ KẾT QUẢ CỦA ĐỀ TÀI ............................................................... 96
V.2 PHẠM VI ỨNG DỤNG .................................................................................... 96
V.3 HƢỚNG PHÁT TRIỂN ...................................................................................... 97
V.3.1 Triển khai áp dụng trên các ngôn ngữ khác ................................................ 97
V.3.2 Thử nghiệm xây dựng một refactoring tool tích hợp vào VS.NET ........... 97
TÀI LIỆU THAM KHẢO ................................................................................................. 98
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
5
DANH MỤC HÌNH ẢNH
Tên hình ảnh Trang
H.3.1: Kiến trúc nền tảng .NET 46
H.3.2: Môi trường phát triển ứng dụng VS.NET 48
H.4.1: Đặc tả kịch bản giải pháp triển khai 49
H.4.2: Trình chức năng refactor tích hợp trong VS.NET 50
H.4.3: Trình chức năng refactor của Visual Assit X for VS.NET 51
H.4.4: Trình chức năng refactor của C# Refactory for VS.NET 52
H.4.5: Trình chức năng refactor của .NET Refactor for .NET 53
H.4.6: Trình chức năng refactor của CodeIT.Once for .NET 54
H.4.7: Trình chức năng refactor của JetBrances ReShape 55
H.4.8: Trình chức năng refactor của DevExpress Refactor!™ Pro 56
H.4.9: Minh họa kỹ thuật Change Signature trong JetBrains ReSharper 58
H.4.10: Kết quả minh họa kỹ thuật Change Signature 58
H.4.11: Minh họa kỹ thuật Convert Method to Property của CodeIT.Once 60
H.4.12: Minh họa kỹ thuật Convert Method to Property của ReSharper 61
H.4.13: Kết quả kỹ thuật Convert Method to Property 61
H.4.14: Minh họa kỹ thuật Decompose/Simplify Conditional 63
H.4.15: Kết quả kỹ thuật Decompose/Simplify Conditional 63
H.4.16: Minh họa kỹ thuật Encapsulate Field của Refactor trong VS.NET 65
H.4.17: Minh họa kỹ thuật Encapsulate Field của Visual Assit X for .NET 66
H.4.18: Kết quả kỹ thuật Encapsulate Field 66
H.4.19: Minh họa kỹ thuật Extract Interface của Refactor trong VS.NET 68
H.4.20: Minh họa kỹ thuật Extract Interface của CodeIT.Once 69
H.4.21: Kết quả kỹ thuật Extract Interface 69
H.4.22: Minh họa kỹ thuật Extract Method của Refactor trong VS.NET 71
H.4.23: Kết quả kỹ thuật Extract Method 71
H.4.24: Minh họa kỹ thuật Inline Variable của CodeIT.Once for .NET 73
H.4.25: Kết quả kỹ thuật Inline Variable 73
H.4.26: Minh họa kỹ thuật Promote Local Variable to Parameter của VS.NET 75
H.4.27: Minh họa kỹ thuật Promote Local Variable to Parameter của CodeIT.Once 75
H.4.28: Minh họa kỹ thuật Promote Local Variable to Parameter của ReSharper 76
H.4.29: Kết quả kỹ thuật Promote Local Variable to Parameter 76
H.4.30: Minh họa kỹ thuật Rename Variables của Refactor trong VS.NET 78
H.4.31: Minh họa kỹ thuật Rename Variables của Visual Assit X 79
H.4.32: Kết quả kỹ thuật Rename Variables 79
H.4.33: Sơ đồ lớp của chương trình khi chưa refactoring 82
H.4.34: Màn hình kết quả chạy chương trình khi chưa refactoring 84
H.4.35: Sơ đồ lớp của chương trình sau khi refactoring 91
H.4.36: Màn hình kết quả chạy chương trình sau khi refactoring 93
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
6
MỞ ĐẦU
Trong qui trình phát triển phần mềm hiện nay, một thực tế đang tồn tại ở các công
ty sản xuất phần mềm là các lập trình viên thƣờng xem nhẹ việc tinh chỉnh mã nguồn và
kiểm thử. Ngoài lý do đơn giản vì đó là một công việc nhàm chán, khó đƣợc chấp nhận
đối với việc quản lý vì sự tốn kém và mất thời gian, còn một nguyên nhân khác là chúng
ta không có những phƣơng pháp và tiện ích tốt hỗ trợ cho những việc này. Điều này dẫn
đến việc phần lớn các phần mềm không đƣợc kiểm thử đầy đủ và phát hành với các nguy
cơ lỗi tiềm ẩn.
Phƣơng thức phát triển phần mềm linh hoạt[15] bắt đầu xuất hiện vào đầu những
năm 90 với mục tiêu là phần mềm phải có khả năng biến đổi, phát triển và tiến hóa theo
thời gian mà không cần phải làm lại từ đầu. Phƣơng thức này đƣợc thực hiện dựa trên hai
kỹ thuật chính là tái cấu trúc mã nguồn (refactoring) và kiểm thử (developer testing). Vì
thế việc nghiên cứu và ứng dụng kỹ thuật tái cấu trúc mã nguồn nhằm tối ƣu hóa mã
nguồn và nâng cao hiệu quả kiểm thử là một nhu cầu cần thiết trong quá trình thực hiện
và phát triển phần mềm.
Đề tài “Ứng dụng kỹ thuật tái cấu trúc mã nguồn để triển khai dò tìm và cải
tiến các đoạn mã xấu trong chƣơng trình C#” đƣợc thực hiện với mục đích nghiên cứu
cơ sở lý thuyết kỹ thuật tái cấu trúc mã nguồn và áp dụng để triển khai việc dò tìm và cải
tiến mã xấu (lỗi cấu trúc) trong các chƣơng trình hiện đại và phổ biến hiện nay (C#).
Toàn bộ nội dung của luận văn bao gồm các chƣơng:
Chƣơng 1: Kỹ thuật tái cấu trúc mã nguồn (refectoring)
Chƣơng 2: Mã xấu (bad smells) và giải pháp cải tiến dựa trên refactoring
Chƣơng 3: Nền tảng .NET và ngôn ngữ lập trình C#
Chƣơng 4: Ứng dụng kỹ thuật tái cấu trúc mã nguồn để dò tìm và cải thiện mã xấu
trong các chƣơng trình C#
Chƣơng 5: Kết luận
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
7
CHƢƠNG I: KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN
(REFACTORING)
I.1 ĐỊNH NGHĨA KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN
I.1.1 Ví dụ minh họa
Phƣơng thức tiếp cận và tìm hiểu hiệu quả nhất với một khái niệm hay một kỹ
thuật mới trong tin học là thông qua các ví dụ minh họa [11]. Với ví dụ dƣới đây, chúng
ta sẽ hiểu refactoring là gì cũng nhƣ cách thực hiện và hiệu quả của nó trong qui trình
công nghệ phát triển phần mềm.
Bài toán ví dụ: Chương trình trả lại kết quả danh sách các số nguyên tố.
(Bài toán này sử dụng thuật toán Eratosthenes)
Nội dung thuật toán Eratosthenes:
- Viết một danh sách các số từ 2 tới maxNumbers mà ta cần tìm. Gọi là list A.
- Viết số 2, số nguyên tố đầu tiên, vào một list kết quả. Gọi là list B.
- Xóa bỏ 2 và bội của 2 khỏi list A.
- Số đầu tiên còn lại trong list A là số nguyên tố. Viết số này sang list B.
- Xóa bỏ số đó và tất cả bội của nó khỏi list A.
- Lặp lại các bƣớc 4 and 5 cho tới khi không còn số nào trong list A.
Chƣơng trình khởi đầu:
public class PrimeNumbersGetter {
private int maxNumber;
public PrimeNumbersGetter(int maxNumber){
this.maxNumber = maxNumber;
}
public int[] GetPrimeNumbers() { // Use Eratosthenes's sieve
bool[] numbers = new bool[maxNumber + 1];
for (int i = 0; i < numbers.Length; ++i){
umbers[i] = true;
}
int j = 2;
while (j <= (int)Math.Sqrt(maxNumber) + 1) {
for (int k = j + j; k <= maxNumber; k += j){
numbers[k] = false;
}
j++;
while (!numbers[j]) {
j++;
if (j > maxNumber) break;
}
}
}
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
8
List<int> l = new List<int>();
for (int k = 2; k <= maxNumber; ++k) {
if (numbers[k]) l.Add(k);
}
return l.ToArray();
}
Trƣớc khi refactoring, chúng ta cần viết kiểm thử cho phần mã nguồn đó. Phần
kiểm thử này là yếu tố cần thiết bởi vì quá trình refactoring có thể phát sinh lỗi. Mỗi khi
chúng ta thực hiện một lần refactoring, chúng ta nên thực hiện kiểm thử lại chƣơng trình
một lần, để đảm bảo chƣơng trình không bị lỗi. Kiểm thử tốt làm giảm thời gian cần thiết
để tìm lỗi. Chúng ta nên thực hiện xen kẽ việc kiểm thử và refactoring để mỗi khi có lỗi
phát sinh, thì cũng không quá khó để tìm ra lỗi đó.
Trong ví dụ này, chúng có thể thêm đoạn mã kiểm thử nhƣ sau:
public class Program {
public static void Main(string[] args) {
if (!IsEqualNumbers(new PrimeNumbersGetter(4).GetPrimeNumbers(),
new int[] { 2, 3 }))
return;
if (!IsEqualNumbers(new PrimeNumbersGetter(5).GetPrimeNumbers(),
new int[] { 2, 3, 5 }))
return;
if (!IsEqualNumbers(new PrimeNumbersGetter(6).GetPrimeNumbers(),
new int[] { 2, 3, 5 }))
return;
if (!IsEqualNumbers(new PrimeNumbersGetter(100).GetPrimeNumbers(),
new int[] { 2, 3, 5, 7,
11, 13, 17, 19, 23, 29, 31, 37, 41, 43,47,
53, 59, 61, 67, 71, 73, 79, 83, 89, 97 }))
return;
Console.WriteLine("Success!");
}
private static bool IsEqualNumbers(int[] numbers1, int[] numbers2){
if (numbers1.Length != numbers2.Length) return false;
for (int i = 0; i < numbers1.Length; ++i) {
if (numbers1[i] != numbers2[i]) return false;
}
}
return true;
}
Ta nhận thấy rằng phƣơng thức này quá dài, và nó xử lý rất nhiều công việc khác
nhau. Trong trƣờng hợp này, nên sử dụng kĩ thuật “Extract Method” trong các kĩ thuật
refactoring nhằm tạo ra các phƣơng thức nhỏ hơn, dễ đọc và dễ bảo trì khi có yêu cầu
thay đổi chƣơng trình.
Với đoạn mã nguồn khởi tạo list các số ban đầu ( list A ):
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
9
bool[] numbers = new bool[maxNumber + 1];
for (int i = 0; i < numbers.Length; ++i){
numbers[i] = true;
}
Ta nên trích xuất nó thành một phƣơng thức khác, sử dụng “Extract Method”.
public int[] GetPrimeNumbers() {
bool[] numbers = InitialNumbers();
// Other codes.
}
private bool[] InitialNumbers(){
bool[] numbers = new bool[maxNumber + 1];
for (int i = 0; i < numbers.Length; ++i){
numbers[i] = true;
}
return numbers;
}
Sau khi thực hiện việc refactoring nhƣ trên, chúng ta nên nhớ rằng phải thực hiện
chạy lại chƣơng trình kiểm thử để đảm báo rằng việc refactoring không làm thay đổi tính
đúng đắn của chƣơng trình.
Tƣơng tự với đoạn mã nguồn thực hiện xuất ra danh sách kết quả chứa các số nguyên tố
(list B)
List<int> l = new List<int>();
for (int k = 2; k <= maxNumber; ++k) {
if (numbers[k]) l.Add(k);
}
return l.ToArray();
Ta cũng tách nó ra thành một phƣơng thức khác
public int[] GetPrimeNumbers() {
// Other codes.
return GetPrimeNumbersArray(numbers);
}
private int[] GetPrimeNumbersArray(bool[] numbers) {
List<int> l = new List<int>();
for (int k = 2; k <= maxNumber; ++k) {
if (numbers[k]) l.Add(k);
}
return l.ToArray();
}
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
10
Bây giờ chúng ta sẽ tinh chỉnh ở phần mã nguồn còn lại, đó là vòng lặp while
int j = 2;
while (j <= (int)Math.Sqrt(maxNumber) + 1){
for (int k = j + j; k <= maxNumber; k += j){
numbers[k] = false;
}
j++;
while (!numbers[j]){
j++;
if (j > maxNumber) break;
}
}
Với đoạn mã nguồn trên, câu lệnh if là không cần thiết, ta có thể bỏ đi, đƣa điều kiện lên
vòng while nhƣ sau:
int j = 2;
while (j <= (int)Math.Sqrt(maxNumber) + 1) {
for (int k = j + j; k <= maxNumber; k += j) {
numbers[k] = false;
}
j++;
while (!numbers[j] && j < maxNumber) {
j++;
}
}
Ta thấy rằng, câu lệnh điều khiển trong vòng for không “đẹp” và khó đọc, ta nên sửa đổi
tên biến k thành i.
int j = 2;
while (j <= (int)Math.Sqrt(maxNumber) + 1) {
for (int i = 2; i * j <= maxNumber; i++){
numbers[i * j] = false;
}
j++;
while (!numbers[j] && j < maxNumber) {
j++;
}
}
Với vòng while ở trên, mục đích chỉ là duyệt danh sách các phần tử trong list A, nên ta có
thể chuyển sang sử dụng vòng lặp for nhƣ sau:
for (int j = 2; j <= (int)Math.Sqrt(maxNumber) + 1; j++) {
if (!numbers[j]) continue;
for (int i = 2; i * j <= maxNumber; i++) {
numbers[i * j] = false;
}
}
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
11
Với đoạn mã nguồn
for (int i = 2; i * j <= maxNumber; i++) {
numbers[i * j] = false;
}
Thực hiện việc xóa bỏ các bội số của các số nguyên tố. Do đó, có thể tách chúng ra thành
một phƣơng thức. Kết quả thu đƣợc là:
public int[] GetPrimeNumbers() {
bool[] numbers = InitialNumbers();
for (int j = 2; j <= (int)Math.Sqrt(maxNumber) + 1; j++) {
if (!numbers[j]) continue;
RemoveMultiple(numbers, j);
}
return GetPrimeNumbersArray(numbers);
}
private void RemoveMultiple(bool[] numbers, int j) {
for (int i = 2; i * j <= maxNumber; i++) {
numbers[i * j] = false;
}
}
Tiếp tục với đoạn mã nguồn
private void RemoveMultiple(bool[] numbers, int j) {
for (int i = 2; i * j <= maxNumber; i++) {
numbers[i * j] = false;
}
}
Ta nên đặt tên biến, tham số truyền vào sao cho mã nguồn trở nên dễ đọc nhất.
private void RemoveMultiple(bool[] numbers, int number) {
for (int i = 2; i * number <= maxNumber; i++) {
numbers[i * number] = false;
}
}
Đến đây ta thấy rằng phƣơng thức GetPrimenumbers() đã trở nên ngắn gọn, dễ
đọc hơn nhất nhiều. Tuy nhiên, chúng ta cũng cần nghĩ rằng chƣơng trình này đã thực sự
đẹp chƣa và có cần refactor nữa hay không?
Ta nhận thấy rằng biến numbers đƣợc sử dụng là tham số để truyền vào một số
phƣơng thức. Do đó, ta nên chuyển khai báo biến numbers thành biến thành viên của lớp.
Khi đó, các phƣơng thức sẽ sử dụng trực tiếp biến thành viên này, chứ không phải sử
dụng tham số truyền vào. Khi chuyển biến numbers thành biến thành viên, thì phƣơng
thức InitialNumbers() không cần nữa, mà ta sẽ chuyển khởi tạo biến này trong
constructor của lớp. Khi đó chúng ta cần phải xóa hết các tham số truyền vào trong các
phƣơng thức sử dụng biến numbers.
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
12
Khi đó lớp PrimeNumbersGetter
public class PrimeNumbersGetter {
private int maxNumber;
public PrimeNumbersGetter(int maxNumber) {
this.maxNumber = maxNumber;
}
public int[] GetPrimeNumbers() {
bool[] numbers = InitialNumbers(); // Other codes.
}
// Other codes
private bool[] InitialNumbers() {
bool[] numbers = new bool[maxNumber + 1];
for (int i = 0; i < numbers.Length; ++i) {
numbers[i] = true;
}
return numbers;
}
}
Sẽ đƣợc chỉnh sửa thành
public class PrimeNumbersGetter {
private int maxNumber;
private bool[] numbers;
public PrimeNumbersGetter(int maxNumber) {
this.maxNumber = maxNumber;
this.numbers = new bool[maxNumber + 1];
for (int i = 0; i < numbers.Length; ++i) {
numbers[i] = true;
}
}
public int[] GetPrimeNumbers() {
// Other codes.
}
// Other codes, Method InitialNumbers() is nomore available.
}
Việc sử dụng biến numbers theo kiểu bool[] đã đƣợc định nghĩa sẵn trong Thƣ
viện System.Collections, đó là kiểu BitArray. Do đó, ta nên chuyển khai báo của biến
numbers thành kiểu BitArray. Nhƣ vậy, ta sẽ loại bỏ đƣợc việc khởi tạo giá trị cho biến
mảng numbers, bởi BitArray đã thực hiện điều đó. Chúng ta cần lƣu ý rằng biến mảng
numbers lúc này chỉ đánh số từ 2 cho đến maxNumber, do đó, nó chỉ có (maxNumber -
1) phần tử, và nếu số duyệt là j thì vị trí của nó là numbers[j - 2] Sau khi sửa, ta có đoạn
mã nguồn sau:
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
13
public class PrimeNumbersGetter {
private int maxNumber;
private BitArray numbers;
public PrimeNumbersGetter(int maxNumber) {
this.maxNumber = maxNumber;
this.numbers = new BitArray(maxNumber - 1, true);
}
public int[] GetPrimeNumbers() {
for (int j = 2; j <= (int)Math.Sqrt(maxNumber) + 1; j++) {
if (!numbers[j - 2]) continue;
RemoveMultiple(j);
}
return GetPrimeNumbersArray();
}
private void RemoveMultiple(int number){
for (int i = 2; i * number <= maxNumber; i++) {
numbers[i * number - 2] = false;
}
}
private int[] GetPrimeNumbersArray() {
List<int> l = new List<int>();
for (int k = 2; k <= maxNumber; ++k) {
if (numbers[k - 2]) l.Add(k);
}
return l.ToArray();
}
Với phƣơng thức
private void RemoveMultiple(int number) {
for (int i = 2; i * number <= maxNumber; i++) {
numbers[i * number - 2] = false;
}
}
mục đích của nó là loại bỏ các số không phải là số nguyên tố, ta nên tách nó ra với việc
thêm một phƣơng thức, và đặt tên theo đúng ý nghĩa của nó.
private void RemoveMultiple(int number) {
for (int i = 2; i * number <= maxNumber; i++) {
RemoveNumber(i * number);
}
}
private void RemoveNumber(int number) {
numbers[number - 2] = false;
}
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
14
Tƣơng tự nhƣ vậy, khi duyệt để lấy ra phần tử trong list nếu để là
numbers[number - 2] thì khó đọc. Do đó có thể chuyển thành phƣơng thức và đặt tên cho
nó. Ta viết đƣợc phƣơng thức nhƣ sau:
private bool Remains(int number) {
return numbers[number - 2];
}
Thay trong chƣơng trình, ta có hình ảnh của mã nguồn:
public class PrimeNumbersGetter {
private int maxNumber;
private BitArray numbers;
public PrimeNumbersGetter(int maxNumber){
this.maxNumber = maxNumber;
this.numbers = new BitArray(maxNumber - 1, true);
}
public int[] GetPrimeNumbers() {
// Use Eratosthenes's sieve
for (int j = 2; j <= (int)Math.Sqrt(maxNumber) + 1; j++){
if (!Remains(j)) continue;
RemoveMultiple(j);
}
return GetPrimeNumbersArray();
}
private void RemoveMultiple(int number){
for (int i = 2; i * number <= maxNumber; i++) {
RemoveNumber(i * number);
}
}
private bool Remains(int number) {
return numbers[number - 2];
}
private void RemoveNumber(int number) {
numbers[number - 2] = false;
}
private int[] GetPrimeNumbersArray() {
List<int> l = new List<int>();
for (int k = 2; k <= maxNumber; ++k) {
if (Remains(k)) l.Add(k);
}
return l.ToArray();
}
}
Bây giờ ta xem xét việc tạo lớp mới từ những thành phần liên quan đến nhau –
còn gọi là phƣơng thức “Extract Class”.
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
15
Ở trong bài toán này , ta có thể tạo ra một lớp internal chứa dữ liệu liên quan đến
danh sách các số, và các xử lý trên danh sách đó. Chuyển các phƣơng thức Remains(),
RemoveNubmer(), GetPrimeNumberArray() sang lớp mới. Kết quả nhƣ sau:
public class PrimeNumbersGetter {
private int maxNumber;
public PrimeNumbersGetter(int maxNumber) {
this.maxNumber = maxNumber;
}
public int[] GetPrimeNumbers() {
Sieve sieve = new Sieve(maxNumber);
// Use Eratosthenes's sieve
for (int j = 2; j <= (int)Math.Sqrt(maxNumber) + 1; j++) {
if (!sieve.Remains(j)) continue;
RemoveMultiple(sieve, j);
}
return sieve.GetPrimeNumbersArray();
}
private void RemoveMultiple(Sieve sieve, int number){
for (int i = 2; i * j <= maxNumber; i++) {
sieve.RemoveNumber(i * number);
}
}
}
internal class Sieve {
private int maxNumber;
private BitArray numbers;
internal Sieve(int maxNumber) {
this.maxNumber = maxNumber;
this.numbers = new BitArray(maxNumber - 1, true);
}
internal bool Remains(int number) {
return numbers[number - 2];
}
internal void RemoveNumber(int number) {
numbers[number - 2] = false;
}
internal int[] GetPrimeNumbersArray() {
List<int> l = new List<int>();
for (int k = 2; k <= maxNumber; ++k) {
if (Remains(k)) l.Add(k);
}
}
return l.ToArray();
}
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
16
Ta thấy rằng phƣơng thức GetPrimeNumbers() mục đích chính là trả lại danh sách các số
nguyên tố cần tìm. Để có đƣợc danh sách các số nguyên tố, có thể có rất nhiều thuật toán.
Mặt khác, phƣơng thức này lại cài đặt bên trong nó thuật toán Eratosthenes's sieve. Ta
nên tách riêng nó thành một lớp khác, để phƣơng thức GetPrimeNumbers() chỉ cần có giá
trị trả về theo một thuật toán nào đó.(Ở đây là thuật toán Eratosthenes). Kết quả nhƣ sau:
public class PrimeNumbersGetter {
private int maxNumber;
public PrimeNumbersGetter(int maxNumber) {
this.maxNumber = maxNumber;
}
public int[] GetPrimeNumbers() {
return new Eratosthenes(maxNumber).GetPrimeNumbers();
}
}
internal class Eratosthenes {
private int maxNumber;
internal Eratosthenes(int maxNumber) {
this.maxNumber = maxNumber;
}
public int[] GetPrimeNumbers(){
Sieve sieve = new Sieve(maxNumber);
for (int j = 2; j <= (int)Math.Sqrt(maxNumber) + 1; j++){
if (!sieve.Remains(j)) continue;
RemoveMultiple(sieve, j);
}
return sieve.GetPrimeNumbersArray();
}
private void RemoveMultiple(Sieve sieve, int number) {
for (int i = 2; i * j <= maxNumber; i++) {
sieve.RemoveNumber(i * number);
}
}
}
Ta xem xét đoạn mã nguồn sau:
for (int j = 2; j <= (int)Math.Sqrt(maxNumber) + 1; j++) {
if (!sieve.Remains(j)) continue;
RemoveMultiple(sieve, j);
}
Công việc của đoạn mã nguồn là tìm các phần tử là số nguyên tố đầu tiên, sau đó
loại bỏ đi các bội số của chúng trong list A ban đầu. Ta tạo ra method
GetRemainNumbers() có chứa các số nguyên tố đầu tiên. Ta nghĩ tới việc dùng foreach
để duyệt các phần tử trong GetRemainNumbers(). Muốn sử dụng foreach thì
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
17
GetRemainNumbers() phải đƣợc cài đặt interface IEnumerable và sử dụng câu lệnh yield
return. Kết quả nhƣ sau:
public int[] GetPrimeNumbers() {
Sieve sieve = new Sieve(maxNumber);
foreach (int i in GetRemainNumbers(sieve)){
RemoveMultiple(sieve, i);
}
return sieve.GetPrimeNumbersArray();
}
private IEnumerable<int> GetRemainNumbers(Sieve sieve) {
for (int i = 2; i <= (int)Math.Sqrt(maxNumber) + 1; i++) {
if (sieve.Remains(i)) yield return i;
}
}
Tƣơng tự nhƣ vậy, ta xem xét phƣơng thức GetPrimeNumberArray(), ta cũng có thể sử
dụng interface Ienumerable
internal int[] GetPrimeNumbersArray() {
List<int> l = new List<int>();
for (int k = 2; k <= maxNumber; ++k) {
if (Remains(k)) l.Add(k);
}
return l.ToArray();
}
Chuyển thành
internal int[] GetPrimeNumbersArray() {
return new List<int>(GetRemainNumbers()).ToArray();
}
private IEnumerable<int> GetRemainNumbers() {
for (int k = 2; k <= maxNumber; ++k) {
if (Remains(k)) yield return k;
}
}
Trong quá trình refactor, việc đặt lại tên biến cần đƣợc chú ý, bởi vì bản thân tên
biến có thể coi nhƣ là một lời comment hiệu quả nhất nói lên công việc của đoạn mã
nguồn.Ví dụ phƣơng thức sau:
private void RemoveMultiple(Sieve sieve, int j) {
for (int i = 2; i * j <= maxNumber; i++){
sieve.RemoveNumber(i * j);
}
}
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
18
Ta nên đặt lại tên tham số truyền vào, biến j thành biến number
private void RemoveMultiple(Sieve sieve, int number) {
for (int i = 2; i * number <= maxNumber; i++) {
sieve.RemoveNumber(i * number);
}
}
Tƣơng tự nhƣ vậy, biến đếm k trong phƣơng thức GetRemainNumbers() cũng nên
chuyển thành biến đếm i.
Và đây là kết quả của quá trình refactoring:
public class PrimeNumbersGetter {
private int maxNumber;
public PrimeNumbersGetter(int maxNumber) {
this.maxNumber = maxNumber;
}
public int[] GetPrimeNumbers(){
return new Eratosthenes(maxNumber).GetPrimeNumbers();
}
}
internal class Eratosthenes {
private int maxNumber;
internal Eratosthenes(int maxNumber){
this.maxNumber = maxNumber;
}
public int[] GetPrimeNumbers() {
Sieve sieve = new Sieve(maxNumber);
foreach (int i in GetRemainNumbers(sieve)) {
RemoveMultiple(sieve, i);
}
return sieve.GetPrimeNumbersArray();
}
private IEnumerable<int> GetRemainNumbers(Sieve sieve) {
for (int i = 2; i <= (int)Math.Sqrt(maxNumber) + 1; i++){
(sieve.Remains(i)) yield return i;
}
}
private void RemoveMultiple(Sieve sieve, int number) {
for (int i = 2; i * number <= maxNumber; i++) {
sieve.RemoveNumber(i * number);
}
}
}
internal class Sieve {
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
19
private int maxNumber;
private BitArray numbers;
internal Sieve(int maxNumber) {
this.maxNumber = maxNumber;
this.numbers = new BitArray(maxNumber - 1, true);
}
internal bool Remains(int number) {
return numbers[number - 2];
}
internal void RemoveNumber(int number) {
numbers[number - 2] = false;
}
internal int[] GetPrimeNumbersArray(){
return new List<int>(GetRemainNumbers()).ToArray();
}
private IEnumerable<int> GetRemainNumbers() {
for (int i = 2; i <= maxNumber; ++i) {
if (Remains(i)) yield return i;
}
}
}
Thông qua ví dụ ta trình bày trên, ta có thể thấy việc refactoring thực sự đơn giản
nhƣng chỉ với các bƣớc đơn giản nhƣ vậy thôi cũng đã làm cho đoạn mã nguồn dễ đọc và
dễ hiểu hơn rất nhiều. Vậy refactoring là gì và hiệu quả của nó nhƣ thế nào trong quá
trình phát triển phần mềm.
I.1.2 Định nghĩa kỹ thuật tái cấu trúc mã nguồn
Chúng ta có hai khái niệm định nghĩa [4] khi tiếp cận thuật ngữ tái cấu trúc mã nguồn
(Refectoring):
- Định nghĩa 1 (Danh từ): Sự thay đổi cấu trúc nội tại phần mềm để dễ hiểu hơn và
ít tốn chi phí để cập nhật mà không làm thay đổi ứng xử bên ngoài.
- Định nghĩa 2 (Động từ): Tái cấu trúc lại phần mềm thông qua việc áp dụng các
bƣớc refactoring mà không làm thay đổi ứng xử bên ngoài.
Nhƣ vậy refactoring có phải làm sạch đoạn chƣơng trình? Đúng nhƣ vậy, nhƣng
refactoring còn tiến xa hơn bởi cung cấp các kỹ thuật trong việc làm sạch đoạn chƣơng
trình hiệu quả hơn và kiểm soát hành vi. Trong quá trình refactoring, chúng ta cần chú ý
đến yếu tố làm sạch mã nguồn hiệu quả hơn và tối thiểu bug.
Nhƣ vậy theo định nghĩa, mục tiêu đầu tiên của refactoring là làm cho chƣơng
trình dễ đọc và khi cần thiết có thể cập nhật thì vẫn không làm thay đổi hoặc có nhƣng
không đáng kể đến các hành vi ứng xử bên ngoài của phần mềm. Nhƣ vậy có gì khác biệt
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
20
giữa refactoring và việc tối ƣu hiệu năng xử lý. Cũng giống nhƣ refactoring, tối ƣu hiệu
năng xử lý không làm thay đổi hành vi của các thành phần nghĩa là chỉ thay đổi cấu trúc
bên trong. Tuy nhiên mục tiêu chúng khác nhau. Tối ƣu vận hành thƣờng làm cho đoạn
chƣơng trình khó hiểu hơn, nhƣng chúng ta cần phải thực hiện nó để tăng tốc độ chúng ta
cần.
Mục tiêu kế tiếp mà chúng ta cần lƣu ý đó là refactoring không làm thay đổi ứng
xử bên ngoài của phần mềm. Phần mềm sẽ thực thi và xử lý các chức năng nhƣ trƣớc.
Bất kỳ ngƣời dùng nào, kể cả ngƣời dùng cuối hay ngƣời lập trình không thể cảm nhận
về những sự thay đổi này.
I.2 HIỆU QUẢ CỦA TÁI CẤU TRÚC MÃ NGUỒN
I.2.1 Refactoring cải thiện thiết kế phần mềm
Thiết kế chƣơng trình luôn tìm ẩn nhiều rủi ro và dễ bị hƣ tổn. Khi có sự thay đổi
chƣơng trình (thay đổi hiện thực hóa mục tiêu ngắn hạn hay sự thay đổi không đƣợc hiểu
thấu đáo thiết kế chƣơng trình) thì khả năng chƣơng trình bị mất cấu trúc là hoàn toàn có
thể xảy ra. Khi việc mất cấu trúc chƣơng trình sẽ có tác động làm cho ngƣời phát triển
khó nhìn thấy thiết kế chƣơng trình, càng khó bảo trì và nhanh chóng bị hƣ tổn.
Trong quá trình thiết kế phầm mềm, nếu chúng ta áp dụng refactoring sẽ là một
giải pháp hiệu quả vì refactoring sẽ làm gọn chƣơng trình. Công việc này đƣợc thực hiện
nhằm mục đích chuyển đổi những gì thực sự không đúng chỗ về đúng vị trí. Đoạn
chƣơng trình đƣợc thiết kế hợp lý thƣờng chiếm nhiều dòng mã nguồn và khả năng trùng
lặp mã nguồn. Nhƣ vậy khía cạnh quan trọng của cải tiến thiết kế là loại bỏ những mã
nguồn lặp. Điều quan trọng của vấn đề này nằm ở những thay đổi tƣơng lai đoạn mã
nguồn. Bằng việc loại bỏ sự trùng lắp, đảm bảo rằng đoạn chƣơng trình chỉ làm một lần
và chỉ một là điều thiết yếu của thiết kế tốt. Khi đó phần mềm sẽ trở nên dễ hiểu và đảm
bảo những hạn chế thấp nhất trong quá trình phát triển và cập nhật. Refactoring giúp
nâng cao khả năng tinh gọn và cải biến của chƣơng trình.
I.2.2 Refactoring làm mã nguồn phần mềm dễ hiểu
Lập trình là sự đối thoại và giao tiếp theo nhiều cách với máy tính. Chúng ta viết
mã lệnh để yêu cầu máy tính cần làm những gì và phản hồi chính xác những gì chúng ta
bảo nó thực hiện. Trong chu kỳ sống của phầm mềm sẽ có nhiều ngƣời cùng tham gia
vào việc phát triển và bảo trì. Khi đó yếu tố cần thiết ở đây là qui chuẩn về lập trình
(coding). Việc áp dụng refactoring (thông qua việc sửa đổi định danh, từ ngữ, cách đặt
tên cho các thành phần trong mã nguồn) giúp làm cho đoạn mã nguồn tuân theo qui
chuẩn để có khả năng đọc đƣợc và chƣơng trình dễ hiểu hơn. Khi chƣa refactoring, đoạn
mã nguồn của chúng ta có thể chạy nhƣng chƣa đƣợc cấu trúc hoàn chỉnh. Việc
refactoring tuy chiếm một ít thời gian nhƣng có thể làm cho đoạn mã nguồn có cấu trúc
rõ ràng và dễ hiểu.
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
21
Ngoài ra việc sử dụng refactoring thông qua các thủ thuật: sắp xếp lại trật tự các
dòng lệnh, các vòng lặp, các điều kiện, ràng buộc nhằm làm cho logic của mã nguồn tốt
hơn, số lƣợng dòng lệnh đƣợc cực tiểu hóa,….
I.2.3 Refactoring giúp phát hiện và hạn chế lỗi
Refactoring giúp hiểu đoạn mã nguồn từ đó giúp chúng ta trong việc phát hiện lỗi.
Trong quá trình tìm lỗi, việc các lập trình viên phải đọc hàng ngàn dòng mã nguồn để tìm
lỗi là điều thƣờng xuyên xảy ra. Lúc này refactoring sẽ giúp chúng ta hiểu sâu những
đoạn mã nguồn làm gì, từ đó có thể có đƣợc những suy luận và phán đoán về các khả
năng lỗi xảy ra và tìm đúng đến đoạn mã nguồn cần chỉnh sửa. Một khi làm rõ cấu trúc
chƣơng trình chính là chúng ta làm rõ những giả định mà chúng ta mong muốn chƣơng
trình thực hiện để tránh phát sinh lỗi.
Ngoài ra việc sắp đặt lại các logic luồng làm việc của mã nguồn giúp cho luồng xử
lý rõ ràng hơn và tránh các sai sót có khả năng xảy ra. Một lập trình viên nổi tiếng Kent
Beck từng khẳng định về tính hiệu quả của refactoring trong việc phát hiện và hạn chế
lỗi: „Tôi không là lập trình giỏi; tôi chỉ là lập trình tốt với thói quen .. Refacotring giúp
tôi trở nên hiệu quả trong viết chƣơng trình có độ chắc chắn”.
I.2.4 Refactoring giúp đấy nhanh quá trình phát triển phần mềm
Refactoring giúp đấy nhanh quá trình phát triển phần mềm thông qua các hiệu quả
mà nó mang lại:
- Tăng tính dùng lại: mã nguồn tốt, rõ ràng sẽ có lợi khi đƣợc sử dụng lại cho các
module khác của cùng ứng dụng hoặc đƣợc dùng nhƣ một bộ thƣ viện sử dụng
cho nhiều ứng dụng, module khác nhau.
- Tăng tính tiến hóa: một mã nguồn tốt có lợi ích và chu kỳ sống cụ thể do công
nghệ thông tin ngày càng phát triển. Mã nguồn tốt có thể có thời gian sử dụng lâu
hơn và khả năng tự phát triển, nâng cấp, kế thừa khi ứng dụng có nhu cầu phát
triển thêm mà không phải bị vứt bỏ để viết lại từ đầu.
- Tăng tính gần gũi với ngƣời dùng: có những ứng dụng hay nhƣng lại phức tạp
cho ngƣời sử dụng hay ngƣời đọc. Chẳng hạn nhƣ phần giao tiếp ngƣời dùng (user
interface) cần đƣợc cải thiện để tăng tính dễ dùng, dễ hiểu, linh hoạt hơn và làm
cho giao tiếp ngƣời dùng sử dụng đƣợc hết các khả năng của mã nguồn cung cấp.
Refactoring không hẳn làm thay đổi các cƣ xử, hoạt động bên ngài của phần mềm.
Chủ yếu là cải thiện phần cấu trúc bên trong nhằm làm tối ƣu chức năng của phần mềm
để phần mềm xử lý, nhanh hơn, tốt hơn, an toàn hơn và cố thể phù hợp với nhiều môi
trƣờng hoặc thay đổi mới cho ngƣời dùng trong quá trình sử dụng. Giảm thiểu những sai
sót và tăng thời gian sống cho phần mềm. Là một bƣớc không thể thiếu và có thể đƣợc áp
dụng trong suốt các quá trình phát triển phần mềm.
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
22
Ngày nay Refactoring chính là một chuẩn mực coding của mọi lập trình viên khi
làm việc theo nhóm, khi bắt đầu làm việc ở công ty lớn, các lập trình viên sẽ đƣợc huấn
luyện và đào tạo để tuân thủ các yêu cầu làm việc: nhƣ quy tắc đặt tên biến, khi viết mã
nguồn áp dụng partern nào, xây dựng unit test ra sao ...
I.3 KHI NÀO THỰC HIỆN TÁI CẤU TRÚC MÃ NGUỒN
Nhƣ trình bày ở trên, việc áp dụng kỹ thuật refactoring đem lại những hiệu quả cải
tiến trong qui trình phát triển phần mềm hiện đại. Vậy chúng ta sẽ thực hiện nó trong
những trƣờng hợp nào và tần suất nhƣ thế nào là hợp lý?
Một lời khuyên từ các nhà phát triển phần mềm hiện đại là trong tất cả các trƣờng
hợp, chúng ta đều phải dành thời gian cho việc refactoring. Refectoring là công việc mà
chúng ta phải thực hiện thƣờng xuyên trong chu kì phát triển và bảo dƣỡng phần mềm.
Chuyên gia Don Roberts đã gợi ý luật “tam trùng (the rule of three) [4]” trong việc xem
xét khi nào chúng ta nên refactoring:
- Lần đầu tiên chúng ta làm điều gì, chúng ta chỉ làm nó thôi.
- Lần thứ hai chúng ta làm điều gì tƣơng tự. Chúng ta có thể chấp nhập lặp lại.
- Nhƣng nếu lần thứ ba việc đó lặp lại, chúng ta refactor.
I.3.1 Refactor khi thêm chức năng
Thời điểm phổ biến nhất để refactor là khi chúng ta muốn thêm chức năng mới
vào phần mềm. Lý do đầu tiên thƣờng để refactor ở đây là giúp hiểu đoạn mã nguồn
chƣơng trình chúng ta cần thay đổi. Đoạn mã nguồn này có lẽ đƣợc viết bởi nhiều ngƣời
khác hay ngay cả khi chính ta viết nó lâu ngày thì chúng ta cũng cần phải suy nghĩ để
hiểu những gì đoạn mã nguồn đang làm. Một khi đã hiểu đƣợc những gì đoạn mã nguồn
sẽ làm, điều cần thiết lúc này là chúng ta refactor mã nguồn để làm sao quá trình đọc hiểu
mã nguồn đƣợc rõ ràng và nhanh hơn sau đó refactor nó. Sau khi refactor, các bƣớc xảy
ra tƣơng tự ở phần sau sẽ đƣợc bỏ qua vì chúng ta có thể hiểu nhiều và sáng tỏ đoạn mã
nguồn đã quen thuộc.
Một xu hƣớng khác để áp dụng refactoring ở đây là khi gặp một thiết kế mà không
thể giúp chúng ta thêm chức năng dễ dàng. Trong lập trình đôi lúc chúng ta gặp những
trƣờng hợp nhìn vào thiết kế và tự nói “ Nếu tôi thiết kế theo cách này, thì việc thêm
chức năng có thể sẽ dễ dàng?”. Trong trƣờng hợp này, không quá lo lắng về sai lầm đã
qua – chúng ta sẽ thực hiện bằng refactoring. Thực hiện điều đó trong chừng mực để cải
thiện thời gian sắp tới dễ hơn. Sau khi refactor, quá trình thêm chức năng sẽ tiến triển
nhanh và trôi chảy hơn.
I.3.2 Refactor khi cần sửa lỗi
Điều trƣớc tiên khi chỉnh sửa lỗi là ta phải hiểu rõ về bản chất thực thi của đoạn
mã cần chỉnh sửa. Để làm đƣợc việc đó, chúng ta cần phải làm cho đoạn mã dễ đọc và
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
23
hiểu hơn. Nhƣ vậy trong quá trình sửa lỗi, chúng ta nhìn và phân tích đoạn mã nguồn để
hiểu và refactor để cải thiện sự hiểu biết của mình. Thông thƣờng chúng ta dự đoán qui
trình hoạt động của đoạn mã hiện tại để tìm lỗi. Tuy nhiên không phải lúc nào chúng ta
cũng có thể phát hiện ra lỗi từ mã nguồn vì nó không rõ đủ cho chúng ta nhìn ra có lỗi.
Một cách để nhìn vào vấn đề này đó là nếu chúng ta nhận đƣợc một báo cáo lỗi phát sinh,
đó là dấu hiệu chúng ta cần refactoring.
I.3.3 Refactor khi thực hiện duyệt chƣơng trình
Ngoài ra chúng ta còn sử dụng refactoring trong quá trình duyệt mã nguồn đƣợc
viết bởi ngƣời khác.Trong quá trình duyệt chƣơng trình, ngoài việc đọc hiểu và kiểm tra
tính chính xác của đoạn mã cũng nhƣ đề xuất các đề nghị. Khi đƣa ra ý kiến hoặc đề nghị
chúng ta xem xét hoặc chúng có thể thực hiện đƣợc dễ dàng hoặc phải refactoring. Nếu
vậy, tiến hành refactor. Khi thực hiện vài lần, chúng ta có thể nhìn thấy rõ ràng hơn
những gì đoạn mã nguồn trông giống khi thay thế. Kết quả là chúng ta có thể đạt đến ý
tƣởng ở mức hai đó là bạn không bao giờ nhận ra bạn không refactor.
I.4 CÁC KỸ THUẬT TÁI CẤU TRÚC MÃ NGUỒN
Refactoring là một trong những phƣơng pháp nhằm nâng cao chất lƣợng phần
mềm đã bắt đầu đƣợc nghiên cứu và ứng dụng những năm 90 trong qui trình phát triển
phần mềm. Qua quá trình nghiên cứu và phát triển, một tập các kỹ thuật refactoring đã
đƣợc đặc tả chi tiết và phần lớn các kỹ thuật refactoring trên đã và đang dần đƣợc tích
hợp vào trong các công cụ phát triển phần mềm nhằm hỗ trợ cho các nhà phát triển trong
việc rút ngắn thời gian tạo nên các phần mềm có chất lƣợng cao và ổn định, đáp ứng tốt
các yêu cầu hoạt động của hiện tại và những thay đổi cần thiết trong tƣơng lai.
I.4.1 Danh mục các kỹ thuật tái cấu trúc mã nguồn
Dƣới đây là danh mục các kỹ thuật tái cấu trúc mã nguồn đƣợc liệt kê theo nhóm:
STT Kỹ thuật Refactoring Diễn giải/Mục đích sử dụng
Composing Methods – Định nghĩa phương thức
1 Extract Method
Định nghĩa phương thức mới dựa trên trích
chọn tập đoạn mã nguồn
2 Inline Method Hợp nhất các phương thức lại với nhau
3 Inline Temp
Thay thế các tham chiếu của một biến tạm
bằng biểu thức
4 Replace Temp with Query
Chuyển đổi biểu thức thành phương thức. Thay
thế các tham chiếu của biến tạm nhận được từ
biểu thức bị chuyển đổi bởi phương thức vừa
tạo mới.
5 Introduce Explaining Variable
Đặt/thay thế kết quả của biểu thức hoặc một
phần của biểu thức vào biến tạm cùng tên.
6 Split Temporary Variable
Tạo mới các biến tạm riêng biệt tương ứng với
từng đoạn mã
7 Remove Assignments to Parameter
Sử dụng biến tạm thay thế cho việc gán giá trị
mới cho tham số.
8 Replace Method with Method Object Chuyển đổi phương thức thành đối tượng sở
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
24
hữu. Khi đó tất cả các biến cục bộ trở thành
các thuộc tính của đối tượng
9 Substitute Algorithm Thay thế thuật toán trong thân phương thức
Moving Features Between Objects – Dịch chuyển chức năng giữa các đối tượng
10 Move Method Dịch chuyển phương thức giữa các lớp
11 Move Field Hoán đổi/dịch chuyển thuộc tính giữa các lớp
12 Extract Class
Tạo một lớp mới và dịch chuyển các thuộc tính
và phương thức có liên quan từ lớp cũ sang
13 Inline Class
Hợp nhất các lớp riêng có quan hệ thành một
lớp chung
14 Hide Delegate
Tạo các phương thức trung gian truy cập gián
tiếp đến lớp khác
15 Remove Middle Man
Tường minh các phương thức truy cập ở mỗi
lớp (ngược với Hide Delegate)
16 Introduce Foreign Method
Tạo ra một phương thức trong một lớp client
có tham số là một đại diện của một lớp server.
17 Introduce Local Extension
Tạo một lớp mới chứa các phương thức mở
rộng đi kèm với một lớp con hoặc một lớp bao
Organizing Data – Tổ chức dữ liệu
18 Self Encapsulate Field
Tạo ra các phương thức truy xuất và thiết lập
giá trị các thuộc tính và sử dụng chúng thay vì
truy xuất trực tiếp.
19 Replace Data Value with Object Thay thế giá trị dữ liệu bằng đối tượng
20 Change Value to Reference Chuyển đổi giá trị thành tham chiếu
21 Change Reference to Value Chuyển đổi tham chiếu thành giá trị
22 Replace Array with Object
Thay thế mảng bởi đối tượng với phần tử của
mảng tương ứng với thuộc tính của đối tượng
23 Duplicate Observed Data Đa hình dữ liệu
24
Change Unidirectional Association
to Bidirectional
Chuyển đổi từ liên kết đơn ánh sang xuất song
ánh trong quan hệ giữa 2 lớp
25
Change Bidirectional Association to
Unidirectional
Chuyển đổi từ liên kết song ánh sang xuất
đơn ánh trong quan hệ giữa 2 lớp
26
Replace Magic Number with
Symbolic Constant
Sử dụng biến hằng thay cho giá trị số tường
minh
27 Encapsulate Field Chuyển đổi thuộc tính chung thành riêng
28 Encapsulate Collection Thêm mớI hoặc xóa bỏ phương thức
29 Replace Record with Data Class Thay thế kiểu dữ liệu bảng ghi bởi lớp dữ liệu
30 Replace Type Code with Class
Thay thế mã kiểu số đếm hoặc liệt kê bới một
lớp mớI
31 Replace Type Code with Subclasses Thay thế mã kiểu dữ liệu thành lớp con
32
Replace Type Code with
State/Strategy
Thay thế mã kiểu dữ liệu thành một đối tượng
tĩnh
33 Replace Subclass with Fields
Biến đổi các phương thức ở các thuộc tính siêu
lớp và rút gọn các lớp con
Simplifying Conditional Expressions – Đơn giản hóa các biểu thức điều kiện
34 Decompose Conditional
Tạo mới các phương thức từ mệnh đề và thân
xứ lý của câu lệnh điều kiện.
35 Consolidate Conditional Expression
Tạo mới một phương thức từ việc hợp nhất các
biểu thức điều kiện
36
Consolidate Duplicate Conditional
Fragments
Hợp nhất và di chuyển các phân đoạn trùng
lặp ra bên ngoài thân câu lệnh điều kiện
37 Remove Control Flag
Sử dụng câu lệnh break hoặc return thay thế
cho biến cờ hiệu trong các thân vòng lặp
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: Nhiêu Lập Hòa
25
38
Replace Nested Conditional with
Guard Clauses
Thay thế các điều kiện rẽ nhánh bởi các mệnh
đề so khớp
39
Replace Conditional with
Polymorphism
Thay thế phép so sánh điều kiện bởi tính chất
đa hình của phương thức
40 Introduce Null Object Thay thế giá trị NULL bởi một đối tượng NULL
41 Introduce Assertion
Xác nhận tính hợp lệ của dữ liệu trước khi xử
lý
Making Method Calls Simpler – Đơn giản hóa việc gọi phương thức
42 Rename Method Đổi tên phương thức
43 Add Parameter Bổ sung tham số cho phương thức
44 Remove Parameter Xóa bỏ tham số của phương thức
45 Separate Query from Modifier
Tách thành hai phương thức riêng biệt khi vừa
truy xuất và cập nhật trên phương thức gốc.
46 Parameterize Method
Tạo mới một phương thức và sử dụng tham số
cho các giá trị khác nhau
47
Replace Parameter with Explicit
Methods
Tạo mới một phương thức cho mỗi giá trị của
tham số
48 Preserve Whole Object
Chuyển nguyên một đối tượng thay vì các giá
trị riêng lẻ
49 Replace Parameter with Method Rút gọn tham số trong phương thực
50 Introduce Parameter Object
Thay thế một nhóm các tham số đồng dạng
bởi một đối tượng
51 Remove Setting Method Xóa bỏ phương thức dư thừa đã được thiết lập
52 Hide Method Chuyển thành phương thức riêng
53
Replace Constructor with Factory
Method
Thay thế hàm dựng bởi phương thức sản xuất
54 Encapsulate Downcast Định dạng kiểu trả về trong thân phương thức
55 Replace Error Code with Exception
Tạo mới một phương thức từ một phân đoạn
mã để kiểm soát các trường hợp (lỗi) ngoại lệ
56 Replace Exception with Test
Kiểm tra các trường hợp (lỗi) ngoại lệ có khả
năng phát sinh
Dealing with Generalization – Liên hệ tổng quát hóa
57 Pull Up Field Dịch chuyển thuộc tính lên lớp cha
58 Pull Up Method Dịch chuyển phương thức lên lớp cha
59 Pull Up Constructor Body
Tạo một hàm dựng chung ở lớp cha, và gọi lại
nó từ các phương thức ở lớp con.
60 Push Down Method Dịch chuyển phương thức về lớp con
61 Push Down Field Dịch chuyển thuộc tính về lớp con
62 Extract Subclass
Trích xuất/chia tách thành các lớp con với tập
các chức năng riêng
63 Extract Superclass
Định nghĩa mới một lớp cha và di chuyển các
chức năng chung đến lớp này từ các lớp con
64 Extract Interface
Định nghĩa một giao diện từ việc trích chọn
một tập con các thành viên trong lớp
65 Collapse Hierarchy
Hợp nhất thành một lớp nếu 2 lớp cha và con
(quan hệ kế thừa) có ít sự khác biệt.
66 Form Template Method
Đồng nhất các phương thức ở lớp con và dịch
chuyển lên lớp cha
67 Replace Inheritance with Delegation
Hoán chuyển từ tính kế thừa sang tính ủy
quyền giữa hai lớp
68 Replace Delegation with Inheritance
Hoán chuyển từ tính tính ủy quyền sang tính
kế thừa giữa hai lớp
Other Refactorings – Một số kỹ thuật khác