Ứ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#

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á

pdf99 trang | Chia sẻ: huyen82 | Lượt xem: 2244 | Lượt tải: 0download
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:

  • pdfCH0654.pdf
Tài liệu liên quan