Tải bản đầy đủ (.doc) (34 trang)

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

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (428.12 KB, 34 trang )

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


Chương 1. Giới thiệu..........................................................................................................5
Chương 2. Các nguyên lý về refactoring............................................................................6
2.1. Định nghĩa refactoring............................................................................................6
2.2. Hai chiếc mũ............................................................................................................6
2.3. Tại sao cần refactor?...............................................................................................7
2.3.1. Refactoring giúp cải thiện thiết kế...................................................................7
2.3.2. Refactoring giúp phần mềm dễ hiểu hơn.........................................................7


2.3.3. Refactoring giúp bạn lập trình nhanh hơn........................................................7
2.4. Khi nào nên refactor?..............................................................................................8
2.4.1. Nguyên tắc 3 lần...............................................................................................8
2.4.2. Refactor khi thêm chức năng............................................................................8
2.4.3. Refactor khi sửa lỗi..........................................................................................8
2.4.4. Refactor khi xem lại mã nguồn (code review).................................................8
2.5. Các vấn đề khi refactor............................................................................................8
2.5.1. Cơ sở dữ liệu....................................................................................................8
2.5.2. Thay đổi giao diện............................................................................................9
2.6. Refactoring với thiết kế...........................................................................................9
2.7. Refactoring với hiệu năng.....................................................................................10
Chương 3. Nhận biết mã nguồn cần refactor...................................................................11
3.1. Trùng lặp mã.........................................................................................................11
3.2. Phương thức dài....................................................................................................11
3.3. Lớp lớn..................................................................................................................12
Chương 4. Xây dựng bộ kiểm thử....................................................................................14
4.1. Giá trị của mã kiểm thử.........................................................................................14
Chương 5. Các phương pháp refactor..............................................................................15
5.1. Định dạng của các phương pháp refactor..............................................................15
5.2. Trích phương thức.................................................................................................15
5.2.1. Tóm tắt...........................................................................................................15
5.2.2. Động cơ..........................................................................................................16
5.2.3. Ví dụ: Không có biến nội bộ..........................................................................16
5.2.4. Ví dụ: Có sử dụng biến nội bộ.......................................................................17
5.2.5. Ví dụ: Gán lại một biến nội bộ.......................................................................19
5.3. Nhập phương thức (Inline method).......................................................................20
5.3.1. Tóm tắt...........................................................................................................20
5.3.2. Động cơ..........................................................................................................20
5.4. Nhập biến tạm.......................................................................................................21
5.4.1. Tóm tắt...........................................................................................................21

5.4.2. Động cơ..........................................................................................................21
5.5. Di chuyển phương thức.........................................................................................21
5.5.1. Tóm tắt...........................................................................................................21
Page 2


5.5.2. Động cơ..........................................................................................................21
5.5.3. Ví dụ...............................................................................................................22
Chương 6. Các công cụ refactor.......................................................................................23
6.1. Refactor bằng công cụ...........................................................................................23
6.2. Refactor mã nguồn Java bằng Eclipse...................................................................23
6.2.1. Thực đơn refactor...........................................................................................24
6.2.2. Đổi tên............................................................................................................24
6.2.3. Trích phương thức..........................................................................................25
6.2.4. Nhập phương thức..........................................................................................26
6.2.5. Nhập biến tạm................................................................................................27
6.2.6. Di chuyển phương thức..................................................................................27
Phụ lục A. Tài liệu tham khảo..........................................................................................29
Phụ lục A. Danh sách “bad smells in code”.....................................................................30
Phụ lục A. Danh sách các phương pháp refactor.............................................................31
Phụ lục A. Các công cụ refactor mã nguồn Java trong Eclipse Helios............................34

Page 3


Page 4


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 5


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 6


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 7



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 8


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.
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ế
Page 9


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 10


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 11


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 12


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 13


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 14


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 15


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 16



}
//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 17



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 18


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 19


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 20


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 21


