1. Trang chủ
  2. » Công Nghệ Thông Tin

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
Tài liệu đã được kiểm tra trùng lặp

Đ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

Thông tin cơ bản

Định dạng
Số trang 22
Dung lượng 480,6 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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

Trang 1

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

Trang 2

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

Trang 3

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

Trang 4

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:

Trang 5

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

Trang 6

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ứ

Trang 7

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:

Trang 8

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

Trang 9

entries.add(new Map.Entry() {

public boolean equals(Object obj) {

Map.Entry entry = (Map.Entry) obj;

return ((key == null) ?

public int hashCode() {

return ((key == null) ?

0 :

key.hashCode()) ^ ((value == null) ?

0 :

Trang 11

Bên cạnh việc sử dụng nhiều toán tử tam phân lồng nhau (chúng luôn luôn là một chỉ báo tốt 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 String key = enumeration.nextElement().toString();

final Object value = context.getAttribute(key);

entries.add(new Map.Entry() {

// remaining code elided, shown in Listing 1

Liệt kê 3 cho thấy đoạn mào đầu cho lần xuất hiện thứ hai của đoạn mã trùng lặp:

Liệt kê 3 Phần mào đầu thứ hai cho đoạn mã bị trùng lặp

Trang 12

while (enumeration.hasMoreElements()) {

final String key = enumeration.nextElement().toString();

final Object value = context.getInitParameter(key);

entries.add(new Map.Entry() {

// remaining code elided, shown in Listing 1

Sự khác biệt duy nhất trong toàn bộ vòng lặp while là lời gọi

context.getAttribute(key) trong Liệt kê 2 so với lời gọi

context.getInitParameter(key) trong Liệt kê 3 Rõ ràng là phần này có thể đượ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 giấu một sự thực là đây là cái gì đó mà khung 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

Trang 13

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 giới hạn các ngôn ngữ (đặc biệt là các ngôn ngữ có hỗ 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 với các giá trị khác nhau cho các biến Sự trùng lặp này không xuất hiện trong các công cụ như CPD bởi vì các giá trị trong mỗi cá thể của cơ sở hạ tầng được lặp lại thực sự là duy nhất, nhưng tuy nhiên nó vẫn làm tổn hại đến mã của bạn

Dưới đây là một ví dụ Giả sử tôi có một lớp nhân viên (employee) đơn giản với một vài trường, như trong liệt kê 4:

Liệt kê 4 Một lớp nhân viên đơn giản

public class Employee {

private String name;

private int salary;

private int hireYear;

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

HÌNH ẢNH LIÊN QUAN

Hình 1. Sử dụng lớp ActionForm của Struts - 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
Hình 1. Sử dụng lớp ActionForm của Struts (Trang 5)
Hình 3. Kết quả chạy CPD trên cơ sở mã Struts 2 - 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
Hình 3. Kết quả chạy CPD trên cơ sở mã Struts 2 (Trang 8)

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm

w