Báo cáo Luận văn Thạc sĩ kỹ thuật ngành khoa học máy tính

pdf 99 trang phuongnguyen 4760
Bạn đang xem 20 trang mẫu của tài liệu "Báo cáo Luận văn Thạc sĩ kỹ thuật ngành khoa học máy tính", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • pdfbao_cao_luan_van_thac_si_ky_thuat_nganh_khoa_hoc_may_tinh.pdf

Nội dung text: Báo cáo Luận văn Thạc sĩ kỹ thuật ngành khoa học máy tính

  1. 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
  2. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 LỜI CAM ĐOAN Tôi xin cam đoan nội dung luận văn "Ứng dụng kỹ thuật tái cấu trúc mã nguồn để triển khai dò tìm và cải tiến các đọan mã xấu trong chƣơng trình C# ", dƣới sự hƣớng dẫn của TS. Nguyễn Thanh Bình, là công trình do tôi trực tiếp nghiên cứu. Tôi xin cam đoan các số liệu, kết quả nghiên cứu trong luận văn là trung thực và chƣa từng đƣợc công bố trong bất cứ công trình nào trƣớc đây. Tác giả Nhiêu Lập Hòa Học viên thực hiện: Nhiêu Lập Hòa 2
  3. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 Học viên thực hiện: Nhiêu Lập Hòa 3
  4. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 Học viên thực hiện: Nhiêu Lập Hòa 4
  5. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 Học viên thực hiện: Nhiêu Lập Hòa 5
  6. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 Học viên thực hiện: Nhiêu Lập Hòa 6
  7. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 maxNumber) break; } } } Học viên thực hiện: Nhiêu Lập Hòa 7
  8. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 ): Học viên thực hiện: Nhiêu Lập Hòa 8
  9. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 bool[] numbers = new bool[maxNumber + 1]; for (int i = 0; i l = new List (); for (int k = 2; k l = new List (); for (int k = 2; k <= maxNumber; ++k) { if (numbers[k]) l.Add(k); } return l.ToArray(); } Học viên thực hiện: Nhiêu Lập Hòa 9
  10. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 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; } } Học viên thực hiện: Nhiêu Lập Hòa 10
  11. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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. Học viên thực hiện: Nhiêu Lập Hòa 11
  12. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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: Học viên thực hiện: Nhiêu Lập Hòa 12
  13. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 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; } Học viên thực hiện: Nhiêu Lập Hòa 13
  14. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 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”. Học viên thực hiện: Nhiêu Lập Hòa 14
  15. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Ở 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 l = new List (); for (int k = 2; k <= maxNumber; ++k) { if (Remains(k)) l.Add(k); } } return l.ToArray(); } Học viên thực hiện: Nhiêu Lập Hòa 15
  16. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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ì Học viên thực hiện: Nhiêu Lập Hòa 16
  17. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 l = new List (); for (int k = 2; k (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); } } Học viên thực hiện: Nhiêu Lập Hòa 17
  18. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 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 { Học viên thực hiện: Nhiêu Lập Hòa 18
  19. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 Học viên thực hiện: Nhiêu Lập Hòa 19
  20. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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. Học viên thực hiện: Nhiêu Lập Hòa 20
  21. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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. Học viên thực hiện: Nhiêu Lập Hòa 21
  22. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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à Học viên thực hiện: Nhiêu Lập Hòa 22
  23. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 Định nghĩa phương thức mới dựa trên trích 1 Extract Method 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 Thay thế các tham chiếu của một biến tạm 3 Inline Temp bằng biểu thức 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ừ 4 Replace Temp with Query biểu thức bị chuyển đổi bởi phương thức vừa tạo mới. Đặt/thay thế kết quả của biểu thức hoặc một 5 Introduce Explaining Variable phần của biểu thức vào biến tạm cùng tên. Tạo mới các biến tạm riêng biệt tương ứng với 6 Split Temporary Variable từng đoạn mã Sử dụng biến tạm thay thế cho việc gán giá trị 7 Remove Assignments to Parameter mới cho tham số. 8 Replace Method with Method Object Chuyển đổi phương thức thành đối tượng sở Học viên thực hiện: Nhiêu Lập Hòa 23
  24. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 Tạo một lớp mới và dịch chuyển các thuộc tính 12 Extract Class và phương thức có liên quan từ lớp cũ sang Hợp nhất các lớp riêng có quan hệ thành một 13 Inline Class lớp chung Tạo các phương thức trung gian truy cập gián 14 Hide Delegate tiếp đến lớp khác Tường minh các phương thức truy cập ở mỗi 15 Remove Middle Man lớp (ngược với Hide Delegate) Tạo ra một phương thức trong một lớp client 16 Introduce Foreign Method có tham số là một đại diện của một lớp server. Tạo một lớp mới chứa các phương thức mở 17 Introduce Local Extension 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 Tạo ra các phương thức truy xuất và thiết lập 18 Self Encapsulate Field 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ị Thay thế mảng bởi đối tượng với phần tử của 22 Replace Array with Object 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 Change Unidirectional Association Chuyển đổi từ liên kết đơn ánh sang xuất song 24 to Bidirectional ánh trong quan hệ giữa 2 lớp Change Bidirectional Association to Chuyển đổi từ liên kết song ánh sang xuất 25 Unidirectional đơn ánh trong quan hệ giữa 2 lớp Replace Magic Number with Sử dụng biến hằng thay cho giá trị số tường 26 Symbolic Constant 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 Thay thế mã kiểu số đếm hoặc liệt kê bới một 30 Replace Type Code with Class lớp mớI 31 Replace Type Code with Subclasses Thay thế mã kiểu dữ liệu thành lớp con Replace Type Code with Thay thế mã kiểu dữ liệu thành một đối tượng 32 State/Strategy tĩnh Biến đổi các phương thức ở các thuộc tính siêu 33 Replace Subclass with Fields 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 Tạo mới các phương thức từ mệnh đề và thân 34 Decompose Conditional xứ lý của câu lệnh điều kiện. Tạo mới một phương thức từ việc hợp nhất các 35 Consolidate Conditional Expression biểu thức điều kiện Consolidate Duplicate Conditional Hợp nhất và di chuyển các phân đoạn trùng 36 Fragments lặp ra bên ngoài thân câu lệnh điều kiện Sử dụng câu lệnh break hoặc return thay thế 37 Remove Control Flag cho biến cờ hiệu trong các thân vòng lặp Học viên thực hiện: Nhiêu Lập Hòa 24
  25. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Replace Nested Conditional with Thay thế các điều kiện rẽ nhánh bởi các mệnh 38 Guard Clauses đề so khớp Replace Conditional with Thay thế phép so sánh điều kiện bởi tính chất 39 Polymorphism đ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 Xác nhận tính hợp lệ của dữ liệu trước khi xử 41 Introduce Assertion 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 Tách thành hai phương thức riêng biệt khi vừa 45 Separate Query from Modifier truy xuất và cập nhật trên phương thức gốc. Tạo mới một phương thức và sử dụng tham số 46 Parameterize Method cho các giá trị khác nhau Replace Parameter with Explicit Tạo mới một phương thức cho mỗi giá trị của 47 Methods tham số Chuyển nguyên một đối tượng thay vì các giá 48 Preserve Whole Object trị riêng lẻ 49 Replace Parameter with Method Rút gọn tham số trong phương thực Thay thế một nhóm các tham số đồng dạng 50 Introduce Parameter Object 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 Replace Constructor with Factory 53 Thay thế hàm dựng bởi phương thức sản xuất Method 54 Encapsulate Downcast Định dạng kiểu trả về trong thân phương thức Tạo mới một phương thức từ một phân đoạn 55 Replace Error Code with Exception mã để kiểm soát các trường hợp (lỗi) ngoại lệ Kiểm tra các trường hợp (lỗi) ngoại lệ có khả 56 Replace Exception with Test 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 Tạo một hàm dựng chung ở lớp cha, và gọi lại 59 Pull Up Constructor Body 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 Trích xuất/chia tách thành các lớp con với tập 62 Extract Subclass các chức năng riêng Định nghĩa mới một lớp cha và di chuyển các 63 Extract Superclass chức năng chung đến lớp này từ các lớp con Định nghĩa một giao diện từ việc trích chọn 64 Extract Interface một tập con các thành viên trong lớp Hợp nhất thành một lớp nếu 2 lớp cha và con 65 Collapse Hierarchy (quan hệ kế thừa) có ít sự khác biệt. Đồng nhất các phương thức ở lớp con và dịch 66 Form Template Method chuyển lên lớp cha Hoán chuyển từ tính kế thừa sang tính ủy 67 Replace Inheritance with Delegation quyền giữa hai lớp Hoán chuyển từ tính tính ủy quyền sang tính 68 Replace Delegation with Inheritance kế thừa giữa hai lớp Other Refactorings – Một số kỹ thuật khác Học viên thực hiện: Nhiêu Lập Hòa 25
  26. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 69 Tease Apart Inheritance Giảm cấp trong quan hệ kế thừa Convert Procedural Design to Chuyền đổi hướng thủ tục sang hướng đối 70 Objects tượng 71 Separate Domain from Presentation Đối tượng hóa các thể hiện Phát sinh hoặc thay đổi thứ bậc của các lớp 72 Extract Hierarchy trong quna hệ kế thừa. 73 Reorder Parameters Thay đổi vị trí các tham số trong phương thức Promote Local Variable to Chuyển biến cục bộ thành tham số của 74 Parameter 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 Chuyển đổi phương thức thành đặc tính của 77 Convert Method to Property lớp Chuyển đổi đặc tính của lớp thành phương 78 Convert Property to Method 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 Sử dụng biến được khởi gán trong một biểu 83 Extract Variable 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. Học viên thực hiện: Nhiêu Lập Hòa 26
  27. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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. Học viên thực hiện: Nhiêu Lập Hòa 27
  28. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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. Học viên thực hiện: Nhiêu Lập Hòa 28
  29. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 - 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(); } } Học viên thực hiện: Nhiêu Lập Hòa 29
  30. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 đó Học viên thực hiện: Nhiêu Lập Hòa 30
  31. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 II.2.4 Long Parameter List - Danh sách tham số quá dài Trong lập trình hƣớng thủ tục, việc trao đổi mọi thứ dữ liệu giữa các thủ tục và hàm thông qua việc truyền tham số. Một giải pháp khác tốt hơn là sử dụng dữ liệu toàn cục nhƣng tiềm ẩn nhiều rủi ro trong việc kiểm soát. Với lập trình hƣớng đối tƣợng thì khác, chúng ta không cần phải chuyển mọi thứ thông qua một danh sách dài các tham số khó hiểu mà chỉ là một ít dữ liệu cơ sở vừa đủ cho việc truy suất tất cả các giá trị cần thiết khác luôn đƣợc cập nhật mới. - Sử dụng Replace Parameter with Method để nhận dữ liệu từ một đối tƣợng đã biết. Đối tƣợng này có thể là một trƣờng dữ liệu hoặc một tham số khác. Sử dụng Preserve Whole Object để thay thế một nhóm dữ liệu trong lớp bởi một thành phần dữ liệu thay thế chung của bản thân lớp đó.Với những mục dữ liệu riêng mà không gắn liền với một đối tƣợng nào cả, sử dụng Introduce Parameter Object - Một ngoại lệ quan trọng trong việc thực hiện những thay đổi này, đó là khi chúng ta không muốn tạo ra một sự phụ thuộc từ đối tƣợng đƣợc gọi đến một đối tƣợng lớn hơn. Giải pháp hợp lý trong trƣờng hợp này là chuyển dữ liệu nhƣ các tham số, khi đó cần chú ý đến những rủi ro có liên quan. Nếu danh sách tham số quá dài hoặc thƣờng xuyên thay đổi, chúng ta cần xem xét lại cấu trúc phụ thuộc. Ví dụ 1: Sử dụng Replace Parameter with Method để rút gọn tham số phƣơng thức public double getPrice() { int basePrice = _quantity * _itemPrice; int discountLevel; if (_quantity > 100) discountLevel = 2; else discountLevel = 1; double finalPrice = discountedPrice (basePrice, discountLevel); return finalPrice; } private double discountedPrice (int basePrice, int discountLevel) { if (discountLevel == 2) return basePrice * 0.1; else return basePrice * 0.05; } public double getPrice() { int basePrice = _quantity * _itemPrice; int discountLevel = getDiscountLevel(); double finalPrice = discountedPrice (basePrice); return finalPrice; } private int getDiscountLevel() { if (_quantity > 100) return 2; else return 1; } Học viên thực hiện: Nhiêu Lập Hòa 31
  32. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 private double discountedPrice (int basePrice) { if (getDiscountLevel() == 2) return basePrice * 0.1; else return basePrice * 0.05; } Ví dụ 2: Với những tham số là các mục dữ liệu riêng mà không gắn liền với một đối tƣợng nào cả, sử dụng Introduce Parameter Object II.2.5 Divergent Change – Cấu trúc lớp ít có tính khả biến Cấu trúc của phần mềm phải đƣợc thiết kế sao cho dễ dàng trong việc thay đổi và cập nhật. Nếu một lớp bị thay đổi theo nhiều cách khác nhau tùy thuộc vào các nguyên nhân khác nhau thì chúng ta nên chia lớp đó ra làm hai, trong đó mỗi lớp đƣợc tạo ra dựa trên yếu tố đặc trƣng thuận lợi với các cách thay đổi nêu trên. Trong trƣờng hợp chia tách này, giải pháp chính là sử dụng Extract Class. Ví dụ: Sử dụng kỹ thuật 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 II.2.6 Shotgun Surgery – Lớp đƣợc thiết kế không hợp lý và bị phân rã Trái ngƣợc với Divergent Change là Shotgun Surgery. Mỗi khi chúng ta thực hiện một sự thay đổi trong chƣơng trình, thông thƣờng việc thay đổi này kéo theo một chuỗi các thay đổi nhỏ ở nhiều lớp khác nhau và diễn ra ở khắp nơi. Lúc này việc phát hiện chính xác và thay đổi toàn bộ là một điều rất khó khăn không loại trừ có thể thiếu sót ở một nơi quan trọng nào đó. - Sử dụng Move Method & Move Field để dịch chuyển (thâu gôm) các phƣơng thức và thuộc tính từ lớp này sang lớp khác để làm cho chƣơng trình có cấu trúc rõ hơn. - Nếu chƣa có lớp nào thích hợp để tích hợp vào thì tạo một lớp mới. Và trong trƣờng hợp này, chúng ta sử dụng giải pháp Inline Class để tích hợp các thành phần thay đổi này. Học viên thực hiện: Nhiêu Lập Hòa 32
  33. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Ví dụ 1: Nếu một thuộc tính aField đƣợc sử dụng nhiều hơn trong lớp khác Class 2 thay vì lớp chứa nó Class 1 thì chúng ta nên sử dụng Move Field để dịch chuyển phƣơng thức đó sang lớp kia. Ví dụ 2: Nếu hai lớp có qui mô nhỏ (không làm gì nhiều) và có quan hệ với nhau thì nên sử dụng Inline Class để hợp nhất chúng lại một lớp. II.2.7 Feature Envy – Phân bố phƣơng thức giữa các lớp không hợp lý Trong lập trình hƣớng đối tƣợng, đôi lúc chúng ta gặp trƣờng hợp một phƣơng thức của một lớp mà nó sử dụng hoặc hỗ trợ nhiều chức năng (thuộc tính và phƣơng thức) cho một lớp khác hơn chính lớp chứa nó. Khi đó chúng ta sẽ sử dụng Extract Method và Move Method để trích xuất và dịch chuyển phƣơng thức vào một vị trị lớp thích hợp nhất. Ví dụ: Một phƣơng thức của một lớp Class 1 mà nó sử dụng nhiều chức năng (thuộc tính và phƣơng thức) của một lớp khác Class 2 hơn chính nó thì sử dụng Move Method để dịch chuyển phƣơng thức đó sang lớp kia. Học viên thực hiện: Nhiêu Lập Hòa 33
  34. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 II.2.8 Data Clumps – Gôm cụm dữ liệu Thông thƣờng trong mã nguồn, chúng ta cũng gặp trƣờng hợp cùng lúc ba hoặc bốn mục dữ liệu thƣờng đi chung với nhau (nhiều thuộc tính trong lớp, các tham số truyền cho phƣơng thức, ) và đƣợc sử dụng ở nhiều nơi. Một giải pháp tốt hơn là gôm cụm chúng lại và chuyển sang một lớp đối tƣợng: - Nếu các mục dữ liệu đó là các thuộc tính -> sử dụng Extract Class để định nghĩa các lớp mới phù hợp với từng nhóm thuộc tính. - Nếu các mục dữ liệu là các tham số -> sử dụng Introduce Parameter Object hoặc Preserve Whole Object để làm gọn hơn biểu thức tham số truyền vào và thân chƣơng trình xử lý Ví dụ: Thay vì truy xuất từng giá trị thuộc tính riêng lẻ của một đối tƣợng và truyền các giá trị này theo kiểu tham số cho một phƣơng thức. Chúng ta sẽ sử dụng kỹ thuật Preserve Whole Object để truyền nguyên đối tƣợng đó. int low = daysTempRange().getLow(); int high = daysTempRange().getHigh(); withinPlan = plan.withinRange(low, high); withinPlan = plan.withinRange(daysTempRange()); II.2.9 Primitive Obsession – Khả năng thể hiện dữ liệu của lớp bị hạn chế Một trong những mã xấu thƣờng gặp là sự gộp chung các lớp đối tƣợng có quan hệ với nhau thành một lớp đối tƣợng chung có cấu trúc phức tạp và khó hiểu. Trong trƣờng hợp này, chúng ta cần phải tùy biến cấu trúc ban đầu cho hợp lý hơn: - Nếu một thuộc tính dữ liệu trong lớp cần đƣợc bổ sung thêm thộng tin hoặc hành vi, sử dụng Replace Data Value with Object để chuyển đổi thuộc tính dữ liệu đó sang một lớp đối tƣợng. - Nếu giá trị dữ liệu là một mã kiểu (type code) thì sử dụng Replace Type Code with Class để chuyển sang một lớp mới - Nếu mã kiểu (type code) là điều kiện rẻ hƣớng (làm ảnh hƣởng hành vi của một lớp) thì sử dụng Replace Type Code with Subclasses hoặc Replace Type Code with State/Strategy để chuyển đổi mã kiểu này sang một đối tƣợng tĩnh - Nếu là các thuộc tính ban đầu thƣờng đi chung thì sử dụng Extract Class, và nếu đó là các danh sách tham số thì sử dụng Introduce Parameter Object hoặc Replace Array with Object trong trƣờng hợp một mảng dữ liệu để chuyển đổi sang một lớp đối tƣợng tƣơng ứng. Học viên thực hiện: Nhiêu Lập Hòa 34
  35. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Ví dụ 1: Chúng ta có một lớp Order dùng để lƣu các dữ liệu về đơn hàng của đối tƣợng khách hàng trong đó customer là một thuộc tính chuỗi của lớp. Trên thực tế, thì đôi lúc ngƣời sử dụng cần biết thêm thông tin về khách hàng tƣơng trong đơn hàng (địa chỉ, số điện thoại, ). Lúc này chúng ta cần sử dụng kỹ thuật Replace Data Value with Object để chuyển đổi dữ liệu customer sang một đối tƣợng Customer. Ví dụ 2: Sử dụng Replace Type Code with Class để thay kiểu dữ liệu số bởi một lớp class Person { public static final int O = 0; public static final int A = 1; public static final int B = 2; public static final int AB = 3; private int _bloodGroup; public Person (int bloodGroup) { _bloodGroup = bloodGroup; } public void setBloodGroup(int arg) { _bloodGroup = arg; } public int getBloodGroup() { return _bloodGroup; } } class BloodGroup { public static final BloodGroup O = new BloodGroup(0); public static final BloodGroup A = new BloodGroup(1); public static final BloodGroup B = new BloodGroup(2); public static final BloodGroup AB = new BloodGroup(3); private static final BloodGroup[] _values = {O, A, B, AB}; private final int _code; private BloodGroup (int code ) { _code = code; } public int getCode() { return _code; } Học viên thực hiện: Nhiêu Lập Hòa 35
  36. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 public static BloodGroup code(int arg) { return _values[arg]; } } class Person { public static final int O = BloodGroup.O. getCode(); public static final int A = BloodGroup.A.getCode(); public static final int B = BloodGroup.B.getCode(); public static final int AB = BloodGroup.AB.getCode(); private BloodGroup _bloodGroup; public Person (BloodGroup bloodGroup ) { _bloodGroup = bloodGroup; } public int getBloodGroupCode() { return _bloodGroup.getCode(); } public void setBloodGroup(BloodGroup arg) { _bloodGroup = arg; } public BloodGroup getBloodGroup() { return _bloodGroup; } } II.2.10 Switch Statements – Khối lệnh điều kiện rẽ hƣớng không hợp lý Các câu lệnh switch thƣờng là nguyên nhân phát sinh việc trùng lặp và chúng nằm rải rác khắp ở nhiều nơi trong thân chƣơng trình. Trong trƣờng hợp cần thêm một giá trị điều kiện rẻ nhánh mới, chúng ta phải thực hiện việc kiểm tra tất cả các câu lệnh switch này. Một giải pháp thích hợp và cải tiến hơn là cần đƣợc xét đến đó là sử dụng lớp và tính chất đa hình (polymorphism) trong hƣớng đối tƣợng. - Đối với các mã kiểu, sử dụng kết hợp các kỹ thuật Extract Method, Move Method và Replace Type Code with Subclasses/State/Strategy để tạo nên các lớp mới từ việc trích xuất các khối lệnh điều kiện rẽ hƣớng sau đó dịch chuyển các lớp mới tạo này vào trong các lớp mà ở đó tính đa hình đƣợc sử dụng. Và cuối cùng là Replace Conditional with Polymorphism. - Nếu đó chỉ là một vài trƣờng hợp rẽ nhánh trong một phƣơng thức đơn mà tính chất đa hình không cần thiết, một giái pháp thích hợp hơn là áp dụng Replace Parameter with Explicit Methods hoặc Introduce Null Object trong trƣờng hợp có một điều kiện Null Ví dụ 1: Câu lệnh switch bên dƣới lựa chọn các xử lý khác nhau tùy thuộc vào giá trị của kiểu đối tƣợng. Sử dụng Replace Conditional with Polymorphism để chuyển đổi mỗi nhánh của điều kiện thành một phƣơng thức chồng (overriding method) trong mỗi lớp con. Học viên thực hiện: Nhiêu Lập Hòa 36
  37. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 double getSpeed() { switch (_type) { case EUROPEAN: return getBaseSpeed(); case AFRICAN: return getBaseSpeed() - getLoadFactor() * _numberOfCoconuts; case NORWEGIAN_BLUE: return (_isNailed) ? 0 : getBaseSpeed(_voltage); } throw new RuntimeException ("Should be unreachable"); } Ví dụ 2: Nếu chúng ta có phƣơng thức mà nó thực thi các mã lệnh khác nhau phụ thuộc vào giá trị của các tham số đƣợc liệt kê thì sử dụng Replace Parameter with Explicit Methods để tạo một phƣơng thức riêng tƣơng tứng với mỗi giá trị của tham số. void setValue (String name, int value) { if (name.equals("height")) _height = value; if (name.equals("width")) _width = value; Assert.shouldNeverReachHere(); } void setHeight(int arg) { _height = arg; } void setWidth (int arg) { _width = arg; } Học viên thực hiện: Nhiêu Lập Hòa 37
  38. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 II.2.11 Lazy Class – Lớp đƣợc định nghĩa không cần thiết Một lớp đối tƣợng đƣợc tạo ra bao giờ cũng có những lợi ích của nó, tuy nhiên đi kèm với những lợi ích đó là những rủi ro có thể xảy ra và phí tổn trong quá trình hoạt động và bảo trì. Vì vậy chúng ta cần phải cân xét hai yếu tố này khi xác định sự hiện diện của một lớp. Trong quá trình refactoring, đến một lúc nào đó có thể chúng ta nhận ra rằng sự hiện diện của một lớp đối tƣợng A là không cần thiết, lúc này chúng ta có thể sử dụng kỹ thuật Collapse Hierarchy hoặc Inline Class để giản lƣợc hoặc tích hợp nó vào trong một lớp khác. Ví dụ: Nếu 2 lớp có quan hệ kế thừa không có sự khác biệt nhau nhiều, sử dụng Collapse Hierarchy để tích hợp chúng lại với nhau (giản lƣợc lớp con). II.2.12 Speculative Generality – Cấu trúc bị thiết kế dƣ thừa Đối với những phƣơng thức và lớp đối tƣợng đƣợc thiết kế nhƣng trên thực tế không thật sự dùng đến thì chúng ta nên xem xét và loại bỏ. - Sử dụng Collapse Hierarchy để loại bỏ các lớp trừu tƣợng (abstract classses) thật sự ít khi đƣợc sử dụng. - Xóa bỏ tính chất ủy quyền không cần thiết giữa các lớp với kỹ thuật Inline Class - Sử dụng Remove Parameter để xóa bỏ các tham số không thật sự dùng đến trong thân phƣơng thức - Sử dụng Rename Method để thay đổi tên phƣơng thức với mục đích dễ hiểu nhất Ví dụ: Một tham số mà không thật sự dùng đến trong thân phƣơng thức thì nên dùng Remove Parameter để xóa bỏ. Học viên thực hiện: Nhiêu Lập Hòa 38
  39. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 II.2.13 Temporary Field – Lạm dụng thuộc tính tạm thời Chúng ta cũng có thể xem xét việc từ chối định nghĩa và sử dụng các biến thành viên không đƣợc sử dụng thƣờng xuyên bởi lớp. - Trên thực tế khi gặp trƣờng hợp các biến mồ côi, sử dụng Extract Class để gôm nhóm chúng cùng các mã lệnh có liên quan và đặt vào trong một lớp mới. Sử dụng Introduce Null Object nếu biến đó tham chiếu đến trƣờng hợp đặc biệt Null. - Với các thuật toán phức tạp làm phát sinh các thuộc tính tạm trong quá trình xử lý thì nên hạn chế việc truyền vào một danh sách tham số quá lớn. II.2.14 Message Chains –Chuỗi phƣơng thức liên hoàn khó kiểm soát Khi gặp một chuỗi các phƣơng thức/thông điệp yêu cầu xử lý đƣợc cấu trúc nhƣ thế này getA().getB().getC().getD().getE().doIt(); Đây là một hình thức quan hệ kết buộc trong cấu trúc giữa các phƣơng thức mà bất kỳ sự thay đổi nào trong mối quan hệ này cũng làm ảnh hƣởng kết quả nhận đƣợc. Trong trƣờng hợp này tùy theo quan điểm của từng ngƣời, có ngƣời vẫn chấp nhận sự hiện diện trong mã nguồn, có ngƣời thì cho rằng đó là một cấu trúc xấu cần phải cải tiến dựa trên kỹ thuật Extract Method và Move Method II.2.15 Middle Man – Quan hệ ủy quyền không hợp lý/logic Tính chất ủy quyền trong hƣớng đối tƣợng rất hữu dụng, tuy nhiên đôi lúc nó cũng bị lạm dụng quá mức. Nếu một lớp hoạt động nhƣ một sự ủy quyền nhƣng lại thực hiện một số công việc bổ sung không hữu ích thì chúng ta cũng nên xem xét để hủy bỏ trong một số trƣờng hợp. - Nhìn vào giao diện của một lớp, nếu thấy rằng ½ các phƣơng thức ủy quyền đến một lớp khác, thì hãy sử dụng Remove Middle Man để tái cấu trúc lại tính chất ủy quyền của các phƣơng thức giữa các lớp này. - Nếu chỉ xảy ra với vài phƣơng thức, sử dụng Inline Classs để tích hợp/hợp nhất chúng vào trong lớp gọi. - Nếu có thêm vào các hành vi xử lý, sử dụng Replace Delegation with Inheritance để chuyển đổi quan hệ ủy quyền không hợp lý này trong một lớp con của một đối tƣợng thực tế (quan hệ thừa kế) Học viên thực hiện: Nhiêu Lập Hòa 39
  40. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Ví dụ: Cải tiến mã xấu về quan hệ ủy quyền không hợp lý/logic thông qua kỹ thuật Remove Middle Man class Person { Department _department; public Person getManager() { return _department.getManager(); } } class Department { private Person _manager; public Department (Person manager) { _manager = manager; } public Person getManager() { return _manager; } } //Để tìm manager của một người manager = john.getManager(); class Person { Department _department; public Department getDepartment() { return _department; } } class Department { private Person _manager; public Person getManager() { return _manager; } } //Để tìm manager của một người manager = john.getDepartment().getManager(); Học viên thực hiện: Nhiêu Lập Hòa 40
  41. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 II.2.16 Inapproprite Intimacy - Cấu trúc thành phần riêng không hợp lý Một trong những ƣu điểm của hƣớng đối tƣợng là tính đóng nhằm hạn chế các truy cập vào các thành phần riêng của lớp. Tuy nhiên nó cũng đem lại một số hạn chế trong một vài trƣờng hợp, chẳng hạn là tốn nhiều thời gian hơn trong việc truy cập và can thiệp vào các thành phần này. Sử dụng Move Method và Move Field để chia tách thành các phần giảm bởi các bƣợc truung gian việc truy cập đến các thành phần riêng tƣ. II.2.17 Alternative Classes with Different Interfaces - Đặc tả lớp không rõ ràng Với các phƣơng thức tuy có nội dung thực hiện các việc nhƣ nhau nhƣng về mặt ý nghĩa khác nhau thì nên sử dựng Rename Method để xác định tên sao cho dễ hiểu và thể hiện đƣợc mục đích của phƣơng thức. Khi cần thiết có thể áp dụng Move Method để dịch chuyển các hành vi xử lý tạo tính đồng nhất trong các giao thức thực hiện Ví dụ: Nếu tên phƣơng thức không rõ ràng và đặc tả đƣợc mục đích của của phƣơng thức thì nên sử dụng Rename Method để đổi tên cho hợp lý. II.2.18 Incomplete Library Class – Sử dụng thƣ viện lớp chƣa đƣợc hòan chỉnh Nhƣ ta biết thì đặc tính nổi bật của hƣớng đối tƣợng là khả năng tái sử dụng. Thông thƣờng trong lập trình hƣớng đối tƣợng, các lập trình viên thƣờng sử dụng các tập lớp đối tƣợng đã đƣợc các nhà phát triển thiết kế sẵn. Tuy nhiên không phải bao giờ các lớp đối tƣợng này cũng đã đƣợc thiết kế chính xác và hòan chỉnh. Nhƣ vậy một số hạn chế có thể gặp phải trong quá trình tái sử dụng các lớp đối tƣợng - Nếu thƣ viện lớp thiếu cần bổ sung 1 –2 phƣơng thức cần thiết, sử dụng Introduce Foregin Method để bổ sung - Với số lƣợng cần bổ sung nhiều hơn, chúng ta sử dụng Introduce Local Extension Ví dụ 1: Khi cần thêm một phƣơng thức vào một lớp phục vụ nhƣng không thể chỉnh sửa lớp -> Sử dụng Introduce Foregin Method để tạo một phƣơng thức ở lớp khách hàng có đối số truyền vào là một thực thể của lớp phục vụ. Date newStart = new Date (previousEnd.getYear(), previousEnd.getMonth(), previousEnd.getDate() + 1); Date newStart = nextDay(previousEnd); private static Date nextDay(Date arg) { return new Date (arg.getYear(),arg.getMonth(), arg.getDate() + 1); } Học viên thực hiện: Nhiêu Lập Hòa 41
  42. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Ví dụ 2: Khi cần thêm một vài phƣơng thức vào một lớp nhƣng không thể chỉnh sửa lớp -> Sử dụng Introduce Local Extension để định nghĩa một mới mà nó chứa các phƣơng thức bổ sung. Lớp mở rộng này sẽ là lớp con hoặv lớp bao của lớp ban đầu. II.2.19 Data Class – Lớp dữ liệu độc lập Với những lớp mà nội dung chỉ gồm các thuộc tính cùng các phƣơng thức truy vấn và thiết lập (getting & setting) giá trị trên các thuộc tính đó mà không có gì khác. - Sử dụng Encapsulate Field để chuyển thuộc tính chung (public) thành riêng (private) và cung cấp các phƣơng thức trên thuộc tính đó - Một phƣơng thức trả về một tập hợp, sử dụng Encapsulate Collection để cải biến kết quả trả về là một tập hợp có thuộc tính chỉ đọc và tạo thêm một số phƣơng thức hỗ trợ thêm mới hoặc hủy. - Với những thuộc tính mà không có sự thay đổi, nên sử dụng Remove Setting Method để hủy bỏ các phƣơng thức cập nhật giá trị trên thuộc tính đó. - Có thể sử dụng thêm các kỹ thuật Move Method, Extract Method và Hide Method trên các phƣơng thức truy vấn và thiết lập để tái cấu trúc lớp đƣợc hợp lý hơn Ví dụ 1: Sử dụng Encapsulate Field để chuyển thuộc tính chung (public) thành riêng (private) và cung cấp các phƣơng thức trên thuộc tính đó public String _name; private String _name; public String getName() {return _name;} public void setName(String arg) {_name = arg;} Ví dụ 2: Sử dụng Remove Setting Method để xóa bỏ phƣơng thức cập nhật trên các thuộc tính bất biến Học viên thực hiện: Nhiêu Lập Hòa 42
  43. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 II.2.20 Refused Bequest – Quan hệ kế thừa không hợp lý/logic Một lớp con kế thừa một số thuộc tính và phƣơng thức từ lớp cha không cần thiết. Điều này đồng nghĩa với việc xác định thứ tự các lớp trong quan hệ kế thừa có khả năng không hợp lý. - Nếu một thuộc tính hoặc hành vi xử lý (phƣơng thức) trên lớp cha chỉ liên quan đến một số lớp con, sử dụng Push Down Method & Push Down Field chuyển nó đến các lớp con này. Lớp cha chỉ nên nắm giữ các thành phần chung. - Một lớp con chỉ sử dụng một phần giao diện của lớp cha hoặc không thừa kế dữ liệu, sử dụng Replace Inheritance with Delegation để tạo một thuộc tính mới và điều chỉnh các phƣơng thức ủy quyền đến lớp cha Ví dụ 1: Dùng Push Down Method để dịch chuyển một phƣơng thức từ lớp cha về lớp con có sử dụng đến. Ví dụ 2: Sử dụng Replace Inheritance with Delegation để tái cấu trúc một quan hệ kế thừa II.2.21 Comments – Chú thích không cần thiết Chú thích trong mã nguồn là một việc nên làm để mã nguồn dễ hiểu, tuy nhiên việc lạm dụng chúng là không nên. Một chƣơng trình có quá nhiều chú thích không cần thiết đôi lúc cũng tạo sự khó khăn và tăng chi phí trong việc dò tìm và cập nhật lỗi. Học viên thực hiện: Nhiêu Lập Hòa 43
  44. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 II.3 NHẬN XÉT VÀ KẾT LUẬN Trong chƣơng này chúng ta tập trung tìm hiểu về các mã xấu thƣờng gặp trong quá trình thiết kế và lập trình phần mềm. Nhìn chung các mã xấu này rất đa dạng và hầu nhƣ có khả năng tồn tại ở khắp mọi nơi trong thân chƣơng trình: - Thiết kế lớp đối tƣợng không chặt chẽ - Đặt trên biến không rõ ràng - Khai báo và sử dụng trƣờng dữ liệu (thuộc tính lớp) không hợp lý - Nội dung phƣơng thức dài dòng và phức tạp - Khai báo và sử dụng biến trung gian không cần thiết - V.v Vấn đề nhận biết và chỉnh sửa các mã xấu này là không đơn giản, mỗi lỗi đều có những dấu hiệu cũng nhƣ giải pháp riêng để nhận biết và cải tiến. Và tất cả các giải pháp chỉnh sửa và cải tiến đều đều dựa trên kỹ thuật tái cấu trúc mã nguồn đã đƣợc trình bày ở trên. Dựa vào kỹ thuật này, chỉ qua một vài bƣớc biến đổi, chƣơng trình sẽ trở nên rõ ràng, dễ hiểu và xa hơn là làm tăng tính khả biến của chƣơng trình trong tƣơng lai. Trong phạm vi của đề tài, phần trình bày tập trung vào các mã xấu cơ bản nhất, đã đƣợc kiểm nghiệm thực tế trên một số ngôn ngữ lập trình và bƣớc đầu có những kết quả nhất định. Và ở phần tiếp theo, chúng ta sẽ hiện thực việc ứng dụng kỹ thuật refactoring để triển khai việc dò tìm và cải tiến mã xấu trong một ngôn ngữ lập trình cụ thể (C#) trong môi trƣờng phát triển ứng dụng Visual Studio .NET Học viên thực hiện: Nhiêu Lập Hòa 44
  45. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 CHƢƠNG III: NỀN TẢNG .NET VÀ NGÔN NGỮ LẬP TRÌNH C# III.1 TỔNG QUAN VỀ NỀN TẢNG .NET Trong thế giới điện toán, những cải tiến và thay đổi vẫn thƣờng xuyên xảy ra. Đây là những biến đổi tất yếu và có tác động thúc đẩy sự phát triển. Một thách thức đối với bất kì nhà lập trình hay những công việc chuyên về CNTT nào là theo kịp những biến đổi liên tục và những sự phát triển trong công nghệ. Sự khởi xƣớng ý tƣởng .NET là một bƣớc đột phá mới của Microsoft. Đó là nền tảng cho các dịch vụ Web XML, là thế hệ phần mềm kế tiếp kết nối thế giới thông tin, các thiết bị và tất cả mọi ngƣời trong một thể thống nhất. Nền tảng .NET cho phép tạo ra và sử dụng các ứng dụng, các quá trình và các website dựa trên XML nhƣ những dịch vụ chia xẻ, kết nối thông tin và hoạt động cùng nhau trên bất cứ nền tảng hay thiết bị thông minh nào, nhằm mục đích cung cấp những giải pháp theo yêu cầu cho các tổ chức và cá nhân riêng biệt. III.1.1 Định nghĩa .NET Về tổng thể .NET có nghĩa một nền tảng hơn là một sản phẩm đơn lẻ, cho nên cách định nghĩa nó có thể đa dạng, có phần hơi khó hiểu và mơ hồ. Một cách đơn giản .NET đƣợc định nghĩa dƣới dạng một khung ứng dụng (application framework). .NET cung cấp một khung cho những ứng dụng nào đƣợc xây dựng và thực thi trên đó. Nó xác định những ứng dụng, cách thức truy nhập các hàm nhƣ thế nào qua các hệ thống và các mạng. Nói cách khác, .NET là một cách để xây dựng các ứng dụng và các dịch vụ mà nó hoạt động không phụ thuộc vào một nền tảng (platform) nào. III.1.2 Mục tiêu của .NET Trong quá trình nghiên cứu và phát triển, Microsoft đã đề cập một ý tƣởng là đến một lúc nào, các nhà phát triển công nghệ phải đua tranh với nhau ở mức ứng dụng và dịch vụ chứ không phải ở mức nền tảng (platform level). Vì thế nền tảng .NET đƣợc Microsoft xây dựng nhằm thực hiện các mục tiêu: - Định hƣớng ngành công nghiệp điện toán cạnh tranh trên sản phẩm thay vì các chuẩn nền tảng - Đẩy mạnh truyền thông thƣơng mại điện tử (TMĐT). III.1.3 Dịch vụ của .NET Để thực thi mô hình .NET, một vài khối hợp nhất (building block) cơ sở đƣợc cấu trúc theo một trình tự nhất định (các block này định rõ các dịch vụ Web đƣợc xây dựng nhƣ thế nào). Các dịch vụ này cố gắng để trợ giúp các nhà phát triển xây dựng các ứng dụng .NET. Microsoft định nghĩa các dịch vụ khối hợp nhất .NET sau đây: - Authentication: Sử dụng các công nghệ Authentication (chứng thực) cũng nhƣ Passport (hộ chiếu) của Microsoft, các nhà phát triển tạo ra các dịch vụ cho riêng mình và bảo vệ các dịch vụ nhƣ mong muốn. Học viên thực hiện: Nhiêu Lập Hòa 45
  46. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 - Messaging: Hệ thống Messaging (truyền thông điệp) cùng các đặc tính của nó trong .NET có thể đƣợc phân tán đến bất kì thiết bị nào do tính không phụ thuộc nền tảng của chúng. - Personalized Experience (kinh nghiệm cá nhân): .NET cho ngƣời dùng nhiều kiểm soát hơn thông qua các qui tắc xử lý dữ liệu và quyền ƣu tiên mà nó xác định rõ dữ liệu phải đƣợc di chuyển và quản lý nhƣ thế nào. - XML (Extensible Markup Language): XML đƣợc xem nhƣ một ngôn ngữ chung mà nó cho phép dữ liệu đƣợc di chuyển từ dạng này sang dạng khác trong khi bảo trì tính toàn vẹn của nó. Cùng với SOAP, XML có thể cung cấp một dịch vụ linh hoạt để quản lý và điều khiển dữ liệu. III.1.4 Kiến trúc của .NET .NET gồm có hai phần: Framework và Integrated Development Environment (IDE). - .NET Framework: Là một platform mới làm đơn giản việc phát triển ứng dụng trong môi trƣờng phân tán của Internet. Nó cung cấp tất cả những gì cần thiết căn bản. Framework có nghĩa là cái khung hay khung cảnh trong đó ta dùng những hạ tầng cơ sở theo một qui ƣớc nhất định để công việc trôi chảy. .NET Framework có hai thành phần chính là môi trƣờng thực thi chung (Common Language Runtime) và thƣ viện lớp cơ sở (.NET Framework Base Classes). Hình 3.1: Kiến trúc nền tảng .NET - Integrated Development Environment (IDE): Cung cấp một môi trƣờng giúp ta triển khai các ứng dụng một cách dễ dàng và nhanh chóng. Học viên thực hiện: Nhiêu Lập Hòa 46
  47. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 III.2 NGÔN NGỮ LẬP TRÌNH C# III.2.1 Tổng quan về ngôn ngữ lập trình C# Phần lớn các ứng dụng trên nền tảng .NET này đều đƣợc phát triển và lập trình trên ngôn ngữ C#. Đó là một ngôn ngữ mới hoàn toàn hƣớng đối tƣợng đƣợc phát triển bởi đội ngũ kỹ sƣ của Microsoft, trong đó ngƣời dẫn đầu là Anders Hejlsberg và Scott Wiltamut. Các ý tƣởng phát triển C# dựa trên nền tảng và kinh nghiệm của các ngôn ngữ hƣớng đối tƣợng khác (C++ và Java) và đƣợc thiết kế riêng để dùng cho Microsoft's .NET Framework Ngôn ngữ C# khá đơn giản, chỉ khoảng 80 từ khóa và hơn mƣời mấy kiểu dữ liệu đƣợc xây dựng sẵn. Tuy nhiên, ngôn ngữ C# có ý nghĩa cao khi nó thực thi những khái niệm lập trình hiện đại. C# bao gồm tất cả những hỗ trợ cho cấu trúc, thành phần component, lập trình hƣớng đối tƣợng. Những tính chất đó hiện diện trong một ngôn ngữ lập trình hiện đại. III.2.2 Đặc trƣng của các ngôn ngữ lập trình C# ♦ Ngôn ngữ lập trình hƣớng đối tƣợng - Hoàn toàn là hƣớng đối tƣợng - Tính chất đóng gói, đa hình và kế thừa - Mọi lớp đối tƣợng đều kế thừa từ lớp gốc System.Object ♦ Tinh gọn và cải tiến so với C++ & Java - Loại bỏ một vài sự phức tạp và rối rắm của những ngôn ngữ nhƣ C++ & Java (macro, những template, đa kế thừa, và lớp cơ sở ảo, con trỏ,.v.v) - Không yêu cầu phải chia ra tập tin header và tập tin nguồn giống nhƣ trong ngôn ngữ C++. - Hỗ trợ kiểu XML, cho phép chèn các tag XML để phát sinh tự động các document cho lớp. ♦ Ngôn ngữ hiện đại - Tự động trong việc chuẩn bị vùng nhớ cho các đối tƣợng, xử lý ngoại lệ.,v.v - Bổ sung những kiểu dữ liệu mở rộng, và bảo mật mã nguồn - Lập trình viên không quan tâm đến việc thu gôm và giải phóng bộ nhớ ♦ Một số đặc trƣng khác - Mạnh mẽ và mềm dẻo với khả năng đáp ứng nhiều các dự án khác nhau - Đơn giản về số lƣợng từ khóa - Hƣớng module với thành phần là các lớp và phƣơng thức có thể tái sử dụng. - Phổ biến và đƣợc giới lập trình viên tin cậy Học viên thực hiện: Nhiêu Lập Hòa 47
  48. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 III.3 MÔI TRƢỜNG PHÁT TRIỂN ỨNG DỤNG VISUAL STUDIO .NET Visual Studio.NET (VS.NET) là môi trƣờng phát triển phần mềm (IDE) đƣợc Microsoft phát triển dùng để triển khai các ứng dụng trên nền .NET, nó có các đặt tính nổi bật nhƣ sau: - Có thể sử dụng bất kỳ ngôn ngữ nào mà .NET flatform hỗ trợ (C++,C#, VB/ASP.NET và J#) - Quản lý các thành phần bên trong theo cửa sổ - Cung cấp môi trƣờng thiết kế giao diện trực quan - Hỗ trợ các công cụ mạnh có thể thao tác với database thông qua ADO.NET VS.NET quản lý ứng dụng theo kiểu các giải pháp (solution) và dự án (sroject). Project là một ứng dụng xử lý một hệ thống, solution là tổng hợp của các project tạo nên một ứng dụng có thể giải quyết mọi công việc trong hệ thống. Môi trƣờng phát triển ứng dụng VS.NET bao gồm các thành phần cơ bản nhƣ sau Hình 3.2: Môi trƣờng phát triển ứng dụng Visual Studio .NET (2005) - Solution Explorer: hiển thị tất cả các project, các files theo dạng cây - Toolbox: chứa các điều khiển cơ bản có thể sử dụng trên Webforms và trên Winforms, ngoài ra còn có các điều khiển ActiveX, XML Web services, các phần tử HTML và các đối tƣợng khác. - Server Explorer: cung cấp môi trƣờng điều khiển và quản lý các servers nắm ngoài ứng dụng thông qua VS.NET. - Class View: mô tả cấu trúc logic của các lớp, các thành phần trong lớp theo cây. - Properties: cho phép thiết lập thuộc tính của các controls, classes và projects. Học viên thực hiện: Nhiêu Lập Hòa 48
  49. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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# IV.1 GIẢI PHÁP VÀ CÔNG CỤ HỖ TRỢ REFACTOR IV.1.1 Đặc tả giải pháp triển khai Trên cơ sở lý thuyết về kỹ thuật tái cấu trúc mã nguồn và dấu hiệu nhận biết mã xấu đã đƣợc nghiên cứu và trình bày. Một giải pháp tổng thể cho việc triển khai xây dựng một tiện ích hỗ trợ kỹ thuật tái cấu trúc mã nguồn để dò tìm và cải tiến mã xấu mã nguồn C# có khả năng tích hợp trong môi trƣờng phát triển ứng dụng Visual Studio .NET đƣợc đặc tả nhƣ sau: Hình 4.1: Đặc tả kịch bản giải pháp triển khai Giải pháp tổng thể bao gồm các thành phần sau: - Chƣơng trình nguồn: Mã nguồn của chƣơng trình C# tồn tại mã xấu - Chƣơng trình đích: Mã nguồn của chƣơng trình C# đã đƣợc cải tiến khắc phục mã xấu - Môi trƣờng thực hiện: Visual Studio .NET (2003/2005/2008) - Công cụ tích hợp hỗ trợ các kỹ thuật refactoring: Refactor trong VS.NET, Visual Assit X, C# Refactory, .NET Refactor, CodeIT.Once for .NET, JetBrains ReSharper, DevExpress Refactor!™ Pro Học viên thực hiện: Nhiêu Lập Hòa 49
  50. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 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 Trong phạm vi của đề tài này, chúng ta sẽ tập trung tìm hiểu một số công cụ và tiện ích đƣợc tích hợp trong Visual Studio .NET hỗ trợ việc dò tìm và cải tiến mã xấu trong mã nguồn chƣơng trình C#. ♦ Refactor trong Visual Studio .NET [13] Trong bộ công cụ và môi trƣờng phát triển ứng dụng Visual Studio.NET (2005/2008) , Microsoft đã tích hợp một số kỹ thuật refactoring cơ bản thông qua tiện ích Refactor: - Encapsulate Field - Promote Local Variable to Parameter - Extract Method - Remove Parameters - Extract Interface - Rename - Generate Method Stub - Reorder Parameters Hình 4.2: Màn hình trình chức năng refactor tích hợp trong Visual Studio .NET (2005) Học viên thực hiện: Nhiêu Lập Hòa 50
  51. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 ♦ Visual Assit X for Visual Studio [16] Visual Assit X for Visual Studio là một chƣơng trình tiện ích đƣợc hãng sản xuất và phát triển phần mềm Whole Tomato phát triển, cho phép tích hợp vào Visual Studio nhằm hỗ trợ các lập trình viên thực hiện các chức năng refactor trong quá trình thiết kế và soạn thảo mã nguồn các chƣơng trình họ .NET Visual Assit X for Visual Studio hỗ trợ một số kỹ thuật refactoring: - Add Member - Document Mothod - Change Method Signature - Encapsulate Field - Create Declaration - Extract Method - Create Implementation - Rename Hình 4.3: Màn hình trình chức năng refactor của Visual Assit X for Visual Studio.NET Học viên thực hiện: Nhiêu Lập Hòa 51
  52. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 ♦ C# Refactory for Visual Studio .NET [7] C# Refactory một chƣơng trình tiện ích do Xtreme Simplicity phát triển có khả năng tích hợp vào Visual Studio cho phép thực hiện một số chức năng refactoring trên mã nguồn chƣơng trình C#. C# Refactory for Visual Studio .NET cung cấp một số kỹ thuật refactoring: - Change Method Signature - Extract Superclass - Copy Class - Extract Variable - Decompose Conditional - Push Up Members - Encapsulate Field - Rename Local Variable - Extract Class - Rename Member - Extract Interface - Rename Parameter - Extract Method - Rename Type Hình 4.4: Màn hình trình chức năng refactor của C# Refactory for Visual Studio .NET Học viên thực hiện: Nhiêu Lập Hòa 52
  53. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 ♦ .NET Refactor for C# & VB.NET [12] .NET Refactor là một tiện ích do KnownDotNet phát triển, có khả năng tích hợp vào Visual Studio .NET để hỗ trợ một số kỹ thuật refactoring trên các mã nguồn của chƣơng trình C# và VB.NET: - Extract Expression to Function - Simlify Conditional - Extract Method - Stub New Method - Extract Interface - Refactor Strings - Extract Object - Rename Local Variable - Extract Superclass - Rename Parameters - Insert Const or String - Reorder Variables - Promote Local Variables - Replace Magic Number Hình 4.5: Màn hình trình chức năng refactor của .NET Refactor for C# & VB.NET Học viên thực hiện: Nhiêu Lập Hòa 53
  54. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 ♦ CodeIT.Once for .NET [8] CodeIT.Once for .NET là một trong các công cụ hỗ trợ phát triển phần mềm do vbCity.com và DevCity.Net phát triển. CodeIT.Once đƣợc tích hợp vào Visual Studio và cung cấp một số chức năng refactor trên mã nguồn 2 ngôn ngữ C# và VB.NET. Danh mục các kỹ thuật refactoring hỗ trợ trong CodeIT.Once for .NET: - Add Parameter - Move Class - Convert Method To Property - Promote Local Variable to Parameter - Convert Property To Method - Remove Parameters - Decompose Conditional - Rename Fileds - Inline Variable - Rename Local Variables - Introduce Constant - Rename Methods - Encapsulate Field - Rename Parameters - Extract Interface - Rename Types - Extract Method - Reorder Parameters Hình 4.6: Màn hình trình chức năng refactor của CodeIT.Once for .NET Học viên thực hiện: Nhiêu Lập Hòa 54
  55. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 ♦ JetBrains ReSharper [10] Tiện ích ReSharper đƣợc công ty phát triển giải pháp và công cụ phần mềm JetBrains xây dựng có khả năng tích hợp vào Visual Studio nhằm hỗ trợ các lập trình thực hiện refactoring trên các mã nguồn chƣơng trình C#, VB/ASP.Net và XML. Danh mục các kỹ thuật refactoring hỗ trợ trong JetBrains ReSharper: - Change Method Signature - Extract Interface - Convert Abstract Class to Interface - Extract Method - Convert Anonymous to Named Type - Inline Method - Convert Extension Method to Plain Static - InlineVariable - Convert Interface to Abstract Class - Introduce Parameter - Convert Method to Property - Introduce Variable - Convert Property to Method - Pull Down Members - Convert Static to Extension Method - Pull Up Members - Encapsulate Field - Rename - Extract Class - Replace Constructor with - Extract Superclass Factory Method Hình 4.7: Màn hình trình chức năng refactor của JetBrances ReShape Học viên thực hiện: Nhiêu Lập Hòa 55
  56. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 ♦ DevExpress Refactor!™ Pro [9] Danh mục các kỹ thuật refactoring hỗ trợ trong DevExpress Refactor!™ Pro: - Add to Interface - Introduce Constant - Combine Conditionals - Introduce Parameter - Convert Method to Property - Introduce Variable - Convert Property to Method - Promote Local Variable to Parameter - Decompose Conditional - Simplify/Split Conditional - Decompose Parameter - Simplify Expression - Encapsulate Field - Rename - Extract Class - Remove Redundant Assignment - Extract Interface - Remove Redundant Conditional - Extract Method - Reorder Parameters/Variables - Extract Property - Replace Temp with Query Hình 4.8: Màn hình trình chức năng refactor của DevExpress Refactor!™ Pro Học viên thực hiện: Nhiêu Lập Hòa 56
  57. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 IV.1.3 Thử nghiệm minh họa các công cụ hỗ trợ refactor trong VS.Net Dựa trên các chức năng refactoing trong các công cụ tiện ích đƣợc tích hợp vào Visual Studio .NET. Phần nội dung dƣới dây sẽ hiện thực minh họa việc áp dụng một số kỹ thuật refactoring trong việc dò tìm và cải tiến mã xấu trong mã nguồn chƣơng trình C#: - Change Signature - Convert Method to Property - Decompose/ Simplify Conditional - Encapsulate Field - Extract Interface - Extract Method - Inline Variable - Promote Local Variable to Parameter - Rename ♦ Change Signature Change Signature cho phép thực hiện việc thay đổi tính chất/đặc trƣng của một phƣơng thức: thêm mới, xóa bới, hoán đổi trật tự hoặc thay đổi kiểu các tham số cũng nhƣ kiểu trả về của phƣơng thức Xét đoạn chƣơng trình thực hiện phƣơng thức Add để giá trị của cộng 2 số nguyên: public class Calculator { public int Add(int number1, int number2) { return number1 + number2; } } Một trong những dấu hiệu có thể nhận biết và tái cấu trúc của đoạn chƣơng trình này là chuyển đối kiểu của tham số và trả về phƣơng thức từ kiểu int sang double để có phạm vi hoạt động rộng hơn. public class Calculator { public double Add(double number1, double number2) { return number1 + number2; } } Học viên thực hiện: Nhiêu Lập Hòa 57
  58. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Hình 4.9: Màn hình minh họa kỹ thuật Change Signature trong JetBrains ReSharper Hình 4.10: Màn hình kết quả minh họa kỹ thuật Change Signature Học viên thực hiện: Nhiêu Lập Hòa 58
  59. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 ♦ Convert Method to Property Convert Method to Property cho phép chuyển đổi các phƣơng thức truy vấn (get) và thiết lập (set) trên một trƣờng dữ liệu thành các đặc tính của trƣờng dữ liệu đó trong lớp. Xét đoạn chƣơng trình có nội dung nhƣ sau: namespace RefactoringExamples { class Foo { static int x; public static int GetX() { return x; } public static void SetX(int value) { x = value; } public static void bar() { int myX = Foo.GetX(); Foo.GetX() = myX + 1; } } } Ta thấy rằng để truy xuất và gán giá trị cho biến x trong lớp Foo phải thông qua 2 hàm là SetX() và GetX() trông rất bất tiện và không tự nhiên. Bây giờ chúng ta sẽ có một cách đơn giản hơn là chuyển đổi các phƣơng thức trên sang tính chất properties của trƣờng dữ liệu x thông qua kỹ thuật Convert Method to Property Kết quả sau khi chuyển đổi sẽ nhƣ sau: namespace RefactoringExamples { class Foo { static int x; public static int X { get { return x; } set { x = value; } } public static void bar() { int myX = Foo.X; Foo.X = myX + 1; } } } Bây giờ chƣơng trình trông gọn gàng, dễ nhìn và tự nhiên hơn khi sử dụng. Học viên thực hiện: Nhiêu Lập Hòa 59
  60. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Hình 4.11: Màn hình minh họa kỹ thuật Convert Method to Property của CodeIT.Once Học viên thực hiện: Nhiêu Lập Hòa 60
  61. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Hình 4.12: Màn hình minh họa kỹ thuật Convert Method to Property của ReSharper Hình 4.13: Màn hình kết quả kỹ thuật Convert Method to Property Học viên thực hiện: Nhiêu Lập Hòa 61
  62. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 ♦ Decompose/Simplify Conditional Decompose/Simplify Conditional cho phép thực hiện việc chuyển đổi/đơn giản hóa một biểu thức phức tạp trong thân mệnh đề điều kiện if thành một phƣơng thức có kiểu trả về kiểu luận lý (đúng hoặc sai) public class SampleApp { public const string SummerStart = "6/1/2005 12:00:00 AM"; private const string SummerEnd = "8/31/2005 12:00:00 AM"; private static float _winterRate = 5.0; private static float _summerRate = 5.4; private static double CalculateCharge(int quantity) { int charge; System.DateTime chargeDate = System.DateTime.Now; if (System.DateTime.Compare(chargeDate, SummerStart) > 0 | System.DateTime.Compare(chargeDate, SummerEnd) 0 | System.DateTime.Compare(chargeDate, SummerEnd) < 0); } } Học viên thực hiện: Nhiêu Lập Hòa 62
  63. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Hình 4.14: Màn hình minh họa kỹ thuật Decompose/Simplify Conditional Hình 4.15: Màn hình kết quả kỹ thuật Decompose/Simplify Conditional Học viên thực hiện: Nhiêu Lập Hòa 63
  64. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 ♦ Encapsulate Field Encapsulate Field cho phép chuyển đổi và đóng gói các trƣờng dữ liệu trong lớp. Một chƣơng trình đƣợc thiết kế nhƣ sau: public struct MyPoint { public int x; public int y; public void SetLocation() { Console.Write("Enter X position: "); x = int.Parse(Console.ReadLine()); Console.Write("Enter Y position: "); y = int.Parse(Console.ReadLine()); } public void PrintLocation() { // Print new location. Console.WriteLine("[{0}, {1}]", x, y); } } Lỗi dễ nhận thấy nhất trong thiết kế này là các trƣờng dữ liệu x, y của cấu trúc MyPoint đƣợc khai báo dƣới dạng public thay vì nên đƣợc khai báo private để đảm bảo sự đóng gói cần thiết. Sử dụng kỹ thuật Encapsulate Field để tái cấu trúc mã nguồn chƣơng trình này public struct MyPoint { private int x; public int X { get { return x; } set { x = value; } } private int y; public int Y { get { return y; } set { y = value; } } public void SetLocation() { Console.Write("Enter X position: "); X = int.Parse(Console.ReadLine()); Console.Write("Enter Y position: "); Y = int.Parse(Console.ReadLine()); } public void PrintLocation() { // Print new location. Console.WriteLine("[{0}, {1}]", X, Y); } } Học viên thực hiện: Nhiêu Lập Hòa 64
  65. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Hình 4.16: Màn hình minh họa kỹ thuật Encapsulate Field của Refactor trong VS.NET Ghi chú: Áp dụng trên cả 2 biến x và y Học viên thực hiện: Nhiêu Lập Hòa 65
  66. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Hình 4.17: Màn hình minh họa kỹ thuật Encapsulate Field của Visual Assit X for .NET Hình 4.18: Màn hình kết quả kỹ thuật Encapsulate Field Học viên thực hiện: Nhiêu Lập Hòa 66
  67. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 ♦ Extract Interface Extract Interface cho phép định nghĩa một giao diện của lớp. Một đặc tính rất ling động của C# trong việc thay thế sự đa kế thừa. Một lớp đối tƣợng TwoDShape thể hiện các đặc tính hình vẽ trong môi trƣờng 2D đƣợc lập trình nhƣ sau: class TwoDShape { public void Draw() { /* Some interesting code. */ } public Rectangle GetBoundingRect() { /* More interesting code. */ } public Color GetArgb() { /* Even more interesting code. */ } } Trong trƣờng hợp này, chúng ta có thể sử dụng đặc tính Interface(giao diện) trong ngôn ngữ C# nhằm đảm bảo một sự ràng buộc, giao kết cho lớp phải thực hiện một số các phƣơng thức bắt buộc. Ở đây chúng ta sẽ định nghĩa một interface IRender cho lớp TwoDShape, lớp này sẽ có các phƣơng thức Draw(), GetBoundingRect(), GetArgb() đƣợc khai báo trong giao diện IRender class TwoDShape : IRender { public void Draw() { /* Some interesting code. */ } public Rectangle GetBoundingRect() { /* More interesting code. */ } public Color GetArgb() { /* Even more interesting code. */ } } interface IRender { void Draw(); Color GetArgb(); Rectangle GetBoundingRect(); } Ghi chú: Interface giống nhƣ một lớp chỉ chứa các phƣơng thức trừu tƣợng, và đƣợc sử dụng thay thế cho các lớp trừu tƣợng để tạo ra các sự ràng buộc giữa những lớp và các thành phần client của nó. Học viên thực hiện: Nhiêu Lập Hòa 67
  68. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Hình 4.19: Màn hình minh họa kỹ thuật Extract Interface của Refactor trong VS.NET Học viên thực hiện: Nhiêu Lập Hòa 68
  69. Luận văn tốt nghiệp cao học – Khóa 2005 - 2008 Hình 4.20: Màn hình minh họa kỹ thuật Extract Interface của CodeIT.Once Hình 4.21: Màn hình kết quả kỹ thuật Extract Interface Học viên thực hiện: Nhiêu Lập Hòa 69