tác hơn.
5.5.3. Ví dụ
Một lớp biểu diễn tài khoản minh hoạ cho kỹ thuật này:
class Account...

double overdraftCharge() {
if (_type.isPremium()) {
double result = 10;
if (_daysOverdrawn > 7) result += (_daysOverdrawn - 7) * 0.85;
return result;
}
else return _daysOverdrawn * 1.75;
}
double bankCharge() {
double result = 4.5;
if (_daysOverdrawn > 0) result += overdraftCharge();
return result;
}
private AccountType _type;
private int _daysOverdrawn;
Giả sử bạn có một vài loại tài khoản, mỗi loại có quy tắc riêng để tính tiền thấu chi
(overdraft charge). Nên di chuyển hàm overdraftCharge() đến các loại tài khoản đó.

Page 22


Chương 6. Các công cụ refactor
6.1.

Refactor bằng công cụ

Refactor sử dụng công cụ hiệu quả hơn nhiều so với refactor bằng tay. Ngay cả khi bạn
có bảo hiểm bằng bộ kiểm thử, refactor bằng tay vẫn tốn thời gian hơn rất nhiều.
Với sự hỗ trợ ngày càng cao của các công cụ, refactoring ngày càng ít tách rời khỏi lập
trình. Chúng ta sẽ không nói “bây giờ tôi đang lập trình” và “bây giờ tôi đang refactor”

nữa mà chỉ nói “Trích phần này của phương thức, đẩy nó lên lớp cha sau đó gọi phương
thức mới vừa tạo trong lớp con mà tôi đang làm việc”. Vì bạn không kiểm thử sau các
thao tác refactor tự động hoá nên việc chuyển đổi giữa hai chiếc mũ trở nên ít rõ ràng
hơn nhiều.
6.2.

Refactor mã nguồn Java bằng Eclipse

Ngày nay các công cụ refactor cho các ngôn ngữ định kiểu tĩnh đã rất đầy đủ. Để bạn
nắm được refactor áp dụng trong thực tế như thế nào, tôi sẽ sử dụng Eclipse với ngôn
ngữ Java để minh hoạ. Dưới đây trình bày một số công cụ thường dùng, danh sách đầy
đủ các công cụ được hỗ trợ bởi Eclipse Helios được cho trong Phụ lục D.

Page 23


6.2.1. Thực đơn refactor

Tại vị trí đang soạn thảo, nhấn phải chuột và chọn refactor hoặc sử dụng tổ hợp phím
Alt-Shift-T, bạn sẽ nhận được một thực đơn những thao tác refactor khả dĩ. Danh sách
này tuỳ thuộc vào nơi bạn đặt con trỏ nên nó có thể không cho thấy hết những khả năng
của Eclipse.
6.2.2. Đổi tên
Đây là thao tác hay dùng nhất. Khi đặt con trỏ ở tên một lớp, biến hay phương thức và
nhấn tổ hợp phím Alt-Shift-R, tên được chọn sẽ được đánh dấu ở tất cả những nơi nó
xuất hiện trong cửa số soạn thảo hiện thời. Khi đó bạn hãy nhập tên mới bạn mong
muốn và nhấn enter. Tất cả những tham chiếu đến đối tượng đó cả trong và ngoài cửa sổ
hiện thời đều được đổi theo.

Page 24



Bạn cũng có thể chọn một tệp mã nguồn Java và nhấn tổ hợp phím Alt-Shift-R hoặc
nhấn phím F2 để đổi tên lớp bên trong và cùng tên với tệp đó.

6.2.3. Trích phương thức
Lựa chọn một đoạn mã và nhấn tổ hợp phím Alt-Shift-M hoặc kích hoạt thực đơn
refactor và chọn mục Extract method.
Trong hộp hội thoại, hãy điền tên phương thức mới, bạn có thể sắp xếp thứ tự các tham
số cũng như thay đổi tên, kiểu của tham số.

Page 25


×