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á
99 trang |
Chia sẻ: huyen82 | Lượt xem: 2244 | Lượt tải: 0
Tóm tắt tài liệu Ứ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#, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
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 l = new List();
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 l = new List();
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 l = new List();
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 l = new List();
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 l = new List();
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 l = new List();
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 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 l = new List();
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(GetRemainNumbers()).ToArray();
}
private IEnumerable 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;
publ._.ic 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 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(GetRemainNumbers()).ToArray();
}
private IEnumerable 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
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 26
69 Tease Apart Inheritance Giảm cấp trong quan hệ kế thừa
70
Convert Procedural Design to
Objects
Chuyền đổi hướng thủ tục sang hướng đối
tượng
71 Separate Domain from Presentation Đối tượng hóa các thể hiện
72 Extract Hierarchy
Phát sinh hoặc thay đổi thứ bậc của các lớp
trong quna hệ kế thừa.
73 Reorder Parameters Thay đổi vị trí các tham số trong phương thức
74
Promote Local Variable to
Parameter
Chuyển biến cục bộ thành tham số của
phương thức
75 Convert Abstract Class to Interface Chuyển đổi lớp ảo thành giao diện
76 Convert Interface to Abstract Class Chuyển đổi giao diện thành lớp ảo
77 Convert Method to Property
Chuyển đổi phương thức thành đặc tính của
lớp
78 Convert Property to Method
Chuyển đổi đặc tính của lớp thành phương
thức
79 Rename parameter Đổi tên tham số
80 Rename Local Variable Đổi tên biến cục bộ
81 Remove Redundant Conditional Xóa bỏ điều kiện dư thừa
82 Rename Type Đổi tên các thành phần trong lớp
83 Extract Variable
Sử dụng biến được khởi gán trong một biểu
thức được chọn
I.5 NHẬN XÉT VÀ KẾT LUẬN
Với những nội dung đã đƣợc trình bày ở trên về cơ sở lý thuyết của kỹ thuật tái
cấu trúc mã nguồn (refactoring), đó là một kỹ thuật làm thay đổi cấu trúc nội tại phần
mềm, làm cho 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.
Hiện tại kỹ thuật mới này đang đƣợc áp dụng và triển khai ở các quốc gia có nền
công nghiệp phần mềm phát triển (Mỹ, Nhật, Ấn Độ,..) và đang tiến đến một tiêu chuẩn
trong qui trình phát triển phần mềm trong tƣờng lai gần. Trong một bài báo về tƣơng lai
của ngành công nghệ phần mềm, một chuyên gia trong lãnh vực quản lý và tƣ vấn các
chiến lƣợc phần mềm Alex Iskold đã đƣa ra một nhận định rằng nền công nghệ phần
mềm trong tƣơng lai gần sẽ phát triển theo phƣơng pháp phần mềm phát triển linh hoạt
thay cho phƣơng pháp mô hình thác nƣớc đã tồn tại [15]. Phƣơng pháp phát triển phần
mềm linh hoạt (Agile Development Method) ngoài việc đáp ứng khả năng tạo ra các phần
mềm có sự ổn định cao còn có khả năng thích nghi và tiến hóa để thích hợp với môi
trƣờng hoạt động. Phƣơng pháp này dựa trên hai kỹ thuật chính đó là:
- Refactoring - Tái cấu trúc mã nguồn
- Developer Testing – Hoạt động kiểm thử do chính lập trình viên đảm nhận
Nhƣ vậy vấn đề nghiên cứu và ứng dụng kỹ thuật refactoring là một xu hƣớng tất
yếu và cần thiết trong lãnh vực phát triển công nghệ phần mềm ngày nay.
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 27
CHƢƠNG II: LỖI CẤU TRÚC TRONG MÃ NGUỒN
(BAD SMELLS IN CODE)
II.1 KHÁI NIỆM VỀ LỖI CẤU TRÚC (BAD SMELLS)
Trong khoa học máy tính, mã xấu hay lỗi cấu trúc (bad smells) là tất cả những dấu
hiệu tồn tại trong mã nguồn của chƣơng trình mà nó tiềm ẩn khả năng xảy ra lỗi trong
quá trình hoạt động. Các dấu hiệu đó có thể là: chƣơng trình đƣợc thiết kế không logic,
các phân đoạn mã nguồn có cấu trúc không đồng nhất và khó hiểu, mã nguồn trùng lắp,
tên hàm và biến khó kiểm soát, lớp và phƣơng thức phức tạp,..v.v..
Thông thƣờng các dấu hiệu này sẽ đƣợc các nhà phát triển và lập trình phát hiện
và tinh chỉnh qua các bƣớc trong qui trình phát triển phần mềm dựa trên việc refactoring
mã nguồn. Nhƣ vậy có thể xem mã xấu là điều kiện để thực thi việc refactoring mã nguồn
của một chƣơng trình hay nói đúng hơn đó là cặp song hành: nếu một mã nguồn chƣơng
trình có bad smells thì refactoring để làm cho chƣơng trình tốt hơn.
II.2 LỖI CẤU TRÚC VÀ GIẢI PHÁP CẢI TIẾN
Dựa trên kinh nghiệm nhiều năm lập trình và nghiên cứu về refactoring, hai
chuyên gia Kent Beck và Marting Fowler đã đề xuất một tập các mã xấu thƣờng gặp[4]
và giải pháp cải tiến dựa trên kỹ thuật refactoring
II.2.1 Duplicated Code - Trùng lặp mã
Nếu trong mã nguồn tồn tại những đoạn mã trùng lặp ở nhiều nơi:
- Sử dụng Extract Method để làm triệt tiêu các đoạn mã trùng lặp bên trong một lớp
- Khi hai lớp đồng kế thừa từ một lớp cha (sibling classes) có các mã nguồn trùng
lặp, áp dụng Extract Method trong cả hai lớp này sau đó dùng Pull Up Method đến
lớp cha
- Nếu tồn tại những đoạn mã tƣơng tự nhau thì sử dụng Extract Method trên những
phần tƣơng tự nhau này. Và sau đó có thể áp dụng tiếp kỹ thuật Form Template
Method.
- Nếu có các phƣơng thức cùng thực hiện một công việc với các thuật toán khác
nhau, thì chọn ra một thuật toán tốt nhất và áp dụng Substitute Algorithm
- Nếu hai lớp không có quan hệ với nhau mà có các đoạn mã trùng lặp, sử dụng
Extract Class trên một lớp và sau đó dùng thành phần lớp mới đƣợc tạo ra cho lớp
còn lại.
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 28
Ví dụ 1: Hai lớp con Salesman và Engneer đồng kế thừa từ lớp cha Employee có phƣơng
thức getName bị trùng lặp => Sử dụng Pull Up Method
Ví dụ 2: Sử dụng Substitute Algorithm để thay mới thuật tóan trong thân phƣơng thức
làm cho chƣơng trình ngắn gọn và dễ hiểu hơn.
String foundPerson(String[] people){
for (int i = 0; i < people.length; i++) {
if (people[i].equals ("Don")){
return "Don";
}
if (people[i].equals ("John")){
return "John";
}
if (people[i].equals ("Kent")){
return "Kent";
}
}
return "";
}
String foundPerson(String[] people){
List candidates = Arrays.asList(new String[] {"Don", "John", "Kent"});
for (int i=0; i<people.length; i++)
if (candidates.contains(people[i]))
return people[i];
return "";
}
II.2.2 Long Method – Phƣơng thức phức tạp
Một trong những yêu cầu đối với một chƣơng trình nguồn là hạn chế các phƣơng
thức đƣợc tổ chức với số lƣợng dòng mã quá nhiều. Điều này sẽ gây khó khăn cho việc
kiểm soát và đọc hiểu mã lệnh trong quá trình cập nhật. Khi đó giải pháp ở đây là chia
tách để làm cho các phƣơng thức nhỏ và tinh gọn hơ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 29
- Khi gặp các phƣơng thức có nhiều dòng mã, 99% là chúng ta sử dụng kỹ thuật
Extract Method để làm ngắn các phƣơng thức này bằng cách tạo ra một số phƣơng
thức mới từ việc trích chọn một số đoạn mã từ phƣơng thức ban đầu.
- Nếu một phƣơng thức đi kèm với nhiều tham số và biến tạm, sử dụng Replace
Temp with Query để triệt tiêu các biến tạm này. Với danh sách dài của các tham
số, chúng ta sử dụng Introduce Parameter Object hoặc Preserve Whole Object để
làm ít chúng lại. Nếu nhƣ lúc này vẫn còn nhiều biến tạm và tham số, một giải
pháp triệt để hơn là dùng Replace Method with Method Object
- Khi trích xuất một khối mã lệnh ra làm phƣơng thức riêng, một giải pháp hiệu quả
nhất trong việc xác định tên của phƣơng thức mới tạo này sao cho phù hợp là dựa
vào thông tin chú giải về mục đích của khối mã lệnh đó.
- Khi gặp các điều kiện và vòng lặp, chúng ta nên sử dụng Decompose Conditional
để tách và tạo ra các phƣơng thức riêng
Ví dụ 1: Sử dụng kỹ thuật Extract Method để làm ngắn phƣơng thức bằng cách tạo ra
một phƣơng thức mới từ việc trích chọn một số đoạn mã từ phƣơng thức ban đầu.
class Program
{
static void Main(string[] args){
Console.WriteLine("*** Please enter your credentials ***");
// Get user name and password.
string userName;
string passWord;
Console.Write("Enter User Name: ");
userName = Console.ReadLine();
Console.Write("Enter Password: ");
passWord = Console.ReadLine();
}
}
class Program
{
static void Main(string[] args){
Console.WriteLine("*** Please enter your name ***");
GetCredentials();
Console.ReadLine();
}
private static void GetCredentials(){
string userName;
string passWord;
Console.Write("Enter User Name: ");
userName = Console.ReadLine();
Console.Write("Enter Password: ");
passWord = Console.ReadLine();
}
}
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 30
Ví dụ 2: Sử dụng 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.
double basePrice = _quantity * _itemPrice;
if (basePrice > 1000)
return basePrice * 0.95;
else
return basePrice * 0.98;
if (basePrice() > 1000)
return basePrice() * 0.95;
else
return basePrice() * 0.98;
...
double basePrice() {
return _quantity * _itemPrice;
}
II.2.3 Large Class – Qui mô lớp lớn
- Một lớp có qui mô lớn thƣờng chứa nhiều biến số và tồn tại trùng lặp mã nguồn.
Sử dụng Extract Class để đóng gói các biến thƣờng đi chung với nhau lại. Sử
dụng Extract Subclass khi các phƣơng thức kết hợp với các biến mở rộng chức
năng của lớp. Thông thƣờng không phải lúc nào tất cả các biến trong một lớp cũng
luôn đƣợc sử dụng , vì vậy chúng ta cũng có thể sử dụng Extract Class hoặc
Extract Subclass để trích xuất chúng nhiều lần.
- Một thủ thuật hữu ích nữa là xem xét mục đích sử dụng của các lớp kết hợp với kỹ
thuật Extract Interface để tổ chức và phân chia lớp cho hợp lý.
- Trong lập trình hƣớng đối tƣợng, khi gặp một lớp GUI có qui mô lớn cần chuyển
dữ liệu và hoạt động xử lý đến một đối tƣợng có phạm vi riêng. Điều này yêu cầu
có sự trùng lắp dữ liệu ở hai nơi cũng nhƣ sự đồng nhất trong quá trình động bộ.
Lúc này Duplicate Observed Data trong refactoring sẽ đƣợc xem xét và sử dụng.
Ví dụ: Một lớp mà có một số chức năng chỉ đƣợc sử dụng cho một vài thực thể
(instances) cá biệt thì nên sử dụng Extract Subclass để tạo một lớp con đi kèm các chức
năng đó
Luận văn tốt nghiệp cao học – Khóa 2005 - 2008
Học viên thực hiện: ._.Hòa 80
IV.1.4 Nhận xét và đánh giá
Qua quá trình thử nghiệm và đánh giá việc dò tìm và cải tiến mã xấu trong mã
nguồn C# , ta có đƣợc bảng tóm lƣợc một số chức năng refactor trong các giải pháp/công
cụ đƣợc tích hợp vào Visual Studio .NET nhƣ sau
STT Kỹ thuật Refactoring
R
e
fa
c
to
r
in
V
S
2
0
0
5
V
is
u
a
l
A
s
s
it
X
C
#
R
e
fa
c
to
ry
.N
E
T
R
e
fa
c
to
r
C
o
d
e
IT
.O
n
c
e
R
e
S
h
a
rp
e
r
R
e
fa
c
to
r!
™
P
ro
1 Add Member X
2 Add to Interface X
3 Change Signature X X X
4 Combine Conditionals X
5 Convert Abstract Class to Interface X
6 Convert Anonymous to Named Type X
7 Convert EXtension Method to Plain Static X
8 Convert Interface to Abstract Class X
9 Convert Method to Property X X X
10 Convert Property to Method X X X
11 Convert Static to EXtension Method X
12 Decompose/Simplify Conditional X X X X
13 Decompose Parameter X
14 Encapsulate Field X X X X X X
15 Extract Class X X
16 Extract Interface X X X X X X
17 Extract Method X X X X X X X
18 Extract Object X
19 Extract Property X X
20 Extract Superclass X X X
21 Extract Variable X
22 Generate Method Stub X
23 Inline Method X
24 Inline Variable X X
25 Insert Cont or String X
26 Introduce Constant X X
27 Introduce Variable X X
28 Move Class X
29 Promote Local Variable to Parameter X X X X X
30 Pull Down Members X
31 Pull Up Members X X
32 Simplify Expression X
33 Remove Redundant Assignment X
34 Remove Redundant Conditional X
35 Rename Variable/Method/Parameter/Type… X X X X X X
36 Reorder Parameters/Variables X X X X
37 Replace Constructor with Factory Method X
38 Replace Magic Number X
39 Replace Temp with Query X
40 Reverse Conditional X
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 81
Dựa vào bảng dữ liệu trên, ta thấy rằng hầu hết các kỹ thuật refactoring cơ bản đã
đƣợc các hãng thứ ba phát triển và tích hợp trong các công cụ hỗ trợ lập trình và phát
triển phần mềm. Việc triển khai đƣợc áp dụng trên nhiều công cụ khác nhau trên cùng
một kỹ thuật xác định nhằm phân tích và đánh giá kết quả. Về tổng quan các chức năng
đều hoạt động chính xác trên cơ sở lý thuyết kỹ thuật refactoring và có cùng chung kết
quả, tuy nhiên vẫn có sự khác nhau về tính hiệu quả của từng công cụ tiện ích:
- Số lƣợng các kỹ thuật refactoring mà công cụ hỗ trợ: CodeIT.Once, JetBrains
ReSharper, Refactor!™ Pro hỗ trợ nhiều kỹ thuật hơn các công cụ còn lại
- Hỗ trợ tính năng tùy biến trong quá trình thực hiện: Cùng thực hiện chức
năng Promote Local Variable to Parameter nhƣng CodeIT.Once cho phép tùy biến
đổi tên tham số khi thực hiện còn Refactor in VS.NET thì không (lấy tên biến làm
tên tham số)
- Mã nguồn ngôn ngữ có khả năng hỗ trợ: Refactor in VS.NET và Refactor!™
Pro hỗ trợ tất cả các ngôn ngữ thuộc họ .NET trong khi các công cụ còn lại chủ
yếu chỉ là C# và VB.NET
- Khả năng nhận biết mã xấu và tính tiện dụng: Khi đánh dấu chọn và áp dung
refactor trên một biến trong thân chƣơng trình. Refactor in VS.NET hiển thị tất cả
các chức năng hỗ trợ bao gồm cả Extract Interface (trên thực tế kỹ thuật này
không hỗ trợ trong trƣờng hợp này và khi chọn thì hệ thống báo không hợp lệ),
trong khi các công cụ khác CodeIT.Once, JetBrains ReSharper và Refactor!™ Pro
có thể tự động nhận biết và chỉ kích hoạt cho phép chọn các chức năng hợp lệ
(chức năng Extract Interface đã bị ẩn đi).
Nhƣ vậy qua kết quả nghiên cứu và ứng dụng của đề tài này, chúng ta cũng đƣa ra
một đề xuất dành cho các lập trình viên và nhà phát triển trong việc xác định và chọn lựa
một công cụ refactoring có chất lƣợng tốt và tiện dụng trong quá trình phát triển phần
mềm: CodeIT.Once, JetBrains ReSharper hoặc Refactor!™ Pro.
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#
Trên cơ sở lý thuyết của kỹ thuật tái cấu trúc mã nguồn và dấu hiệu mã xấu đã
đƣợc trình bày, nội dung phần này chúng ta sẽ áp dụng một trong những công cụ hỗ trợ
rafactor đƣợc đề xuất (CodeIT.Once) để tiến hành triển khai việc dò tìm và cải tiến các
đoạn mã xấu trên một chƣơng trình C# thực 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 82
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ế
♦ Yêu cầu bài tóan: Lập trình một chƣơng trình truy xuất và cập nhật dự liệu thông tin
thời tiết của các đài khí tƣợng. Thông tin dữ liệu thời thiết bao gồm: vị trí khu vực, nhiệt
độ và độ ẩm không khí tại khu vực đó. Các đài khác nhau có thể sử dụng các đơn vị đo
khác nhau(ví dụ với nhiệt độ thì Hà Nội sử dụng độ C còn NewWork dùng độ F)
♦ Cấu trúc và mã nguồn chƣơng trình ban đầu
Chƣơng trình đƣợc thiết kế và cấu trúc mã nguồn ban đầu nhƣ sau
WeatherData
_temp: int
_humidity: int
_location: int
_stations: event
WeatherStations()
Get_Temp()
Set_Temp ()
Get_Humidity()
Set_Humidity
Get_Location()
HanoiWeat erStation
_weather:WeatherData
showWe t er()
NewYorkWeatherStation
_weather: WeatherData
showWeather()
1 1 1 1
Hình 4.33: Sơ đồ lớp của chƣơng trình khi chƣa refactoring
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
using System;
using System.Collections.Generic;
using System.Text;
namespace RefactoringExample
{
public delegate void WeatherUpdated();
class WeatherData
{
public int _temp;
public int _humidity;
public string _location;
protected event WeatherUpdated _stations;
public WeatherUpdated WeatherStations
{
get { return _stations; }
set { _stations = value; }
}
public int Get_Temp()
{
return _temp;
}
public void Set_Temp (int temperature)
{
_temp = temperature;
foreach (WeatherUpdated s in _stations.GetInvocationList()) s();
}
public int Get_Humidity()
{
return _humidity;
}
public void Set_Humidity(int weather_humidity)
{
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 83
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
_humidity = weather_humidity;
foreach (WeatherUpdated s in _stations.GetInvocationList()) s();
}
public string Get_Location()
{
return _location;
}
public WeatherData(string location, int temp,int humility)
{
_location = location;
_temp = temp;
_humidity = humility;
}
}
class HanoiWeatherStation
{
protected WeatherData _weather;
public HanoiWeatherStation(WeatherData weather)
{
_weather = weather;
_weather.WeatherStations += new WeatherUpdated(showWeather);
}
protected void showWeather()
{
System.Console.WriteLine("Ha Noi Weather Station:");
System.Console.WriteLine("The weather at " + _weather.Get_Location());
System.Console.Write("Temperature: " + (_weather.Get_Temp() - 273) + "C, ");
System.Console.WriteLine("Humidity: " + _weather.Get_Humidity() + "%\n");
}
}
class NewYorkWeatherStation
{
protected WeatherData _weather;
public NewYorkWeatherStation(WeatherData weather)
{
_weather = weather;
_weather.WeatherStations += new WeatherUpdated(showWeather);
}
protected void showWeather()
{
System.Console.WriteLine("New York Weather Station:");
System.Console.WriteLine("The weather at " + _weather. Get_Location());
System.Console.Write("Temperature:" + (((_weather. Get_Temp() - 273)
* 9 / 5) + 32) + "F, ");
System.Console.WriteLine("Humidity: " + _weather. Get_Humidity() + "%");
int outdoor_temperature = _weather. Get_Temp() - 273;
int outdoor_humidity = _weather. Get_Humidity();
if (outdoor_temperature 50)
System.Console.WriteLine("It may start to snow!");
System.Console.WriteLine("\n");
}
}
public class MainEntry
{
public static void Main()
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 84
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
{
WeatherData weather = new WeatherData("Sa Pa", 283, 40);
HanoiWeatherStation hanoi = new HanoiWeatherStation(weather);
NewYorkWeatherStation newYork = new NewYorkWeatherStation(weather);
System.Console.WriteLine("WEATHER FORCAST INFORMATION \n");
weather.Set_Temp(289);
weather.Set_Humidity(60);
weather.Set_Temp(273);
System.Console.Read();
}
}
}
Và khi chạy chƣơnng trình cho ra kết quả dƣới đây:
Hình 4.34: Màn hình kết quả chạy chƣơng trình khi chƣa refactoring
♦ Tiến hành tái cấu trúc mã nguồn
Đầu tiên xem sơ đồ lớp, ta thấy 2 lớp HanoiWeatherStation và NewYorkWeatherStation
có chung thuộc tính (_weather) và phƣơng thức (showWeather). Trong trƣờng hợp này
chúng ta nên sử dụng kỹ thuật Extract Hierarchy đề tái cấu trúc sơ đồ lớp này
WeatherData
......
.......
HanoiWeatherStation
showWeather()
NewYorkWeatherStation
showWeather()
1
WeatherStation
_weather: WeatherData
showWeather()
1
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 85
namespace RefactoringExample
{
……
class WeatherStation
{
protected WeatherData _weather;
protected void showWeather() { }
}
class HanoiWeatherStation : WeatherStation
{
public HanoiWeatherStation(WeatherData weather)
{
…………
}
public void showWeather()
{
………………
}
}
class NewYorkWeatherStation : WeatherStation
{
public NewYorkWeatherStation(WeatherData weather)
{
………………
}
public void showWeather()
{
…………
}
}
………
}
Bƣớc tiếp theo ta thấy rằng nên đổi tên lớp WeatherData thành WeatherSensor
cho hợp lý và chính xác hơn. Sử dụng kỹ thuật Rename Class trong tiện ích
CodeIT.Once để thực hiện phép thay đổi này.
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 86
namespace RefactoringExample
{
……
class WeatherSensor
{
………..
}
class HanoiWeatherStation : WeatherStation
{
public HanoiWeatherStation(WeatherSersor weather)
{
…………
}
public void showWeather()
{
………………
}
}
class NewYorkWeatherStation : WeatherStation
{
public NewYorkWeatherStation(WeatherSensor weather)
{
………………
}
public void showWeather()
{
…………
}
}
public class MainEntry
{
public static void Main()
{
WeatherSensor weather = new WeatherSensor("Sa Pa", 283, 40);
………..
}
}
}
Trong quá trình xem lại sự thay đổi của mã nguồn chƣơng trình sau khi đổi tên
lớp WeatherData, ta phát hiện ra rằng việc sử dung tên trƣờng dữ liệu _temp để lƣu dữ
liệu về nhiệt độ là không chính xác và dễ nhầm lẫn -> dùng kỹ thuật Rename Field để
đổi tên trƣờng dữ liệu _temp thành _temparature
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 87
class WeatherSensor
{
public int _temperature;
public int _humidity;
………
}
Nhƣ vậy trong nhóm kỹ thuật Rename, ta đã tiến hành đổi tên lớp và trƣờng dữ
liệu. Ta thấy rằng nội dung của phƣơng thức showWeather là hiển thị thông tin dữ liệu
thời tiết của một khu vực nên đổi tên phƣơng thức này ShowWeatherInfo
namespace RefactoringExample
{
………
class WeatherStation
{
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 88
…….
protected void ShowWeatherInfo() { }
}
class HanoiWeatherStation : WeatherStation
{
public HanoiWeatherStation(WeatherSensor weather)
{
………
_weather.WeatherStations += new WeatherUpdated(ShowWeatherInfo);
}
protected void ShowWeatherInfo()
{
……….
}
}
class NewYorkWeatherStation : WeatherStation
{
public NewYorkWeatherStation(WeatherSensor weather)
{
……….
_weather.WeatherStations += new WeatherUpdated(ShowWeatherInfo);
}
protected void ShowWeatherInfo()
{
……….
}
}
}
Các trƣờng dữ liệu _temperature, _humidity, _location trong lớp WeartherSensor
đƣợc khai báo dƣới dạng public -> tiến hành đóng gói các trƣờng dữ liệu này thông qua
kỹ thuật Encapsulate Field. Đồng thời xóa bỏ các phƣơng thức getting và setting trên
các trƣờng dữ liệu này.
namespace RefactoringExample
{
…….
class WeatherSensor
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 89
{
private int _temperature;
public int KelvinDegrees
{
get { return _temperature; }
set
{ _temperature = value;
foreach (WeatherUpdated s in _stations.GetInvocationList()) s();
}
}
private int _humidity;
public int Humidity
{
get { return _humidity; }
set
{
_humidity = value;
foreach (WeatherUpdated s in _stations.GetInvocationList()) s();
}
}
private string _location;
public string Location
{
get { return _location; }
}
………..
}
……..
class HanoiWeatherStation : WeatherStation
{
………
protected void ShowWeatherInfo()
{
………
System.Console.Write("Temperature: " + (_weather.KelvinDegrees - 273) +
"C, ");
System.Console.WriteLine("Humidity: " + _weather.Humidity + "%\n");
}
}
class NewYorkWeatherStation : WeatherStation
{
………..
protected void ShowWeatherInfo()
{
…………
System.Console.Write("Temperature:" + (((_weather.KelvinDegrees - 273)
* 9 / 5) + 32) + "F, ");
System.Console.WriteLine("Humidity: " + _weather.Humidity + "%");
int outdoor_temperature = _weather.KelvinDegrees - 273;
int outdoor_humidity = _weather.Humidity;
………..
}
}
public class MainEntry
{
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 90
public static void Main()
{
………
weather.KelvinDegrees = 289;
weather.Humidity = 60;
weather.KelvinDegrees = 273;
System.Console.Read();
}
}
}
Ta thấy rằng đọan mã nguồn
foreach (WeatherUpdated s in _stations.GetInvocationList()) s();
đƣợc lặp lại lần trong nội dung lớp WeatherSensor -> áp dụng kỹ thuật Extract Method
để trích xuất nó ra làm một phƣơng thức riêng.
public int KelvinDegrees
{
……….
set { _temperature = value;
NotifyWeatherStations();
}
}
public int Humidity
{
…………
set {
_humidity = value;
NotifyWeatherStations();
}
}
private void NotifyWeatherStations()
{
foreach (WeatherUpdated s in _stations.GetInvocationList()) 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 91
Trong phƣơng thức ShowWeatherInfo của lớp NewYorkWeatherStation có sử
dụng 2 biến tạm outdoor_temperature, outdoor_humidity. Cách sử dụng này là không cần
thiết -> Sữ dụng Remove Local Variable để xóa bỏ các biến này.
…….
if ((_weather.KelvinDegrees - 273) 50)
System.Console.WriteLine("It may start to snow!");
…….
Lúc này ta thấy cấu trúc chƣơng trình đã chặt chẽ và dễ hiểu hơn. Và đã đến lúc
chúng ta tạm hài lòng với kết quả có đƣợc sau quá trình ứng dụng kỹ thuật tái cấu trúc
mã nguồn và các công cụ hỗ trợ để cải tiến các đoạn mã xấu trong chƣơng trình ban đầu.
♦ Cấu trúc và mã nguồn chƣơng trình sau khi tái cấu trúc
WeatherSensor
_temperature: int
_humidity: int
_location: string
_stations event
KelvinDegrees()
Humidity()
Location()
WeatherStations()
NotifyWeatherStations()
HanoiWeatherStation
ShowWeatherInfo()
NewYorkWeatherStation
ShowWeatherInfo()
1
WeatherStation
_weather: WeatherSensor
ShowWeatherInfo()
1
Hình 4.35: Màn hình kết quả chạy chƣơng trình sau khi refactoring
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
using System;
using System.Collections.Generic;
using System.Text;
namespace RefactoringExample
{
public delegate void WeatherUpdated();
class WeatherSensor
{
private int _temperature;
public int KelvinDegrees
{
get { return _temperature; }
set {
_temperature = value;
NotifyWeatherStations();
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 92
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
}
}
private int _humidity;
public int Humidity
{
get { return _humidity;}
set {
_humidity = value;
NotifyWeatherStations();
}
}
private string _location;
public string Location
{
get { return _location;}
}
protected event WeatherUpdated _stations;
public WeatherUpdated WeatherStations
{
get { return _stations; }
set { _stations = value; }
}
private void NotifyWeatherStations()
{
foreach (WeatherUpdated s in _stations.GetInvocationList()) s();
}
public WeatherSensor(string location, int temp, int humility)
{
_location = location;
_temperature = temp;
_humidity = humility;
}
}
class WeatherStation
{
protected WeatherSensor _weather;
protected void ShowWeatherInfo(){}
}
class HanoiWeatherStation : WeatherStation
{
public HanoiWeatherStation(WeatherSensor weather)
{
_weather = weather;
_weather.WeatherStations += new WeatherUpdated(ShowWeatherInfo);
}
public void ShowWeatherInfo()
{
System.Console.WriteLine("Hanoi Weather Station:");
System.Console.WriteLine("The weather at " + _weather.Location);
System.Console.Write("Temperature: " + (_weather.KelvinDegrees -
273) + "'C, ");
System.Console.WriteLine("Humidity: " + _weather.Humidity +"%\n");
}
}
class NewYorkWeatherStation : WeatherStation
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 93
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
{
public NewYorkWeatherStation(WeatherSensor weather)
{
_weather = weather;
_weather.WeatherStations += new WeatherUpdated(ShowWeatherInfo);
}
public void ShowWeatherInfo()
{
System.Console.WriteLine("New York Weather Station:");
System.Console.WriteLine("The weather at " + _weather.Location);
System.Console.Write("Temperature:" + (((_weather.KelvinDegrees -
273) * 9 / 5) + 32) + "'F, ");
System.Console.WriteLine("Humidity: " + _weather.Humidity + "%");
if ((_weather.KelvinDegrees - 273) 50)
System.Console.WriteLine("It may start to snow!");
System.Console.WriteLine("\n");
}
}
public class MainEntry
{
public static void Main()
{
WeatherSensor weather = new WeatherSensor("Sa Pa", 283, 40);
HanoiWeatherStation hanoi = new HanoiWeatherStation(weather);
NewYorkWeatherStation newYork = new NewYorkWeatherStation(weather);
weather.KelvinDegrees = 289;
weather.Humidity = 60;
weather.KelvinDegrees = 273;
System.Console.Read();
}
}
}
Và đây là kết quả thực thi của chƣơng trình sau khi đƣợc tái cấu trúc:
Hình 4.36: Màn hình kết quả chạy chƣơng trình sau khi refactoring
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 94
Trong quá trình tái cấu trúc mã nguồn, mỗi khi áp dung xong một kỹ thuật chúng
ta đều phải trải qua giai đọan kiểm thử và thực thi chƣơng trình với các bộ kiểm thử để
đảm bảo rằng kết quả là đồng nhất.
IV.2.2 Phân tích và đánh giá kết quả thực hiện
Với chƣơng trình trên chúng ta đã sử dụng một số 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:
- Extract Hierarchy
- Rename Class
- Rename Field
- Rename Method
- Encapsulate Field
- Extract Method
- Remove Local Variable
Đối chiếu kết quả trƣớc và sau khi thực hiện, ta có đánh giá nhận xét nhƣ sau:
♦ Định tính:
- Sau khi thực hiện tái cấu trúc, thiết kế lớp và cấu trúc chƣơng trình rõ ràng và chặt
chẽ hơn
- Các đoạn mã trùng lặp và biến tạm không cần thiết đã đƣợc loại bỏ
- Các tên lớp, trƣờng dữ liệu và phƣơng thức trở nên hợp lý và dễ hiểu
- Dữ liệu đƣợc đóng gói và an tòan hơn
♦ Định lƣợng:
Ta có bảng số liệu thống kê về các thành phần khác nhau trƣớc và sau khi thực
hiện tái cấu trúc mã nguồn nhƣ sau:
Thành phần so sánh
Áp dụng kỹ thuật tái cấu trúc mã nguồn
Trước Sau
Số dòng lệnh 102 106
Số lớp đối tượng 4 5
Số trường dữ liệu/thuộc tính lớp 6 5
Số phương thức 12 11
Số biến cục bộ sử dung 2 0
Trong 5 chỉ tiêu so sánh, ta thấy rằng có 3 chỉ tiêu đƣợc cải thiện (giảm) đó là: số
lƣợng thuộc tính lớp, phƣơng thức và biến cục bộ. Về số dòng mã, có sự chênh lệch
không đáng kể (4) nên có thể xem nhƣ là tƣơng đƣơng. Thành phần còn lại cần xét đến là
sự thay đổi về số lƣợng lớp đối tƣợng.
Ta thấy rằng với bài tóan nêu ra nhƣ trên, việc tạo mới một lớp dẫn xuất
WeatherStation là cần thiết giúp cho cấu trúc các lớp đối tƣợng đƣợc rõ ràng và có sự
đồng nhất(thuộc tính và hành vi xử lý). Việc tạo mới WeatherStation cũng làm cho
chƣơng trình có dễ dàng thay đổikhi có yêu cầu bổ sung thên các đài khí tƣợng (tạo thêm
lớp mới kế thừa từ lớp dẫn xuất WeatherStation.
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 95
IV.3 NHẬN XÉT VÀ KẾT LUẬN
Trên cơ sở lý thuyết đã đƣợc nghiên cứu và trình bày, chúng ta đã đề xuất một
giải pháp tổng thể trong việc áp dụng kỹ thuật tái cấu trúc mã nguồn để triển khai xây
dựng công cụ hỗ trợ việc dò tìm và cải tiến các đoạn mã xấu trong mã nguồn C#. Đồng
thời tiến hành thử nghiệm và ứng dụng các công cụ hỗ trợ refactor hiện có để dò tìm và
tái cấu trúc các đoạn mã xấu trên một chƣơng trình C# thực tế.
Kết quả triển khai trên chƣơng trình thực tế đã làm sáng tỏ và chứng minh những
hiệu quả mà kỹ thuật tái cấu trúc mã nguồn mang lại trong việc cải thiện chất lƣợng
chƣơng trình. Đó là:
- Cấu trúc chƣơng trình trở nên rõ ràng và chặt chẽ hơn.
- Tính khả biến của chƣơng trình đƣợc nâng cao (dễ thay đổi và cập nhật theo yêu
cầu mới)
- Hạn chế và tối thiểu hóa sự trùng lặp mã lệnh và dƣ thừa dữ liệu
- Trong quá trình tái cấu trúc mã nguồn, cần thực hiện các bƣớc kiểm thử -> tăng
khả năng kiểm sóat và phát hiện lỗi, đảm bảo chƣơng trình họat động chính xác
khi triển khai sử dụng.
- V.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 96
CHƢƠNG V: KẾT LUẬN
V.1 ĐÁNH GIÁ KẾT QUẢ CỦA ĐỀ TÀI
Qua quá trình nghiên cứu và triển khai ứng dụng kỹ thuật tái cấu trúc mã nguồn
trong việc dò tìm và cải tiến các đoạn mã xấu trong chƣơng trình C#. Đề tài đã đạt đƣợc
một số kết quả:
- Nắm rõ cơ sở lý thuyết của kỹ thuật tái cấu trúc mã nguồn (refactoring)
- Dấu hiệu nhận biết mã xấu trong chƣơng trình và các giải pháp cải tiến dựa trên
kỹ thuật tái cấu trúc mã nguồn.
- Tiếp cận và đề xuất giải pháp triển khai công cụ hỗ trợ chức năng refactor trong
quá trình phát triển phần mềm.
- Tìm hiểu và ứng dụng các công cụ hỗ trợ chức năng refactor hiện có đƣợc tích
hợp trong môi trƣờng phát triển VS.NET để dò tìm và cải tiến các đọan mã xấu
trong chƣơng trình C#.
- Đánh giá hiệu quả của các công cụ hỗ trợ chức năng refactor hiện có -> đƣa ra
khuyến cáo dành cho các nhà lập trình và phát triển phần mềm trong việc lựa chọn
và sử dụng các công cụ hỗ trợ refactor phù hợp.
- Bài báo công bố các kết quả nghiên cứu và ứng dụng của đề tài
V.2 PHẠM VI ỨNG DỤNG
Với những kết quả đã đạt đƣợc, đề tài có ý nghĩa thực tiễn trong lãnh vực nghiên
cứu và ứng dụng các công nghệ mới của ngành công nghệ phần mềm tiên tiến. Đặc biệt
là tiến trình chuẩn hóa các qui trình và kiểm thử phần mềm trong tƣơng lai.
Đề tài có khả năng ứng dụng trong phạm vi và lãnh vực:
- Triển khai ứng dụng kỹ thuật refactoring trong việc dò tìm và cải tiến các đọan mã
xấu trong chƣơng trình C# cũng nhƣ các ngôn ngữ khác thuộc họ .NET
- Tài liệu tham khảo dành cho các sinh viên chuyên nghành khoa học máy tính và
lập trình viên trong vấn đề tìm hiểu và ứng dụng kỹ thuật tái cấu trúc mã nguồn.
- Đề xuất giải pháp và khuyến cáo dành cho các nhà lập trình và phát triển phần
mềm trong việc lựa chọn và sử dụng các công cụ hỗ trợ refactor phù hợp (trên các
ngôn ngữ lập trình thuộc họ .NET)
- Cơ sở lý thuyết cho việc xây dựng và triển khai các công cụ dò tìm các đoạn mã
xấu dựa trên kỹ thuật tái cấu trúc mã nguồn cho các ngôn ngữ lập trình.
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 97
V.3 HƢỚNG PHÁT TRIỂN
V.3.1 Triển khai áp dụng trên các ngôn ngữ khác
Mở rộng triển khai áp dụng kỹ thuật tái cấu trúc mã nguồn cho các ngôn ngữ
khác: C++, VB.NET, Java, Delphi,…
Ví dụ: Triển khai kỹ thuật Promote Local Variable to Parameter trong đoạn chƣơng
trình mã nguồn VB.NET
Public Class SampleApp
Private Const SummerStart As Date = #6/1/2005#
Private Const SummerEnd As Date = #8/31/2005#
Private Shared _winterRate As Double = 5.0
Private Shared _summerRate As Double = 5.4
Private Shared Function CalculateCharge(ByVal quantity As Integer) As Double
Dim charge As Integer
Dim chargeDate As Date = Date.Now
If Date.Compare(chargeDate, SummerStart) > 0 Or _
Date.Compare(chargeDate, SummerEnd) < 0 Then
charge = quantity * _winterRate
Else
charge = quantity * _summerRate
End If
Return charge
End Function
Public Shared Sub Main()
Console.WriteLine("Charge: " + CalculateCharge(10).ToString())
End Sub
End Class
Kết quả sau khi refactor:
Public Class SampleApp
Private Const SummerStart As Date = #6/1/2005#
Private Const SummerEnd As Date = #8/31/2005#
Private Shared _winterRate As Double = 5.0
Private Shared _summerRate As Double = 5.4
Private Shared Function CalculateCharge(ByVal chargeDate As Date,
ByVal quantity As Integer) As Double
Dim charge As Integer
If Date.Compare(chargeDate, SummerStart) > 0 Or _
Date.Compare(chargeDate, SummerEnd) < 0 Then
charge = quantity * _winterRate
Else
charge = quantity * _summerRate
End If
Return charge
End Function
Public Shared Sub Main()
Console.WriteLine("Charge: " + CalculateCharge(Date.Now, 10).ToString())
End Sub
End Class
V.3.2 Thử nghiệm xây dựng một refactoring tool tích hợp vào VS.NET
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 98
TÀI LIỆU THAM KHẢO
Tiếng Việt
[1] Phạm Văn Việt, Trƣơng Lập Vĩ. Tìm hiểu ngôn ngữ C# và viết một chƣơng
trình minh họa. Đồ án tốt nghiệp – Khoa CNTT – ĐH KHTN TP.HCM, 2002.
Tiếng nƣớc ngoài
[2] Jesse Liberty. Programming C#. O‟Reilly, 2001
[3] Joseph W. Yoder. Refactoring Principles. University of Illinois, 2004
[4] Martin Fowler. Refactoring: Improving the design of existing code. Addison
Wesley,1999.
Trang web
[5] Bad Smells in Code
[6] Building a Refactoring Plug-in for VS.NET
[7] C# Refactory - C# Refactoring tool.
[8] CodeIT.Once for .NET
[9] DevExpress Refactor!™ Pro
[10] JetBrains ReSharper- Code Refactoring
[11] Nhập môn Refactoring
v.com.vn/UsolV.dotNetCenter.OfficialSite/TechnicalDocuments
/An_Introduction_of_Refactoring/Default.htm
[12] .NET Refactor for C# & VB.NET
[13] Refactoring C# Code Using Visual Studio 2005
[14] Refactoring HomePage
[15] The Future of Software Development
[16] Visual Assit X for Visual Studio
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 99
Ý KIẾN NHẬN XÉT VÀ ĐÁNH GIÁ
GV hƣớng dẫn:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Chủ tịch hội đồng xét duyệt:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
._.
Các file đính kèm theo tài liệu này:
- CH0654.pdf