Tái cấu trúc cho mọi người Làm thế nào và tại sao lại sử dụng các tính năng tái cấu trúc được tự động hóa của Eclipse David Gallardo, Tư vấn phần mềm Tóm tắt: Eclipse cung cấp tập các p
Trang 1Tái cấu trúc cho mọi người
Làm thế nào và tại sao lại sử dụng các tính năng tái cấu trúc được tự động hóa của Eclipse
David Gallardo, Tư vấn phần mềm
Tóm tắt: Eclipse cung cấp tập các phép tái cấu trúc (refactoring) tự động mạnh
mẽ, so với những thứ khác, cho phép bạn đổi tên các phần tử Java™, di chuyển các lớp và các gói, tạo các giao diện từ các lớp cụ thể, chuyển các lớp lồng nhau vào các lớp mức cao nhất và lấy ra một phương thức mới từ các đoạn mã trong một phương thức cũ Việc trở nên quen thuộc với các công cụ tái cấu trúc của Eclipse là một cách hay để cải thiện năng suất của bạn Tổng quan về tính năng tái cấu trúc của Eclipse này, kèm với các ví dụ, giải thích cách thức và lí do sử dụng từng tính năng
Lí do cần tái cấu trúc?
Tái cấu trúc (Refactoring) là thay đổi cấu trúc của một chương trình mà không làm
thay đổi chức năng của nó Tái cấu trúc là một kỹ thuật mạnh, nhưng nó cần được thực hiện cẩn thận Mối nguy hiểm chính là các lỗi vô ý có thể được đưa vào, đặc biệt là khi tái cấu trúc được thực hiện bằng tay Mối nguy hiểm này dẫn đến một
sự chỉ trích thường xuyên về tái cấu trúc: tại sao lại sửa chữa mã nếu nó không bị hỏng?
Có một vài lý do để bạn có thể muốn tái cấu trúc mã Đầu tiên là bắt nguồn của câu chuyện cổ tích: cơ sở mã rất cổ của sản phẩm đáng kính được kế thừa hoặc nếu không thì xuất hiện bí ẩn Nhóm phát triển ban đầu đã biến mất Một phiên bản mới, với các tính năng mới, phải được tạo ra, nhưng mã không còn hiểu được nữa Nhóm phát triển mới, làm việc cả đêm lẫn ngày, giải mã nó, vẽ bản đồ nó và
Trang 2sau nhiều kế hoạch và thiết kế, phá hỏng mã hoàn toàn Cuối cùng, cẩn thận, họ đặt nó tất cả trở lại với nhau theo tầm nhìn mới Đây là tái cấu trúc trên quy mô khác thường và một ít vẫn còn hoạt động để nói về chuyện này
Một kịch bản thực tế hơn là một yêu cầu mới được đưa vào cho dự án đòi hỏi thay đổi thiết kế Thật là vụn vặt cho dù yêu cầu này đã được đưa vào do sơ xuất trong
kế hoạch ban đầu hoặc do cách tiếp cận lặp lại (chẳng hạn như phát triển nhanh nhẹn hoặc phát triển dựa vào thử nghiệm) đang được sử dụng để thận trọng đưa vào các yêu cầu trong suốt quá trình phát triển Đây là tái cấu trúc trên một quy
mô nhỏ hơn nhiều và nó thường yêu cầu thay đổi hệ thống phân cấp lớp, có lẽ do đưa vào các giao diện hoặc lớp trừu tượng, chia tách các lớp, sắp xếp lại các lớp và v.v
Một lý do cuối cùng để tái cấu trúc, khi các công cụ tái cấu trúc tự động có sẵn, chỉ đơn giản là một phím tắt để tạo mã ở vị trí đầu tiên một cái gì đó giống như cách sử dụng một chương trình kiểm tra lỗi chính tả (spellchecker) để phân loại một từ khi bạn không chắc cách đánh vần nó Việc sử dụng tái cấu trúc nhàm chán này chẳng hạn để tạo ra các phương thức getter và setter có thể là một bộ tiết kiệm thời gian hiệu quả một khi bạn đã quen thuộc với các công cụ này
Các công cụ tái cấu trúc của Eclipse không được dự kiến để sử dụng cho phép tái cấu trúc tại một quy mô khác thường một vài công cụ có nhưng chúng là vô giá để làm thay đổi mã trong quá trình diễn biến của một ngày làm việc của lập trình viên trung bình, cho dù điều đó liên quan đến các kỹ thuật phát triển nhanh nhẹn hay không Cuối cùng, bất kỳ hoạt động phức tạp nào có thể được tự động hóa đều là nhàm chán, cần tránh Việc biết các công cụ tái cấu trúc Eclipse có sẵn những gì và cách sử dụng đã dự kiến của chúng, sẽ cải thiện rất nhiều năng suất của bạn
Trang 3Có hai cách quan trọng để bạn có thể làm giảm nguy cơ làm hỏng mã Một cách là phải có một bộ đầy đủ các bài thử nghiệm bộ phận cho mã đó: mã phải vượt qua các bài thử nghiệm cả trước và sau khi tái cấu trúc Cách thứ hai là sử dụng một công cụ tự động hoá, chẳng hạn như các tính năng tái cấu trúc của Eclipse, để thực hiện phép tái cấu trúc này
Cách kết hợp thử nghiệm kỹ lưỡng và tái cấu trúc tự động đặc biệt mạnh mẽ và đã chuyển nghệ thuật bí ẩn này thành một công cụ thường ngày, có ích Khả năng thay đổi cấu trúc mã của bạn mà không cần thay đổi chức năng của nó, theo cách nhanh chóng và an toàn, thêm chức năng hoặc cải thiện việc bảo trì của nó có thể ảnh hưởng đáng kể đến cách bạn thiết kế và phát triển mã, cho dù bạn kết hợp nó vào một phương thức nhanh nhẹn chính thức hay không
Các kiểu tái cấu trúc trong Eclipse
Các công cụ tái cấu trúc của Eclipse có thể được nhóm lại thành ba thể loại rõ ràng (và đây là thứ tự mà chúng xuất hiện trong trình đơn Refactoring):
1 Thay đổi tên và tổ chức vật lý của mã, bao gồm đổi tên các trường, các biến, các lớp và các giao diện và di chuyển các gói và các lớp
2 Thay đổi tổ chức logic của mã ở mức lớp, gồm việc chuyển các lớp ẩn danh thành các lớp lồng nhau, chuyển các lớp lồng nhau thành các lớp mức cao nhất, tạo ra các giao diện từ các lớp cụ thể và di chuyển các phương thức hoặc các trường từ một lớp đến lớp con hoặc siêu lớp
Trang 43 Thay đổi mã trong một lớp, gồm chuyển các biến chuyển địa phương thành các trường lớp, chuyển mã chọn trong phương thức thành một phương thức tách biệt và tạo ra các phương thức getter và setter cho các trường
Một số phép tái cấu trúc gần như không khớp với ba thể loại này, đặc biệt là Thay đổi chữ kí phương thức (Change Method Signature), có trong thể loại thứ ba ở đây Ngoài những trường hợp ngoại lệ này, các phần theo sau sẽ thảo luận về các công cụ tái cấu trúc của Eclipse theo thứ tự này
Tổ chức lại và đổi tên lại vật lý
Bạn rõ ràng có thể đổi tên hoặc di chuyển các tệp xung quanh trong hệ thống tệp
mà không cần một công cụ đặc biệt, nhưng làm như vậy với các tệp mã nguồn Java có thể đòi hỏi bạn phải chỉnh sửa nhiều tệp để cập nhật các câu lệnh import (nhập khẩu) hoặc package (gói) Tương tự như vậy, bạn có thể dễ dàng đổi tên các lớp, các phương thức và các biến bằng cách sử dụng một trình soạn thảo văn bản
để tìm kiếm và thay thế chức năng, nhưng bạn cần phải làm điều này cẩn thận, vì các lớp khác nhau có thể có các phương thức hoặc các biến cùng tên; có thể rất nhàm chán để duyệt qua tất cả các tệp trong một dự án để đảm bảo chắc chắn rằng mọi cá thể được xác định và được thay đổi chính xác
Di chuyển và Đổi tên (Rename and Move) của Eclipse có thể thực hiện các thay đổi này một cách thông minh, trong suốt toàn bộ dự án, mà không có sự can thiệp của người dùng, vì Eclipse hiểu mã theo ngữ nghĩa và có thể xác định các tham chiếu đến một phương thức, biến cụ thể, hoặc các tên lớp Việc thực hiện nhiệm
vụ này dễ dàng giúp đảm bảo rằng phương thức, biến và các tên lớp thể hiện rõ ràng ý định của chúng
Trang 5Thật dễ dàng tìm ra mã có các tên không phù hợp hoặc gây hiểu nhầm vì mã đã được thay đổi để thực hiện khác so với kế hoạch ban đầu đã lập Ví dụ, một
chương trình tìm kiếm các từ cụ thể trong một tệp có thể được mở rộng để làm việc với các trang Web bằng cách sử dụng lớp URL để có được một InputStream (luồng đầu vào) Nếu trước tiên người ta đã gọi file (tệp) cho luồng đầu vào này, thì nó cần được thay đổi để phản ánh tính chất tổng quát mới hơn của nó, có lẽ là sourceStream (luồng nguồn) Các nhà phát triển thường không tạo ra các thay đổi như thế này vì nó có thể là quá trình lộn xộn và nhàm chán Tất nhiên, điều này làm cho mã khó hiểu với nhà phát triển tiếp theo, tức người phải tiếp tục làm việc với nó
Để đổi tên một phần tử Java, chỉ cần nhấn vào nó trong khung nhìn Package
Explorer (Trình thám hiểm gói) hoặc chọn nó trong một tệp nguồn Java, sau đó
chọn Refactor > Rename Trong hộp thoại, chọn tên mới và chọn xem Eclipse có
cần thay đổi các tham chiếu đến tên không Các trường chính xác được hiển thị tùy thuộc vào kiểu phần tử mà bạn chọn Ví dụ, nếu bạn chọn một trường có phương thức getter và setter, bạn cũng có thể cập nhật các tên của các phương thức này để phản ánh trường mới Hình 1 chỉ ra một ví dụ đơn giản
Hình 1 Đổi tên một biến địa phương
Trang 6Giống như tất cả các phép tái cấu trúc Eclipse, sau khi bạn đã xác định mọi thứ
cần thiết để thực hiện tái cấu trúc, bạn có thể nhấn Preview (Xem trước) để xem
sự thay đổi mà Eclipse đề xuất thực hiện, trong một hộp thoại so sánh, nó cho phép bạn bác bỏ hoặc chấp nhận từng thay đổi trong mỗi tệp bị tác động tới Nếu
bạn tin vào khả năng của Eclipse để thay đổi đúng, bạn có thể chỉ cần nhấn OK
Tất nhiên, nếu bạn không chắc chắn phép tái cấu trúc sẽ làm gì, trước tiên bạn sẽ muốn xem trước, nhưng với các phép tái cấu trúc đơn giản như Đổi tên và Di chuyển thì điều này thường không cần thiết
Di chuyển thực hiện rất nhiều việc giống như với Đổi tên: Bạn chọn một phần tử Java (thường là một lớp), xác định vị trí mới của nó và xác định liệu các tham
khảo có nên được cập nhật không Sau đó bạn có thể chọn Preview để kiểm tra các thay đổi hoặc nhấn OK để thực hiện ngay lập tức phép tái cấu trúc như trong
Hình 2
Trang 7Hình 2 Di chuyển một lớp từ một gói này sang một gói khác
Trên một số nền tảng (đặc biệt là Windows), bạn cũng có thể di chuyển các lớp từ một gói hoặc thư mục đến gói hoặc thư mục khác bằng cách kéo và thả chúng vào khung nhìn Package Explorer Tất cả các tài liệu tham khảo sẽ được cập nhật tự động
Trang 8
Định nghĩa lại các mối quan hệ lớp
Một tập nhiều phép tái cấu trúc của Eclipse cho phép bạn thay đổi các mối quan hệ lớp của bạn tự động Các phép tái cấu trúc này không có ích lợi chung như các kiểu tái cấu trúc mà Eclipse phải cung cấp, nhưng có giá trị vì chúng thực hiện nhiệm vụ khá phức tạp Khi chúng được dùng, chúng rất có ích
Tăng cường lớp ẩn danh và lồng nhau
Hai phép tái cấu trúc, Chuyển đổi lớp ẩn danh thành lớp lồng nhau (Convert
Anonymous Class to Nested) và Chuyển đổi lớp lồng nhau tới lớp mức cao nhất (Convert Nested Type to Top Level), là như nhau trong đó chúng di chuyển một lớp ngoài hướng của nó tới nơi có cơ hội để bao bọc
Lớp ẩn danh là loại viết nhanh cú pháp, cho phép bạn thuyết minh một lớp thực hiện một lớp hay giao diện trừu tượng ở nơi bạn cần đến nó, không cần phải cho
nó một tên lớp rõ ràng Điều này thường được sử dụng khi tạo các người nghe trong giao diện người sử dụng chẳng hạn Trong Liệt kê 1, giả định rằng Bag là một giao diện được định nghĩa ở nơi khác để khai báo hai phương thức, get() và set()
Liệt kê 1 Lớp Bag
public class BagExample
{
Trang 10}
}
Khi lớp ẩn danh trở nên lớn đến mức mã trở nên khó đọc, bạn nên nghĩ đến việc tạo cho lớp ẩn danh một lớp thích hợp; để giữ gìn sự bao bọc (nói cách khác, để ẩn giấu nó khỏi các lớp bên ngoài không cần biết về nó), bạn nên tạo cho lớp này một lớp lồng nhau chứ không phải là một lớp cao nhất Bạn có thể làm điều này bằng
cách nhấn vào bên trong lớp ẩn danh và chọn Refactor > Convert Anonymous Class to Nested Nhập tên cho lớp này, chẳng hạn như BagImpl, khi được nhắc và sau đó chọn Preview hoặc OK Việc này sẽ thay đổi mã như trong Liệt kê 2
Liệt kê 2 Lớp Bag được tái cấu trúc
public class BagExample
Trang 12Chuyển đổi lớp lồng nhau tới lớp mức cao nhất là có ích khi bạn muốn tạo một lớp lồng nhau có sẵn cho các lớp khác Bạn có thể, ví dụ, đang sử dụng một đối tượng giá trị bên trong một lớp chẳng hạn như lớp BagImpl ở trên Nếu sau này bạn quyết định rằng dữ liệu này nên được dùng chung giữa các lớp, thì phép tái cấu trúc này sẽ tạo một tệp lớp mới từ lớp lồng nhau Bạn có thể làm điều này bằng cách làm nổi bật tên lớp trong tệp nguồn (hoặc nhấn vào tên lớp trong khung nhìn
Outline) và chọn Refactor > Convert Nested Type to Top Level
Phép tái cấu trúc này sẽ yêu cầu bạn cung cấp một tên cho cá thể kèm theo Nó có thể đưa ra đề nghị, như example (ví dụ), mà bạn có thể chấp nhận Ý nghĩa của
việc này sẽ được làm rõ trong giây lát Sau khi nhấn OK, mã cho lớp BagExample
kèm theo sẽ được thay đổi như thể hiện trong Liệt kê 3
Liệt kê 3 Lớp Bag được tái cấu trúc
public class BagExample
Trang 13}
}
Lưu ý rằng khi một lớp được lồng nhau, nó có quyền truy cập tới các thành viên của lớp bên ngoài Để duy trì chức năng này, phép tái cấu trúc sẽ bổ sung một cá
thể của lớp BagExample kèm theo vào lớp đã lồng nhau trước đây Đây là biến
của cá thể mà trước đây bạn đã được yêu cầu cung cấp một tên cho nó Nó cũng
tạo ra một hàm tạo để thiết lập biến của cá thể này Lớp mới BagImpl do phép tái
cấu trúc tạo ra được hiển thị trong Liệt kê 4
Liệt kê 4 Lớp BagImpl
final class BagImpl implements Bag
Trang 14Di chuyển thành viên trong hệ thống phân cấp lớp
Trang 15Hai phép tái cấu trúc khác, Đẩy xuống (Push Down) và Kéo lên (Pull Up), di chuyển các phương thức lớp hoặc các trường từ một lớp đến lớp con hoặc siêu lớp của nó, tương ứng Giả sử bạn có một lớp trừu tượng Vehicle (xe cộ), được xác định như sau trong Liệt kê 5
Liệt kê 5 Lớp trừu tượng Vehicle
public abstract class Vehicle
{
protected int passengers;
protected String motor;
Trang 16Liệt kê 6 Lớp Automobile
public class Automobile extends Vehicle
{
Trang 17private String make;
private String model;
public String getMake()
Trang 18}
}
Chú ý rằng một thuộc tính của Vehicle là motor (động cơ) Thật tốt nếu bạn biết rằng bạn sẽ chỉ luôn đề cập đến các loại xe cơ giới có động cơ, nhưng nếu bạn muốn cho phép những thứ như các thuyền có mái chèo (rowboat), bạn có thể muốn đẩy thuộc tính motor xuống từ lớp Vehicle vào trong lớp Automobile Để
làm điều này, chọn motor trong khung nhìn Outline, rồi chọn Refactor > Push Down
Eclipse đủ thông minh để nhận ra rằng bạn không thể luôn luôn di chuyển một
trường bằng chính nó và cung cấp một nút Add Required, nhưng điều này không
luôn hoạt động đúng trong Eclipse 2.1 Bạn cần phải xác minh rằng các phương thức bất kỳ phụ thuộc vào trường này cũng bị đẩy xuống Trong trường hợp này,
có hai, phương thức getter và setter đi cùng trường motor, như trong Hình 3
Trang 19Hình 3 Thêm các thành viên cần thiết
Sau khi nhấn OK, trường motor và các phương thức getMotor() và setMotor() sẽ
được chuyển đến lớp Automobile Liệt kê 7 cho thấy hình dạng của lớp
Automobile sau phép tái cấu trúc này
Liệt kê 7 Lớp Automobile được tái cấu trúc
public class Automobile extends Vehicle
{
Trang 20private String make;
private String model;
protected String motor;
public String getMake()
Trang 22lớp Bus Một cách thể hiện mối quan hệ như vậy là tạo ra một giao diện,
Motorized, trong đó Automobile và Bus sẽ thực hiện, nhưng RowBoat thì không
Cách dễ nhất để tạo ra giao diện Motorized là sử dụng tái cấu trúc Lấy ra giao diện (Extract Interface) trên Automobile Để làm điều này, chọn lớp Automobile trong
khung nhìn Outline và sau đó chọn Refactor > Extract Interface từ trình đơn
Hộp thoại sẽ cho phép bạn chọn những phương thức nào bạn muốn đưa vào trong giao diện như trong Hình 4
Hình 4 Lấy ra giao diện Motorized
Sau khi chọn OK, một giao diện được tạo ra, như được hiển thị trong Liệt kê 8
Liệt kê 8 Giao diện Motorized
Trang 23public interface Motorized
{
public abstract String getMotor();
public abstract void setMotor(String string);
}
Và việc khai báo lớp cho Automobile được thay đổi như sau:
public class Automobile extends Vehicle implements Motorized
Sử dụng một siêu kiểu (supertype)
Phép tái cấu trúc cuối cùng có trong thể loại này là Sử dụng siêu kiểu ở nơi có thể (Use Supertype Where Possible) Hãy xem xét một ứng dụng quản lý hàng tồn kho
ô tô Từ đầu đến cuối, nó sử dụng các đối tượng của kiểu Automobile Nếu bạn muốn có thể xử lý tất cả các kiểu xe cộ, bạn có thể sử dụng phép tái cấu trúc này
để thay đổi các tham chiếu đến Automobile thành các tham chiếu đến Vehicle (xem Hình 5) Nếu bạn thực hiện bất kỳ việc kiểm tra kiểu nào trong mã của bạn bằng cách sử dụng toán tử instanceof, bạn sẽ cần phải xác định xem nó có phù hợp
để sử dụng các kiểu cụ thể hoặc siêu kiểu không và kiểm tra tùy chọn đầu tiên Sử dụng siêu kiểu đã chọn trong các biểu thức 'instanceof', cho phù hợp