Đăng ký Đăng nhập
Trang chủ Công nghệ thông tin Kỹ thuật lập trình Bài tập lớn nhập môn công nghệ phần mềm đề tài refactoring...

Tài liệu Bài tập lớn nhập môn công nghệ phần mềm đề tài refactoring

.DOC
33
475
75

Mô tả:

Bài tập lớn nhập môn công nghệ phần mềm đề tài refactoring
Bài tập lớn Nhập môn Công nghệ Phần mềm Đề tài: Refactoring Sinh viên : Lê Ngọc Minh Lớp : Khoa học máy tính Khoá : 52 SHSV : 20071946 Giáo viên : Lê Đức Trung Hà Nội, tháng 11/2015 Page 1 Mục lục Chương 1. Giới thiệu 4 Chương 2. Các nguyên lý về refactoring 5 2.1. Định nghĩa refactoring 5 2.2. Hai chiếc mũ 5 2.3. Tại sao cần refactor? 6 2.3.1. Refactoring giúp cải thiện thiết kế 6 2.3.2. Refactoring giúp phần mềm dễ hiểu hơn 6 2.3.3. Refactoring giúp bạn lập trình nhanh hơn 6 2.4. Khi nào nên refactor? 7 2.4.1. Nguyên tắc 3 lần 7 2.4.2. Refactor khi thêm chức năng 7 2.4.3. Refactor khi sửa lỗi 7 2.4.4. Refactor khi xem lại mã nguồn (code review)7 2.5. Các vấn đề khi refactor 7 2.5.1. Cơ sở dữ liệu 7 2.5.2. Thay đổi giao diện 8 2.6. Refactoring với thiết kế 8 2.7. Refactoring với hiệu năng 9 Chương 3. Nhận biết mã nguồn cần refactor 10 3.1. Trùng lặp mã 10 3.2. Phương thức dài 10 3.3. Lớp lớn 11 Chương 4. Xây dựng bộ kiểm thử 12 4.1. Giá trị của mã kiểm thử 12 Chương 5. Các phương pháp refactor 13 5.1. Định dạng của các phương pháp refactor 13 5.2. Trích phương thức 13 5.2.1. Tóm tắt 13 5.2.2. Động cơ 14 5.2.3. Ví dụ: Không có biến nội bộ 14 5.2.4. Ví dụ: Có sử dụng biến nội bộ 15 5.2.5. Ví dụ: Gán lại một biến nội bộ 16 5.3. Nhập phương thức (Inline method) 17 5.3.1. Tóm tắt 17 5.3.2. Động cơ 18 5.4. Nhập biến tạm 18 5.4.1. Tóm tắt 18 Page 2 5.4.2. Động cơ 19 5.5. Di chuyển phương thức 19 5.5.1. Tóm tắt 19 5.5.2. Động cơ 19 5.5.3. Ví dụ 19 Chương 6. Các công cụ refactor 21 6.1. Refactor bằng công cụ 21 6.2. Refactor mã nguồn Java bằng Eclipse 21 6.2.1. Thực đơn refactor 21 6.2.2. Đổi tên 22 6.2.3. Trích phương thức 23 6.2.4. Nhập phương thức 23 6.2.5. Nhập biến tạm 24 6.2.6. Di chuyển phương thức 24 Phụ lục A. Tài liệu tham khảo 26 Phụ lục B. Danh sách “bad smells in code” 27 Phụ lục C. Danh sách các phương pháp refactor 28 Phụ lục D. Các công cụ refactor mã nguồn Java trong Eclipse Helios 31 Page 3 Chương 1. Giới thiệu Refactoring là quá trình thay đổi một hệ thống phần mềm nhằm cái tiến cấu trúc bên trong nhưng không làm biến đổi hành vi bên ngoài của nó. Refactoring là việc dọn dẹp mã nguồn một cách có tổ chức sao cho tối thiểu hoá khả năng gây ra lỗi. Về bản chất refactoring là nâng cấp thiết kế của mã nguồn sau khi nó đã được viết ra. Trong những hiểu biết thông thường về công nghệ phần mềm, chúng ta thiết kế trước sau đó mới cài đặt. Cần có thiết kế tốt trước sau đó mới có cài đặt tốt. Qua thời gian, mã nguồn bị sửa đổi và sự nhất quán của hệ thống, cấu trúc của nó theo thiết kế, dần dần mờ nhạt. Quá trình cài đặt chuyển dần từ chế tạo sang chắp vá. Refactoring là cách làm ngược lại. Khi refactor bạn có thể lấy một thiết kế tồi, thậm chí là hỗn loạn, và làm lại để nó trở thành một mã nguồn được thiết kế tốt. Mỗi bước đều đơn giản, thậm chí rất đơn giản. Bạn chỉ chuyển một trường từ lớp này sang lớp khác, lấy vài đoạn mã ra khỏi phương thức để tạo ra phương thức riêng và đẩy vài đoạn mã lên hay xuống cây thừa kế. Tuy nhiên tác động tích luỹ của những thay đổi nhỏ đó có thể cái thiện đáng kể thiết kế. Đó là sự đảo ngược của khái niệm software decay. Khi refactor bạn sẽ nhận thấy sự phân phối công việc thay đổi. Thiết kế thay vì diễn ra đầu tiên, lại diễn ra liên tục trong suốt quá trình phát triển. Bạn học được cách cải tiến thiết kế từ việc xây dựng hệ thống. Kết quả của sự tương tác này dẫn đến một chương trình với thiết kế luôn tốt khi quá trình phát triển tiếp diễn. Qua quá trình phát triển của công nghệ phần mềm, cùng với sự phát triển mạnh mẽ của phần cứng thì yêu cầu về hiệu suất xử lý ngày càng ít quan trọng thay vào đó, tính dễ hiểu được đề cao. Do đó các kỹ thuật refactoring cũng ngày càng được chú ý nhằm nâng cao chất lượng phần mềm. Thực tế học tập của sinh viên Việt Nam cho thấy các phần mềm được viết trên ghế nhà trường thường không có được thiết kế tốt ngay từ ban đầu. Dẫn đến tình trạng này có rất nhiều nguyên nhân chủ quan lẫn khách quan như thiếu kinh nghiệm, thiếu thời gian, chưa chú trọng đến quy trình phát triển phần mềm v.v... Để những phần mềm này vượt ra khỏi phạm vi của những bài tập lớn, đồ án môn học, đồ án tốt nghiệp... tiếp tục phát triển thành sản phẩm thực tế và thành công trong cuộc sống thì kỹ năng refactoring là rất cần thiết. Page 4 Chương 2. Các nguyên lý về refactoring 2.1. Định nghĩa refactoring Thuật ngữ refactoring được phát minh bởi Ward Cunningham và Kent Beck vào khoảng những năm 1980 khi làm việc với ngôn ngữ Smalltalk. Trong cuốn sách “Refactoring: Improving the Design of Existing Code” cung cấp hai định nghĩa như sau: Refactoring (danh từ): một sự thay đổi ở cấu trúc bên trong của phần mềm giúp nó dễ hiểu và dễ sửa đổi hơn mà không làm thay đổi hành vi bên ngoài. Refactor (động từ): tái cấu trúc phần mềm bằng cách áp dụng một loạt thao tác refactoring mà không làm thay đổi hành vi bên ngoài. Trong một số tài liệu tiếng Việt, refactoring được chuyển ngữ thành “cải tiến mã nguồn” tuy nhiên cách nói này khá dài dòng và cũng không diễn tả được hết ý nghĩa của thuật ngữ. Tài liệu này sẽ để nguyên thuật ngữ tiếng Anh với hai dạng như trên, ngoài ra refactoring cũng được hiểu là tổng hợp các phương pháp và công cụ để tiến hành refactor phần mềm nói chung. Như vậy, hiểu theo nghĩa nào đó refactor chỉ đơn giản là dọn dẹp mã nguồn. Tuy nhiên bằng cách áp dụng những phương pháp, công cụ của refactoring, việc dọn dẹp mã nguồn sẽ hiệu quả hơn đáng kể. Cần phân biệt refactoring và tối ưu hoá hiệu năng (performance optimization). Mục đích của refactoring là làm cho phần mềm dễ hiểu và dễ sửa chữa hơn. Tối ưu hoá hiệu năng cũng không làm thay đổi hành vi bên ngoài của phần mềm (trừ tốc độ) mà chỉ thay đổi cấu trúc bên trong tuy nhiên việc tối ưu hoá thường dẫn đến những đoạn mã nguồn khó hiểu và khó sửa chữa. Một đặc tính quan trọng nữa của refactoring là không thay đổi hành vi bên ngoài của phần mềm. Phần mềm vẫn phải thực hiện những chức năng giống hệt những gì nó làm trước đó. Người dùng, kể cả end user và lập trình viên, không thể nhận ra rằng có gì đó vừa thay đổi. 2.2. Hai chiếc mũ Kent Beck sử dụng hình ảnh hai chiếc mũ để chỉ quá trình phát triển phần mềm có refactor. Bạn chia thời gian ra thành hai hoạt động tách biệt: thêm chức năng và refactor. Khi thêm chức năng, bạn không nên thay đổi những đoạn mã đã có, bạn chỉ thêm vào những khả năng mới. Bạn có thể đo tiến độ bằng cách tạo ra các bộ test và đảm bảo nó chạy. Khi refactor, bạn không thêm chức năng nào mà chỉ tái cấu trúc mã nguồn. Bạn cũng không thêm bộ test nào (trừ khi phát hiện ra một bộ test bạn đã bỏ lỡ trước đó) và chỉ thay đổi một bộ test khi thực sự cần thiết để phù hợp với sự thay đổi của giao diện. Page 5 2.3. Tại sao cần refactor? 2.3.1. Refactoring giúp cải thiện thiết kế Không có refactoring, thiết kế của phần mềm sẽ phân rã (software decay). Khi mã nguồn được thay đổi để hiện thực hoá những mục tiêu ngắn hạn hoặc thay đổi mà không hiểu đầy đủ thiết kế, mã nguồn mất dần cấu trúc. Ngày càng khó nhận ra thiết kế bằng cách đọc mã nguồn. Refactoring giống như sắp xếp lại mã nguồn. Bạn xếp lại những gì không ở đúng chỗ của nó. Sự mất cấu trúc của mã nguồn có hiệu ứng tích luỹ. Càng khó nhận ra thiết kế từ mã nguồn thì càng khó duy trì thiết kế đó. Refactoring thường xuyên có thể giúp mã nguồn giữ được hình dạng. Mã nguồn được thiết kế không tốt thường chữa nhiều đoạn mã làm cùng một việc, thông thường là do mã nguồn hay làm cùng một việc ở những chỗ khác nhau. Vì thế một khía cạnh quan trọng của cải tiến thiết kế là xoá bỏ những đoạn mã trùng lặp. Tầm quan trọng của việc này nằm ở những thay đổi mã nguồn trong tương lai. Giảm lượng mã không giúp hệ thống chạy nhanh hơn chút nào nhưng lại khiến việc sửa đổi dễ dàng hơn rất nhiều. Có ít mã cần phải hiểu hơn và chỉ cần sửa ở một nơi sự thay đổi sẽ được áp dụng trên toàn bộ hệ thống. 2.3.2. Refactoring giúp phần mềm dễ hiểu hơn Lập trình giống như cuộc trò chuyện với máy tính. Bạn viết mã để bảo máy tính cần phải làm gì và nó sẽ trả lời bằng cách làm chính xác điều bạn bảo. Bạn cần xoá bỏ khoảng cách giữa điều bạn muốn máy tính làm với điều bạn nói với nó. Lập trình theo cách hiểu này chỉ đơn giản là nói chính xác điều bạn muốn. Tuy nhiên còn có những người khác cần sử dụng mã nguồn của bạn. Ai đó sẽ thử đọc mã nguồn của bạn trong thời gian vài tháng để có thể sửa đổi vài chỗ. Chúng ta dễ quên mất người sử dụng bổ sung đó, trong khi họ thực ra lại là những người quan trọng nhất. Ai bận tâm nếu máy tính mất thêm vài xung nhịp để dịch? Nhưng sự khác biệt sẽ rất lớn nếu một lập trình viên mất một tuần để thực hiện một sự sửa đổi trong khi đáng lẽ mất vài giờ nếu anh ta hiểu mã nguồn của bạn. Bạn cũng có thể sử dụng refactoring như một cách để hiểu những đoạn mã của người khác. Đọc một vài dòng mã và cố gắng thay đổi nó để phản ánh đúng hơn cách hiểu của bạn sau đó thử chạy lại xem nó còn làm việc hay không. Với cách làm này dần dần bạn sẽ có hiểu biết chắc chắn về hệ thống và việc trở lại những đoạn mã đã được refactoring sẽ dễ dàng hơn nhiều. 2.3.3. Refactoring giúp bạn lập trình nhanh hơn Điều này nghe như một nghịch lý. Để nâng cao chất lượng mã nguồn, chúng ta cần đầu tư thời gian vào việc refactoring, chẳng phải việc đó sẽ làm giảm năng suất? Page 6 Thực tế chứng minh rằng thiết kế tốt là rất quan trọng đối với tốc độ phát triển phần mềm. Không có một thiết kế tốt, bạn có thể tiến nhanh một lúc nhưng sau đó sẽ sớm chậm lại. Bạn phải dành thời gian tìm và sửa lỗi thay vì thêm chức năng mới. Việc sửa đổi lâu hơn vì bạn phải cố hiểu hệ thống và tìm những đoạn mã trùng lặp. Thiết kế tốt là điều kiện cần để duy trì tốc độ phát triển phần mềm và refactoring giúp bạn chống lại sự phân rã (decay) của thiết kế, thậm chí còn cải thiện thiết kế nữa. 2.4. Khi nào nên refactor? 2.4.1. Nguyên tắc 3 lần Lần đầu tiên bạn làm gì đó, hãy làm. Lần thứ hai bạn làm điều tương tự, bạn nhận ra sự lặp lại nhưng vẫn làm. Lần thứ ba bạn làm điều tương tự, hãy refactor. 2.4.2. Refactor khi thêm chức năng Hoặc một phần đoạn mã cần viết đã được viết ở đâu đó hoặc bạn nhận thấy việc thay đổi chút ít thiết kế có thể giúp cài đặt chức năng nhanh chóng hơn, hãy refactor. Đừng chỉ bỏ qua những sai sót trong thiết kế, hãy refactor nó để công việc tiếp theo được thuận lợi hơn. 2.4.3. Refactor khi sửa lỗi Lỗi được báo cáo là dấu hiệu cho thấy cần refactor. Refactoring giúp bạn hiểu rõ mã nguồn hơn và tìm và loại bỏ lỗi dễ dàng hơn. 2.4.4. Refactor khi xem lại mã nguồn (code review) Xem lại mã nguồn giúp lan truyền kiến thức trong nhóm phát triển, tạo cơ hội cho các lập trình viên cũ truyền kinh nghiệm cho những lập trình viên mới hơn. Refactor khiến người review không chỉ tưởng tượng ra mã nguồn trông ra sao với những đề nghị của mình mà thực sự nhìn thấy nó. Quá trình xem lại có kết quả rõ ràng hơn, không chỉ là một vài đề nghị mà là những đề nghị đã được thực hiện. 2.5. Các vấn đề khi refactor 2.5.1. Cơ sở dữ liệu Nhiều ứng dụng gắn chặt với lược đồ dữ liệu hỗ trợ nó, đây là một lý do khiến cơ sở dữ liệu khó thay đổi. Một lý do khác là việc chuyển đổi dữ liệu có thể rất lâu và nguy hiểm. Với các cơ sở dữ liệu phi đối tượng một cách giải quyết là đặt một tầng phần mềm giữa mô hình đối tượng và mô hình cơ sở dữ liệu. Bằng cách đó bạn có thể tách rời các thay đổi giữa hai mô hình. Bạn cũng không cần thiết phải tạo một lớp riêng biệt từ đầu. Bạn có thể xây dựng lớp này khi nhận ra mô hình đối tượng không còn ổn định. Page 7 Cơ sở dữ liệu hướng đối tượng vừa hỗ trợ vừa cản trở. Một vài cơ sở dữ liệu hướng đối tượng có khả năng chuyển đổi tự động từ phiên bản này sang phiên bản khác của đối tượng. Nếu không, bạn phải rất cẩn thận khi chuyển đổi dữ liệu bằng tay. Bạn có thể thoải mái di chuyển các hành vi nhưng với các trường thì phải cần thận. Có thể sử dụng hàm truy cập để tạo ra cảm giác là dữ liệu đã được chuyển và khi chắc chắn về sự thay đổi bạn có thể thực sự di chuyển trường. 2.5.2. Thay đổi giao diện Khi làm việc với đối tượng bạn có thể tuỳ ý thay đổi nội dung bên trong miễn là giao diện vẫn giữ nguyên. Nếu cần thay đổi giao diện và bạn có thể thay đổi tất cả những nơi sử dụng nó, bạn chỉ việc sửa tất cả đồng thời. Tuy nhiên nếu bạn có một giao diện đã được công bố, mọi việc trở nên phức tạp hơn nhiều. Bạn không thể sửa những đoạn mã do người khác viết sử dụng giao diện của bạn. Bạn cần một quy trình phức tạp hơn nhiều. Khi refactor một giao diện đã được công bố, bạn cần giữ lại toàn bộ giao diện cũ, ít nhất cho đến khi người sử dụng có thể phản ứng với sự thay đổi. Nếu bạn đổi tên một hàm, hãy giữ lại hàm cũ và cho nó gọi đến hàm mới. Đừng sao chép thân hàm vì bạn sẽ lại mất thời gian xử lý sự trùng lặp mã nguồn. Bạn cũng nên sử dụng công cụ deprecate của Java để thông báo những chỗ bị phản đối. Công bố giao diện có ích lợi của nó nhưng cũng gây nhiều khó khăn vì vậy nếu có thể, đừng công bố giao diện. Tất nhiên nếu bạn thiết kế thư viện lập trình, điều đó là không tránh khỏi. Nhưng nếu bạn làm một phần mềm với nhóm làm việc gồm ba người, đừng công bố giao diện từ người này đến người kia. Đơn giản là hãy mở mã nguồn ra và sửa đổi. 2.6. Refactoring với thiết kế Một số người ví thiết kế với bản vẽ của kĩ sư và lập trình với công việc của thợ xây. Tuy nhiên phần mềm khác với nhà cửa đường xá. Alistair Cockburn nói, “Khi thiết kế tôi có thể nghĩ rất nhanh nhưng suy nghĩ của tôi đầy những lỗ hổng nhỏ.” Một số khác tranh luận rằng refactoring là sự thay thế cho thiết kế. Trong cách tiếp cận này bạn hoàn toàn không thiết kế chút nào. Bạn chỉ lập trình theo cách đầu tiên nghĩ ra, làm cho nó chạy đúng và sau đó refactor. Thực tế cách làm này có thể thực hiện được nhưng không phải cách hiệu quả nhất. Khi áp dụng refactoring, bạn không đặt áp lực lên thiết kế ban đầu. Bạn không tìm kiếm giải pháp tốt nhất mà chấp nhận một giải pháp hợp lý. Cùng với quá trình phát triển và hiểu rõ hơn bài toán bạn phát hiện ra những giải pháp hợp lý hơn mà đưa nó vào hệ thống. Page 8 Một kết quả quan trọng của refactoring là sự đơn giản trong thiết kế. Với cách thiết kế hoàn chỉnh từ đầu, bạn cần tìm ra giải pháp thật mềm dẻo để đáp ứng sự thay đổi của yêu cầu. Vấn đề là thiết kế mềm dẻo thì phức tạp và tốn kém hơn thiết kế đơn giản. Khi tiếp cận vấn đề với refactoring, bạn sẽ tự hỏi “Để refactor thiết kế đơn giản này thành thiết kế mềm dẻo có khó không?” Nếu câu trả lời là “khá dễ”, bạn chỉ việc cài đặt giải pháp đơn giản. Như vậy refactoring dẫn đến những thiết kế đơn giản hơn mà không phải hy sinh sự mềm dẻo. Khi bạn đã có một nhận thức nhất định về những gì có thể refactor dễ dàng, bạn thậm chí không nghĩ đến thiết kế mềm dẻo nữa. Chỉ việc xây dựng những gì đơn giản nhất làm việc được. Hầu hết thời gian bạn sẽ không cần dùng đến những thiết kế mềm dẻo và phức tạp nữa. 2.7. Refactoring với hiệu năng Mối quan ngại phổ biến với refactoring là làm giảm hiệu năng của chương trình. Trong hầu hết trường hợp refactoring thực sự làm chương trình chậm đi. Tuy nhiên bí mật của những chương trình chạy nhanh, trừ những trường hợp cực kì nghiêm ngặt, là trước tiên nó được làm để dễ dàng tinh chỉnh, sau đó mới được tinh chỉnh để đạt tốc độ mong muốn. Một điều thú vị về hiệu năng là các chương trình thường tốn nhiều thời gian nhất vào một phần nhỏ của mã nguồn. Do vậy nếu bạn tối ưu hoá toàn bộ mã nguồn như nhau thì bạn sẽ lãng phí 90% công sức vào việc tối ưu hoá những đoạn mã không được chạy nhiều. Áp dụng nhận xét này bạn sẽ viết những chương trình tốt mà không để ý nhiều đến hiệu năng. Đến khi bắt đầu bước tối ưu hoá, thường khá muộn trong quá trình phát triển, bạn bắt đầu sử dụng chương trình profiler để phân tích những đoạn mã tốn thời gian và bộ nhớ nhiều nhất. Tập trung vào những điểm nóng này, bạn sửa đổi mã nguồn và kiểm tra sự tiến bộ về hiệu năng cho đến khi đạt được mục tiêu. Refactoring giúp sự tối ưu hoá dễ dàng hơn theo hai cách. Thứ nhất, vì cài đặt nhanh hơn, bạn có nhiều thời gian hơn để tối ưu hoá. Thứ hai, mã nguồn được chia nhỏ hơn để dễ dàng phát hiện những điểm nóng và dễ hiểu hơn để bạn tối ưu hoá dễ dàng. Page 9 Chương 3. Nhận biết mã nguồn cần refactor Hiểu thế nào là refactoring không có nghĩa là bạn đã biết nên refactor lúc nào và ở đâu. Mục đích của chương này là đưa ra những chỉ dẫn để bạn nhận biết những vấn đề trong mã nguồn và biết cách khắc phục. Các tác giả Martin Fowler và Kent Beck gọi chúng là “bad smells in code” (tạm dịch: mùi hôi trong mã nguồn). Đây không phải là những thước đo chuẩn để nói rõ đoạn mã nào có vấn đề và đoạn mã nào không, những thước đo như thế thậm chí có thể không tồn tại. Tuy nhiên bạn sẽ xây dựng được cho mình những cảm nhận riêng rằng bao nhiêu biến là quá nhiều, phương thức bao nhiêu dòng là quá dài... Danh sách các “bad smells” được đề xuất bởi Martin Fowler và Kent Beck tương đối dài, ở đây xin giới thiệu một số vấn đề thường gặp. Hãy tham khảo Phụ lục B để xem danh sách đầy đủ. 3.1. Trùng lặp mã Đứng đầu trong những “mùi hôi” là mã nguồn trùng lặp. Nếu bạn thấy cùng một cấu trúc xuất hiện ở nhiều nơi, bạn có thể chắc chắn rằng sẽ tốt hơn nếu bạn hợp nhất chúng lại. Đơn giản nhất là trường hợp cùng một biểu thức xuất hiện trong hai phương thức của cùng một lớp. Khi đó bạn chỉ việc Trích phương thức và gọi nó từ cả hai nơi. Một vấn đề thường gặp khác là khi cùng một biểu thức được sử dụng ở hai lớp chị em. Bạn có thể xoá bỏ sự trùng lặp này bằng cách Trích phương thức ở cả hai lớp rồi sau đó Kéo lên. Nếu đoạn mã tương tự nhưng không giống hệt nhau, bạn cần Trích phương thức để tách đoạn giống và đoạn khác nhau. Sau đó bạn có thể Tổ chức phương thức mẫu. Nếu các phương thức làm cùng một việc với những giải thuật khác nhau, bạn có thể chọn giải thuật rõ ràng hơn và sử dụng Thay thế giải thuật. Nếu bạn có những đoạn mã trùng lặp trong các lớp không liên quan, hãy xem xét đến việc sử dụng Trích lớp từ một lớp và sử dụng thành phần mới ở lớp còn lại. Một khả năng khác là phương thức đáng lẽ chỉ thuộc về một lớp và được gọi bởi lớp còn lại hoặc phương thức thuộc về một lớp thứ ba và được sử dụng bởi cả hai lớp. Bạn cần quyết định xem phương thức có nghĩa ở đâu và đảm bảo rằng nó phải ở đó mà không phải nơi nào khác. 3.2. Phương thức dài Chương trình hướng đối tượng tốt nhất và sống lâu nhất là chương trình có các phương thức ngắn. Mọi người đều nhận ra rằng phương thức càng dài thì càng khó hiểu. Các phương thức ngắn với tên gọi được đặt hợp lý có thể tiết kiệm rất nhiều thời gian vì bạn Page 10 không cần đọc thân hàm. Trong 99% trường hợp, tất cả những gì cần làm để rút ngắn phương thức là Trích phương thức. Hãy tìm những phần của phương thức kết hợp được với nhau và tạo ra phương thức mới. Nếu bạn có một phương thức với rất nhiều tham số và biến tạm, những thành phần này có thể cản trở việc trích phương thức. Bạn sẽ phải truyền quá nhiều tham số và biến tạm như là tham số cho những phương thức được trích ra và kết quả còn khó đọc hơn cả lúc đầu. Bạn có thể sử dụng Thay biến tạm bởi truy vấn để loại bỏ một số biến tạm. Danh sách tham số dài có thể được trở thành gọn gàng với các phương pháp Tạo đối tượng tham số và Giữ đối tượng nguyên vẹn. Nếu bạn đã thực hiện những cách trên mà vẫn có quá nhiều biến tạm và tham số, đã đến lúc dùng đến pháo hạng nặng: Thay phương thức bằng đối tượng phương thức. Làm thế nào để xác định những đoạn mã có thể trích ra? Cách khá tốt là tìm chú thích. Một đoạn mã với chú thích chỉ ra rằng nó có thể được thay bởi một phương thức có tên dựa trên nội dung chú thích. Thậm chí một dòng cũng đáng để trích nếu nó cần phải được giải thích. Các câu lệnh điều kiện và vòng lặp cũng là dấu hiệu cần trích phương thức. Sử dụng Phân ly điều kiện để xử lý các biểu thức điều kiện. Với vòng lặp, trích nó và mã nguồn bên trong thành một phương thức riêng. 3.3. Lớp lớn Khi một lớp làm quá nhiều việc, nó thường thể hiện ở chỗ có quá nhiều biến thể hiện (instance variables). Khi một lớp có quá nhiều biến thể hiện, trùng lặp mã nguồn chắc chắn không xa. Bạn có thể Trích lớp để nhóm một số biến. Chọn những biến có thể chung sống với nhau trong một thành phần. Chẳng hạn “depositAmount” và “depositCurrency” có nhiều khả năng cùng thuộc về một thành phần. Nói chung, chia sẻ một tiền tố hoặc hậu tố trong một tập con của các biến trong một lớp gợi ý cần một thành phần mới. Nếu thành phần này thích hợp làm lớp con, bạn sẽ thấy Trích lớp con dễ thực hiện hơn. Đôi khi một lớp không sử dụng tất cả các biến thể hiện của nó trong tất cả thời gian. Nếu thế, bạn có thể áp dụng Trích lớp và Trích lớp con nhiều lần. Cũng như lớp có quá nhiều biến, một lớp có quá nhiều mã là mảnh đất màu mỡ cho sự trung lặp mã nguồn, hỗn loạn và sự thất bại. Giải pháp đơn giản nhất là giảm sự dư thừa trong bản thân lớp. Nếu bạn có những phương thức dài 500 dòng mã với rất nhiều phần chung, bạn có thể biến nó thành năm phương thức mười dòng với mười phương thức hai dòng trích từ phương thức ban đầu. Page 11 Giải pháp thường gặp cho trường hợp này là Trích lớp và Trích lớp con. Một mẹo hữu ích là sử dụng Trích giao diện để xác định khách hàng sử dụng lớp như thế nào. Cách này có thể cho bạn một vài ý tưởng về cách chia nhỏ thêm lớp. Nếu lớp lớn của bạn lại là một lớp GUI, bạn có thể phải chuyển dữ liệu và hành vi sang một đối tượng nghiệp vụ khác. Điều này có thể cần phải nhân bản dữ liệu chuyển cả hai nơi và giữ dữ liệu đồng bộ. Nhân bản dữ liệu gọi ý cách thực hiện việc này. Page 12 Chương 4. Xây dựng bộ kiểm thử Nếu bạn muốn refactor, điều kiện tiên quyết là phải có bộ kiểm thử chắc chắn. Ngay cả khi bạn có những công cụ có thể tự động hoá refactoring, bạn vẫn cần kiểm thử. Sẽ còn rất lâu nữa trước khi mọi thao tác refactor đều có thể thực hiện bằng công cụ. 4.1. Giá trị của mã kiểm thử Nếu bạn quan sát hầu hết các lập trình viên sử dụng thời gian của họ, bạn sẽ nhận ra rằng viết mã thực ra chỉ chiếm một phần nhỏ. Một ít thời gian để tìm hiểu cần phải làm gì để viết tiếp, một ít để thiết kế, nhưng hầu hết thời gian được dùng để gỡ lỗi. Chắc chắn bạn sẽ nhớ những tiếng đồng hồ dài gỡ lỗi, nhiều khi giữa đêm khuya. Mọi lập trình viên đều có thể kể chuyện một lỗi khiến anh ta mất cả ngày (hoặc nhiều hơn) để tìm ra. Sửa lỗi thường thường khá nhanh, nhưng tìm ra nó là cả một cơn ác mộng. Và khi bạn sửa một lỗi, luôn luôn có khả năng một lỗi khác sẽ xuất hiện mà bạn không biết cho đến rất lâu sau. Bạn sẽ mất nhiều năm để tìm ra lỗi đó. Mã nguồn tự kiểm tra (self-testing code) là cách để máy tính tự động kiểm tra lỗi giúp bạn. Các công cụ hiện nay cho phép bạn viết những đoạn mã khi được chạy sẽ đưa ra thông báo lỗi nếu những kỳ vọng không được thoả mãn. Ngay khi viết một hàm nào đó, hãy viết ngay mã kiểm tra sự thực hiện của hàm đó. Bạn chỉ phải viết mã này một lần, mỗi khi refactor hay bất cứ thay đổi nào khác với mã nguồn, bạn có thể chạy lại nó để đảm bảo mọi thứ vẫn hoạt động chính xác. Theo Martin Fowler, “một bộ kiểm thử là chiếc máy phát hiện lỗi mạnh mẽ loại bỏ thời gian tốn để tìm lỗi.” Trên thực tế, dành thời gian để viết mã kiểm thử là một cách tiết kiệm thời gian và phát triển phần mềm nhanh hơn. Thậm chí một số phương pháp phát triển phần mềm hiện đại yêu cầu viết mã kiểm thử trước khi viết chương trình. Bằng việc viết ra các trường hợp có thể xảy ra, bạn hiểu rõ mình mong muốn điều gì ở tính năng sắp cài đặt. Viết test cũng giúp bạn tập trung vào giao diện hơn là cài đặt (luôn luôn là thói quen tốt). Nó cũng có nghĩa bạn biết chắc khi nào hoàn thành việc viết mã: khi test chạy được. Định nghĩa refactoring yêu cầu quá trình này không làm thay đổi hành vi bên ngoài của hệ thống. Sử dụng mã kiểm thử là một cách rõ ràng để đạt được mục đích này. Mỗi khi bạn thực hiện một thao tác refactoring, hãy nhớ chạy lại toàn bộ test để đảm báo nó không gây ra lỗi trước khi thực hiện thao tác tiếp theo. Page 13 Chương 5. Các phương pháp refactor Martin Fowler trong cuốn sách của ông dẫn ra một danh sách dài các phương thức khác nhau để refactor. Trong giới hạn của tài liệu này tôi không có điều kiện trình bày hết tất cả mà chỉ chọn ra vài “gương mặt” tiêu biểu. Danh sách đầy đủ các phương pháp refactor có ở Phụ lục C. 5.1. Định dạng của các phương pháp refactor Trong các phần tiếp theo, các phương pháp refactor được trình bày với một định dạng thống nhất như sau: • Bắt đầu bằng tên của phương pháp • Sau đó là tóm tắt về tình huống bạn cần sử dụng phương pháp và nội dung phương pháp. • Phần động cơ mô tả tại sao phương pháp nên được thực hiện và những trường hợp không nên dùng. • Phần cơ chế trong sách được lược bỏ do các công cụ hiện tại đã hỗ trợ rất tốt những thao tác refactor thường dùng. • Các ví dụ cho một trường hợp rất đơn giản mà phương pháp được sử dụng để minh hoạ cách nó làm việc. 5.2. Trích phương thức 5.2.1. Tóm tắt Một đoạn mã nào đó có thể được nhóm lại. Chuyển đoạn mã thành một phương thức, đặt tên để giải thích mục đích của phương thức đó. void printOwing(double amount) { printBanner(); //print details System.out.println ("name:" + _name); System.out.println ("amount" + amount); } Chuyển thành: void printOwing(double amount) { Page 14 printBanner(); printDetails(amount); } void printDetails (double amount) { System.out.println ("name:" + _name); System.out.println ("amount" + amount); } 5.2.2. Động cơ Trích phương thức từ những phương thức dài hoặc những đoạn mã cần được chú thích là một cách để hiểu mã tốt hơn. Phương thức ngắn và được đặt tên tốt có nhiều ích lợi. Trước hết, nó tăng cơ hội được các phương thức khác sử dụng khi các phương thức được tinh chỉnh. Thứ hai, nó giúp các phương thức cấp cao hơn trông giống như một dãy các chú thích. Việc override cũng dễ dàng hơn. Đôi khi mọi người băn khoăn một phương thức nên dài bao nhiêu. Độ dài của phương thức thực ra không phải vấn đề, điều quan trọng là khoảng cách về ngữ nghĩa giữa tên phương thức và thân phương thức. Nếu việc trích rút cải thiện tính rõ ràng, hãy làm điều đó, kể cả khi tên phương thức còn dài hơn đoạn mã bạn vừa trích. 5.2.3. Ví dụ: Không có biến nội bộ void printOwing() { Enumeration e = _orders.elements(); double outstanding = 0.0; // print banner System.out.println ("**************************"); System.out.println ("***** Customer Owes ******"); System.out.println ("**************************"); // calculate outstanding while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); Page 15 } //print details System.out.println ("name:" + _name); System.out.println ("amount" + outstanding); } Ta trích ra đoạn mã in banner: void printOwing() { Enumeration e = _orders.elements(); double outstanding = 0.0; printBanner(); // calculate outstanding while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); } //print details System.out.println ("name:" + _name); System.out.println ("amount" + outstanding); } void printBanner() { // print banner System.out.println ("**************************"); System.out.println ("***** Customer Owes ******"); System.out.println ("**************************"); } 5.2.4. Ví dụ: Có sử dụng biến nội bộ Với sự tồn tại của biến nội bộ: các tham số truyền vào hoặc biến tạm được định nghĩa trong thân phương thức, ta có vấn đề cần giải quyết. Bạn cần phải xử lý các biến này hoặc chấp nhận không thể refactor được. Page 16 void printOwing() { Enumeration e = _orders.elements(); double outstanding = 0.0; printBanner(); // calculate outstanding while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); } //print details System.out.println ("name:" + _name); System.out.println ("amount" + outstanding); } Chuyển thành: void printOwing() { Enumeration e = _orders.elements(); double outstanding = 0.0; printBanner(); // calculate outstanding while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); } printDetails(outstanding); } void printDetails (double outstanding) { System.out.println ("name:" + _name); System.out.println ("amount" + outstanding); } Page 17 5.2.5. Ví dụ: Gán lại một biến nội bộ Trong trường hợp này tình hình trở nên phức tạp hơn. Ta sẽ chỉ đề cập đến biến tạm. Nếu có một phép gán vào tham số, bạn nên ngay lập tức áp dụng phương pháp Xoá phép gán vào tham số. void printOwing() { Enumeration e = _orders.elements(); double outstanding = 0.0; printBanner(); // calculate outstanding while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); } printDetails(outstanding); } Sau khi trích: void printOwing() { printBanner(); double outstanding = getOutstanding(); printDetails(outstanding); } double getOutstanding() { Enumeration e = _orders.elements(); double outstanding = 0.0; while (e.hasMoreElements()) { Order each = (Order) e.nextElement(); outstanding += each.getAmount(); } return outstanding; Page 18 } 5.3. Nhập phương thức (Inline method) 5.3.1. Tóm tắt Thân một hàm cũng rõ ràng như tên của nó. Đặt trực tiếp thân phương thức vào nơi gọi nó và xoá nó đi. int getRating() { return (moreThanFiveLateDeliveries()) ? 2 : 1; } boolean moreThanFiveLateDeliveries() { return _numberOfLateDeliveries > 5; } Được refactor thành: int getRating() { return (_numberOfLateDeliveries > 5) ? 2 : 1; } 5.3.2. Động cơ Một nguyên tắc lập trình là sử dụng những phương thức ngắn được đặt tên sao cho thể hiện được mục đích của nó vì những phương thức như thế giúp mã nguồn rõ ràng và dễ đọc hơn. Nhưng đôi khi bạn bắt gặp những phương thức mà thân của nó cũng rõ ràng như cái tên của nó. Hoặc bạn refactor một đoạn mã thành một phương thức cũng rõ ràng như tên của nó. Khi điều này xảy ra, bạn nên loại bỏ những phương thức đó. Chỉ dẫn có thể có ích, nhưng chỉ dẫn không cần thiết chỉ gây bực mình. Trường hợp khác mà Nhập phương thức nên được áp dụng là khi bạn có một nhóm các phương thức bị phân chia không phù hợp. Bạn có thể nhập chúng lại thành một phương thức lớn sau đó trích lại thành các phương thức khác. Bạn nên làm động tác này trước khi áp dụng Thay phương thức bằng đối tượng phương thức. Bạn nhập các lời gọi từ phương thức có hành động bạn mong muốn trong đối tượng phương thức. Di chuyển một phương thức sẽ dễ dàng hơn phương thức và những phương thức nó sử dụng. Đôi khi người ta sử dụng quá nhiều chỉ dẫn và dường như các phương thức chỉ đơn giản uỷ quyền cho phương thức khác. Trong những trường hợp đó, một số chỉ dẫn là đáng giá nhưng không phải tất cả. Bằng cách Nhập phương thức, ta có thể giữ lại những phương thức hữu ích và loại bỏ những gì còn lại. Page 19 5.4. Nhập biến tạm 5.4.1. Tóm tắt Bạn có một biến tạm được gán một lần với một biểu thức đơn giản và nó gây cản trở các refactoring khác. Thay thể tất cả tham chiếu đến biến tạm đó bằng một biểu thức. double basePrice = anOrder.basePrice(); return (basePrice > 1000) Trở thành: return (anOrder.basePrice() > 1000) 5.4.2. Động cơ Trong phần lớn trường hợp Nhập biến tạm được sử dụng như một phần của Thay biến tạm bằng truy vấn, đó là động cơ chính. Trường hợp duy nhất Nhập biến tạm được sử dụng một mình là khi bạn thấy một biến tạm được gán giá trị trả về của một phương thức. Thường thường biến này không gây hại và bạn có thể mặc kệ nó một cách an toàn. Nếu biến này gây cả trở các phương pháp refactoring, ví dụ như Trích phương thức, đến lúc cần nhập nó. 5.5. Di chuyển phương thức 5.5.1. Tóm tắt Một phương thức được sử dụng bởi một lớp khác nhiều hơn là lớp mà nó được định nghĩa. Tạo một phương thức mới với thân tương tự trong lớp sử dụng nó nhiều nhất. Hoặc chuyển phương thức cũ thành những sự uỷ quyền hoặc xoá hẳn nó đi. 5.5.2. Động cơ Di chuyển phương thức là phần không thể tách rời của refactoring. Thường khi lớp có quá nhiều hành vi hay các lớp tương tác quá nhiều, chúng bị gắn với nhau (coupled). Bằng cách di chuyển vài phương thức, bạn có thể làm cho các lớp đơn giản hơn và hợp lý hơn. Sau khi bạn di chuyển một trường nào đó, hãy nhìn qua các phương thức trong lớp để tìm ra những phương thức truy cập đối tượng khác nhiều hơn chính đối tượng mà nó được đặt. Khi bạn tìm thấy một phương thức cần di chuyển, bạn hãy nhìn vào các phương thức gọi nó, các phương thức nó gọi đến và các phương thức nạp chồng trong cây thừa kế. Đánh giá có nên tiếp tục dựa trên đối tượng mà phương thức có nhiều tương Page 20
- Xem thêm -

Tài liệu liên quan