Kiến trúc tiến hóa và thiết kế nổi dần: Tái cấu trúc mã nguồn hướng theo thiết kế pdf

22 205 0
Kiến trúc tiến hóa và thiết kế nổi dần: Tái cấu trúc mã nguồn hướng theo thiết kế pdf

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

Kiến trúc tiến hóa và thiết kế nổi dần: Tái cấu trúc mã nguồn hướng theo thiết kế Tìm và thu thập thiết kế ẩn trong mã của bạn Neal Ford, Kiến trúc phần mềm, ThoughtWorks Tóm tắt: Các bài viết trước đây của loạt bài viết này thảo luận về việc kiểm thử đơn vị giúp bạn có một thiết kế tốt hơn như thế nào. Nhưng nếu bạn đã có rất nhiều mã, thì làm thế nào bạn có thể khám phá các yếu tố thiết kế ẩn bên trong các mã đó? Bài viết trước đã bàn về xây dựng các đích cấu trúc cho mã của bạn. Trong bài viết này, tác giả Neal Ford của của loạt bài viết mở rộng các ý tưởng đó và nói về các kỹ thuật sử dụng tái cấu trúc mã nguồn để cho phép thiết kế nổi dần lên. Trong hai bài viết "Thiết kế hướng kiểm thử, phần 1" và "Thiết kế hướng kiểm thử, phần 2," tôi đã nói về cách mà việc kiểm thử có thể dẫn đến thiết kế tốt hơn cho các dự án mới. Trong phần "Phương thức hợp thành và SLAP," (N.D: SLAP là viết tắt “single level of abstraction principle” - nguyên tắc chỉ một mức trừu tượng) tôi có nói về hai mẫu trọng yếu — phương thức hợp thành và nguyên tắc chỉ một mức trừu tượng — hai mẫu này mang lại cho bạn một cái đích tổng thể cho cấu trúc mã của bạn. Hãy ghi nhớ các mẫu này. Khi bạn có một dự án phần mềm đang tồn tại rồi, thì tuyến đường để phát hiện và thu thập các yếu tố thiết kế nằm trong việc cấu trúc lại mã nguồn. Trong cuốn sách kinh điển Tái cấu trúc mã nguồn, của mình, Martin Fowler đã định nghĩa tái cấu trúc mã nguồn "là một kỹ thuật có quy tắc để cấu trúc lại phần chính yếu hiện tại của mã, thay đổi cấu trúc bên trong của nó mà không thay đổi hành vi bên ngoài của nó" (xem phần Tài nguyên). Cấu trúc lại mã nguồn là một phép chuyển đổi cấu trúc có mục đích. Có một cơ sở mã dễ cấu trúc lại là một mục tiêu đáng khen ngợi của bất kỳ dự án nào. Trong bài viết này, tôi nói về cách sử dụng việc tái cấu trúc mã nguồn như thế nào để tìm ra một thiết kế chưa được sử dụng đúng mức còn ẩn giấu trong mã của bạn. Về loạt bài viết này Loạt bài viết này nhằm cung cấp một phối cảnh tươi mới về các khái niệm thường được thảo luận nhưng khó nắm bắt về kiến trúc và thiết kế phần mềm. Thông qua các ví dụ cụ thể, Neal Ford mang đến cho bạn một nền tảng vững chắc cho cách làm thực tế lanh lẹn của kiến trúc tiến hóa và thiết kế nổi dần. Bằng cách trì hoãn các quyết định quan trọng về thiết kế và kiến trúc cho đến thời điểm quyết định cuối cùng, bạn có thể ngăn ngừa được những phức tạp không cần thiết không để chúng ngầm phá hoại các dự án phần mềm của bạn Các kiểm thử đơn vị là cái lưới an toàn chính cho phép bạn tuỳ ý cải tiến cơ sở mã của mình. Nếu bạn có mức bao quát kiểm thử là 100 phần trăm mã của dự án của mình, thì bạn có thể cấu trúc lại mã của mình mà không gặp rắc rối nào. Nếu bạn không theo đuổi mức kiểm thử đó, thì việc quá hăng hái cấu trúc lại mã nguồn sẽ nguy hiểm hơn. Các thay đổi được khoanh vùng rất dễ áp dụng và bạn có thể thấy tác dụng ngay lập tức của chúng, nhưng các rạn vỡ do tác dụng phụ lâu dài về sau này sẽ làm cho bạn điêu đứng. Phần mềm sẽ dẫn đến những điểm kết dính không mong muốn, và một thay đổi nhỏ đối với một phần của mã có thể lan truyền qua cơ sở mã, gây ra lỗi cho hàng trăm dòng mã từ việc thay đổi đó. Sự tự tin để sửa đổi mã và tìm ra những lỗi lan xa này là một dấu hiệu nổi bật của kiểm thử đơn vị bao quát mọi nơi. Một dự án kéo dài trong 2 năm của công ty tư vấn ThoughtWorks đã được người phụ trách kỹ thuật tiến hành 53 lần cấu trúc lại mã nguồn khác nhau cho đến tận ngày trước khi dự án đi vào hoạt động. Ông đã làm điều này với sự tự tin thanh thản vì dự án bao trùm toàn bộ mã. Làm thế nào để đưa cơ sở mã của bạn tới chỗ có thể thực hiện được những đợt tái cấu trúc mã nguồn rộng lớn? Một lựa chọn là từ chối viết thêm mã khác cho đến khi bạn có thời gian để thêm các phép kiểm thử cho toàn bộ dự án. Ngay khi bạn đề xuất việc này thì bạn sẽ bị đuổi việc và bạn có thể đi làm việc cho một công ty coi trọng việc kiểm thử đơn vị hơn. Cách tiếp cận này có thể là không tối ưu. Lựa chọn tốt nhất tiếp theo của bạn là làm cho những những thành viên khác trong nhóm của bạn nhận thức được giá trị của kiểm thử và bắt đầu thêm dần dần các phép kiểm thử cho các phần trọng yếu nhất của mã của bạn. Bạn hãy vạch một đường thẳng trên cát và tuyên bố một ngày trong tương lai gần: "Bắt đầu từ thứ năm tới, mức bao quát kiểm thử của chúng ta sẽ luôn tăng lên." Mỗi khi bạn viết một mã mới, thì hãy thêm một phép kiểm thử, và mỗi khi bạn sửa một lỗi, thì bạn hãy viết một phép kiểm thử. Bằng cách dần dần thêm các phép kiểm thử cho các phần nhạy cảm nhất (các tính năng mới và các vùng bị lỗi), bạn thêm các phép thử vào đúng nơi chúng có ích nhất. Các phép kiểm thử đơn vị kiểm tra hành vi nguyên tử. Tuy nhiên, nếu cơ sở mã của bạn không tuân theo mô hình lý tưởng của phương thức hợp thành thì điều gì sẽ xảy ra? Nói cách khác, điều gì sẽ xảy ra nếu tất cả các phương thức của bạn có hàng chục hoặc hàng trăm dòng mã, và mỗi phương thức thực hiện rất nhiều tác vụ? Bạn có thể sử dụng khung công tác kiểm thử đơn vị để viết các phép kiểm thử chức năng mức thô hơn cho các phương thức đó, bạn quan tâm chủ yếu đến việc biến đổi trạng thái của đầu vào và đầu ra của của phương thức. Việc này không tốt như các phép thử đơn vị vì chúng không kiểm tra từng mảnh nhỏ của hành vi, nhưng còn hơn là không làm gì. Đối với những phần thực sự trọng yếu của mã của bạn, bạn có thể xem xét việc thêm một số kiểm thử chức năng như một lưới an toàn trước khi bạn bắt đầu cấu trúc lại mã nguồn. Các cơ chế của việc cải tiến mã nguồn rất đơn giản, và bây giờ tất cả các môi trường phát triển tích hợp (IDE) chính đều có sự hỗ trợ cấu trúc lại mã nguồn rất tuyệt vời. Điều khó khăn là ở chỗ tìm ra cái gì để cấu trúc lại. Phần còn lại của bài viết bàn về vấn đề này. Gắn kết với cơ sở hạ tầng Tất cả mọi người trong thế giới Java sử dụng khung công tác để khởi động việc phát triển và cung cấp cơ sở hạ tầng quan trọng thuộc loại tốt nhất (cơ sở hạ tầng mà bạn không cần phải viết). Nhưng có một mối nguy hiểm ẩn núp trong khung công tác, cả khung công tác mã nguồn thương mại lẫn khung công tác mã nguồn mở: chúng luôn luôn cố gắng làm cho bạn kết dính quá mật thiết với chúng, điều này có thể làm cho khó nhìn thấy thiết kế được ẩn trong mã của bạn. Các khung công tác và máy chủ ứng dụng có các lớp trợ giúp lôi kéo bạn đi theo tuyến đường phát triển đơn giản hơn nhiều: nếu bạn chỉ nhập khẩu và sử dụng một số lớp của chúng, thì để hoàn thành một tác vụ cụ thể sẽ dễ dàng hơn nhiều. Một ví dụ kinh điển là Struts, khung công tác web mã nguồn mở vô cùng phổ biến. Khung công tác Strust bao gồm một bộ các lớp trợ giúp để xử lý các việc vặt phổ biến cho bạn. Ví dụ: Nếu bạn cho phép các lớp miền của bạn mở rộng từ lớp ActionForm của Struts thì khung công tác Struts sẽ tự động điền các trường trong biểu mẫu yêu cầu, xử lý việc xác thực và các sự kiện vòng đời, và thực hiện các hành vi có ích khác. Nói cách khác, khung công tác Struts mang đến một sự đánh đổi: hãy sử dụng các lớp của chúng tôi và công việc phát triển của bạn sẽ dễ dàng hơn nhiều. Khung công tác này khuyến khích bạn tạo ra một cấu trúc như được thể hiện trong hình 1: Hình 1. Sử dụng lớp ActionForm của Struts Hộp màu vàng bao gồm các lớp miền của bạn, nhưng khung công tác Struts khuyến khích bạn mở rộng nó từ lớp ActionForm để kế thừa được các hành vi hữu ích của nó. Tuy nhiên, bây giờ bạn đã kết dính một cách vô vọng mã của mình vào khung công tác Struts. Bạn không còn có thể sử dụng lớp miền của bạn trong bất cứ cái gì khác, ngoài một ứng dụng Struts. Nó cũng làm tổn hại đến thiết kế của các lớp miền của bạn bởi vì lớp tiện ích này bây giờ phải nằm ở trên đỉnh của hệ thống phân cấp các đối tượng của bạn, không cho phép bạn sử dụng thừa kế để củng cố các hành vi chung. Hình 2 cho thấy một cách tiếp cận tốt hơn: Hình 2. Thiết kế được cải tiến, bằng các sử dụng phép hợp thành để tách rời khỏi khung công tác Struts Trong phiên bản này các lớp miền của bạn không phụ thuộc vào lớp ActionForm của Struts. Thay vào đó, một giao diện xác định ngữ nghĩa cho cả lớp miền của bạn và lớp ScheduleItemForm đóng vai trò như một cầu nối giữa miền của bạn và khung công tác. Cả hai lớp ScheduleItemImpl và ScheduleItemForm thực hiện các giao diện, và lớp ScheduleItemForm nắm giữ một tham chiếu đến lớp miền của bạn thông qua hợp thành hơn là thừa kế. Được phép để cho lớp trợ giúp của Struts duy trì một phụ thuộc vào lớp của bạn, nhưng điều ngược lại là không được: bạn không nên để cho các lớp của bạn có sự phụ thuộc vào khung công tác. Bây giờ, bạn được tự do sử dụng lớp ScheduleItem của bạn trong các kiểu ứng dụng khác (Ứng dụng Swing, tầng dịch vụ, vv). Kết dính với cơ sở hạ tầng rất dễ dàng và phổ biến mọi nơi trong nhiều ứng dụng. Khung công tác làm cho dễ dàng hơn nữa việc tận dụng các dịch vụ của chúng khi bạn nhập khẩu các món quà của chúng. Bạn nên cưỡng lại các cám dỗ. Mẫu đặc thù (được định nghĩa trong các bài viết trước là các mẫu nhỏ, có trong ứng dụng của bạn) khó phát hiện ra hơn trong mã của bạn nếu vỏ ngoài của khung công tác che phủ mọi thứ. Các vi phạm đối với nguyên tắc DRY Trong cuốn sách Lập trình viên thực dụng (The Pragmatic Programmer), các tác giả Andy Hunt và Dave Thomas đã định nghĩa nguyên tắc DRY : Don't Repeat Yourself (đừng lặp lại chính bản thân bạn) (xem phần Tài nguyên). Hai khía cạnh của sự vi phạm nguyên tắc DRY — sao chép mã lệnh và sao chép cấu trúc — có thể ảnh hưởng đến thiết kế. Mã sao chép Sao chép trong mã lệnh làm mờ thiết kế bởi vì bạn không thể tìm thấy các mẫu đặc thù. Mã sao chép có sự các khác biệt không dễ phát hiện ở nơi này nơi khác, ngăn cản không cho bạn xác định cách sử dụng thực sự của một phương thức hay một sưu tập các phương thức. Và, tất nhiên mọi người đều biết rằng viết mã nhờ sao chép cuối cùng sẽ luôn gây phiền toái cho bạn, bởi vì bạn chắc chắn phải thay đổi hành vi, và khó theo dõi tất cả các nơi mà bạn đã sao chép mã. Làm thế nào để bạn tìm được các đoạn sao chép đã lẻn vào cơ sở mã của bạn? Các IDE hoặc bao gồm sẵn các trình phát hiện sao chép (ví dụ như IntelliJ) hoặc cung cấp chúng dưới dạng các trình cắm thêm (ví dụ như Eclipse). Cũng có các công cụ độc lập, cả mã nguồn mở (chẳng hạn như CPD - Copy/Paste Detector - công cụ phát hiện sao chép) lẫn thương mại (chẳng hạn như Simian) (xem phần Tài nguyên). Dự án CPD là một phần của công cụ phân tích mã nguồn PMD. Đó là một ứng dụng dựa trên Swing, ứng dụng này phân tích một số lượng cấu hình được các thẻ bài (token) cả trong một tệp tin riêng lẻ lẫn trong nhiều tệp tin. Tôi cần một cơ sở mã không tầm thường làm nạn nhân ví dụ, vì vậy tôi chọn dự án Struts đã nói ở trên. Khi chạy CPD trên cơ sở mã Struts 2 cho kết quả như trong hình 3: Hình 3. Kết quả chạy CPD trên cơ sở mã Struts 2 CPD tìm thấy nhiều sự trùng lặp trong cơ sở mã Struts. Phần nhiều các trùng lặp này liên quan đến việc bổ sung hỗ trợ portlet (cổng web con) cho Struts. Trong thực tế, hầu hết các phần sao chép giữa các tệp tin là thuộc về các tệp PortletXXX và XXX (Ví dụ: PortletApplicationMap và ApplicationMap). Điều này cho thấy sự hỗ trợ portlet đã không được thiết kế tốt. Đây là một “mùi” chính toát ra từ mã lệnh mỗi khi có nhiều trùng lặp mã như vậy để bổ sung thêm hành vi vào một khung công tác hiện có. Một cách thức “sạch” hơn là thông qua thừa kế hoặc kết hợp để mở rộng khung công tác hiện có, và thậm chí đó là lời tố cáo tệ hơn, nếu cả sự thừa kế hoặc kết hợp đều không thực hiện được. Một vấn đề trùng lặp phổ biến khác trong cơ sở mã này nằm trong các tệp tin ApplicationMap.java và Sorter.java. Tệp ApplicationMap.java chứa một đoạn 27 dòng mã bị trùng lặp, như trong lệt kê 1: Liệt kê 1. Mã bị trùng lặp trong tệp tin ApplicationMap.java entries.add(new Map.Entry() { public boolean equals(Object obj) { Map.Entry entry = (Map.Entry) obj; return ((key == null) ? (entry.getKey() == null) : key.equals(entry.getKey())) && ((value == null) ? (entry.getValue() == null) : value.equals(entry.getValue())); } public int hashCode() { return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode()); } public Object getKey() { return key; } public Object getValue() { return value; } public Object setValue(Object obj) { context.setAttribute(key.toString(), obj); return value; } }); [...]... về việc sử dụng biện pháp cấu trúc lại mã nguồn như là một công cụ để giúp hiểu và nhận biết thiết kế nổi dần lên Tôi đã nói về việc dính kết vào cơ sở hạ tầng và các tổn hại mà nó gây ra cho thiết kế của bạn Phần lớn bài viết này nói về sự trùng lặp dưới nhiều khía cạnh khác nhau Giao điểm của việc cấu trúc lại mã nguồn và thiết kế là một lĩnh vực phong phú; bài viết tiếp theo tiếp tục chủ đề này bằng... công tác Struts phải luôn luôn làm, ngăn không cho gói đoạn mã đó và đưa lên một chỗ có ý nghĩa hơn Một cách để làm sạch thiết kế của nhiều lớp trong cơ sở mã Struts là nhận thức được rằng mẫu đặc thù này tồn tại và củng cố hành vi đó Các trùng lặp về cấu trúc Một hình thức trùng lặp khó phát hiện hơn và do đó xảo quyệt hơn là sự trùng lặp về cấu trúc Các nhà phát triển, từng làm việc với một số lượng... hireYear; } } Có lớp đơn giản này, tôi muốn có khả năng sắp xếp theo bất kỳ trường nào của lớp Ngôn ngữ Java có một cơ chế để đổi khác trật tự sắp xếp thông qua việc tạo ra các lớp trình so sánh (comparator), các lớp này thực hiện giao diện Comparator Các trình so sánh theo tên và theo lương như trong liệt kê 5: Liệt kê 5 Các trình so sánh theo tên và theo lương public class EmployeeNameComparator implements... được tham số hoá, xếp gập các đoạn mã trùng lặp thành một phương thức riêng Ví dụ này từ khung công tác Struts là một minh hoạ hoàn hảo về mã sao chép rẻ tiền, không những không cần thiết mà còn rất dễ sửa chữa Thực vậy, điều này minh họa rằng cách thu thập và bổ sung các mục vào tập hợp các thuộc tính là một mẫu đặc thù trong cơ sở mã của Struts Việc cho phép các đoạn mã giống nhau nằm ở nhiều nơi che... tạp của các cấu trúc vượt quá độ phức tạp của phiên bản dựa trên phản xạ Nếu sự phản xạ làm cho bạn bực mình, thì bạn có thể gắn thêm một ít điểm nữa cho nhân tố gây bực mình đó! Ở mọi mức độ, mỗi giải pháp đều có cả phí tổn lẫn lợi ích gắn kết với nó, và trách nhiệm của bạn là cân nhắc sự đánh đổi ấy Tôi đã quen với phép phản xạ trong ngôn ngữ Java và các ngôn ngữ khác, vì vậy tôi có xu hướng chọn... xét các mã trong hình 4, nơi tôi đã đặt hai trình so sánh cạnh nhau: Hình 4 Các trình so sánh được đặt cạnh nhau Như bạn có thể thấy, thành ngữ cùng khoảng trống, nhưng các giá trị khác nhau áp dụng rất đúng Hầu hết các mã là trùng lặp; phần khác nhau duy nhất là giá trị trả về Bởi vì tôi đang sử dụng cơ sở hạ tầng phép so sánh theo một cách "tự nhiên” (nghĩa là cách thức đã được các nhà thiết kế ngôn... bao nhiêu? 10, 20 hay 50 thuộc tính? Con số này sẽ thay đổi tuỳ thuộc vào các nhà phát triển phần mềm và các đội phát triển phần mềm Tuy nhiên, nếu bạn đang tìm kiếm một thước đo ít nhiều khách quan hơn, thì tại sao bạn không đo xem phiên bản phản xạ phức tạp như thế nào so với các trình so sánh cá thể ? Trong bài viết "Thiết kế hướng kiểm thử, phần 2," tôi đã giới thiệu về thước đo độ phức tạp chu... phức tạp tương đối của chỉ một phương thức đơn lẻ Một công cụ mã nguồn mở tốt để đo độ phức tạp chu số cho ngôn ngữ Java là công cụ mã nguồn mở JavaNCSS (xem phần Tài nguyên) Nếu tôi chạy JavaNCSS trên một trong các lớp trình so sánh đơn lẻ, thì nó trả về 1, điều này không đáng ngạc nhiên: phương thức đơn trong lớp chỉ có một dòng duy nhất và không có các khối lệnh Khi tôi chạy JavaNCSS trên toàn bộ... trợ siêu lập trình (metaprogramming) yếu kém, chẳng hạn như Java và C #) thì sẽ đặc biệt khó nhìn thấy vấn đề này Hiện tượng trùng lặp cấu trúc được tóm tắt một cách chuẩn xác nhất bằng một cụm từ mà người cùng làm việc với tôi là Pat Farley sử dụng: Cùng một khoảng trống, nhưng có giá trị khác nhau Nói cách khác, bạn đã sao chép mã, mã này gần như giống nhau (nghĩa là khoảng trống là như nhau), nhưng... cho an toàn chỗ làm, vì không một ai khác có thể đọc được mã) , phần thú vị của các mã trùng lặp này không phải là ở chính bản thân mã đó Đó là đoạn mào đầu xuất hiện trước các đoạn mã này trong hai phương thức, nơi có sự trùng lặp Phương thức đầu tiên được hiển thị trong liệt kê 2: Liệt kê 2 Phần mào đầu của lần xuất hiện đầu tiên của đoạn mã trùng lặp while (enumeration.hasMoreElements()) { final . Kiến trúc tiến hóa và thiết kế nổi dần: Tái cấu trúc mã nguồn hướng theo thiết kế Tìm và thu thập thiết kế ẩn trong mã của bạn Neal Ford, Kiến trúc phần mềm, ThoughtWorks. kinh điển Tái cấu trúc mã nguồn, của mình, Martin Fowler đã định nghĩa tái cấu trúc mã nguồn "là một kỹ thuật có quy tắc để cấu trúc lại phần chính yếu hiện tại của mã, thay đổi cấu trúc bên. bắt về kiến trúc và thiết kế phần mềm. Thông qua các ví dụ cụ thể, Neal Ford mang đến cho bạn một nền tảng vững chắc cho cách làm thực tế lanh lẹn của kiến trúc tiến hóa và thiết kế nổi dần.

Ngày đăng: 07/08/2014, 10:22

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan