You are on page 1of 363

Trang 1

www.it-ebooks.info

Trang 2

Mã sạch
www.it-ebooks.info

Trang 3
Robert C. Martin Series
Nhiệm vụ của loạt bài này là cải thiện trạng thái của nghệ thuật thủ công phần mềm.
Những cuốn sách trong bộ này là kỹ thuật, thực dụng và quan trọng. Các tác giả là
các thợ thủ công có kinh nghiệm cao và các chuyên gia chuyên viết về những gì
thực sự hoạt động trong thực tế, trái ngược với những gì có thể hoạt động trên lý thuyết. Bạn sẽ đọc
về những gì tác giả đã làm, không phải những gì anh ta nghĩ bạn nên làm. Nếu cuốn sách là
về lập trình, sẽ có rất nhiều mã. Nếu cuốn sách nói về quản lý,
sẽ là rất nhiều trường hợp nghiên cứu từ các dự án thực tế.
Đây là những cuốn sách mà tất cả những học viên nghiêm túc sẽ có trên giá sách của họ.
Đây là những cuốn sách sẽ được ghi nhớ vì đã tạo ra sự khác biệt và hướng dẫn
chuyên nghiệp để trở thành nghệ nhân thực thụ.
Quản lý các dự án Agile
Sanjiv Augustine
Lập kế hoạch và ước tính nhanh
Mike Cohn
Làm việc hiệu quả với Mã kế thừa
Michael C. Feathers
Agile Java ™: Tạo mã với Phát triển theo hướng kiểm tra
Jeff Langr
Các Nguyên tắc, Mẫu và Thực hành Agile trong C #
Robert C. Martin và Micah Martin
Phát triển phần mềm Agile: Nguyên tắc, Mẫu và Thực hành
Robert C. Martin
Mã sạch: Sổ tay về nghề thủ công phần mềm Agile
Robert C. Martin
UML dành cho lập trình viên Java ™
Robert C. Martin
Phù hợp cho việc phát triển phần mềm: Khung cho các bài kiểm tra tích hợp
Rick Mugridge và Ward Cunningham
Phát triển phần mềm Agile với SCRUM
Ken Schwaber và Mike Beedle
Kỹ thuật phần mềm cực đoan: Một cách tiếp cận
Daniel H. Steinberg và Daniel W. Palmer
Để biết thêm thông tin, hãy truy cập Informit.com/martinseries
www.it-ebooks.info

Trang 4

Mã sạch
Sổ tay Agile
Tay nghề phần mềm
Người cố vấn đối tượng:
Robert C. Martin
Michael C. Feathers Timothy R. Ottinger
Jeffrey J. Langr Brett L. Schuchert
James W. Grenning Kevin Dean Wampler
Object Mentor Inc.
Viết mã sạch là những gì bạn phải làm để gọi mình là một người chuyên nghiệp.
Không có lý do hợp lý nào để làm bất cứ điều gì kém hơn khả năng của bạn.
Sông Upper Saddle, NJ • Boston • Indianapolis • San Francisco
New York • Toronto • Montreal • London • Munich • Paris • Madrid
Capetown • Sydney • Tokyo • Singapore • Thành phố Mexico
www.it-ebooks.info

Trang 5
Nhiều ký hiệu được các nhà sản xuất và người bán sử dụng để phân biệt sản phẩm của họ được tuyên bố là
nhãn hiệu. Nơi những ký hiệu đó xuất hiện trong cuốn sách này và nhà xuất bản đã biết về khiếu nại nhãn hiệu,
các ký hiệu đã được in hoa đầu tiên hoặc viết hoa tất cả.
Các tác giả và nhà xuất bản đã quan tâm đến việc chuẩn bị cuốn sách này, nhưng không bày tỏ hoặc
ngụ ý bảo hành dưới bất kỳ hình thức nào và không chịu trách nhiệm về lỗi hoặc thiếu sót. Không có trách nhiệm pháp lý
cho những thiệt hại ngẫu nhiên hoặc do hậu quả liên quan đến hoặc phát sinh từ việc sử dụng thông tin hoặc
chương trình có ở đây.
Nhà xuất bản giảm giá tuyệt vời cho cuốn sách này khi đặt hàng với số lượng lớn để mua số lượng lớn hoặc
bán hàng đặc biệt, có thể bao gồm các phiên bản điện tử và / hoặc bìa tùy chỉnh và nội dung dành riêng cho
kinh doanh, mục tiêu đào tạo, trọng tâm tiếp thị và sở thích xây dựng thương hiệu. Vui lòng liên lạc để biết thêm thông tin:
Doanh nghiệp và Chính phủ Hoa Kỳ
(800) 382-3419
Corpsales@pearsontechgroup.com
Để bán hàng bên ngoài Hoa Kỳ, vui lòng liên hệ:
Doanh số bán hàng quốc tế
international@pearsoned.com
Bao gồm tài liệu tham khảo và chỉ mục.
ISBN 0-13-235088-2 (pbk .: alk. Paper)
1. Phát triển phần mềm nhanh nhẹn. 2. Phần mềm máy tính — Độ tin cậy. I. Tiêu đề.
QA76.76.D47M3652 2008
005.1 — dc22
2008024750
Bản quyền © 2009 Pearson Education, Inc.
Đã đăng ký Bản quyền. Được in tại Hoa Kỳ. Ấn phẩm này được bảo vệ bởi bản quyền,
và phải có sự cho phép từ nhà xuất bản trước bất kỳ hành vi sao chép bị cấm nào, lưu trữ trong
hệ thống truy xuất, hoặc truyền tải dưới bất kỳ hình thức nào hoặc bằng bất kỳ phương tiện nào, điện tử, cơ khí, photocopy,
ghi âm, hoặc tương tự. Để biết thông tin về quyền, hãy viết thư tới:
Pearson Education, Inc
Bộ phận Quyền và Hợp đồng
501 Phố Boylston, Suite 900
Boston, MA 02116
Fax: (617) 671-3447
ISBN-13: 978-0-13-235088-4
ISBN-10: 0-13-235088-2
Văn bản được in tại Hoa Kỳ trên giấy tái chế tại Courier ở Stoughton, Massachusetts.
In lần đầu tháng 7 năm 2008
www.it-ebooks.info

Trang 6
Đối với Ann Marie: Tình yêu vĩnh cửu của đời tôi.
www.it-ebooks.info

Trang 7
Trang này cố ý để trống
www.it-ebooks.info

Trang 8
vii

Nội dung
Lời nói đầu ... ...............................................x
Giới thiệu: ................................................... ......................................... xxv
Trên bìa ... ........................................ xxix
Chương 1: Mã sạch ............................................. ........................... 1
Sẽ có mã ... ................................. 2
Mã lỗi ... ................................................... 3
Tổng chi phí sở hữu một tin nhắn ........................................... .............4
Thiết kế lại lớn trên bầu trời ............................................ .............. 5
Thái độ................................................. .............................................. 5
Câu hỏi hóc búa cơ bản ... ....................... 6
Nghệ thuật của Quy tắc sạch? ............................................ .......................... 6
Mã sạch là gì? ............................................. ............................. 7
Các trường phái tư tưởng ... ............................... 12
Chúng tôi là tác giả ... ..................................... 13
Quy tắc Hướng đạo sinh .............................................. ............................... 14
Phần tiền truyện và nguyên tắc ... ......................... 15
Kết luận: ................................................... ........................................... 15
Thư mục ... ......................................... 15
Chương 2: Những cái tên có ý nghĩa ............................................. .......... 17
Giới thiệu: ................................................... ......................................... 17
Sử dụng tên có ý định tiết lộ ............................................. ............ 18
Tránh thông tin ... .......................... 19
Tạo sự khác biệt có ý nghĩa ................................................... ............ 20
Sử dụng tên có thể phát âm được ... ................... 21
Sử dụng tên có thể tìm kiếm ... ......................... 22
www.it-ebooks.info

Trang 9
viii
Nội dung
Tránh mã hóa .............................................. .................................. 23
Ký hiệu tiếng Hungary: ................................................... .......................... 23
Tiền tố thành viên ... ............................... 24
Giao diện và triển khai ... ........ 24
Tránh lập bản đồ tinh thần ... ........................ 25
Tên lớp ... ......................................... 25
Tên phương pháp ... ..................................... 25
Đừng dễ thương ............................................. ......................................... 26
Chọn một từ cho mỗi khái niệm ............................................. .................. 26
Đừng chơi chữ .............................................. ............................................... 26
Sử dụng tên miền giải pháp .............................................. ................ 27
Sử dụng tên miền có vấn đề .............................................. ................ 27
Thêm ngữ cảnh có ý nghĩa ... ..................... 27
Không thêm ngữ cảnh vô cớ ............................................ ............... 29
Lời cuối ... .......................................... 30
Chương 3: Chức năng .............................................. ........................... 31
Nhỏ! ...................................................... ...................................................... 34
Chặn và thụt lề ................................................... ......................... 35
Làm một việc ... ........................................ 35
Các phần trong chức năng ... ................. 36
Một mức độ trừu tượng cho mỗi hàm ............................................ .36
Đọc mã từ trên xuống dưới: Quy tắc bước xuống .................. 37
Chuyển đổi câu lệnh ... ............................... 37
Sử dụng tên mô tả ... ......................... 39
Đối số hàm .............................................. ............................ 40
Các dạng đơn nguyên chung ................................................... .................. 41
Cờ đối số ... ................................ 41
Chức năng Dyadic ................................................ .............................. 42
Bộ ba ................................................... ............................................... 42
Đối tượng lập luận .............................................. ............................. 43
Danh sách đối số ... ................................. 43
Động từ và Từ khóa ................................................... .......................... 43
Không có tác dụng phụ .............................................. ............................. 44
Kết quả đối số .............................................. ............................ 45
Tách truy vấn lệnh ................................................... .............. 45
www.it-ebooks.info

Trang 10
ix
Nội dung
Ưu tiên các trường hợp ngoại lệ trả lại mã lỗi ................................... 46
Trích xuất khối thử / bắt ............................................. .................... 46
Xử lý lỗi là một điều ............................................. ............... 47
Nam châm phụ thuộc Error.java ............................................ .47
Đừng lặp lại chính mình ............................................. ............................ 48
Lập trình có cấu trúc ... ................... 48
Làm thế nào để bạn viết các hàm như thế này? .......................................... 49
Kết luận: ................................................... ........................................... 49
SetupTeardownIncluder ............................................. .................... 50
Thư mục ... ........................................ 52
Chương 4: Nhận xét .............................................. ......................... 53
Nhận xét không bù đắp cho mã xấu ....................................... 55
Giải thích về bản thân bằng mã lệnh .............................................. ...................... 55
Ý kiến tốt ... .................................. 55
Ý kiến pháp lý ... ............................... 55
Bình luận cung cấp thông tin ... ..................... 56
Giải thích ý định ... ......................... 56
Làm rõ................................................. ..................................... 57
Cảnh báo hậu quả ................................................... ................. 58
VIỆC CẦN LÀM: ................................................... ............................. 58
Khuếch đại ................................................... ................................... 59
Javadocs trong API công khai .............................................. ...................... 59
Nhận xét sai ... .................................... 59
Lầm bầm ... ......................................... 59
Nhận xét thừa ... ...................... 60
Nhận xét gây hiểu lầm .............................................. ...................... 63
Nhận xét được ủy quyền ... ........................ 63
Bình luận tạp chí ................................................... ............................ 63
Bình luận tiếng ồn ... .............................. 64
Tiếng ồn đáng sợ ... ...................................... 66
Không sử dụng bình luận khi bạn có thể sử dụng
Hàm hoặc một biến .............................................. ......................... 67
Dấu vị trí ................................................... ............................... 67
Đóng dấu ngoặc nhận xét .............................................. .................. 67
Thuộc tính và dòng .................... 68
www.it-ebooks.info

Trang 11
x
Nội dung
Mã đã nhận xét .............................................. ........................ 68
Nhận xét HTML ... ............................ 69
Thông tin phi địa phương .............................................. ....................... 69
Quá nhiều thông tin ............................................... ...................... 70
Kết nối không rõ ràng .............................................. ....................... 70
Tiêu đề chức năng ... .............................. 70
Javadocs trong mã không công khai .............................................. .............. 71
Thí dụ................................................. ........................................... 71
Thư mục ... ......................................... 74
Chương 5: Định dạng .............................................. ........................ 75
Mục đích của việc định dạng .............................................. .................. 76
Định dạng dọc ................................................... ............................. 76
Phép ẩn dụ về tờ báo ... ................. 77
Độ mở theo chiều dọc giữa các khái niệm .............................................. 78
Mật độ dọc ...................................................... ................................ 79
Khoảng cách theo phương thẳng đứng ................................................ .............................. 80
Đặt hàng dọc ................................................... .............................. 84
Định dạng ngang ... ........................ 85
Mật độ và độ mở theo chiều ngang ............................................. ...... 86
Căn chỉnh theo chiều ngang ................................................ ....................... 87
Thụt lề ................................................... ....................................... 88
Phạm vi giả ... ................................. 90
Nội quy của Đội ... ........................................... 90
Quy tắc định dạng của Uncle Bob .............................................. .............. 90
Chương 6: Đối tượng và cấu trúc dữ liệu .................................... 93
Tóm tắt dữ liệu ... .................................. 93
Chống đối xứng dữ liệu / đối tượng ............................................ .................. 95
Định luật Demeter .............................................. .............................. 97
Xác tàu bị đắm ................................................ .................................... 98
Các phép lai ... ............................................ 99
Cấu trúc ẩn ... ............................... 99
Đối tượng truyền dữ liệu ................................................... ........................ 100
Bản ghi hoạt động ... ................................. 101
Kết luận: ................................................... ......................................... 101
Thư mục ... ...................................... 101
www.it-ebooks.info

Trang 12
xi
Nội dung
Chương 7: Xử lý lỗi ............................................. .............. 103
Sử dụng ngoại lệ thay vì mã trả lại ................................... 104
Viết Tuyên bố Thử-Bắt-Cuối cùng của Bạn Đầu tiên ....................... 105
Sử dụng các ngoại lệ đã bỏ chọn ........................................... ................ 106
Cung cấp ngữ cảnh có ngoại lệ .............................................. ....... 107
Xác định các loại ngoại lệ theo nhu cầu của người gọi .................. 107
Xác định dòng chảy bình thường .............................................. ...................... 109
Đừng trả về giá trị vô nghĩa ............................................. ................................. 110
Không vượt qua số vô giá ............................................. ..................................... 111
Kết luận: ................................................... ......................................... 112
Thư mục ... ...................................... 112
Chương 8: Ranh giới .............................................. ...................... 113
Sử dụng mã của bên thứ ba ............................................. ....................... 114
Khám phá và tìm hiểu ranh giới .............................................. .116
Học log4j ................................................ ................................. 116
Kiểm tra học tập tốt hơn miễn phí ............................................ ... 118
Sử dụng mã chưa tồn tại ........................................... ..... 118
Ranh giới sạch ... .............................. 120
Thư mục ... ...................................... 120
Chương 9: Kiểm tra đơn vị ............................................. .......................... 121
Ba định luật TDD ............................................. ...................... 122
Giữ sạch các bài kiểm tra ................................................... ........................... 123
Kiểm tra Kích hoạt khả năng ............................................. ...................... 124
Kiểm tra sạch ... ......................................... 124
Ngôn ngữ kiểm tra miền cụ thể ............................................. ... 127
Tiêu chuẩn kép ... .............................. 127
Một xác nhận cho mỗi bài kiểm tra .............................................. ............................. 130
Khái niệm đơn cho mỗi bài kiểm tra .............................................. .................... 131
ĐẦU TIÊN ... ............................................ 132
Kết luận: ................................................... ......................................... 133
Thư mục ... ...................................... 133
Chương 10: Lớp học ............................................. ............................ 135
Tổ chức lớp ... ............................ 136
Đóng gói: ................................................... ................................ 136
www.it-ebooks.info

Trang 13
xii
Nội dung
Lớp học nên nhỏ! ...................................................... ................ 136
Nguyên tắc trách nhiệm duy nhất .............................................. .138
Sự gắn kết................................................. ......................................... 140
Duy trì kết quả gắn kết trong nhiều nhóm nhỏ .................. 141
Tổ chức để thay đổi ... ...................... 147
Cách ly khỏi sự thay đổi ................................................... ..................... 149
Thư mục ... ...................................... 151
Chương 11: Hệ thống .............................................. .......................... 153
Bạn sẽ xây dựng một thành phố như thế nào? ...................................................... ........ 154
Xây dựng riêng một hệ thống khỏi việc sử dụng nó .................................. 154
Tách chính ................................................... .......................... 155
Nhà máy sản xuất ... ......................................... 155
Hệ thống phụ thuộc .................................................................. ..................... 157
Scaling Up ................................................ .......................................... 157
Mối quan tâm xuyên suốt .............................................. ................... 160
Các proxy Java ................................................................ ........................................ 161
Khung AOP thuần Java .............................................. ............... 163
Các khía cạnh của AspectJ ................................................ ................................. 166
Chạy thử kiến trúc hệ thống ............................................. ..... 166
Tối ưu hóa việc ra quyết định ... ................ 167
Sử dụng các tiêu chuẩn một cách khôn ngoan, khi chúng thêm giá trị có thể chứng minh
được ......... 168
Hệ thống cần ngôn ngữ dành riêng cho miền ..................................... 168
Kết luận: ................................................... ......................................... 169
Thư mục ... ...................................... 169
Chương 12: Sự xuất hiện .............................................. .................... 171
Làm sạch thông qua thiết kế nổi bật ............................................. ... 171
Quy tắc thiết kế đơn giản 1: Chạy tất cả các bài kiểm tra ......................................... 172
Quy tắc thiết kế đơn giản 2–4: Tái cấu trúc lại .......................................... ..172
Không trùng lặp ... ................................... 173
Diễn đạt ... .......................................... 175
Các lớp và phương thức tối thiểu .............................................. ........... 176
Kết luận: ................................................... ......................................... 176
Thư mục ... ...................................... 176
Chương 13: Đồng thời .............................................. ................ 177
Tại sao lại sử dụng đồng thời? ...................................................... ......................... 178
Những lầm tưởng và quan niệm sai lầm ................................................... .............. 179
www.it-ebooks.info

Trang 14
xiii
Nội dung
Thách thức ... ......................................... 180
Nguyên tắc phòng thủ đồng thời ................................................... ....... 180
Nguyên tắc trách nhiệm đơn lẻ ... ....... 181
Hệ quả: Giới hạn phạm vi dữ liệu ........................................... ..... 181
Hệ quả: Sử dụng bản sao dữ liệu ............................................ ........... 181
Hệ quả: Các chủ đề nên càng độc lập càng tốt ............ 182
Biết thư viện của bạn ... ............................ 182
Bộ sưu tập an toàn theo chuỗi .............................................. ................... 182
Biết các mô hình thực thi của bạn .............................................. ............ 183
Người sản xuất người tiêu dùng............................................... ......................... 184
Người đọc-Người viết: ................................................... ............................... 184
Các triết gia ăn uống ... ....................... 184
Cẩn thận với sự phụ thuộc giữa các phương thức đồng bộ hóa ................. 185
Giữ các phần nhỏ được đồng bộ hóa .............................................. .... 185
Viết đúng mã tắt máy rất khó ..................................... 186
Kiểm tra mã phân luồng ................................................... ...................... 186
Xử lý các lỗi giả mạo là vấn đề phân luồng ứng viên ................. 187
Lấy mã chưa đọc của bạn làm việc đầu tiên .................................... 187
Làm cho mã luồng của bạn trở nên dễ hiểu ............................................ 187
Đặt mã luồng của bạn có thể điều chỉnh được ............................................. ... 187
Chạy với nhiều luồng hơn bộ xử lý ....................................... 188
Chạy trên các nền tảng khác nhau .............................................. .............. 188
Công cụ mã của bạn để thử và buộc thất bại ............................ 188
Mã hóa bằng tay ... .................................... 189
Tự động hóa ................................................... ..................................... 189
Kết luận: ................................................... ......................................... 190
Thư mục ... ...................................... 191
Chương 14: Sàng lọc kế thừa ........................................... 193
Thực hiện Args .............................................. ........................ 194
Tôi đã làm điều này như thế nào? ...................................................... ..................... 200
Args: Bản nháp thô ............................................. ........................ 201
Vì vậy, tôi dừng lại ... .................................... 212
Về chủ nghĩa gia tăng ................................................ ......................... 212
Đối số chuỗi ................................................... .............................. 214
Kết luận: ................................................... ........................................ 250
www.it-ebooks.info

Trang 15
xiv
Nội dung
Chương 15: Nội bộ JUnit ............................................. ............. 251
Khung JUnit ............................................... ........................ 252
Kết luận: ................................................... ......................................... 265
Chương 16: Tái cấu trúc ngày nối tiếp ......................................... 267
Đầu tiên, làm cho nó hoạt động ... .............................. 268
Sau đó thực hiện đúng .............................................. ............................. 270
Kết luận: ................................................... ......................................... 284
Thư mục ... ...................................... 284
Chương 17: Mùi và phương pháp chẩn đoán ............................................ .285
Nhận xét ... ......................................... 286
C1: Thông tin không phù hợp .............................................. ......... 286
C2: Nhận xét lỗi thời .............................................. ..................... 286
C3: Nhận xét dư thừa .............................................. ................. 286
C4: Nhận xét viết kém ............................................. ............. 287
C5: Mã nhận xét ra ... ................. 287
Môi trường ... ..................................... 287
E1: Xây dựng yêu cầu nhiều hơn một bước ......................................... 287
E2: Các thử nghiệm yêu cầu nhiều hơn một bước .......................................... 287
Chức năng ... ........................................... 288
F1: Quá nhiều đối số ............................................. ................... 288
F2: Đầu ra đối số .............................................. ...................... 288
F3: Cờ đối số .............................................. .......................... 288
F4: Chức năng chết .............................................. ........................... 288
Khái quát chung ... .............................................. 288
G1: Nhiều ngôn ngữ trong một tệp nguồn .................................. 288
G2: Hành vi rõ ràng không được thực hiện ...................................... 288
G3: Hành vi không chính xác tại ranh giới ..................................... 289
G4: Các quyền an toàn bị ghi đè .............................................. ................... 289
G5: Sao chép ................................................... ............................... 289
G6: Mã ở mức độ trừu tượng sai ........................................ 290
G7: Các lớp cơ sở tùy thuộc vào các phái sinh của chúng ....................... 291
G8: Quá nhiều thông tin ............................................. ................ 291
G9: Mã chết .............................................. ................................. 292
G10: Tách dọc .............................................. .................. 292
G11: Không nhất quán ................................................... .......................... 292
G12: Lộn xộn ........................................... ..................................... 293
www.it-ebooks.info

Trang 16
xv
Nội dung
G13: Khớp nối nhân tạo .............................................. ................... 293
G14: Tính năng đố kỵ .............................................. ............................ 293
G15: Đối số của bộ chọn .............................................. .................. 294
G16: Ý định bị che khuất .............................................. ....................... 295
G17: Trách nhiệm không đúng chỗ .............................................. ......... 295
G18: Tĩnh không phù hợp .............................................. ................. 296
G19: Sử dụng các biến giải thích ............................................. ....... 296
G20: Tên chức năng nói lên tác dụng của chúng .......................... 297
G21: Hiểu thuật toán ............................................. ........ 297
G22: Tạo sự phụ thuộc logic về mặt vật lý ................................... 298
G23: Ưu tiên Đa hình thành If / Else hoặc Switch / Case .................... 299
G24: Tuân theo các quy ước chuẩn ............................................. ... 299
G25: Thay thế các số ma thuật bằng các hằng số có tên .................. 300
G26: Hãy chính xác .............................................. ................................ 301
G27: Cấu trúc so với Công ước ............................................. ........ 301
G28: Đóng gói các điều kiện .............................................. ....... 301
G29: Tránh các điều kiện phủ định ............................................. .... 302
G30: Các chức năng nên làm một việc ........................................... 302
G31: Mối ghép tạm thời ẩn ............................................. ..... 302
G32: Đừng tùy tiện ........................................... ...................... 303
G33: Đóng gói các điều kiện ranh giới ........................................ 304
G34: Chỉ nên giảm thiểu các chức năng
Một mức độ trừu tượng .............................................. .................. 304
G35: Giữ dữ liệu có thể định cấu hình ở mức cao ................................ 306
G36: Tránh điều hướng chuyển tiếp ............................................. ...... 306
Java ... ...................................................... ..307
J1: Tránh danh sách nhập dài bằng cách sử dụng ký tự đại diện ............................ 307
J2: Không kế thừa các hằng số ........................................... ................. 307
J3: Hằng số so với Enums ............................................. .............. 308
Tên ... ................................................ 309
N1: Chọn tên mô tả ............................................. ......... 309
N2: Chọn tên ở mức độ trừu tượng thích hợp .......... 311
N3: Sử dụng Danh pháp Chuẩn khi Có thể ........................... 311
N4: Tên rõ ràng .............................................. ................. 312
N5: Sử dụng tên dài cho phạm vi dài .......................................... .312
N6: Tránh mã hóa .............................................. ........................ 312
N7: Tên nên mô tả tác dụng phụ.  ..................................... 313
www.it-ebooks.info

Trang 17
xvi
Nội dung
Kiểm tra ... ...................................................... .313
T1: Kiểm tra không đầy đủ .............................................. ......................... 313
T2: Sử dụng Công cụ Bảo hiểm!  ...................................................... ............. 313
T3: Đừng bỏ qua những bài kiểm tra tầm thường .......................................... .................. 313
T4: Một bài kiểm tra bị bỏ qua là một câu hỏi về sự mơ hồ .................. 313
T5: Các điều kiện ranh giới thử nghiệm ............................................. ........... 314
T6: Kiểm tra hoàn toàn gần lỗi ............................................ ........ 314
T7: Các mô hình thất bại đang bộc lộ ........................................... .314
T8: Các mẫu bao phủ thử nghiệm có thể được tiết lộ ............................... 314
T9: Kiểm tra sẽ nhanh chóng ............................................ ..................... 314
Kết luận: ................................................... ......................................... 314
Thư mục ... ...................................... 315
Phụ lục A: Đồng thời II ............................................ ............ 317
Ví dụ về Máy khách / Máy chủ .............................................. ........................ 317
Máy chủ ... ...................................... 317
Thêm luồng ... ........................... 319
Quan sát máy chủ .............................................. ....................... 319
Phần kết luận................................................. ..................................... 321
Các con đường thực hiện có thể có .............................................. ................ 321
Số đường dẫn .............................................. .............................. 322
Đào sâu hơn ................................................ .............................. 323
Phần kết luận................................................. ..................................... 326
Biết thư viện của bạn ... ....................... 326
Khuôn khổ người thực thi .............................................. ...................... 326
Giải pháp không chặn .................................................................. ................... 327
Các lớp an toàn không theo luồng .............................................. .................... 328
Sự phụ thuộc giữa các phương pháp
Có thể phá vỡ mã đồng thời .............................................. ............. 329
Chịu đựng thất bại ................................................... .......................... 330
Khóa dựa trên máy khách .............................................. ....................... 330
Khóa dựa trên máy chủ .............................................. ...................... 332
Tăng thông lượng .............................................. ..................... 333
Tính toán thông lượng đơn luồng ...................................... 334
Tính toán thông lượng đa luồng .......................................... 335
Bế tắc ................................................... ............................................ 335
Loại trừ lẫn nhau ................................................ ........................... 336
Khóa & Chờ .................................... 337
www.it-ebooks.info

Trang 18
xvii
Nội dung
Không có quyền ưu tiên................................................ ................................ 337
Vòng chờ ...................................................... .................................. 337
Phá vỡ loại trừ lẫn nhau ................................................... ............. 337
Mở khóa và chờ đợi .............................................. ...................... 338
Phá vỡ dự phòng ............................................................... ...................... 338
Ngắt vòng chờ đợi ................................................... .................... 338
Kiểm tra mã đa luồng ... .............. 339
Hỗ trợ công cụ để kiểm tra mã dựa trên chuỗi ................................ 342
Kết luận: ................................................... ......................................... 342
Hướng dẫn: Ví dụ về mã đầy đủ ............................................. ............. 343
Máy khách / Máy chủ được đọc trước .............................................. ............... 343
Máy khách / Máy chủ sử dụng chủ đề ............................................. ............. 346
Phụ lục B: org.jfree.date.SerialDate ...................................... 349
Phụ lục C: Tham khảo chéo về phương pháp chẩn đoán ........................... 409
Phần kết ... ............................................... 411
Mục lục ... ...................................................... ... 413
www.it-ebooks.info

Trang 19
Trang này cố ý để trống
www.it-ebooks.info

Trang 20
xix

Lời tựa
Một trong những loại kẹo yêu thích của chúng tôi ở đây ở Đan Mạch là Ga-Jol, có hơi cam thảo mạnh
bổ sung hoàn hảo cho thời tiết ẩm ướt và thường lạnh của chúng tôi. Một phần sức hấp dẫn của Ga-Jol đối với
us Danes là những câu nói khôn ngoan hoặc dí dỏm được in trên nắp của mỗi chiếc hộp. Tôi đã mua một hai-
sáng nay gói đồ ăn ngon và thấy rằng nó mang theo cái nhìn cũ của người Đan Mạch:
Ærlighed i små ting er ikke nogen lille ting .
"Trung thực trong những việc nhỏ không phải là một việc nhỏ." Đó là một điềm tốt phù hợp với những gì tôi
đã muốn nói ở đây. Những điều nhỏ nhặt quan trọng. Đây là một cuốn sách về những mối quan tâm khiêm tốn
mà giá trị của nó vẫn không hề nhỏ.
Kiến trúc sư Ludwig mies van der Rohe cho biết Chúa ở trong từng chi tiết . Trích dẫn này nhớ lại
những lập luận đương đại về vai trò của kiến trúc trong phát triển phần mềm, và
rõ ràng trong thế giới Agile. Bob và tôi thỉnh thoảng thấy mình say mê tham gia
cuộc đối thoại này. Và vâng, mies van der Rohe đã chú ý đến tiện ích và những hình thức vượt thời gian
xây dựng nền tảng kiến trúc vĩ đại. Mặt khác, anh cũng đích thân lựa chọn
mọi tay nắm cửa cho mọi ngôi nhà do anh ấy thiết kế. Tại sao? Bởi vì những điều nhỏ nhặt quan trọng.
Trong “cuộc tranh luận” đang diễn ra của chúng tôi về TDD, Bob và tôi đã phát hiện ra rằng chúng tôi đồng ý rằng
kiến trúc đồ đạc có một vị trí quan trọng trong sự phát triển, mặc dù chúng ta có thể có
tầm nhìn về chính xác điều đó có nghĩa là gì. Tuy nhiên, những điều ngụy biện như vậy tương đối không quan trọng,
bởi vì chúng tôi có thể chấp nhận rằng các chuyên gia có trách nhiệm cho một thời gian để suy nghĩ-
lập và lập kế hoạch khi bắt đầu một dự án. Những quan niệm về thiết kế vào cuối những năm 1990 chỉ được thúc
đẩy bởi
các bài kiểm tra và mã đã biến mất từ lâu. Tuy nhiên, sự chú ý đến từng chi tiết là một yếu tố quan trọng hơn
nền tảng của sự chuyên nghiệp hơn bất kỳ tầm nhìn lớn nào. Đầu tiên, đó là thông qua thực hành trong
nhỏ mà các chuyên gia đạt được sự thành thạo và tin tưởng để thực hành nói chung. Thứ hai,
chút nhỏ nhất của kết cấu cẩu thả, của cánh cửa không đóng chặt hoặc hơi
gạch quanh co trên sàn nhà, hoặc thậm chí cả bàn làm việc lộn xộn, hoàn toàn xóa tan sự quyến rũ của
toàn bộ lớn hơn. Đó là những gì về mã sạch.
Tuy nhiên, kiến trúc chỉ là một phép ẩn dụ cho việc phát triển phần mềm và đặc biệt là
một phần của phần mềm cung cấp sản phẩm ban đầu giống như một kiến trúc sư
mang lại một tòa nhà nguyên sơ. Trong những ngày của Scrum và Agile, trọng tâm là nhanh chóng
đưa sản phẩm ra thị trường. Chúng tôi muốn nhà máy chạy với tốc độ cao nhất để sản xuất phần mềm.
Đây là những nhà máy của con người: những lập trình viên suy nghĩ, cảm nhận đang làm việc từ một sản phẩm trở
lại-
nhật ký hoặc câu chuyện người dùng để tạo sản phẩm . Phép ẩn dụ về sản xuất luôn tồn tại mạnh mẽ trong
Suy nghĩ. Các khía cạnh sản xuất của sản xuất ô tô Nhật Bản, của một dây chuyền lắp ráp
thế giới, truyền cảm hứng cho nhiều Scrum.
www.it-ebooks.info

Trang 21
xx
Lời tựa
Tuy nhiên, ngay cả trong ngành công nghiệp ô tô, phần lớn công việc không nằm ở sản xuất mà là ở
bảo trì — hoặc tránh nó. Trong phần mềm, 80% hoặc nhiều hơn những gì chúng ta làm được gọi là
"Bảo trì": hành động sửa chữa. Thay vì ôm trọng tâm của phương Tây điển hình trên thân
sử dụng phần mềm tốt, chúng ta nên suy nghĩ nhiều hơn như những người thợ sửa nhà trong tòa nhà
công nghiệp hoặc cơ khí ô tô trong lĩnh vực ô tô. Quản lý Nhật Bản có gì
nói về điều đó ?
Vào khoảng năm 1951, một cách tiếp cận chất lượng được gọi là Bảo trì Năng suất Toàn diện (TPM) đã ra đời
trên cảnh Nhật Bản. Nó tập trung vào bảo trì hơn là sản xuất. Một trong những
trụ cột chính của TPM là tập hợp các nguyên tắc được gọi là 5S. 5S là một tập hợp các kỷ luật — và
ở đây tôi sử dụng thuật ngữ "kỷ luật" một cách giảng dạy. Các nguyên tắc 5S này trên thực tế nằm ở cơ sở-
tions of Lean — một từ thông dụng khác trên thị trường phương Tây và ngày càng nổi bật
từ thông dụng trong giới phần mềm. Những nguyên tắc này không phải là một lựa chọn. Như chú Bob liên quan đến
vấn đề quan trọng của anh ấy, thực hành phần mềm tốt đòi hỏi kỷ luật như vậy: tập trung, sự hiện diện của tâm trí,
và suy nghĩ. Nó không phải lúc nào cũng chỉ là làm, về việc thúc đẩy thiết bị nhà máy để
đấu với vận tốc tối ưu. Triết lý 5S bao gồm các khái niệm sau:
• Seiri , hoặc tổ chức (nghĩ “sắp xếp” bằng tiếng Anh). Biết mọi thứ đang ở đâu — sử dụng
các phương pháp tiếp cận như đặt tên phù hợp — là rất quan trọng. Bạn nghĩ rằng việc đặt tên cho số nhận dạng
không
quan trọng? Đọc tiếp trong các chương sau.
• Seiton , hoặc ngăn nắp (nghĩ rằng "hệ thống hóa" trong tiếng Anh). Có một câu nói cổ của người Mỹ:
Một nơi cho mọi thứ, và mọi thứ ở đúng vị trí của nó . Một đoạn mã phải ở đâu
bạn mong đợi sẽ tìm thấy nó — và nếu không, bạn nên tính toán lại để đạt được nó.
• Seiso , hoặc dọn dẹp (nghĩ là “tỏa sáng” trong tiếng Anh): Giữ cho nơi làm việc không bị treo
dây điện, dầu mỡ, phế liệu và chất thải. Các tác giả ở đây nói gì về việc xả rác của bạn
mã có nhận xét và dòng mã nhận xét ghi lại lịch sử hoặc mong muốn
tương lai? Loại bỏ chúng.
• Seiketsu , hay tiêu chuẩn hóa: Nhóm thống nhất về cách giữ cho nơi làm việc sạch sẽ.
Bạn có nghĩ rằng cuốn sách này nói bất cứ điều gì về việc có một phong cách mã hóa nhất quán và một bộ
thực hành trong nhóm? Những tiêu chuẩn đó đến từ đâu? Đọc tiếp.
• Shutsuke , hoặc kỷ luật ( tự kỷ luật ). Điều này có nghĩa là có kỷ luật để tuân theo
thực hành và thường xuyên phản ánh công việc của một người và sẵn sàng thay đổi.
Nếu bạn chấp nhận thử thách — vâng, thử thách — khi đọc và áp dụng cuốn sách này,
bạn sẽ hiểu và đánh giá cao điểm cuối cùng. Ở đây, cuối cùng chúng tôi cũng đang lái xe đến
gốc rễ của sự chuyên nghiệp có trách nhiệm trong một nghề cần quan tâm đến cuộc sống
chu kỳ của một sản phẩm. Khi chúng tôi bảo trì ô tô và các máy móc khác theo TPM, phá vỡ-
giảm bảo trì — chờ lỗi xuất hiện — là ngoại lệ. Thay vào đó, chúng tôi đi lên một
cấp độ: kiểm tra máy hàng ngày và sửa chữa các bộ phận bị mòn trước khi chúng bị hỏng, hoặc
tương đương với việc thay dầu 10.000 dặm theo phương ngôn đối với sự hao mòn và hao mòn. Trong mã,
refactor không thương tiếc. Bạn có thể cải thiện thêm một cấp nữa, khi phong trào TPM đổi mới-
hơn 50 năm trước: chế tạo những cỗ máy dễ bảo trì hơn ngay từ đầu. Mak-
việc nhập mã của bạn có thể đọc được cũng quan trọng như làm cho nó có thể thực thi được. Thực hành cuối cùng,
được giới thiệu trong giới TPM vào khoảng năm 1960, là tập trung vào việc giới thiệu toàn bộ máy mới hoặc
www.it-ebooks.info

Trang 22
xxi
Lời tựa
thay thế những cái cũ. Như Fred Brooks khuyên chúng ta, có lẽ chúng ta nên làm lại phần mềm chính-
Các khối đồ đạc được làm từ đầu cứ sau bảy năm hoặc lâu hơn để quét sạch các vết rạn nứt. Có lẽ
chúng ta nên cập nhật hằng số thời gian của Brooks thành thứ tự tuần, ngày hoặc giờ thay vì
nhiều năm. Đó là nơi chi tiết nằm.
Có sức mạnh to lớn về chi tiết, nhưng có điều gì đó khiêm tốn và sâu sắc về điều này
cách tiếp cận cuộc sống, như chúng ta có thể mong đợi một cách rập khuôn từ bất kỳ cách tiếp cận nào cho rằng
Japa-
rễ nese. Nhưng đây không chỉ là cách nhìn của người phương Đông về cuộc sống; Dân gian Anh và Mỹ khôn
ngoan-
dom đầy những lời khuyên nhủ như vậy. Trích dẫn Seiton từ trên chảy ra từ cây bút của
một bộ trưởng Ohio, người theo nghĩa đen xem sự gọn gàng “như một phương thuốc cho mọi mức độ xấu xa.”
Còn Seiso thì sao? Sạch sẽ bên cạnh sự tin kính . Đẹp như một ngôi nhà, một mớ hỗn độn
cái bàn cướp đi vẻ đẹp lộng lẫy của nó. Còn Shutsuke trong những vấn đề nhỏ này thì sao? Người trung thành
trong ít là trung thành trong nhiều . Còn về việc háo hức tái yếu tố vào thời điểm có trách nhiệm,
củng cố vị trí của một người cho các quyết định “lớn” tiếp theo, thay vì trì hoãn nó? A
khâu trong thời gian tiết kiệm chín . Con chim đầu bắt sâu. Đừng trì hoãn cho đến ngày mai
những gì bạn có thể làm hôm nay. (Đó là ý nghĩa ban đầu của cụm từ “người chịu trách nhiệm cuối cùng
moment ”trong Lean cho đến khi nó rơi vào tay các nhà tư vấn phần mềm . ) Làm thế nào về hiệu chỉnh-
ở chỗ của những nỗ lực nhỏ, cá nhân trong một tổng thể lớn? Cây sồi hùng mạnh từ những quả sồi nhỏ
lớn lên. Hoặc làm thế nào về việc tích hợp công việc phòng ngừa đơn giản vào cuộc sống hàng ngày? Một ounce
phòng ngừa có giá trị một pound chữa bệnh. Mỗi ngày một quả táo, bác sĩ không tới nhà. Mã sạch
tôn vinh nguồn gốc sâu xa của trí tuệ bên dưới nền văn hóa rộng lớn hơn của chúng tôi, hoặc nền văn hóa của chúng
tôi như trước đây,
hoặc nên có, và có thể chú ý đến từng chi tiết.
Ngay cả trong các tài liệu kiến trúc vĩ đại, chúng ta cũng tìm thấy những chiếc cưa có tác dụng trở lại những phụ trợ
này
chi tiết đặt ra. Hãy nghĩ về tay nắm cửa của mies van der Rohe. Đó là seiri . Đó là sự chú ý
cho mọi tên biến. Bạn nên đặt tên cho một biến bằng cách sử dụng cùng một cách mà bạn
đặt tên cho đứa con đầu lòng.
Như mọi chủ nhà đều biết, sự chăm sóc và trau chuốt liên tục như vậy không bao giờ kết thúc.
Kiến trúc sư Christopher Alexander - cha đẻ của các mẫu và ngôn ngữ mẫu - quan điểm
bản thân mỗi hành động thiết kế như một hành động sửa chữa nhỏ, cục bộ. Và anh ấy xem sự khéo léo của
cấu trúc tốt để trở thành mục tiêu duy nhất của kiến trúc sư; các hình thức lớn hơn có thể được để lại cho các mẫu
và ứng dụng của họ bởi cư dân. Thiết kế không ngừng liên tục khi chúng tôi thêm một
phòng của một ngôi nhà, nhưng khi chúng tôi chú ý đến việc sơn lại, thay thế những tấm thảm đã sờn hoặc nâng cấp
trong bồn rửa nhà bếp. Hầu hết các nghệ thuật đều lặp lại những tình cảm tương tự. Trong tìm kiếm của chúng tôi
cho những người khác
mô tả ngôi nhà của Đức Chúa Trời như là một trong những chi tiết, chúng ta thấy mình ở trong sự đồng hành tốt của
Tác giả người Pháp thế kỷ 19 Gustav Flaubert. Nhà thơ Pháp Paul Valery khuyên chúng ta rằng
bài thơ không bao giờ được hoàn thành và phải làm lại liên tục, và ngừng làm việc với nó là sự từ bỏ.
Sự bận tâm đến chi tiết như vậy là phổ biến đối với tất cả những nỗ lực xuất sắc. Vì vậy, có thể ở đó
hơi mới ở đây, nhưng khi đọc cuốn sách này, bạn sẽ được thử thách để tiếp thu tốt
những điều mà bạn đã đầu hàng từ lâu trước sự thờ ơ hoặc mong muốn sự tự nhiên và chính đáng
"Phản ứng với sự thay đổi."
Thật không may, chúng tôi thường không coi những mối quan tâm như vậy là nền tảng chính của nghệ thuật
lập trình. Chúng tôi từ bỏ mã của mình sớm, không phải vì nó đã hoàn thành, mà vì giá trị của chúng tôi
hệ thống tập trung nhiều hơn vào hình thức bên ngoài hơn là nội dung của những gì chúng tôi cung cấp.
www.it-ebooks.info

Trang 23
xxii
Lời tựa
Cuối cùng, sự thiếu chú ý này khiến chúng ta phải trả giá: Một xu xấu luôn xuất hiện . Nghiên cứu, không trong
trong ngành công nghiệp cũng như trong học thuật, tự hạ mình xuống vị trí thấp kém của việc giữ mã trong
sạch. Trở lại
trong những ngày tôi làm việc trong tổ chức Nghiên cứu Sản xuất Phần mềm Bell Labs ( Produc-
tion , thực sự!) chúng tôi đã có một số phát hiện phía sau cho thấy rằng
kiểu thụt lề là một trong những chỉ báo có ý nghĩa thống kê nhất về mật độ lỗi thấp.
Chúng tôi muốn nó trở thành kiến trúc hoặc ngôn ngữ lập trình hoặc một số khái niệm cao cấp khác
nên là nguyên nhân của chất lượng; với tư cách là những người được cho là chuyên nghiệp nhờ vào
thành thạo các công cụ và phương pháp thiết kế cao cả, chúng tôi cảm thấy bị xúc phạm bởi giá trị mà nhà máy đó-
máy sàn, người viết mã, thêm vào thông qua ứng dụng đơn giản nhất quán của một thụt đầu dòng
Phong cách. Để trích dẫn cuốn sách của chính tôi 17 năm trước, phong cách như vậy phân biệt sự xuất sắc với
năng lực đơn thuần. Thế giới quan của người Nhật hiểu được giá trị cốt yếu của cuộc sống hàng ngày
công nhân và hơn thế nữa, của các hệ thống phát triển nhờ vào những điều đơn giản, hàng ngày
hành động của những người lao động đó. Chất lượng là kết quả của hàng triệu hành động chăm sóc quên mình —
không chỉ của
bất kỳ phương pháp tuyệt vời nào từ trên trời giáng xuống. Những hành động này đơn giản không có nghĩa là
rằng chúng đơn giản và hầu như không có nghĩa là chúng dễ dàng. Dù sao họ cũng là
kết cấu của sự vĩ đại và hơn thế nữa, của vẻ đẹp, trong bất kỳ nỗ lực nào của con người. Bỏ qua chúng thì không
chưa hoàn toàn là con người.
Tất nhiên, tôi vẫn là người ủng hộ tư duy ở phạm vi rộng hơn, và đặc biệt là
giá trị của các phương pháp tiếp cận kiến trúc bắt nguồn từ kiến thức miền sâu và khả năng sử dụng phần mềm.
Cuốn sách không nói về điều đó — hoặc, ít nhất, nó không rõ ràng về điều đó. Cuốn sách này có một cái hay hơn
thông điệp có độ sâu sắc không nên bị đánh giá thấp. Nó phù hợp với cưa hiện tại
của những người thực sự dựa trên mã như Peter Sommerlad, Kevlin Henney và Giovanni
Asproni. “Mã là thiết kế” và “Mã đơn giản” là thần chú của họ. Trong khi chúng ta phải
hãy cẩn thận để nhớ rằng giao diện là chương trình và cấu trúc của nó có nhiều
để nói về cấu trúc chương trình của chúng tôi, điều quan trọng là phải liên tục áp dụng lập trường khiêm tốn
rằng thiết kế nằm trong mã. Và trong khi làm lại trong phép ẩn dụ sản xuất dẫn đến
chi phí, làm lại trong thiết kế dẫn đến giá trị. Chúng ta nên xem mã của chúng ta như một khớp nối đẹp đẽ
những nỗ lực cao cả của thiết kế — thiết kế như một quá trình, không phải là một điểm cuối tĩnh. Nó nằm trong mã
các chỉ số kiến trúc của sự khớp nối và sự gắn kết phát ra. Nếu bạn nghe Larry Constan-
tine mô tả sự khớp nối và sự gắn kết, anh ấy nói về mặt mã — không phải khái niệm trừu tượng cao cả-
cepts mà người ta có thể tìm thấy trong UML. Richard Gabriel khuyên chúng ta trong bài luận của anh ấy, “Tính
trừu tượng
Descant ”rằng trừu tượng là xấu xa. Mã là chống lại cái ác, và mã sạch có lẽ là thần thánh.
Trở lại với hộp Ga-Jol nhỏ của tôi, tôi nghĩ điều quan trọng cần lưu ý là người Đan Mạch
sự khôn ngoan khuyên chúng ta không chỉ để ý đến những điều nhỏ nhặt, mà còn phải trung thực trong những việc
nhỏ.
nhiều thứ. Điều này có nghĩa là trung thực với mã, trung thực với đồng nghiệp của chúng tôi về trạng thái
mã và hơn hết là trung thực với bản thân về mã của chúng tôi. Chúng tôi đã cố gắng hết sức để
"Để khu cắm trại sạch sẽ hơn chúng tôi thấy"? Chúng tôi đã xác định lại mã của mình trước khi kiểm tra-
nhập vào? Đây không phải là những mối quan tâm ngoại vi mà là những mối quan tâm nằm ngay trung tâm của
Giá trị linh hoạt. Một phương pháp được khuyến nghị trong Scrum là tính toán lại là một phần của vấn đề
cept của "Xong." Cả kiến trúc và mã sạch đều không nhấn mạnh vào sự hoàn hảo, chỉ dựa trên sự trung thực
và làm tốt nhất có thể. Sai lầm là con người; để tha thứ, thiêng liêng. Trong Scrum, chúng tôi thực hiện mọi-
điều có thể nhìn thấy. Chúng tôi không khí quần áo bẩn của mình. Chúng tôi trung thực về trạng thái mã của chúng
tôi vì
www.it-ebooks.info

Trang 24
xxiii
Lời tựa
mã không bao giờ là hoàn hảo. Chúng ta trở thành con người hoàn chỉnh hơn, xứng đáng hơn với thần thánh và gần
gũi hơn
đến sự tuyệt vời đó trong các chi tiết.
Trong nghề nghiệp của mình, chúng tôi rất cần mọi sự giúp đỡ có thể. Nếu mặt bằng cửa hàng sạch sẽ
giảm tai nạn và các công cụ cửa hàng được tổ chức tốt sẽ tăng năng suất, vì vậy tôi là tất cả
chúng. Đối với cuốn sách này, nó là ứng dụng thực tế tốt nhất của các nguyên tắc Lean vào phần mềm I
đã từng thấy trong bản in. Tôi mong đợi không ít từ nhóm nhỏ suy nghĩ thực tế này.
các viduals đã cùng nhau phấn đấu trong nhiều năm không chỉ để trở nên tốt hơn mà còn là món quà
kiến thức của họ đối với ngành trong các công trình như bây giờ bạn tìm thấy trong tay của bạn. Nó để lại
thế giới tốt hơn một chút so với tôi tìm thấy trước khi chú Bob gửi cho tôi bản thảo.
Sau khi hoàn thành bài tập này với những hiểu biết sâu sắc, tôi chuẩn bị dọn dẹp bàn làm việc của mình.
James O. Coplien
Mørdrup, Đan Mạch
www.it-ebooks.info

Trang 25
Trang này cố ý để trống
www.it-ebooks.info

Trang 26
xxv

Giới thiệu
Cửa nào đại diện cho mã của bạn? Cánh cửa nào đại diện cho đội của bạn hoặc công ty của bạn?
Tại sao chúng ta lại ở trong phòng đó? Đây chỉ là một đánh giá mã thông thường hay chúng tôi đã tìm thấy một
luồng
vấn đề khủng khiếp ngay sau khi phát trực tiếp? Chúng ta đang gỡ lỗi trong hoảng loạn, đang nghiền ngẫm mã
mà chúng tôi nghĩ đã hiệu quả? Khách hàng bỏ đi ngày một đông và người quản lý thở phào
Sao lại với sự cho phép của Thom Holwerda.
http://www.osnews.com/story/19266/WTFs_m
(c) 2008 F
ocus Shift
www.it-ebooks.info

Trang 27
xxvi
Giới thiệu
cổ của chúng tôi? Làm thế nào chúng ta có thể đảm bảo rằng chúng ta sẽ đến sau cánh cửa bên phải khi hành trình
đến
khó khăn? Câu trả lời là: sự khéo léo .
Có hai phần để học nghề thủ công: kiến thức và công việc. Bạn phải đạt được
kiến thức về các nguyên tắc, mẫu, thực hành và kinh nghiệm học mà một người thợ thủ công biết, và
bạn cũng phải mài dũa kiến thức đó vào ngón tay, mắt và ruột của mình bằng cách làm việc chăm chỉ và
đang luyện tập.
Tôi có thể dạy bạn vật lý của việc đi xe đạp. Thật vậy, toán học cổ điển là
tương đối đơn giản. Trọng lực, ma sát, mô men động lượng, khối tâm, v.v.
thứ tư, có thể được chứng minh với ít hơn một trang đầy đủ các phương trình. Đưa ra những công thức tôi
có thể chứng minh cho bạn thấy rằng đi xe đạp là thực tế và cung cấp cho bạn tất cả kiến thức mà bạn
cần thiết để làm cho nó hoạt động. Và bạn vẫn sẽ bị ngã trong lần đầu tiên leo lên chiếc xe đạp đó.
Mã hóa không có gì khác biệt. Chúng tôi có thể viết ra tất cả các nguyên tắc "cảm thấy tốt" về sạch sẽ
mã và sau đó tin tưởng bạn thực hiện công việc (nói cách khác, để bạn gục ngã khi bắt đầu
xe đạp), nhưng sau đó loại giáo viên nào sẽ tạo ra chúng tôi, và loại học sinh
điều đó sẽ làm cho bạn?
Không. Đó không phải là cách mà cuốn sách này sẽ hoạt động.
Học cách viết mã sạch là một công việc khó khăn . Nó đòi hỏi nhiều hơn là chỉ kiến thức về
nguyên tắc và khuôn mẫu. Bạn phải đổ mồ hôi cho nó. Bạn phải tự thực hành nó, và xem
bản thân thất bại. Bạn phải xem những người khác thực hành nó và thất bại. Bạn phải thấy họ vấp ngã và
tìm lại các bước của họ. Bạn phải thấy họ đau đớn trước các quyết định và thấy cái giá họ phải trả
đưa ra những quyết định sai lầm.
Hãy chuẩn bị để làm việc chăm chỉ khi đọc cuốn sách này. Đây không phải là một cuốn sách "cảm thấy hay"
bạn có thể đọc trên máy bay và hoàn thành trước khi hạ cánh. Cuốn sách này sẽ giúp bạn làm việc, và
làm việc chăm chỉ . Bạn sẽ làm công việc gì? Bạn sẽ đọc mã — rất nhiều mã.
Và bạn sẽ được thử thách để nghĩ xem điều gì đúng về mã đó và điều gì sai
với nó. Bạn sẽ được yêu cầu làm theo khi chúng tôi tách các mô-đun ra và đặt chúng trở lại
lại với nhau. Điều này sẽ tốn thời gian và công sức; nhưng chúng tôi nghĩ rằng nó sẽ đáng giá.
Chúng tôi đã chia cuốn sách này thành ba phần. Một số chương đầu tiên mô tả các hoàng tử-
mật mã, các mẫu và cách viết mã sạch. Có khá nhiều mã trong những
các chương, và chúng sẽ khó đọc. Họ sẽ chuẩn bị cho bạn phần thứ hai
để đến. Nếu bạn đặt cuốn sách xuống sau khi đọc phần đầu tiên, chúc bạn may mắn!
Phần thứ hai của cuốn sách là phần khó hơn. Nó bao gồm một số nghiên cứu điển hình về
ngày càng phức tạp. Mỗi nghiên cứu điển hình là một bài tập trong việc làm sạch một số mã — trong số
chuyển đổi mã có một số vấn đề thành mã có ít vấn đề hơn. Các chi tiết trong
phần này là dữ dội . Bạn sẽ phải lật đi lật lại giữa tường thuật và
danh sách mã. Bạn sẽ phải phân tích và hiểu mã chúng tôi đang làm việc và
xem xét lý do của chúng tôi để thực hiện mỗi thay đổi chúng tôi thực hiện. Dành một chút thời gian
bởi vì điều này sẽ khiến bạn mất nhiều ngày .
Phần thứ ba của cuốn sách này là phần thưởng. Nó là một chương duy nhất chứa danh sách các heu-
sự mạo hiểm và mùi được thu thập trong khi tạo ra các nghiên cứu điển hình. Khi chúng tôi đi qua và
làm sạch mã trong các nghiên cứu điển hình, chúng tôi đã ghi lại mọi lý do cho các hành động của mình dưới dạng
www.it-ebooks.info

Trang 28
xxvii
Giới thiệu
heuristic hoặc mùi. Chúng tôi đã cố gắng hiểu phản ứng của chính mình đối với mã chúng tôi đang đọc
và thay đổi, và làm việc chăm chỉ để nắm bắt lý do tại sao chúng tôi cảm thấy những gì chúng tôi cảm thấy và làm
những gì chúng tôi đã làm.
Kết quả là một cơ sở kiến thức mô tả cách chúng ta suy nghĩ khi viết, đọc và
mã sạch.
Cơ sở kiến thức này có giá trị hạn chế nếu bạn không đọc kỹ
thông qua các nghiên cứu điển hình trong phần thứ hai của cuốn sách này. Trong những nghiên cứu điển hình đó,
chúng tôi quan tâm đến-
chú thích đầy đủ từng thay đổi mà chúng tôi đã thực hiện với các tham chiếu chuyển tiếp đến kinh nghiệm
học. Những điều này cho-
tham chiếu phường xuất hiện trong dấu ngoặc vuông như sau: [H22]. Điều này cho phép bạn thấy bối cảnh trong
mà những kinh nghiệm học đó đã được áp dụng và viết ra! Không phải bản thân những người nghiên cứu kinh
nghiệm mới là
rất có giá trị, đó là mối quan hệ giữa những khám phá đó và những quyết định rời rạc mà chúng ta
được thực hiện trong khi làm sạch mã trong các nghiên cứu điển hình .
Để giúp bạn thêm về những mối quan hệ đó, chúng tôi đã đặt một tham chiếu chéo ở cuối
của cuốn sách hiển thị số trang cho mọi tài liệu tham khảo chuyển tiếp. Bạn có thể sử dụng nó để nhìn
lên từng nơi đã áp dụng một kinh nghiệm nhất định.
Nếu bạn đọc phần đầu tiên và phần thứ ba và bỏ qua các nghiên cứu điển hình, thì bạn sẽ
đã đọc một cuốn sách "cảm thấy tốt" khác về cách viết phần mềm tốt. Nhưng nếu bạn lấy
thời gian để làm việc thông qua các nghiên cứu điển hình, theo từng bước nhỏ, từng phút quyết định — nếu
bạn đặt mình vào vị trí của chúng tôi và buộc bản thân phải suy nghĩ theo cùng con đường mà chúng tôi
nghĩ, sau đó bạn sẽ hiểu biết phong phú hơn nhiều về các nguyên tắc, mô hình, cách
tices và heuristics. Họ sẽ không còn là kiến thức "cảm thấy tốt" nữa. Họ sẽ
chạm vào ruột, ngón tay và trái tim của bạn. Họ sẽ trở thành một phần của bạn theo cách tương tự
rằng một chiếc xe đạp trở thành một phần mở rộng của ý chí của bạn khi bạn đã thành thạo cách đi nó.
Sự nhìn nhận
Ảnh minh họa
Cảm ơn hai nghệ sĩ của tôi, Jeniffer Kohnke và Angela Brooks. Jennifer chịu trách nhiệm
cho những bức ảnh tuyệt đẹp và sáng tạo ở đầu mỗi chương và cả những bức chân dung
của Kent Beck, Ward Cunningham, Bjarne Stroustrup, Ron Jeffries, Grady Booch, Dave
Thomas, Michael Feathers, và tôi.
Angela chịu trách nhiệm về những bức tranh khéo léo tô điểm cho các phần bên trong của mỗi chương.
Cô ấy đã thực hiện khá nhiều bức ảnh cho tôi trong nhiều năm, bao gồm nhiều bức bên trong-
các mẹo trong Thiết bị phần mềm Agile: Nguyên tắc, Mẫu và Thực hành . Cô ấy cũng là của tôi
đứa con đầu lòng mà tôi rất hài lòng.
www.it-ebooks.info

Trang 29
Trang này cố ý để trống
www.it-ebooks.info

Trang 30
xxix

Trên trang bìa


Hình ảnh trên bìa là M104: The Sombrero Galaxy. M104 nằm ở Xử Nữ và là
chỉ cách chúng ta dưới 30 triệu năm ánh sáng. Tại lõi của nó là một lỗ đen siêu lớn nặng-
có khối lượng khoảng một tỷ mặt trời.
Hình ảnh có khiến bạn liên tưởng đến vụ nổ của mặt trăng quyền năng Klingon Praxis không? Tôi
nhớ rất rõ cảnh trong Star Trek VI cho thấy một vòng các mảnh vụn bay ở xích đạo
tránh xa vụ nổ đó. Kể từ cảnh đó, vòng xích đạo đã trở thành một hiện vật phổ biến
trong các vụ nổ phim khoa học viễn tưởng. Nó thậm chí còn được thêm vào sự bùng nổ của Alderaan trong các
phiên bản sau này
của bộ phim Chiến tranh giữa các vì sao đầu tiên .
Điều gì đã làm cho vòng này hình thành xung quanh M104? Tại sao nó có một trung tâm lớn như vậy
phình ra và một hạt nhân nhỏ và sáng như vậy? Tôi nhìn nó như thể là lỗ đen trung tâm
mất mát và thổi bay lỗ hổng 30.000 năm ánh sáng ở giữa thiên hà. Khốn nạn cho bất kỳ
các nền văn minh có thể nằm trong con đường của sự phá vỡ vũ trụ đó.
Các lỗ đen siêu lớn nuốt chửng toàn bộ các ngôi sao trong bữa trưa, chuyển đổi một phân số khá lớn
tỷ khối của chúng thành năng lượng. E = MC  2 là đủ đòn bẩy, nhưng khi M là khối lượng sao:
Coi chưng! Có bao nhiêu ngôi sao rơi đầu vào con vẹt đó trước khi con quái vật được thỏa mãn?
Kích thước của khoảng trống trung tâm có thể là một gợi ý?
Hình ảnh M104 trên trang bìa là một
sự kết hợp của pho- ánh sáng khả kiến nổi tiếng
đồ thị từ Hubble (bên phải) và gần đây
hình ảnh hồng ngoại từ quỹ đạo Spitzer
đài quan sát (bên dưới, bên phải). Đó là tia hồng ngoại
hình ảnh cho chúng ta thấy rõ bản chất của chiếc nhẫn
của thiên hà. Trong ánh sáng khả kiến, chúng ta chỉ nhìn thấy
cạnh trước của chiếc nhẫn trong hình bóng. Các cen-
tral phình ra che lấp phần còn lại của vòng.
Nhưng trong tia hồng ngoại, các hạt nóng trong
vòng tỏa sáng qua phần phình trung tâm. Các
hai hình ảnh kết hợp cho chúng ta một cái nhìn mà chúng ta đã
chưa từng thấy trước đây và ngụ ý rằng nó đã lâu rồi
là một địa ngục hoành hành của hoạt động.
Ảnh bìa: © Kính viễn vọng Không gian Spitzer
www.it-ebooks.info

Trang 31
Trang này cố ý để trống
www.it-ebooks.info

Trang 32
1
1
Mã sạch
Bạn đang đọc cuốn sách này vì hai lý do. Đầu tiên, bạn là một lập trình viên. Thứ hai, bạn muốn
để trở thành một lập trình viên giỏi hơn. Tốt. Chúng tôi cần những lập trình viên giỏi hơn.
www.it-ebooks.info

Trang 33
2
Chương 1: Mã sạch
Đây là một cuốn sách về lập trình hay. Nó chứa đầy mã. Chúng tôi sẽ xem xét
mã từ mọi hướng khác nhau. Chúng tôi sẽ nhìn xuống nó từ trên xuống, nhìn lên nó từ
dưới cùng và xuyên qua nó từ trong ra ngoài. Vào lúc chúng ta hoàn thành, chúng ta sẽ biết một
rất nhiều về mã. Hơn nữa, chúng ta sẽ có thể phân biệt giữa mã tốt và mã xấu
mã. Chúng tôi sẽ biết cách viết mã tốt. Và chúng tôi sẽ biết cách chuyển đổi mã xấu thành
mã tốt.
Sẽ có mã
Người ta có thể tranh luận rằng một cuốn sách về mã bằng cách nào đó đi sau thời đại — mã đó không
còn vấn đề; mà chúng ta nên quan tâm đến các mô hình và yêu cầu thay thế.
Thật vậy, một số người đã gợi ý rằng chúng ta sắp kết thúc mã. Điều đó sớm tất cả mã sẽ
được tạo ra thay vì được viết. Đơn giản là không cần lập trình viên vì busi-
ness mọi người sẽ tạo ra các chương trình từ các thông số kỹ thuật.
Vô lý! Chúng tôi sẽ không bao giờ loại bỏ mã, bởi vì mã đại diện cho các chi tiết của
các yêu cầu. Ở một mức độ nào đó, những chi tiết đó không thể bị bỏ qua hoặc trừu tượng hóa; Họ phải là
được chỉ định. Và xác định các yêu cầu một cách chi tiết để máy có thể thực hiện chúng là
lập trình . Một đặc điểm kỹ thuật như vậy là mã .
Tôi hy vọng rằng mức độ trừu tượng của các ngôn ngữ của chúng ta sẽ tiếp tục tăng lên. Tôi
cũng hy vọng rằng số lượng ngôn ngữ dành riêng cho miền sẽ tiếp tục tăng. Điều này
sẽ là một điều tốt. Nhưng nó sẽ không loại bỏ mã. Thật vậy, tất cả các thông số kỹ thuật được viết
ở cấp cao hơn và ngôn ngữ dành riêng cho miền sẽ là mã! Nó vẫn sẽ cần
chặt chẽ, chính xác, trang trọng và chi tiết đến mức máy móc có thể hiểu và
Thực hiện nó.
Những người nghĩ rằng một ngày nào đó mã đó sẽ biến mất giống như các nhà toán học
hy vọng một ngày nào đó sẽ khám phá ra một toán học mà không cần phải chính thức. Họ đang hy vọng
rằng một ngày nào đó chúng ta sẽ khám phá ra cách tạo ra những cỗ máy có thể làm những gì chúng ta muốn thay vì
hơn những gì chúng tôi nói. Những cỗ máy này sẽ phải có thể hiểu chúng ta tốt đến mức chúng
có thể chuyển các nhu cầu được chỉ định một cách mơ hồ thành các chương trình thực thi hoàn hảo đáp ứng chính
xác
những nhu cầu đó.
Điều này sẽ không bao giờ xảy ra. Ngay cả con người, với tất cả trực giác và óc sáng tạo của mình,
đã có thể tạo ra các hệ thống thành công từ những cảm giác mơ hồ của khách hàng.
Thật vậy, nếu kỷ luật về đặc tả yêu cầu đã dạy chúng ta bất cứ điều gì, thì đó là
các yêu cầu được chỉ định rõ cũng chính thức như mã và có thể hoạt động như các thử nghiệm thực thi của
mã!
Hãy nhớ rằng mã thực sự là ngôn ngữ mà cuối cùng chúng ta thể hiện yêu cầu-
ments. Chúng tôi có thể tạo các ngôn ngữ gần với yêu cầu hơn. Chúng tôi có thể tạo ra các công cụ
giúp chúng tôi phân tích cú pháp và tập hợp các yêu cầu đó thành các cấu trúc chính thức. Nhưng chúng tôi sẽ
không bao giờ loại bỏ độ chính xác cần thiết — vì vậy sẽ luôn có mã.
www.it-ebooks.info
Trang 34
3
Mã xấu
Mã xấu
Gần đây tôi đã đọc lời tựa cho Kent Beck's
cuốn sách Các mô hình thực hiện.  1 Anh ấy nói, “. . . điều này
cuốn sách dựa trên một tiền đề khá mong manh: rằng
vấn đề mã tốt. . . . ” Một tiền đề mong manh ? Tôi không
đồng ý! Tôi nghĩ tiền đề đó là một trong những tiền đề
mạnh mẽ, được hỗ trợ và quá tải của tất cả các
tiếng ồn trong nghề của chúng tôi (và tôi nghĩ Kent biết điều đó). Chúng tôi
biết mã tốt quan trọng bởi vì chúng tôi đã phải
đối phó quá lâu với sự thiếu hụt của nó.
Tôi biết một công ty, vào cuối những năm 80,
đã viết một ứng dụng giết người . Nó rất phổ biến và rất nhiều
các chuyên gia đã mua và sử dụng nó. Nhưng sau đó
chu kỳ phát hành bắt đầu kéo dài. Lỗi không
sửa chữa từ bản phát hành này sang bản tiếp theo. Thời gian tải
tăng lên và sự cố tăng lên. Tôi nhớ ngày tôi
tắt sản phẩm một cách thất vọng và không bao giờ
đã sử dụng nó một lần nữa. Công ty đã ngừng kinh doanh
một thời gian ngắn sau đó.
Hai thập kỷ sau, tôi gặp một trong những nhân viên đầu tiên của công ty đó và hỏi anh ta
chuyê ̣n gì đã xảy ra. Câu trả lời khẳng định nỗi sợ hãi của tôi. Họ đã đưa sản phẩm đến
thị trường và đã tạo ra một mớ hỗn độn lớn trong mã. Khi họ ngày càng thêm nhiều tính năng,
mã ngày càng trở nên tồi tệ hơn cho đến khi họ không thể quản lý được nữa. Đó là điều tồi tệ
mã đã đưa công ty đi xuống.
Đã bạn đã bao giờ bị cản trở đáng kể theo mã xấu? Nếu bạn là một lập trình viên của
bất kỳ kinh nghiệm nào thì bạn đã cảm thấy trở ngại này nhiều lần. Thật vậy, chúng tôi có tên cho
nó. Chúng tôi gọi nó là lội . Chúng tôi lội qua mã xấu. Chúng tôi khẩu hiệu qua một mớ hỗn độn
brambles và cạm bẫy tiềm ẩn. Chúng tôi đấu tranh để tìm ra con đường của mình, hy vọng một số gợi ý, một số
manh mối, về những gì đang xảy ra; nhưng tất cả những gì chúng ta thấy ngày càng nhiều mã vô tri.
Tất nhiên bạn đã bị cản trở bởi mã xấu. Vậy thì - tại sao bạn lại viết nó?
Bạn có cố gắng đi nhanh không? Bạn có đang vội không? Có lẽ vậy. Có lẽ bạn cảm thấy rằng bạn
không có thời gian để làm tốt công việc; rằng sếp của bạn sẽ tức giận với bạn nếu bạn lấy
thời gian để xóa mã của bạn. Có lẽ bạn chỉ cảm thấy mệt mỏi khi làm việc với chương trình này và
muốn nó kết thúc. Hoặc có thể bạn đã xem xét tồn đọng của những thứ khác mà bạn đã quảng cáo-
phải hoàn thành và nhận ra rằng bạn cần kết hợp mô-đun này lại với nhau để có thể
chuyển sang phần tiếp theo. Tất cả chúng ta đã làm được.
Tất cả chúng tôi đã xem xét mớ hỗn độn mà chúng tôi vừa tạo ra và sau đó đã chọn để nó
Một ngày khác. Tất cả chúng tôi đều cảm thấy nhẹ nhõm khi thấy chương trình lộn xộn của mình hoạt động và quyết
định rằng
1. [Beck07].
www.it-ebooks.info

Trang 35
4
Chương 1: Mã sạch
làm việc lộn xộn còn hơn không. Tất cả chúng tôi đã nói rằng chúng tôi sẽ quay lại và dọn dẹp nó sau. Của
Tất nhiên, những ngày đó chúng ta chưa biết đến định luật của LeBlanc: Sau này không bao giờ bằng .
Tổng chi phí sở hữu một tin nhắn
Nếu bạn đã là một lập trình viên hơn hai hoặc ba năm, bạn có thể đã
bị chậm lại đáng kể bởi mã lộn xộn của người khác. Nếu bạn đã là một lập trình viên
trong hơn hai hoặc ba năm, bạn có thể đã bị chậm lại bởi mã lộn xộn.
Mức độ chậm lại có thể là đáng kể. Trong khoảng một hoặc hai năm, các nhóm
đang di chuyển rất nhanh khi bắt đầu một dự án có thể thấy mình đang di chuyển như một con ốc sên
tốc độ. Mỗi thay đổi mà họ thực hiện đối với mã sẽ phá vỡ hai hoặc ba phần khác của mã. Không
thay đổi là tầm thường. Mọi bổ sung hoặc sửa đổi đối với hệ thống đều yêu cầu rằng
xoắn và thắt nút được "hiểu" để có thể thêm nhiều rối, xoắn và thắt nút.
Theo thời gian, đống rác ngày càng lớn, sâu và cao đến mức họ không thể dọn dẹp được. Đó
không có cách nào cả.
Khi tình trạng lộn xộn hình thành, năng suất của nhóm tiếp tục giảm, tiệm cận
gần bằng không. Khi năng suất giảm, cấp quản lý sẽ làm điều duy nhất họ có thể làm được;
họ bổ sung thêm nhân viên vào dự án với hy vọng tăng năng suất. Nhưng nhân viên mới đó là
không thành thạo trong thiết kế của hệ thống. Họ không biết sự khác biệt giữa một sự thay đổi
phù hợp với ý định thiết kế và thay đổi cản trở ý định thiết kế. Hơn nữa,
họ và tất cả những người khác trong nhóm phải chịu áp lực khủng khiếp để tăng năng suất. Vì thế
tất cả chúng ngày càng tạo ra nhiều mớ hỗn độn hơn, khiến năng suất ngày càng tiến xa về con số không.
(Xem Hình 1-1.)
Hình 1-1
Năng suất so với thời gian
www.it-ebooks.info

Trang 36
5
Tổng chi phí sở hữu một tin nhắn
Thiết kế lại lớn trên bầu trời
Cuối cùng cả đội nổi dậy. Họ thông báo với ban quản lý rằng họ không thể tiếp tục phát triển
trong cơ sở mã đáng sợ này. Họ yêu cầu thiết kế lại. Ban quản lý không muốn chi tiêu
các nguồn lực về thiết kế lại hoàn toàn mới của dự án, nhưng họ không thể phủ nhận rằng sản xuất
nó là khủng khiếp. Cuối cùng, họ tuân theo yêu cầu của các nhà phát triển và cho phép
thiết kế lại lớn trên bầu trời.
Một đội hổ mới được chọn. Mọi người đều muốn tham gia đội này vì đó là một màu xanh lá cây-
dự án thực địa. Họ bắt đầu lại và tạo ra một thứ gì đó thực sự đẹp đẽ. Nhưng chỉ tốt nhất
và sáng nhất được chọn cho đội hổ. Mọi người khác phải tiếp tục duy trì
hệ thống hiện tại.
Bây giờ hai đội đang trong một cuộc đua. Đội hổ phải xây dựng một hệ thống mới
mọi thứ mà hệ thống cũ làm. Không chỉ vậy, họ phải theo kịp những thay đổi
liên tục được tạo cho hệ thống cũ. Quản lý sẽ không thay thế cái cũ
hệ thống cho đến khi hệ thống mới có thể làm mọi thứ mà hệ thống cũ làm.
Cuộc đua này có thể diễn ra trong một thời gian rất dài. Tôi đã thấy nó mất 10 năm. Và vào lúc nó
xong, các thành viên ban đầu của đội hổ đã biến mất từ lâu và các thành viên hiện tại
yêu cầu hệ thống mới phải được thiết kế lại vì nó rất lộn xộn.
Nếu bạn đã trải qua dù chỉ một phần nhỏ của câu chuyện tôi vừa kể, thì bạn đã
biết rằng dành thời gian giữ cho mã của bạn sạch sẽ không chỉ hiệu quả về chi phí; đó là vấn đề của
sinh tồn chuyên nghiệp.
Thái độ
Bạn đã bao giờ lội qua một đống lộn xộn đến mức phải mất hàng tuần để làm những gì đáng lẽ phải có
mất nhiều giờ? Bạn đã thấy những gì đáng lẽ phải là thay đổi một dòng, được thực hiện thay thế trong
hàng trăm mô-đun khác nhau? Các triệu chứng này đều quá phổ biến.
Tại sao điều này xảy ra với mã? Tại sao mã tốt lại nhanh chóng biến thành mã xấu? Chúng tôi
có rất nhiều lời giải thích cho nó. Chúng tôi phàn nàn rằng các yêu cầu đã thay đổi theo cách
cản trở thiết kế ban đầu. Chúng tôi than phiền vì lịch trình quá chặt chẽ để làm mọi thứ đúng.
Chúng ta đổ lỗi cho những người quản lý ngu ngốc và những khách hàng không khoan dung và những kiểu tiếp thị
vô dụng
và chất khử trùng điện thoại. Nhưng lỗi, Dilbert thân mến, không phải ở các vì sao của chúng ta, mà là ở chính
chúng ta.
Chúng tôi không chuyên nghiệp.
Đây có thể là một viên thuốc đắng để nuốt. Làm thế nào mà lộn xộn này có thể là lỗi của chúng tôi ? Vậy còn
yêu cầu? Còn về lịch trình? Còn những người quản lý ngu ngốc và vô dụng thì sao
các loại tiếp thị? Họ không chịu một số trách nhiệm?
Không. Các nhà quản lý và nhà tiếp thị tìm đến chúng tôi để biết thông tin họ cần
lời hứa và cam kết; và ngay cả khi họ không nhìn chúng ta, chúng ta cũng không nên ngại ngùng
về việc nói với họ những gì chúng tôi nghĩ. Người dùng tìm đến chúng tôi để xác thực cách thức các yêu cầu
sẽ phù hợp với hệ thống. Các nhà quản lý dự án tìm đến chúng tôi để giúp đưa ra lịch trình. Chúng tôi
www.it-ebooks.info

Trang 37
6
Chương 1: Mã sạch
đồng lõa sâu sắc trong việc lập kế hoạch của dự án và chia sẻ rất nhiều các phản ứng-
sự nhanh nhẹn cho bất kỳ thất bại nào; đặc biệt là nếu những thất bại đó liên quan đến mã xấu!
"Nhưng chờ đã!" bạn nói. "Nếu tôi không làm những gì người quản lý của tôi nói, tôi sẽ bị sa thải." Chắc là không.
Hầu hết các nhà quản lý muốn sự thật, ngay cả khi họ không hành động như vậy. Hầu hết các nhà quản lý muốn tốt
mã, ngay cả khi họ đang ám ảnh về lịch trình. Họ có thể bảo vệ lịch trình và
yêu cầu với đam mê; nhưng đó là công việc của họ. Nhiệm vụ của bạn là bảo vệ mã bằng
niềm đam mê.
Để lái xe đến điểm này về nhà, điều gì sẽ xảy ra nếu bạn là bác sĩ và có một bệnh nhân yêu cầu
rằng bạn dừng tất cả việc rửa tay ngớ ngẩn để chuẩn bị cho cuộc phẫu thuật vì nó đang
quá nhiều thời gian? 2 Rõ ràng bệnh nhân là ông chủ; và bác sĩ tuyệt đối nên từ chối
tuân thủ. Tại sao? Bởi vì bác sĩ biết nhiều hơn bệnh nhân về các nguy cơ của bệnh
dễ dàng và nhiễm trùng. Sẽ là không chuyên nghiệp (đừng bận tâm đến tội phạm) nếu bác sĩ
tuân thủ bệnh nhân.
Vì vậy, việc các lập trình viên làm theo ý muốn của những người quản lý không chuyên nghiệp cũng là không
chuyên nghiệp.
hiểu những rủi ro của việc làm lộn xộn.
Câu hỏi hóc búa cơ bản
Các lập trình viên phải đối mặt với một câu hỏi hóc búa về các giá trị cơ bản. Tất cả các nhà phát triển có hơn một
vài năm
kinh nghiệm biết rằng những lộn xộn trước đó làm chậm họ. Và tất cả các nhà phát triển đều cảm thấy
áp lực phải thực hiện các vụ lộn xộn để đáp ứng thời hạn. Tóm lại, họ không mất thời gian
đi nhanh!
Các chuyên gia chân chính biết rằng phần thứ hai của câu hỏi hóc búa là sai. Bạn sẽ không
thực hiện thời hạn bằng cách làm cho lộn xộn. Thật vậy, tình trạng lộn xộn sẽ làm bạn chậm lại ngay lập tức, và
sẽ buộc bạn phải bỏ lỡ thời hạn. Cách duy nhất để đưa ra thời hạn — cách duy nhất để
đi nhanh — là luôn giữ cho mã sạch nhất có thể.
Nghệ thuật của Quy tắc sạch?
Giả sử bạn tin rằng mã lộn xộn là một trở ngại đáng kể. Hãy nói rằng bạn chấp nhận
rằng cách duy nhất để nhanh chóng là giữ cho mã của bạn sạch sẽ. Sau đó, bạn phải tự hỏi mình: "Làm thế nào
tôi có viết mã sạch không? ” Cố gắng viết mã sạch sẽ không tốt nếu bạn không biết nó là gì
nghĩa là mã phải sạch!
Tin xấu là viết mã sạch giống như vẽ một bức tranh. Hầu hết chúng ta
biết khi nào một bức tranh được vẽ tốt hay xấu. Nhưng có thể nhận ra nghệ thuật tốt từ
xấu không có nghĩa là chúng ta biết vẽ. Vì vậy, cũng có thể nhận ra mã sạch
từ mã bẩn không có nghĩa là chúng ta biết cách viết mã sạch!
2. Khi rửa tay lần đầu tiên được đề nghị cho các bác sĩ bởi Ignaz Semmelweis vào năm 1847, nó đã bị từ chối vì lý do
các bác sĩ quá bận rộn và không có thời gian để rửa tay giữa các lần khám bệnh.
www.it-ebooks.info

Trang 38
7
Tổng chi phí sở hữu một tin nhắn
Viết mã sạch yêu cầu sử dụng có kỷ luật của vô số kỹ thuật nhỏ được áp dụng
thông qua cảm giác “sạch sẽ” có được một cách cẩn thận. “Code-sense” này là chìa khóa.
Một số người trong chúng ta được sinh ra với nó. Một số người trong chúng ta phải đấu tranh để có được nó. Nó
không chỉ cho chúng tôi
xem mã tốt hay xấu, nhưng nó cũng cho chúng ta thấy chiến lược áp dụng
pline để chuyển đổi mã xấu thành mã sạch.
Một lập trình viên không có “code-sense” có thể nhìn vào một mô-đun lộn xộn và nhận ra
lộn xộn nhưng sẽ không biết phải làm gì với nó. Một lập trình viên có "code-sense" sẽ nhìn
tại một mô-đun lộn xộn và xem các tùy chọn và biến thể. “Code-sense” sẽ giúp điều đó
grammer chọn biến thể tốt nhất và hướng dẫn anh ấy hoặc cô ấy vẽ một chuỗi hành vi
bảo toàn các phép biến hình để đi từ đây đến đó.
Tóm lại, một lập trình viên viết mã sạch là một nghệ sĩ có thể chụp một màn hình trống
thông qua một loạt các biến đổi cho đến khi nó là một hệ thống được mã hóa trang nhã.
Mã sạch là gì?
Có lẽ có rất nhiều định nghĩa đối với lập trình viên. Vì vậy, tôi đã hỏi một số rất
lập trình viên nổi tiếng và có kinh nghiệm sâu sắc những gì họ nghĩ.
Bjarne Stroustrup, người phát minh ra C ++
và tác giả của Lập trình C ++
Ngôn ngữ
Tôi thích mã của mình phải thanh lịch và hiệu quả.  Các
logic nên đơn giản để làm cho nó khó
để lỗi ẩn, các phụ thuộc tối thiểu để
bảo trì dễ dàng, xử lý lỗi hoàn thành
theo một chiến lược rõ ràng, và theo
hình thức gần với tối ưu để không bị cám dỗ
mọi người làm cho mã lộn xộn với unprinci-
cam kết tối ưu hóa.  Mã sạch làm một việc
tốt.
Bjarne sử dụng từ "thanh lịch". Đó là
khá một từ! Từ điển trong MacBook ® của tôi
cung cấp các định nghĩa sau: làm hài lòng
duyên dáng và phong cách về ngoại hình hoặc phong cách; đẹp một cách khéo léo và đơn giản. Chú ý
nhấn mạnh vào từ “làm hài lòng”. Rõ ràng Bjarne nghĩ rằng mã sạch được làm hài lòng đến
đọc. Đọc nó sẽ khiến bạn mỉm cười như một chiếc hộp nhạc được chế tác tốt hoặc thiết kế đẹp
ô tô sẽ.
Bjarne cũng đề cập đến hiệu quả— gấp đôi . Có lẽ điều này không làm chúng ta ngạc nhiên
từ người phát minh ra C ++; nhưng tôi nghĩ có nhiều thứ hơn là ham muốn tốc độ.
Chu kỳ lãng phí là không phù hợp, họ không hài lòng. Và bây giờ hãy lưu ý từ mà Bjarne sử dụng
www.it-ebooks.info

Trang 39
số 8
Chương 1: Mã sạch
để mô tả hậu quả của sự kém cỏi đó. Anh ấy sử dụng từ “cám dỗ”. Có một sâu
sự thật ở đây. Mã xấu cám dỗ sự lộn xộn để phát triển! Khi những người khác thay đổi mã xấu, họ có xu hướng
Làm cho nó tồi tệ hơn.
Thực dụng Dave Thomas và Andy Hunt nói điều này theo một cách khác. Họ đã sử dụng meta-
phor của cửa sổ bị hỏng. 3 Một tòa nhà với các cửa sổ bị vỡ trông có vẻ như không ai quan tâm
nó. Vì vậy, những người khác ngừng quan tâm. Chúng cho phép nhiều cửa sổ bị hỏng hơn. Cuối cùng
họ chủ động phá vỡ chúng. Họ làm bong tróc mặt tiền bằng graffiti và để rác thải thành đống
bài giảng. Một cửa sổ bị hỏng bắt đầu quá trình phân rã.
Bjarne cũng đề cập rằng việc xử lý lỗi phải được hoàn tất. Điều này đi đến chỗ ...
pline của sự chú ý đến các chi tiết. Xử lý lỗi viết tắt chỉ là một cách tạo ra
gammers bóng trên các chi tiết. Rò rỉ bộ nhớ là một, các điều kiện chủng tộc vẫn khác.
Đặt tên không phù hợp nhưng khác. Kết quả là mã sạch thể hiện sự chú ý
chi tiết.
Bjarne kết thúc với khẳng định rằng mã sạch làm tốt một việc. Nó không phải là tình cờ
rằng có rất nhiều nguyên tắc thiết kế phần mềm có thể được đúc kết thành
lời khuyên nhủ. Nhà văn này đến nhà văn khác đã cố gắng truyền đạt suy nghĩ này. Mã lỗi cố gắng làm
quá nhiều, nó có mục đích lẫn lộn và mục đích không rõ ràng. Mã sạch được chú trọng . Mỗi
chức năng, mỗi lớp, mỗi mô-đun thể hiện một thái độ duy nhất mà vẫn hoàn toàn
không bị phân tán và không bị ô nhiễm bởi các chi tiết xung quanh.
Grady Booch, tác giả của Object
Phân tích và thiết kế định hướng với
Các ứng dụng
Mã sạch rất đơn giản và trực tiếp.  Mã sạch
đọc như văn xuôi được viết tốt.  Mã sạch không bao giờ
che khuất ý định của nhà thiết kế mà thay vào đó là đầy
trừu tượng rõ ràng và đường thẳng
kiểm soát.
Grady đưa ra một số điểm giống như
Bjarne, nhưng anh ấy có quan điểm về khả năng đọc . Tôi
đặc biệt thích quan điểm của anh ấy rằng mã sạch sẽ
đọc như văn xuôi được viết tốt. Nghĩ lại về một
cuốn sách thực sự hay mà bạn đã đọc. Hãy nhớ cách các từ biến mất để được thay thế
bằng hình ảnh! Nó giống như xem một bộ phim, phải không? Tốt hơn! Bạn đã thấy các nhân vật, bạn
nghe thấy âm thanh, bạn đã trải nghiệm những điều kỳ diệu và hài hước.
Đọc mã sạch sẽ không bao giờ giống như đọc Chúa tể của những chiếc nhẫn . Tuy nhiên, lít-
phép ẩn dụ ary không phải là một phép ẩn dụ xấu. Giống như một cuốn tiểu thuyết hay, mã sạch sẽ thể hiện rõ ràng
mười-
sions trong vấn đề cần giải quyết. Nó sẽ xây dựng những căng thẳng đó lên cao trào và sau đó đưa ra
3. http://www.pragmaticprogrammer.com/booksellers/2004-12.html
www.it-ebooks.info

Trang 40
9
Tổng chi phí sở hữu một tin nhắn
người đọc rằng “Aha! Tất nhiên!" khi các vấn đề và căng thẳng được giải quyết trong tiết lộ
của một giải pháp rõ ràng.
Tôi thấy việc Grady sử dụng cụm từ "trừu tượng rõ ràng" là một oxymoron hấp dẫn!
Xét cho cùng, từ “sắc nét” gần như là một từ đồng nghĩa với “cụ thể”. Từ điển MacBook của tôi
giữ định nghĩa sau đây về “sắc nét”: dứt khoát nhanh chóng và thực tế, không chần chừ-
tation hoặc chi tiết không cần thiết. Mặc dù có vẻ giống nhau về ý nghĩa, những từ
mang một thông điệp mạnh mẽ. Mã của chúng tôi phải là vấn đề thực tế chứ không phải suy đoán.
Nó chỉ nên chứa những gì cần thiết. Độc giả của chúng tôi nên nhận ra rằng chúng tôi đã
quyết định.
“Big” Dave Thomas, người sáng lập
của OTI, cha đỡ đầu của
Chiến lược Eclipse
Mã sạch có thể được đọc và được nâng cao bởi
nhà phát triển khác với tác giả ban đầu của nó.  Nó có
đơn vị và nghiệm thu.  Nó có ý nghĩa
những cái tên.  Nó cung cấp một cách thay vì nhiều
cách để làm một việc.  Nó có ít depen-
các cơ quan, được xác định rõ ràng, và
cung cấp một API rõ ràng và tối thiểu.  Mã phải được
biết chữ vì tùy thuộc vào ngôn ngữ, không phải tất cả
thông tin cần thiết có thể được thể hiện rõ ràng
chỉ trong mã.
Big Dave chia sẻ mong muốn của Grady về khả năng đọc được-
ity, nhưng với một bước ngoặt quan trọng. Dave khẳng định rằng
mã sạch giúp người khác dễ dàng nâng cao mã. Điều này có vẻ hiển nhiên, nhưng nó có thể-
không được nhấn mạnh quá mức. Rốt cuộc, có một sự khác biệt giữa mã dễ đọc
và mã dễ thay đổi.
Dave gắn kết sự sạch sẽ với các bài kiểm tra! Mười năm trước, điều này sẽ khiến rất nhiều người nhướng mày.
Nhưng kỷ luật về Phát triển theo hướng kiểm tra đã có tác động sâu sắc đến
và đã trở thành một trong những ngành cơ bản nhất của chúng tôi. Dave nói đúng. Mã,
không có thử nghiệm, không phải là sạch sẽ. Cho dù nó có trang nhã đến đâu, cho dù nó có thể đọc được và tích hợp
...
sible, nếu nó không thử nghiệm, nó là ô uế.
Dave sử dụng từ tối thiểu hai lần. Rõ ràng anh ấy coi trọng mã nhỏ, đúng hơn là
hơn mã lớn. Thật vậy, đây là một quy tắc phổ biến trong suốt phiên bản phần mềm-
chắc chắn kể từ khi thành lập. Nhỏ hơn là tốt hơn.
Dave cũng nói rằng mã nên biết chữ . Đây là một tham chiếu mềm cho Knuth biết chữ
lập trình.  4 Kết quả là mã phải được soạn thảo ở dạng sao cho
nó có thể đọc được bởi con người.
4. [Knuth92].
www.it-ebooks.info

Trang 41
10
Chương 1: Mã sạch
Michael Feathers, tác giả của Working
Hiệu quả với Mã kế thừa
Tôi có thể liệt kê tất cả những phẩm chất mà tôi nhận thấy ở
mã sạch, nhưng có một chất lượng bao trùm
dẫn đến tất cả chúng.  Luôn làm sạch mã
có vẻ như nó được viết bởi một người quan tâm.
Không có gì rõ ràng mà bạn có thể làm để
lam no tôt hơn. Tất cả những điều đó đã được suy nghĩ
về tác giả của mã và nếu bạn cố gắng
tưởng tượng những cải tiến, bạn sẽ quay trở lại
bạn đang ở đâu, ngồi đánh giá cao
mã ai đó để lại cho bạn — mã do ai đó để lại-
một người quan tâm sâu sắc đến nghề thủ công.
Một từ: quan tâm. Đó thực sự là chủ đề của
Cuốn sách này. Có lẽ một phụ đề thích hợp
sẽ là Cách Chăm sóc Mã .
Michael đánh nó vào đầu. Mã sạch là
mã đã được chăm sóc. Ai đó đã dành thời gian để giữ cho nó đơn giản và có trật tự.
Họ đã chú ý thích đáng đến các chi tiết. Họ đã quan tâm.
Ron Jeffries, tác giả của Lập trình cực đoan
Đã cài đặt và lập trình cực đoan
Những cuộc phiêu lưu trong C #
Ron bắt đầu lập trình sự nghiệp của mình ở Fortran tại
Bộ Tư lệnh Không quân Chiến lược và đã viết mã trong
hầu hết mọi ngôn ngữ và trên hầu hết mọi
máy móc. Nó trả tiền để xem xét lời nói của mình một cách cẩn thận.
Trong những năm gần đây, tôi bắt đầu và gần kết thúc, với Beck's
quy tắc của mã đơn giản.  Theo thứ tự ưu tiên, mã đơn giản:
• Chạy tất cả các bài kiểm tra;
• Không chứa trùng lặp;
• Thể hiện tất cả các ý tưởng thiết kế có trong
hệ thống;
• Giảm thiểu số lượng các thực thể như lớp,
các phương thức, hàm và những thứ tương tự.
Trong số này, tôi chủ yếu tập trung vào sự trùng lặp. Khi điều tương tự được thực hiện lặp đi lặp lại,
đó là một dấu hiệu cho thấy trong đầu chúng ta có một ý tưởng không được thể hiện rõ trong mã.  Tôi cố gắng để
tìm ra nó là gì. Sau đó, tôi cố gắng diễn đạt ý tưởng đó rõ ràng hơn.
Tính biểu cảm đối với tôi bao gồm những cái tên có ý nghĩa và tôi có khả năng thay đổi
tên của mọi thứ nhiều lần trước khi tôi ổn định. Với các công cụ mã hóa hiện đại như Eclipse,
đổi tên khá rẻ, vì vậy tôi không gặp khó khăn gì khi thay đổi.  Biểu cảm đi
www.it-ebooks.info

Trang 42
11
Tổng chi phí sở hữu một tin nhắn
ngoài tên, tuy nhiên. Tôi cũng xem xét liệu một đối tượng hoặc phương thức có đang thực hiện nhiều hơn một
Điều. Nếu đó là một đối tượng, có lẽ nó cần được chia thành hai hoặc nhiều đối tượng.  Nếu đó là một
, tôi sẽ luôn sử dụng cấu trúc lại Phương pháp trích xuất trên nó, dẫn đến một phương thức
điều đó nói rõ hơn những gì nó làm, và một số điểm phụ cho biết nó được thực hiện như thế nào.
Sự trùng lặp và biểu cảm đưa tôi đi một chặng đường dài để tìm hiểu những gì tôi cho là sạch sẽ
mã và cải thiện mã bẩn chỉ với hai điều này có thể tạo ra sự khác biệt lớn-
ence.  Tuy nhiên, có một việc khác mà tôi biết là phải làm, khó hơn một chút
giải thích.
Sau nhiều năm làm công việc này, đối với tôi dường như tất cả các chương trình đều được tạo thành từ rất
các yếu tố tương tự. Một ví dụ là "tìm những thứ trong một bộ sưu tập."  Cho dù chúng ta có một dữ liệu-
cơ sở của các bản ghi nhân viên hoặc một bản đồ băm gồm các khóa và giá trị, hoặc một mảng các mục của một số
loại, chúng tôi thường thấy mình muốn một món đồ cụ thể từ bộ sưu tập đó. Khi tôi tìm thấy
điều đó xảy ra, tôi thường sẽ kết thúc việc triển khai cụ thể trong một phương thức trừu tượng hơn
hoặc lớp học.  Điều đó mang lại cho tôi một vài lợi thế thú vị.
Tôi có thể triển khai chức năng ngay bây giờ với một cái gì đó đơn giản, chẳng hạn như bản đồ băm, nhưng
vì bây giờ tất cả các tham chiếu đến tìm kiếm đó được bao phủ bởi sự trừu tượng nhỏ của tôi, tôi có thể
thay đổi việc triển khai bất kỳ lúc nào tôi muốn. Tôi có thể tiến lên nhanh chóng trong khi vẫn bảo toàn
khả năng thay đổi sau này.
Ngoài ra, phần tóm tắt của bộ sưu tập thường thu hút sự chú ý của tôi đến những gì "thực sự"
tiếp tục và giúp tôi không đi vào con đường thực hiện thu thập tùy tiện
hành vi khi tất cả những gì tôi thực sự cần là một vài cách khá đơn giản để tìm thấy những gì tôi muốn.
Giảm sự trùng lặp, tính biểu cảm cao và sớm xây dựng được các câu chuyện trừu tượng đơn giản.
Đó là những gì tạo nên mã sạch cho tôi.
Ở đây, trong một vài đoạn văn ngắn, Ron đã tóm tắt nội dung của cuốn sách này. Không
sự trùng lặp, một sự vật, tính biểu cảm, sự trừu tượng nhỏ. Mọi thứ đều ở đó.
Ward Cunningham, người phát minh ra Wiki,
người phát minh ra Fit, người phát minh ra eXtreme
Lập trình. Động lực đằng sau
Mẫu thiết kế. Smalltalk và OO
lãnh đạo tư tưởng. Cha đỡ đầu của tất cả
những người quan tâm đến mã.
Bạn biết rằng bạn đang làm việc trên mã sạch khi mỗi
thói quen bạn đọc hóa ra là khá nhiều
Bạn mong đợi.  Bạn có thể gọi nó là mã đẹp khi
mã cũng làm cho nó giống như ngôn ngữ
được thực hiện cho vấn đề.
Những phát biểu như thế này là đặc trưng của Ward.
Bạn đọc nó, gật đầu và sau đó tiếp tục
chủ đề tiếp theo. Nghe có vẻ hợp lý, quá rõ ràng, đến nỗi nó hầu như không đăng ký như một thứ gì đó
thâm thúy. Bạn có thể nghĩ rằng đó là khá nhiều những gì bạn mong đợi. Nhưng chúng ta hãy xem xét kỹ hơn
nhìn.
www.it-ebooks.info

Trang 43
12
Chương 1: Mã sạch
“. . . khá nhiều những gì bạn mong đợi. ” Lần cuối cùng bạn thấy một mô-đun
là khá nhiều những gì bạn mong đợi? Có nhiều khả năng là các mô-đun bạn nhìn vào sẽ
là khó hiểu, phức tạp, rối? Không phải là đi sai hướng là quy tắc? Bạn không quen với việc thất bại
về việc cố gắng nắm bắt và nắm giữ các chuỗi lý luận phát ra từ toàn bộ hệ thống-
tem và dệt theo cách của họ thông qua mô-đun bạn đang đọc? Lần cuối cùng bạn là khi nào
đọc qua một số mã và gật đầu theo cách bạn có thể đã gật đầu
tại tuyên bố của Ward?
Ward hy vọng rằng khi bạn đọc mã sạch, bạn sẽ không ngạc nhiên chút nào. Thật vậy, bạn
thậm chí sẽ không tốn nhiều công sức. Bạn sẽ đọc nó, và nó sẽ là những gì bạn
hy vọng. Nó sẽ rõ ràng, đơn giản và hấp dẫn. Mỗi mô-đun sẽ tạo tiền đề cho
tiếp theo. Mỗi phần cho bạn biết cách viết tiếp theo. Các chương trình được rằng sạch rất
được viết sâu sắc mà bạn thậm chí không nhận thấy nó. Nhà thiết kế làm cho nó trông lố bịch-
đơn giản đến mức như tất cả các thiết kế đặc biệt.
Và quan niệm của Ward về cái đẹp thì sao? Tất cả chúng ta đều chống lại sự thật rằng lan của chúng ta-
gu thời trang không được thiết kế cho các vấn đề của chúng tôi. Nhưng lời tuyên bố của Ward đã gây trở ngại cho
chúng tôi.
Anh ấy nói rằng mã đẹp làm cho ngôn ngữ giống như nó được tạo ra cho vấn đề ! Vì thế
đó là chúng tôi có trách nhiệm để làm cho ngôn ngữ cái nhìn đơn giản! Ngôn ngữ lớn ở khắp mọi nơi,
hãy cẩn thận! Nó không phải là ngôn ngữ làm cho các chương trình có vẻ đơn giản. Đó là lập trình viên
làm cho ngôn ngữ có vẻ đơn giản!
Trường học trong tưởng tượng
Còn tôi (chú Bob) thì sao? Tôi nghĩ gì
mã sạch là? Cuốn sách này sẽ cho bạn biết, một cách ghê tởm
chi tiết, những gì tôi và đồng bào của tôi nghĩ về
mã sạch. Chúng tôi sẽ cho bạn biết những gì chúng tôi nghĩ tạo ra
tên biến sạch, hàm sạch, sạch
lớp học, v.v. Chúng tôi sẽ trình bày những ý kiến này như là-
lutes, và chúng tôi sẽ không xin lỗi vì sự ngoan cố của chúng tôi.
Đối với chúng tôi, vào thời điểm này trong sự nghiệp của chúng tôi, họ là abso-
đàn luýt. Họ là trường phái suy nghĩ của chúng tôi về sạch sẽ
mã.
Không phải tất cả các võ sĩ đều đồng ý về điều tốt nhất
võ thuật hoặc kỹ thuật tốt nhất trong một môn võ
nghệ thuật. Thường thì các võ sĩ bậc thầy sẽ hình thành
trường tư tưởng riêng và tập hợp sinh viên để
học hỏi từ họ. Vì vậy, chúng ta thấy Gracie Jiu Jistu ,
được thành lập và giảng dạy bởi gia đình Gracie ở Brazil. Chúng tôi thấy Hakkoryu Jiu Jistu , được thành lập
và được dạy bởi Okuyama Ryuho ở Tokyo. Chúng tôi thấy Jeet Kune Do , được thành lập và giảng dạy bởi
Lý Tiểu Long ở Hoa Kỳ.
www.it-ebooks.info

Trang 44
13
Chúng tôi là tác giả
Học sinh của những cách tiếp cận này đắm mình trong những lời dạy của người sáng lập.
Họ cống hiến hết mình để học những gì mà vị sư phụ cụ thể đó dạy, thường là để ...
sự giảng dạy của bất kỳ bậc thầy nào khác. Sau đó, khi các sinh viên phát triển trong lĩnh vực nghệ thuật của họ, họ
có thể
trở thành học viên của một bậc thầy khác để họ có thể mở rộng kiến thức và thực hành.
Một số cuối cùng tiếp tục trau dồi kỹ năng, khám phá các kỹ thuật mới và sáng lập
các trường học riêng.
Không có trường phái khác nhau nào là hoàn toàn đúng . Tuy nhiên, trong một trường học cụ thể, chúng tôi
hành động như thể những lời dạy và kỹ thuật là đúng. Rốt cuộc, có một cách đúng để luyện tập
tice Hakkoryu Jiu Jitsu, hoặc Jeet Kune Do. Nhưng sự đúng đắn này trong một trường học không phải là vô hại-
coi thường những lời dạy của một trường khác.
Hãy coi cuốn sách này là một mô tả về Trường học Mã sạch của Cố vấn Đối tượng . Các
kỹ thuật và dạy trong là cách mà chúng ta thực hành của chúng tôi nghệ thuật. Chúng tôi sẵn sàng
tuyên bố rằng nếu bạn làm theo những lời dạy này, bạn sẽ được hưởng những lợi ích mà chúng tôi đã được hưởng,
và bạn sẽ học cách viết mã sạch sẽ và chuyên nghiệp. Nhưng đừng mắc sai lầm
nghĩ rằng bằng cách nào đó chúng ta “đúng” theo bất kỳ nghĩa tuyệt đối nào. Có những trường khác và
những bậc thầy khác có nhiều tuyên bố về tính chuyên nghiệp như chúng tôi. Nó sẽ yêu bạn
để học hỏi từ họ.
Thật vậy, nhiều khuyến nghị trong cuốn sách này đang gây tranh cãi. Bạn sẽ thăm dò-
bly không đồng ý với tất cả chúng. Bạn có thể không đồng ý với một số người trong số họ. Tốt rồi.
Chúng tôi không thể yêu cầu thẩm quyền cuối cùng. Mặt khác, các khuyến nghị trong cuốn sách này là
những điều mà chúng tôi đã suy nghĩ rất lâu và khó khăn. Chúng tôi đã học chúng qua nhiều thập kỷ
kinh nghiệm và thử và sai lặp lại. Vì vậy, cho dù bạn đồng ý hay không đồng ý, nó sẽ là một
xấu hổ nếu bạn không nhìn thấy, và tôn trọng, quan điểm của chúng tôi.
Chúng tôi là tác giả
Trường @author của Javadoc cho chúng ta biết chúng ta là ai. Chúng tôi là tác giả. Và một điều về
tác giả là họ có độc giả. Thật vậy, các tác giả có trách nhiệm giao tiếp tốt
với độc giả của họ. Lần tới khi bạn viết một dòng mã, hãy nhớ rằng bạn là tác giả,
viết cho người đọc, những người sẽ đánh giá nỗ lực của bạn.
Bạn có thể hỏi: Thực sự đọc được bao nhiêu mã? Không phải hầu hết các nỗ lực đều đi vào
viết nó?
Bạn đã bao giờ phát lại một phiên chỉnh sửa chưa? Trong những năm 80 và 90, chúng tôi có những biên tập viên
như Emacs
theo dõi mọi lần gõ phím. Bạn có thể làm việc trong một giờ và sau đó phát lại toàn bộ
chỉnh sửa phiên như một bộ phim tốc độ cao. Khi tôi làm điều này, kết quả thật hấp dẫn.
Phần lớn quá trình phát lại là cuộn và điều hướng đến các mô-đun khác!
Bob vào mô-đun.
Anh ta cuộn xuống chức năng cần thay đổi.
Anh dừng lại, cân nhắc các lựa chọn của mình.
Ồ, anh ta đang cuộn lên đầu mô-đun để kiểm tra việc khởi tạo một biến.
Bây giờ anh ấy cuộn xuống và bắt đầu nhập.
www.it-ebooks.info

Trang 45
14
Chương 1: Mã sạch
Rất tiếc, anh ấy đang xóa những gì anh ấy đã nhập!
Anh ấy gõ lại.
Anh ấy lại xóa nó đi!
Anh ta gõ một nửa thứ khác nhưng sau đó xóa đi!
Anh ấy cuộn xuống một hàm khác gọi hàm mà anh ấy đang thay đổi để xem nó như thế nào
gọi là.
Anh ấy cuộn lại và nhập mã giống như anh ấy vừa xóa.
Anh ta dừng lại.
Anh ấy lại xóa mã đó!
Anh ấy bật lên một cửa sổ khác và nhìn vào một lớp con.  Chức năng đó có bị ghi đè không?
. . .
Bạn nhận được sự trôi dạt. Thật vậy, tỷ lệ thời gian dành cho việc đọc và viết là hơn 10: 1.
Chúng tôi liên tục đọc mã cũ như một phần của nỗ lực viết mã mới.
Bởi vì tỷ lệ này rất cao, chúng tôi muốn việc đọc mã trở nên dễ dàng, ngay cả khi nó khiến
viết khó hơn. Tất nhiên không có cách nào để viết mã mà không đọc nó, vì vậy hãy làm cho nó
dễ đọc thực sự làm cho nó dễ viết hơn .
Không có lối thoát khỏi logic này. Bạn không thể viết mã nếu bạn không thể đọc sur-
làm tròn mã. Mã bạn đang cố gắng viết hôm nay sẽ khó hoặc dễ viết
tùy thuộc vào độ khó hay dễ đọc của mã xung quanh. Vì vậy, nếu bạn muốn đi nhanh,
nếu bạn muốn hoàn thành nhanh chóng, nếu bạn muốn mã của mình dễ viết, hãy làm cho nó dễ dàng
đọc.
Quy tắc hướng đạo sinh
Nó không đủ để viết mã tốt. Mã phải được giữ sạch sẽ theo thời gian. Chúng tôi có tất cả
đã thấy mã bị thối rữa và xuống cấp theo thời gian. Vì vậy, chúng ta phải có vai trò tích cực trong việc ngăn chặn
suy thoái.
Hội Nam Hướng đạo Hoa Kỳ có một quy tắc đơn giản mà chúng ta có thể áp dụng cho nghề nghiệp của mình.
Để khu cắm trại sạch hơn bạn đã tìm thấy.  5
Nếu tất cả chúng ta đã kiểm tra mã của mình gọn gàng hơn một chút so với khi chúng ta kiểm tra, thì mã
đơn giản là không thể thối rữa. Việc dọn dẹp không cần phải là một cái gì đó lớn lao. Thay đổi một biến
đặt tên cho tốt hơn, chia nhỏ một hàm quá lớn, loại bỏ một hàm nhỏ
sao chép, xóa một câu lệnh if tổng hợp .
Bạn có thể tưởng tượng làm việc trong một dự án mà mã đơn giản trở nên tốt hơn theo thời gian
thông qua? Bạn có tin rằng bất kỳ lựa chọn nào khác là chuyên nghiệp? Thật vậy, không liên tục
cải thiện một phần nội tại của tính chuyên nghiệp?
5. Điều này được phỏng theo lời nhắn từ biệt của Robert Stephenson Smyth Baden-Powell đối với các Hướng đạo sinh: “Hãy cố gắng và rời khỏi thế giới này a
tốt hơn một chút so với bạn tìm thấy nó. . . ”
www.it-ebooks.info

Trang 46
15
Thư mục
Phần tiền truyện và nguyên tắc
Theo nhiều cách, cuốn sách này là “phần tiền truyện” của cuốn sách tôi viết năm 2002 có tựa đề Phần mềm Agile
Phát triển: Nguyên tắc, Mô hình và Thực hành (PPP). Cuốn sách về PPP liên quan đến chính nó
với các nguyên tắc của thiết kế hướng đối tượng và nhiều phương pháp được sử dụng bởi các cấu
các nhà phát triển sional. Nếu bạn chưa đọc PPP, thì bạn có thể thấy rằng nó tiếp tục câu chuyện
nói bởi cuốn sách này. Nếu bạn đã đọc nó, thì bạn sẽ tìm thấy nhiều tình cảm của
cuốn sách đó lặp lại trong cuốn sách này ở cấp độ mã.
Trong cuốn sách này, bạn sẽ tìm thấy những tài liệu tham khảo lẻ tẻ về các nguyên tắc thiết kế khác nhau. Những
bao gồm Nguyên tắc trách nhiệm duy nhất (SRP), Nguyên tắc đóng mở (OCP) và
Nguyên tắc Đảo ngược Phụ thuộc (DIP) cùng những nguyên tắc khác. Những nguyên tắc này được mô tả trong
chiều sâu trong PPP.
Phần kết luận
Sách về nghệ thuật không hứa hẹn biến bạn thành nghệ sĩ. Tất cả những gì họ có thể làm là cung cấp cho bạn một số
các công cụ, kỹ thuật và quy trình suy nghĩ mà các nghệ sĩ khác đã sử dụng. Vì vậy, cuốn sách này cũng có thể-
không hứa sẽ biến bạn trở thành một lập trình viên giỏi. Nó không thể hứa sẽ cung cấp cho bạn “mã cảm giác”.
Tất cả những gì nó có thể làm là cho bạn thấy quy trình suy nghĩ của các lập trình viên giỏi và các thủ thuật, công
nghệ-
niques và các công cụ mà họ sử dụng.
Cũng giống như một cuốn sách về nghệ thuật, cuốn sách này sẽ có đầy đủ các chi tiết. Sẽ có rất nhiều mã.
Bạn sẽ thấy mã tốt và bạn sẽ thấy mã xấu. Bạn sẽ thấy mã xấu được chuyển thành mã tốt
mã. Bạn sẽ thấy danh sách các kinh nghiệm học, các nguyên tắc và kỹ thuật. Bạn sẽ thấy ví dụ sau
thí dụ. Sau đó, tùy thuộc vào bạn.
Hãy nhớ câu chuyện cười cũ về người nghệ sĩ vĩ cầm trong buổi hòa nhạc đã bị lạc trên đường đến một trung tâm ...
mance? Anh ta dừng một ông già ở góc đường và hỏi ông ta làm thế nào để đến Carnegie Hall.
Ông lão nhìn người nghệ sĩ vĩ cầm và cây đàn vĩ cầm được kẹp dưới cánh tay ông ta, và nói: "Thực ...
tice, con trai. Thực hành!"
Thư mục
[Beck07]: Các mẫu triển khai , Kent Beck, Addison-Wesley, 2007.
[Knuth92]: Lập trình chữ , Donald E. Knuth, Trung tâm Nghiên cứu Ngôn ngữ
và Thông tin, Đại học Leland Stanford Junior, 1992.
www.it-ebooks.info

Trang 47
Trang này cố ý để trống
www.it-ebooks.info

Trang 48
17

2
Tên có ý nghĩa
bởi Tim Ottinger
Giới thiệu
Tên có ở khắp mọi nơi trong phần mềm. Chúng tôi đặt tên cho các biến, các hàm của chúng tôi, các đối số của
chúng tôi,
các lớp và các gói. Chúng tôi đặt tên cho các tệp nguồn của mình và các thư mục chứa chúng. Chúng tôi
đặt tên cho các tệp jar và tệp chiến tranh và tệp tai của chúng tôi. Chúng tôi đặt tên và đặt tên và đặt tên. Bởi vì
chúng tôi làm
www.it-ebooks.info

Trang 49
18
Chương 2: Những cái tên có ý nghĩa
rất nhiều, chúng ta nên làm tốt hơn. Sau đây là một số quy tắc đơn giản để tạo
những cái tên hay.
Sử dụng tên có ý định tiết lộ
Có thể dễ dàng nói rằng những cái tên nên bộc lộ ý định. Điều chúng tôi muốn gây ấn tượng với bạn là
chúng tôi rất nghiêm túc về điều này. Việc chọn những cái tên hay tuy mất thời gian nhưng tiết kiệm được nhiều thời
gian hơn.
Vì vậy, hãy cẩn thận với tên của bạn và thay đổi chúng khi bạn tìm thấy những tên tốt hơn. Mọi người
đọc mã của bạn (bao gồm cả bạn) sẽ hạnh phúc hơn nếu bạn làm vậy.
Tên của một biến, hàm hoặc lớp, phải trả lời tất cả các câu hỏi lớn. Nó
sẽ cho bạn biết tại sao nó tồn tại, nó làm gì và nó được sử dụng như thế nào. Nếu một tên yêu cầu một com-
thì cái tên không tiết lộ ý định của nó.
int d; // thời gian trôi qua tính bằng ngày
Tên d không tiết lộ gì. Nó không gợi lên cảm giác về thời gian đã trôi qua, cũng không phải ngày. Chúng tôi
nên chọn một tên chỉ định những gì đang được đo lường và đơn vị của phép đo đó-
đề cập:
int elapsedTimeInDays;
int daysSinceCreation;
int daysSinceModification;
int fileAgeInDays;
Chọn tên thể hiện ý định có thể giúp bạn dễ hiểu và dễ thay đổi hơn nhiều
mã. Mục đích của mã này là gì?
danh sách công khai <int []> getThem () {
List <int []> list1 = new ArrayList <int []> ();
cho (int [] x: theList)
nếu (x [0] == 4)
list1.add (x);
danh sách trả về1;
}
Tại sao rất khó để biết mã này đang làm gì? Không có biểu thức phức tạp.
Khoảng cách và thụt lề hợp lý. Chỉ có ba biến và hai hằng
đề cập. Thậm chí không có bất kỳ lớp ưa thích hoặc phương thức đa hình nào, chỉ là một danh sách
mảng (hoặc có vẻ như vậy).
Vấn đề không phải là sự đơn giản của mã mà là sự không rõ ràng của mã (đồng xu
cụm từ): mức độ mà ngữ cảnh không rõ ràng trong chính mã. Mã ngụ ý-
điều đó đòi hỏi chúng ta phải biết câu trả lời cho những câu hỏi như:
1. Những loại thứ gì trong danh sách ?
2. Ý nghĩa của chỉ số con thứ 0 của một mục trong Danh sách ?
3. Ý nghĩa của giá trị 4 là gì?
4. Tôi sẽ sử dụng danh sách đang được trả về như thế nào?
www.it-ebooks.info

Trang 50
19
Tránh thông tin sai lệch
Câu trả lời cho những câu hỏi này không có trong mẫu mã, nhưng chúng có thể có
đã . Giả sử rằng chúng tôi đang làm việc trong một trò chơi quét mìn. Chúng tôi thấy rằng hội đồng quản trị là một
danh sách
các ô được gọi là danh sách . Hãy đổi tên nó thành gameBoard .
Mỗi ô trên bảng được biểu diễn bằng một mảng đơn giản. Chúng tôi còn thấy rằng số 0
chỉ số dưới là vị trí của giá trị trạng thái và giá trị trạng thái bằng 4 có nghĩa là “được gắn cờ”. Chỉ
bằng cách đặt tên cho các khái niệm này, chúng tôi có thể cải thiện mã đáng kể:
public List <int []> getFlaggedCells () {
Danh sách <int []> flaggedCells = new ArrayList <int []> ();
cho (int [] cell: gameBoard)
if (ô [STATUS_VALUE] == FLAGGED)
flaggedCells.add (ô);
trả về flaggedCells;
}
Lưu ý rằng tính đơn giản của mã không thay đổi. Nó vẫn có cùng một số
toán tử và hằng số, với số lượng cấp lồng nhau chính xác. Nhưng mã
đã trở nên rõ ràng hơn nhiều.
Chúng ta có thể đi xa hơn và viết một lớp đơn giản cho các ô thay vì sử dụng một mảng int s.
Nó có thể bao gồm một chức năng tiết lộ ý định (gọi nó là Được gắn thẻ ) để ẩn số ma thuật
bia. Nó dẫn đến một phiên bản mới của hàm:
danh sách công khai <Cell> getFlaggedCells () {
Danh sách <Cell> flaggedCells = new ArrayList <Cell> ();
cho (Ô ô: gameBoard)
if (cell.isFlagged ())
flaggedCells.add (ô);
trả về flaggedCells;
}
Với những thay đổi tên đơn giản này, không khó để hiểu chuyện gì đang xảy ra. Đây là
sức mạnh của việc lựa chọn những cái tên hay.
Tránh thông tin sai lệch
Lập trình viên phải tránh để lại các manh mối sai làm che khuất ý nghĩa của mã. Chúng ta nên
tránh những từ có nghĩa cố định khác với ý nghĩa của chúng ta. Ví dụ,
hp , aix , và sco sẽ là các tên biến kém vì chúng là tên của Unix plat-
các dạng hoặc biến thể. Ngay cả khi bạn đang mã hóa một cạnh huyền và hp trông giống như một chữ viết tắt hay-
tion, nó có thể không đúng định dạng.
Không đề cập đến một nhóm tài khoản dưới dạng Danh sách tài khoản trừ khi đó thực sự là Danh sách .
Danh sách từ có nghĩa là một cái gì đó cụ thể cho các lập trình viên. Nếu thùng chứa
tài khoản thực sự không phải là một Danh sách , nó có thể dẫn đến kết luận sai. 1 Vì vậy accountGroup hoặc
chùmOfAccounts hoặc chỉ là tài khoản thuần túy sẽ tốt hơn.
1. Như chúng ta sẽ thấy ở phần sau, ngay cả khi vùng chứa là một Danh sách, có lẽ tốt hơn là không nên mã hóa loại vùng chứa vào tên.
www.it-ebooks.info

Trang 51
20
Chương 2: Những cái tên có ý nghĩa
Hãy cẩn thận khi sử dụng các tên khác nhau theo những cách nhỏ. Mất bao lâu để phát hiện ra
sự khác biệt tinh tế giữa XYZControllerForEnoughHandlingOfStrings trong một mô-đun
và ở đâu đó xa hơn một chút, XYZControllerForEnoughStorageOfStrings ? Các
các từ có hình dạng giống nhau một cách đáng sợ.
Đánh vần các khái niệm tương tự tương tự là thông tin . Sử dụng cách viết không phù hợp là dis-
thông tin . Với môi trường Java hiện đại, chúng tôi tận hưởng khả năng hoàn thành mã tự động. Chúng tôi
viết một vài ký tự của tên và nhấn một số tổ hợp phím nóng (nếu có) và được
được thưởng bằng một danh sách các hoàn thành có thể có cho tên đó. Sẽ rất hữu ích nếu tên cho
những thứ rất giống nhau sắp xếp cùng nhau theo bảng chữ cái và nếu sự khác biệt là rất rõ ràng,
bởi vì nhà phát triển có thể chọn một đối tượng theo tên mà không thấy
nhận xét hoặc thậm chí danh sách các phương thức được cung cấp bởi lớp đó.
Một ví dụ thực sự khủng khiếp về những cái tên không phù hợp là việc sử dụng chữ L viết thường hoặc
chữ hoa O làm tên biến, đặc biệt là sự kết hợp. Tất nhiên, vấn đề là
chúng trông gần như hoàn toàn giống các hằng số một và số không.
int a = l;
nếu (O == l)
a = O1;
khác
l = 01;
Người đọc có thể nghĩ đây là một sự liên quan, nhưng chúng tôi đã kiểm tra mã khi
mọi thứ rất phong phú. Trong một trường hợp, tác giả của mã đề xuất sử dụng một phông chữ khác
để sự khác biệt rõ ràng hơn, một giải pháp sẽ phải được chuyển giao cho
tất cả các nhà phát triển tương lai dưới dạng truyền miệng hoặc trong một tài liệu bằng văn bản. Vấn đề được chinh
phục
có tính hoàn thiện và không cần tạo ra các sản phẩm công việc mới bằng cách đổi tên đơn giản.
Làm cho ý nghĩa
Sự khác biệt
Lập trình viên tạo ra vấn đề cho họ-
bản thân khi họ chỉ viết mã cho sat-
isfy một trình biên dịch hoặc trình thông dịch. Ví dụ,
bởi vì bạn không thể sử dụng cùng một tên để giới thiệu
đến hai thứ khác nhau trong cùng một phạm vi,
bạn có thể bị cám dỗ để thay đổi một cái tên
một cách tùy ý. Đôi khi điều này được thực hiện bằng cách viết sai chính tả, dẫn đến
tình huống sửa lỗi chính tả dẫn đến không biên dịch được. 2
Không đủ để thêm chuỗi số hoặc các từ nhiễu, mặc dù trình biên dịch
hài lòng. Nếu các tên phải khác nhau, thì chúng cũng phải có nghĩa khác.
2. Hãy xem xét, ví dụ, hành động thực sự ghê tởm là tạo một biến có tên klass chỉ vì lớp tên đã được sử dụng
cho một cái gì đó khác.
www.it-ebooks.info

Trang 52
21
Sử dụng tên có thể phát âm
Đặt tên theo dãy số (a1, a2, .. aN) ngược lại với đặt tên có chủ đích. Như là
tên không sai định dạng — chúng không có định dạng; họ không cung cấp manh mối cho
ý định của tác giả. Xem xét:
public static void copyChars (char a1 [], char a2 []) {
for (int i = 0; i <a1.length; i ++) {
a2 [i] = a1 [i];
}
}
Hàm này đọc tốt hơn nhiều khi nguồn và đích được sử dụng cho đối số
những cái tên.
Những từ nhiễu là một sự phân biệt vô nghĩa khác. Hãy tưởng tượng rằng bạn có một Sản phẩm
lớp học. Nếu bạn có một tên gọi khác là ProductInfo hoặc ProductData , bạn đã đặt tên khác
ferent mà không làm cho chúng có nghĩa là bất cứ điều gì khác nhau. Thông tin và Dữ liệu là tiếng ồn không rõ ràng
những từ như a , an và the .
Lưu ý rằng không có gì là sai với việc sử dụng công ước prefix như một và các quá lâu
khi chúng tạo ra sự khác biệt có ý nghĩa. Ví dụ, bạn có thể sử dụng a cho tất cả các biến cục bộ
và các cho tất cả các đối số chức năng. 3 Vấn đề xuất hiện khi bạn quyết định gọi một biến thể-
có thể theZork vì bạn đã có một biến khác có tên là zork .
Những từ ồn ào là thừa. Từ biến không bao giờ nên xuất hiện trong một biến
Tên. Từ bảng không bao giờ nên xuất hiện trong một tên bảng. NameString tốt hơn như thế nào
Tên ? Một sẽ Name bao giờ là một số điểm nổi? Nếu vậy, nó phá vỡ một quy tắc trước đó về
thông tin sai lệch. Hãy tưởng tượng tìm một lớp có tên Khách hàng và lớp khác có tên
Đối tượng khách hàng . Bạn nên hiểu sự phân biệt là gì? Cái nào sẽ đại diện
đường dẫn tốt nhất đến lịch sử thanh toán của khách hàng?
Có một ứng dụng mà chúng tôi biết về điều này được minh họa. chúng tôi đã thay đổi tên
để bảo vệ người có tội, nhưng đây là hình thức chính xác của lỗi:
getActiveAccount ();
getActiveAccounts ();
getActiveAccountInfo ();
Làm thế nào để các lập trình viên trong dự án này biết hàm nào trong số các hàm này để gọi?
Trong trường hợp không có các quy ước cụ thể, không thể phân biệt được biến moneyAmount
từ tiền bạc , customerInfo không thể phân biệt với khách hàng , accountData không thể phân biệt-
có thể từ tài khoản , và theMessage là không thể phân biệt từ thông điệp . Phân biệt tên trong
theo cách mà người đọc biết những gì khác biệt cung cấp.
Sử dụng tên có thể phát âm
Con người giỏi ngôn từ. Một phần quan trọng trong bộ não của chúng ta dành riêng cho khái niệm
từ ngữ. Và các từ, theo định nghĩa, có thể phát âm được. Sẽ thật tiếc nếu không dùng
3. Bác Bob đã từng làm điều này trong C ++ nhưng đã từ bỏ thực hành này vì các IDE hiện đại khiến nó không cần thiết.
www.it-ebooks.info

Trang 53
22
Chương 2: Những cái tên có ý nghĩa
lợi thế của phần lớn bộ não của chúng ta đã phát triển để đối phó với ngôn ngữ nói-
guage. Vì vậy, hãy làm cho tên của bạn có thể phát âm được.
Nếu bạn không thể phát âm nó, bạn không thể thảo luận nó mà không nghe như một tên ngốc. "Tốt,
đằng này, trên con ong ba cee enn tee chúng ta có một tiểu luận zee kyew int, thấy không? ” Điều này
vì lập trình là một hoạt động xã hội.
Một công ty tôi biết có genymdhms (ngày, năm, tháng, ngày, giờ, phút,
và thứ hai) nên họ đi vòng quanh và nói “gen tại sao emm dee aich emm ess”. tôi có một
thói quen khó chịu khi phát âm mọi thứ như đã viết, vì vậy tôi bắt đầu nói “gen-yah-mudda-
chủ nhân. ” Sau này nó được nhiều nhà thiết kế và nhà phân tích gọi là
nghe thật ngớ ngẩn. Nhưng chúng tôi đã tham gia vào trò đùa, vì vậy nó rất vui. Vui hay không, chúng tôi đã bao
dung
đặt tên kém. Các nhà phát triển mới phải giải thích các biến cho họ, và sau đó họ
đã nói về nó bằng những từ ngữ bịa đặt ngớ ngẩn thay vì sử dụng các thuật ngữ tiếng Anh thích hợp. So sánh
lớp DtaRcrd102 {
ngày riêng tư genymdhms;
ngày riêng modymdhms;
private final String pszqint = "102";
/ * ... * /
};
đến
khách hàng hạng {
dấu ngày tạo riêng tư;
sửa đổi ngày tháng riêng tư Dấu ấn ;;
private final String recordId = "102";
/ * ... * /
};
Bây giờ có thể trò chuyện thông minh: “Này, Mikey, hãy xem bản ghi này! Thế hệ-
dấu thời gian eration được đặt thành ngày mai! Làm thế nào mà có thể được?"
Sử dụng tên có thể tìm kiếm
Tên một chữ cái và hằng số có một vấn đề cụ thể là chúng không
dễ dàng định vị trên toàn bộ nội dung văn bản.
Người ta có thể dễ dàng nhận được MAX_CLASSES_PER_STUDENT , nhưng số 7 có thể nhiều hơn
khó khăn. Các tìm kiếm có thể đưa ra chữ số như một phần của tên tệp, các định nghĩa hằng số khác-
và trong các biểu thức khác nhau mà giá trị được sử dụng với mục đích khác nhau. Nó thậm chí
tệ hơn khi một hằng số là một số dài và ai đó có thể đã hoán vị các chữ số,
từ đó tạo ra một lỗi đồng thời trốn tránh sự tìm kiếm của lập trình viên.
Tương tự như vậy, tên e là một lựa chọn tồi cho bất kỳ biến nào mà một lập trình viên có thể
cần tìm kiếm. Đây là chữ cái phổ biến nhất trong tiếng Anh và có khả năng xuất hiện
trong mỗi đoạn văn bản trong mọi chương trình. Về mặt này, tên dài hơn tên ngắn hơn
tên và bất kỳ tên nào có thể tìm kiếm được sẽ vượt trội hơn một hằng số trong mã.
Sở thích cá nhân của tôi là các tên chỉ gồm một chữ cái CHỈ có thể được sử dụng làm biến thể địa phương-
ables bên trong các phương pháp ngắn. Độ dài của tên phải tương ứng với kích thước phạm vi của nó
www.it-ebooks.info

Trang 54
23
Tránh mã hóa
[N5]. Nếu một biến hoặc hằng số có thể được nhìn thấy hoặc sử dụng ở nhiều nơi trong một nội dung mã,
bắt buộc phải đặt cho nó một cái tên thân thiện với tìm kiếm. Một lần nữa so sánh
for (int j = 0; j <34; j ++) {
s + = (t [j] * 4) / 5;
}
đến
int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j = 0; j <NUMBER_OF_TASKS; j ++) {
int realTaskDays = taskEstim [j] * realDaysPerIdealDay;
int realTaskWeeks = (ngày thực / WORK_DAYS_PER_WEEK);
sum + = realTaskWeeks;
}
Lưu ý rằng tổng , ở trên, không phải là một tên đặc biệt hữu ích nhưng ít nhất là có thể tìm kiếm được. Các
mã được đặt tên cố ý tạo ra một hàm dài hơn, nhưng hãy cân nhắc xem nó dễ dàng hơn bao nhiêu
sẽ là tìm WORK_DAYS_PER_WEEK hơn là tìm tất cả các địa điểm mà 5 đã được sử dụng và lọc
danh sách xuống chỉ những trường hợp có ý nghĩa dự định.
Tránh mã hóa
Chúng tôi có đủ các mã hóa để xử lý mà không làm tăng thêm gánh nặng của chúng tôi. Mã hóa
loại hoặc phạm vi thông tin vào tên chỉ đơn giản là tạo thêm gánh nặng cho việc giải mã. Nó
dường như không hợp lý khi yêu cầu mỗi nhân viên mới phải học thêm một bảng mã khác “lan-
guage ”ngoài việc học phần nội dung mã (thường là đáng kể) mà chúng sẽ hoạt động-
ăn nhập. Đó là một gánh nặng tinh thần không cần thiết khi cố gắng giải quyết một vấn đề. Tên được mã hóa
hiếm khi phát âm và dễ gõ sai.
Ký hiệu Hungary
Ngày xưa, khi chúng tôi làm việc bằng các ngôn ngữ bị thách thức về độ dài tên, chúng tôi đã vi phạm điều này
loại trừ sự cần thiết, và với sự hối tiếc. Fortran bắt buộc mã hóa bằng cách tạo ra chữ cái đầu tiên a
mã cho loại. Các phiên bản đầu tiên của BASIC chỉ cho phép một chữ cái cộng với một chữ số. người Hungary
Notation (HN) đã đưa điều này lên một tầm cao mới.
HN được coi là khá quan trọng trong Windows C API, khi mọi-
thứ là một xử lý số nguyên hoặc một con trỏ dài hoặc một con trỏ void , hoặc một trong một số hàm-
tations of "string" (với các công dụng và thuộc tính khác nhau). Trình biên dịch đã không kiểm tra các loại trong
những ngày đó, vì vậy các lập trình viên cần một chiếc nạng để giúp họ nhớ các kiểu.
Trong các ngôn ngữ hiện đại, chúng ta có hệ thống kiểu chữ phong phú hơn nhiều và các trình biên dịch nhớ
và thực thi các kiểu. Hơn nữa, có xu hướng đối với các lớp học nhỏ hơn và ngắn hơn
để mọi người thường có thể thấy điểm khai báo của mỗi biến mà họ
đang sử dụng.
www.it-ebooks.info

Trang 55
24
Chương 2: Những cái tên có ý nghĩa
Lập trình viên Java không cần mã hóa kiểu. Các đối tượng được gõ mạnh và chỉnh sửa
môi trường đã nâng cao để chúng phát hiện lỗi loại rất lâu trước khi bạn có thể chạy
biên dịch! Vì vậy ngày nay HN và các dạng mã hóa kiểu khác chỉ đơn giản là trở ngại.
Chúng khiến việc thay đổi tên hoặc kiểu của một biến, hàm hoặc lớp khó hơn. Họ
làm cho việc đọc mã khó hơn. Và chúng tạo ra khả năng hệ thống mã hóa
sẽ đánh lừa người đọc.
PhoneNumber phoneString;
// tên không thay đổi khi thay đổi kiểu!
Tiền tố thành viên
Bạn cũng không cần đặt tiền tố cho các biến thành viên bằng m_ nữa. Các lớp học và func của bạn-
tions phải đủ nhỏ để bạn không cần chúng. Và bạn nên sử dụng một bản chỉnh sửa-
môi trường ing làm nổi bật hoặc tô màu các thành viên để làm cho chúng khác biệt.
lớp công cộng Phần {
chuỗi riêng m_dsc; // Mô tả dạng văn bản
void setName (Tên chuỗi) {
m_dsc = tên;
}
}
_________________________________________________
lớp công cộng Phần {
Mô tả chuỗi;
void setDescription (Mô tả chuỗi) {
this.description = mô tả;
}
}
Bên cạnh đó, mọi người nhanh chóng học cách bỏ qua tiền tố (hoặc hậu tố) để xem ý nghĩa
một phần của tên. Chúng ta càng đọc nhiều mã, chúng ta càng ít thấy các tiền tố. Cuối cùng thì
tiền tố trở nên lộn xộn khó thấy và là điểm đánh dấu của mã cũ hơn.
Giao diện và Triển khai
Đây đôi khi là một trường hợp đặc biệt cho các bảng mã. Ví dụ: giả sử bạn đang xây dựng
Một BSTRACT F actory cho việc tạo ra các hình dạng. Nhà máy này sẽ là một giao diện và sẽ
được thực hiện bởi một lớp cụ thể. Bạn nên đặt tên cho chúng là gì? IShapeFactory và
Hình dạng nhà máy ? Tôi muốn để các giao diện không được trang trí. I trước đó , rất phổ biến trong
di sản ngày nay, tốt nhất là gây xao nhãng và tệ nhất là quá nhiều thông tin. Tôi không
muốn người dùng của tôi biết rằng tôi đang giao cho họ một giao diện. Tôi chỉ muốn họ biết rằng
đó là một ShapeFactory . Vì vậy, nếu tôi phải mã hóa giao diện hoặc triển khai, tôi chọn
việc thực hiện. Gọi nó là ShapeFactoryImp , hay thậm chí là CShapeFactory gớm ghiếc , là pref-
có thể mã hóa giao diện.
www.it-ebooks.info

Trang 56
25
Tên phương pháp
Tránh lập bản đồ tinh thần
Người đọc không cần phải dịch tên bạn sang tên khác mà họ đã có
biết rôi. Vấn đề này thường phát sinh từ sự lựa chọn không sử dụng các thuật ngữ miền có vấn đề
cũng không phải điều khoản miền giải pháp.
Đây là vấn đề với các tên biến chỉ có một chữ cái. Chắc chắn một bộ đếm vòng lặp có thể
được đặt tên là i hoặc j hoặc k (mặc dù không bao giờ là l !) nếu phạm vi của nó rất nhỏ và không có tên nào khác có
thể
lộn với nó. Điều này là do những tên đơn của bộ đếm vòng lặp là truyền thống.
Tuy nhiên, trong hầu hết các ngữ cảnh khác, tên chỉ có một chữ cái là một lựa chọn tồi; nó chỉ là một nơi
mà người đọc phải suy nghĩ về khái niệm thực tế. Không thể có chuyện tồi tệ hơn-
con trai vì sử dụng tên c hơn vì a và b đã được sử dụng.
Nói chung các lập trình viên là những người khá thông minh. Người thông minh đôi khi thích thể hiện
vượt qua sự thông minh của họ bằng cách thể hiện khả năng vận động trí óc của họ. Rốt cuộc, nếu bạn có thể tin
cậy-
hãy nhớ rằng r là phiên bản có cấu trúc thấp hơn của url với máy chủ và lược đồ
loại bỏ, thì rõ ràng bạn phải rất thông minh.
Một sự khác biệt giữa một lập trình viên thông minh và một lập trình viên chuyên nghiệp là
người chuyên nghiệp hiểu rằng rõ ràng là vua . Các chuyên gia sử dụng quyền hạn của họ cho mục đích tốt
và viết mã mà người khác có thể hiểu được.
Tên lớp
Các lớp và đối tượng phải có tên danh từ hoặc cụm danh từ như Khách hàng , WikiPage ,
Tài khoản và AddressParser . Tránh các từ như Người quản lý , Bộ xử lý , Dữ liệu hoặc Thông tin trong tên
của một lớp. Tên lớp không được là động từ.
Tên phương pháp
Các phương thức phải có tên động từ hoặc cụm động từ như postPayment , deletePage hoặc save .
Bộ truy cập, bộ đột biến và vị từ phải được đặt tên theo giá trị của chúng và có tiền tố là get ,
thiết lập , và là theo tiêu chuẩn javabean. 4
string name = worker.getName ();
customer.setName ("mike");
nếu (paycheck.isPosted ()) ...
Khi các hàm tạo bị quá tải, hãy sử dụng các phương thức nhà máy tĩnh có tên
mô tả các đối số. Ví dụ,
Điểm tựa phức hợp = Complex.FromRealNumber (23.0);
nói chung là tốt hơn
Điểm tựa phức hợp = new Complex (23.0);
Cân nhắc việc thực thi việc sử dụng chúng bằng cách đặt các hàm tạo tương ứng ở chế độ riêng tư.
4. http://java.sun.com/products/javabeans/docs/spec.html
www.it-ebooks.info

Trang 57
26
Chương 2: Những cái tên có ý nghĩa
Đừng dễ thương
Nếu những cái tên quá thông minh, chúng sẽ
chỉ đáng nhớ với những người chia sẻ
cảm giác hài hước của tác giả, và chỉ miễn là
như những người này nhớ về trò đùa. Sẽ
họ biết hàm có tên là gì
HolyHandGrenade phải làm gì? Chắc chắn rồi,
nó dễ thương, nhưng có thể trong trường hợp này
DeleteItems có thể là một cái tên tốt hơn.
Chọn sự rõ ràng hơn giá trị giải trí.
Sự dễ thương trong mã thường xuất hiện dưới dạng thông tục hoặc tiếng lóng. Ví dụ,
không sử dụng tên whack () có nghĩa là kill () . Đừng kể những câu chuyện cười phụ thuộc vào văn hóa như
eatMyShorts () có nghĩa là hủy bỏ () .
Nói những gì bạn có nghĩa là. Có nghĩa là những gì bạn nói.
Chọn một từ cho mỗi khái niệm
Chọn một từ cho một khái niệm trừu tượng và gắn bó với nó. Ví dụ, thật khó hiểu với
có tìm nạp , truy xuất và nhận như các phương thức tương đương của các lớp khác nhau. Bạn khỏe không
nhớ tên phương thức đi với lớp nào? Đáng buồn thay, bạn thường phải nhớ
công ty, nhóm hoặc cá nhân nào đã viết thư viện hoặc lớp học để ghi nhớ
thuật ngữ đã được sử dụng. Nếu không, bạn sẽ mất rất nhiều thời gian để duyệt qua các tiêu đề và
mẫu mã trước đó.
Các môi trường chỉnh sửa hiện đại như Eclipse và IntelliJ-cung cấp các manh mối nhạy cảm với ngữ cảnh,
chẳng hạn như danh sách các phương thức bạn có thể gọi trên một đối tượng nhất định. Nhưng lưu ý rằng danh sách
không sử dụng-
ally cho bạn những nhận xét bạn đã viết xung quanh tên hàm và danh sách tham số của bạn.
Bạn thật may mắn nếu nó cung cấp tên tham số từ các khai báo hàm. Chức năng
tên phải đứng một mình và chúng phải nhất quán để bạn có thể chọn
phương pháp trực tràng mà không cần thăm dò thêm.
Tương tự như vậy, thật khó hiểu khi có một bộ điều khiển và một người quản lý và một trình điều khiển trong cùng một
cơ sở mã. Sự khác biệt cơ bản giữa DeviceManager và Protocol-
Bộ điều khiển ? Tại sao cả hai đều không phải là người kiểm soát hoặc cả hai đều không phải là người quản lý ? Cả hai
đều là Trình điều khiển
có thật không? Cái tên khiến bạn mong đợi hai đối tượng có kiểu rất khác nhau cũng như
có các lớp khác nhau.
Một từ vựng nhất quán là một lợi ích tuyệt vời cho các lập trình viên, những người phải sử dụng mã của bạn.
Đừng chơi chữ
Tránh sử dụng cùng một từ cho hai mục đích. Sử dụng cùng một thuật ngữ cho hai ý tưởng khác nhau
thực chất là một cách chơi chữ.
www.it-ebooks.info

Trang 58
27
Thêm ngữ cảnh có ý nghĩa
Nếu bạn tuân theo quy tắc "một từ cho mỗi khái niệm", bạn có thể kết thúc với nhiều lớp
ví dụ, có một phương thức thêm . Miễn là tham số liệt kê và trả về giá trị của
các phương thức add khác nhau tương đương về mặt ngữ nghĩa, tất cả đều tốt.
Tuy nhiên, một người có thể quyết định sử dụng từ thêm cho "nhất quán" khi họ không
thực tế là thêm theo cùng một nghĩa. Giả sử chúng ta có nhiều lớp trong đó add sẽ tạo
giá trị mới bằng cách thêm hoặc nối hai giá trị hiện có. Bây giờ giả sử chúng ta đang viết một
lớp mới có một phương thức đặt tham số duy nhất của nó vào một tập hợp. Chúng ta có nên gọi không
phương pháp này thêm ? Nó có vẻ nhất quán vì chúng tôi có rất nhiều phương pháp bổ sung khác,
nhưng trong trường hợp này, ngữ nghĩa khác nhau, vì vậy chúng ta nên sử dụng tên như insert hoặc append
thay thế. Để gọi phương thức mới, add sẽ là một cách chơi chữ.
Với tư cách là tác giả, mục tiêu của chúng tôi là làm cho mã của chúng tôi dễ hiểu nhất có thể. Chúng tôi muốn
mã của chúng tôi để đọc lướt nhanh, không phải là một nghiên cứu căng thẳng. Chúng tôi muốn sử dụng bìa mềm
phổ biến
mô hình theo đó tác giả chịu trách nhiệm làm cho mình rõ ràng chứ không phải học thuật
mô hình trong đó công việc của học giả là đào sâu ý nghĩa ra khỏi trang giấy.
Sử dụng tên miền giải pháp
Hãy nhớ rằng những người đọc mã của bạn sẽ là lập trình viên. Vì vậy, hãy tiếp tục và sử dụng
thuật ngữ khoa học máy tính (CS), tên thuật toán, tên mẫu, thuật ngữ toán học, v.v. Nó
không khôn ngoan khi rút mọi tên khỏi miền có vấn đề bởi vì chúng tôi không muốn
đồng nghiệp phải chạy đi chạy lại để hỏi khách hàng rằng mọi cái tên có nghĩa gì
khi họ đã biết khái niệm bằng một tên khác.
Tên AccountVisitor có ý nghĩa rất lớn đối với một lập trình viên quen thuộc với
mẫu V ISITOR . Lập trình viên nào không biết JobQueue là gì? Có
rất nhiều thứ rất kỹ thuật mà các lập trình viên phải làm. Chọn tên kỹ thuật cho
những thứ đó thường là khóa học thích hợp nhất.
Sử dụng tên miền có vấn đề
Khi không có "lập trình viên-eese" cho những gì bạn đang làm, hãy sử dụng tên từ prob-
miền lem. Ít nhất thì lập trình viên duy trì mã của bạn có thể hỏi một chuyên gia tên miền
ý nghĩa của nó.
Việc tách các khái niệm miền giải pháp và miền vấn đề là một phần công việc của một chuyên gia giỏi
máy xay và thiết kế. Mã liên quan nhiều hơn đến khái niệm miền có vấn đề
nên có tên được rút ra từ miền vấn đề.
Thêm ngữ cảnh có ý nghĩa
Có một vài cái tên có ý nghĩa về bản thân nó — hầu hết đều không. Thay thế,
bạn cần đặt tên theo ngữ cảnh cho người đọc của bạn bằng cách đặt chúng bằng tên tốt
các lớp, hàm hoặc không gian tên. Khi tất cả những cách khác không thành công, thì tiền tố tên có thể được phân
loại-
chính yếu như một phương sách cuối cùng.
www.it-ebooks.info

Trang 59
28
Chương 2: Những cái tên có ý nghĩa
Hãy tưởng tượng rằng bạn có các biến có tên firstName , lastName , street , houseNumber , city ,
trạng thái và mã vùng . Kết hợp với nhau, khá rõ ràng rằng chúng tạo thành một địa chỉ. Nhưng nếu
bạn vừa thấy biến trạng thái được sử dụng một mình trong một phương thức? Bạn có tự động không
suy ra rằng nó là một phần của địa chỉ?
Bạn có thể thêm ngữ cảnh bằng cách sử dụng các tiền tố: addrFirstName , addrLastName , addrState , v.v.
trên. Ít nhất người đọc sẽ hiểu rằng các biến này là một phần của cấu trúc lớn hơn. Của
tất nhiên, một giải pháp tốt hơn là tạo một lớp có tên Địa chỉ . Sau đó, ngay cả trình biên dịch cũng biết
rằng các biến thuộc về một khái niệm lớn hơn.
Hãy xem xét phương pháp trong Liệt kê 2-1. Các biến có cần một hàm ý nghĩa hơn không
bản văn? Tên hàm chỉ cung cấp một phần của ngữ cảnh; thuật toán cung cấp phần còn lại.
Khi bạn đọc qua hàm, bạn sẽ thấy rằng ba biến, số , động từ và
pluralModifier , là một phần của thông báo "đoán số liệu thống kê". Thật không may, bối cảnh phải
được suy ra. Khi bạn lần đầu tiên nhìn vào phương pháp này, ý nghĩa của các biến là không rõ ràng.
Hàm hơi dài và các biến được sử dụng xuyên suốt. Để phân chia func-
chia thành các phần nhỏ hơn, chúng tôi cần tạo một lớp GuessSt StatisticsMessage và tạo
ba trường biến của lớp này. Điều này cung cấp một bối cảnh rõ ràng cho ba biến. Họ
là dứt khoát một phần của GuessStatisticsMessage . Việc cải thiện ngữ cảnh cũng cho phép
thuật toán trở nên gọn gàng hơn nhiều bằng cách chia nhỏ nó thành nhiều hàm nhỏ hơn. (Xem
Liệt kê 2-2.)
Liệt kê 2-1
Các biến có ngữ cảnh không rõ ràng.
private void printGuessSt Statistics (char application, int count) {
Số chuỗi;
Động từ chuỗi;
String pluralModifier;
if (count == 0) {
số = "không";
verb = "are";
pluralModifier = "s";
} else if (count == 1) {
số = "1";
động từ = "là";
pluralModifier = "";
} khác {
number = Integer.toString (đếm);
verb = "are";
pluralModifier = "s";
}
Chuỗi đoánMessage = String.format (
"Có% s% s% s% s", động từ, số, ứng cử viên, số nhiều
);
in (đoánMessage);
}
www.it-ebooks.info

Trang 60
29
Không thêm ngữ cảnh vô cớ
Không thêm ngữ cảnh vô cớ
Trong một ứng dụng tưởng tượng có tên “Gas Station Deluxe”, việc đặt tiền tố cho mỗi
lớp với GSD . Thành thật mà nói, bạn đang làm việc chống lại các công cụ của bạn. Bạn gõ G và nhấn phím com-
khóa khẩn cấp và được thưởng bằng một danh sách dài hàng dặm về mọi hạng trong hệ thống. Đó là
khôn ngoan? Tại sao IDE khó giúp bạn?
Tương tự như vậy, giả sử bạn đã phát minh ra lớp MailingAddress trong mô-đun kế toán của GSD và
bạn đặt tên nó là GSDAccountAddress . Sau đó, bạn cần một địa chỉ gửi thư cho khách hàng của bạn-
ứng dụng nguyên vẹn. Bạn có sử dụng GSDAccountAddress không? Nghe có vẻ giống với cái tên đúng không? Mười
trong số
17 ký tự là thừa hoặc không liên quan.
Liệt kê 2-2
Các biến có ngữ cảnh.
public class GuessSt StatisticsMessage {
số chuỗi tư nhân;
động từ chuỗi riêng;
private String pluralModifier;
public String make (char application, int count) {
createPluralDependentMessageParts (đếm);
return String.format (
"Có% s% s% s% s",
động từ, số, ứng cử viên, số nhiềuModifier);
}
private void createPluralDependentMessageParts (int count) {
if (count == 0) {
thereAreNoLetters ();
} else if (count == 1) {
thereIsOneLetter ();
} khác {
thereAreManyLetters (số lượng);
}
}
private void thereAreManyLetters (int count) {
number = Integer.toString (đếm);
verb = "are";
pluralModifier = "s";
}
private void thereIsOneLetter () {
số = "1";
động từ = "là";
pluralModifier = "";
}
private void thereAreNoLetters () {
số = "không";
verb = "are";
pluralModifier = "s";
}
}
www.it-ebooks.info

Trang 61
30
Chương 2: Những cái tên có ý nghĩa
Tên ngắn hơn thường tốt hơn tên dài hơn, miễn là chúng rõ ràng. Thêm không
nhiều ngữ cảnh hơn cho một cái tên là cần thiết.
Tên accountAddress và customerAddress là những tên phù hợp cho các trường hợp của
Địa chỉ lớp nhưng có thể là tên nghèo cho các lớp. Địa chỉ là một cái tên tốt cho một lớp học. Nếu tôi
cần phân biệt giữa địa chỉ MAC, địa chỉ cổng và địa chỉ Web, tôi có thể
hãy xem xét địa chỉ bưu điện , MAC và URI . Các tên kết quả chính xác hơn, đó là
điểm của tất cả các đặt tên.
Từ cuối cùng
Điều khó nhất khi chọn những cái tên hay là nó đòi hỏi kỹ năng mô tả tốt và
một nền tảng văn hóa chung. Đây là một vấn đề giảng dạy chứ không phải là một vấn đề kỹ thuật, kinh doanh hoặc
vấn đề quản lý. Kết quả là nhiều người trong lĩnh vực này không học cách làm việc đó rất tốt.
Mọi người cũng sợ đổi tên mọi thứ vì sợ rằng một số nhà phát triển khác sẽ
vật. Chúng tôi không chia sẻ nỗi sợ hãi đó và thấy rằng chúng tôi thực sự biết ơn khi tên thay đổi
(Để tốt hơn). Hầu hết thời gian chúng ta không thực sự ghi nhớ tên của các lớp học và meth-
ods. Chúng tôi sử dụng các công cụ hiện đại để xử lý các chi tiết như vậy để chúng tôi có thể tập trung vào việc
mã đọc giống như đoạn văn và câu, hoặc ít nhất giống như bảng và cấu trúc dữ liệu (một sen-
tence không phải lúc nào cũng là cách tốt nhất để hiển thị dữ liệu). Bạn có thể sẽ khiến một số người ngạc nhiên-
một khi bạn đổi tên, giống như bạn có thể làm với bất kỳ cải tiến mã nào khác. Đừng để nó
ngăn cản bạn trong đường đi của bạn.
Thực hiện theo một số quy tắc này và xem liệu bạn có không cải thiện khả năng đọc của
mã. Nếu bạn đang duy trì mã của người khác, hãy sử dụng các công cụ tái cấu trúc để giúp giải quyết những
các vấn đề. Nó sẽ trả hết trong ngắn hạn và tiếp tục trả về lâu dài.
www.it-ebooks.info

Trang 62
31

3
Chức năng
Trong những ngày đầu lập trình, chúng tôi đã soạn ra các hệ thống quy trình và quy trình con.
Sau đó, trong kỷ nguyên Fortran và PL / 1, chúng tôi đã soạn các hệ thống chương trình, chương trình con,
và các chức năng. Ngày nay chỉ có chức năng tồn tại từ những ngày đầu tiên. Các chức năng là
tuyến đầu tiên của tổ chức trong bất kỳ chương trình nào. Viết chúng tốt là chủ đề của chương này.
www.it-ebooks.info

Trang 63
32
Chương 3: Chức năng
Hãy xem xét mã trong Liệt kê 3-1. Thật khó để tìm thấy một hàm dài trong FitNesse, 1 nhưng
sau một chút tìm kiếm tôi đã bắt gặp cái này. Nó không chỉ dài mà còn bị trùng lặp
mã, rất nhiều chuỗi lẻ và nhiều kiểu dữ liệu và API lạ và không rõ ràng. Xem cách
bạn có thể hiểu được nó trong ba phút tới.
1. Một công cụ kiểm tra mã nguồn mở. www.fitnese.org
Liệt kê 3-1
HtmlUtil.java (FitNesse 20070619)
public static String testableHtml (
PageData pageData,
boolean includeSuiteSetup
) ném Ngoại lệ {
WikiPage wikiPage = pageData.getWikiPage ();
Bộ đệm StringBuffer = new StringBuffer ();
if (pageData.hasAttribute ("Kiểm tra")) {
if (includeSuiteSetup) {
WikiPage suiteSetup =
PageCrawlerImpl.getInheritedPage (
SuiteResponder.SUITE_SETUP_NAME, wikiPage
);
if (suiteSetup! = null) {
WikiPagePath pagePath =
suiteSetup.getPageCrawler (). getFullPath (suiteSetup);
String pagePathName = PathParser.render (pagePath);
buffer.append ("! include -setup.")
.append (pagePathName)
.append ("\ n");
}
}
Thiết lập WikiPage =
PageCrawlerImpl.getInheritedPage ("SetUp", wikiPage);
if (setup! = null) {
WikiPagePath setupPath =
wikiPage.getPageCrawler (). getFullPath (thiết lập);
String setupPathName = PathParser.render (setupPath);
buffer.append ("! include -setup.")
.append (setupPathName)
.append ("\ n");
}
}
buffer.append (pageData.getContent ());
if (pageData.hasAttribute ("Kiểm tra")) {
WikiPage teardown =
PageCrawlerImpl.getInheritedPage ("TearDown", wikiPage);
if (teardown! = null) {
WikiPagePath dropsDownPath =
wikiPage.getPageCrawler (). getFullPath (teardown);
String eyeDownPathName = PathParser.render (ráchDownPath);
buffer.append ("\ n")
.append ("! bao gồm -teardown.")
.append (ráchDownPathName)
.append ("\ n");
}
www.it-ebooks.info

Trang 64
33
Chức năng
Bạn có hiểu chức năng sau ba phút học không? Chắc là không. Có
quá nhiều diễn ra trong đó với quá nhiều mức độ trừu tượng khác nhau. Có lạ
chuỗi và các lệnh gọi hàm lẻ được trộn lẫn với các câu lệnh if lồng nhau kép được điều khiển bởi
cờ.
Tuy nhiên, chỉ với một số phương pháp đơn giản chiết xuất, một số đổi tên, và một chút
tái cấu trúc, tôi đã có thể nắm bắt được mục đích của hàm trong chín dòng của Liệt kê 3-2.
Xem liệu bạn có thể hiểu điều đó trong 3 phút tới hay không.
if (includeSuiteSetup) {
WikiPage suiteTeardown =
PageCrawlerImpl.getInheritedPage (
SuiteResponder.SUITE_TEARDOWN_NAME,
wikiPage
);
if (suiteTeardown! = null) {
WikiPagePath pagePath =
suiteTeardown.getPageCrawler (). getFullPath (suiteTeardown);
String pagePathName = PathParser.render (pagePath);
buffer.append ("! include -teardown.")
.append (pagePathName)
.append ("\ n");
}
}
}
pageData.setContent (buffer.toString ());
trả về trangData.getHtml ();
}
Liệt kê 3-2
HtmlUtil.java (đã cấu trúc lại)
public static String renderPageWithSetupsAndTeardown (
PageData pageData, boolean isSuite
) ném Ngoại lệ {
boolean isTestPage = pageData.hasAttribute ("Kiểm tra");
if (isTestPage) {
WikiPage testPage = pageData.getWikiPage ();
StringBuffer newPageContent = new StringBuffer ();
includeSetupPages (testPage, newPageContent, isSuite);
newPageContent.append (pageData.getContent ());
includeTeardownPages (testPage, newPageContent, isSuite);
pageData.setContent (newPageContent.toString ());
}
trả về trangData.getHtml ();
}
Liệt kê 3-1 (tiếp theo)
HtmlUtil.java (FitNesse 20070619)
www.it-ebooks.info

Trang 65
34
Chương 3: Chức năng
Trừ khi bạn là sinh viên của FitNesse, nếu không bạn có thể không hiểu tất cả các chi tiết.
Tuy nhiên, bạn có thể hiểu rằng chức năng này thực hiện việc bao gồm một số thiết lập và
xé nhỏ các trang thành một trang thử nghiệm và sau đó hiển thị trang đó thành HTML. Nếu bạn quen
với JUnit, 2 bạn có thể nhận ra rằng chức năng này thuộc về một số loại dựa trên Web
khung thử nghiệm. Và, tất nhiên, điều đó là chính xác. Phân tích thông tin đó từ Liệt kê 3-2
khá dễ dàng, nhưng nó khá bị che khuất bởi Liệt kê 3-1.
Vậy điều gì làm cho một hàm như Liệt kê 3-2 dễ đọc và dễ hiểu? Làm sao
chúng ta có thể làm cho một hàm truyền đạt ý định của nó không? Chúng ta có thể cung cấp các thuộc tính nào cho
các chức năng của mình
điều đó sẽ cho phép một người đọc bình thường xâm nhập vào loại chương trình mà họ sống bên trong?
Nhỏ!
Quy tắc đầu tiên của các chức năng là chúng phải nhỏ. Quy tắc thứ hai của chức năng là
chúng phải nhỏ hơn thế . Đây không phải là một khẳng định mà tôi có thể biện minh. Tôi không thể cung cấp
bất kỳ tham chiếu nào đến nghiên cứu cho thấy rằng các chức năng rất nhỏ sẽ tốt hơn. Những gì tôi có thể nói
bạn là trong gần bốn thập kỷ, tôi đã viết các hàm thuộc mọi kích cỡ khác nhau. Tôi đã viết-
mười vài câu kinh tởm 3.000 dòng khó chịu. Tôi đã viết nhiều hàm trong 100 đến 300
phạm vi dòng. Và tôi đã viết các hàm dài từ 20 đến 30 dòng. Kinh nghiệm này là gì
đã dạy tôi, qua quá trình thử nghiệm lâu dài, rằng các hàm phải rất nhỏ.
Vào những năm tám mươi, chúng ta thường nói rằng một chức năng không được lớn hơn toàn màn hình.
Tất nhiên, chúng tôi đã nói rằng tại thời điểm khi màn hình VT100 có 24 dòng x 80 cột, và
các biên tập viên của chúng tôi đã sử dụng 4 dòng cho mục đích quản trị. Ngày nay với một phông chữ quay xuống
và một màn hình lớn đẹp, bạn có thể vừa với 150 ký tự trên một dòng và 100 dòng trở lên trên
màn. Các dòng không được dài 150 ký tự. Các hàm không được dài 100 dòng.
Các hàm hiếm khi dài 20 dòng.
Hàm phải ngắn đến mức nào? Năm 1999, tôi đến thăm Kent Beck tại nhà của anh ấy ở Ore-
gon. Chúng tôi đã ngồi xuống và lập trình cùng nhau. Có lúc anh ấy cho tôi thấy một
chương trình Java / Swing nhỏ mà ông gọi là Sparkle . Nó tạo ra một hiệu ứng hình ảnh trên màn hình
rất giống với cây đũa thần của bà tiên đỡ đầu trong phim Cô bé lọ lem. Như bạn
di chuyển chuột, những tia sáng lấp lánh sẽ nhỏ giọt từ con trỏ với một cái nhìn thỏa mãn,
rơi xuống dưới cùng của cửa sổ thông qua một trường hấp dẫn mô phỏng. Khi Kent
cho tôi xem mã, tôi bị ấn tượng bởi tất cả các chức năng nhỏ như thế nào. Tôi đã quen với func-
tions trong các chương trình Swing rằng chiếm dặm không gian thẳng đứng. Mọi chức năng trong chương trình này-
gam chỉ dài hai, ba hoặc bốn dòng. Mỗi thứ đều rõ ràng. Từng kể
một câu chuyện. Và mỗi thứ dẫn bạn đến phần tiếp theo theo một thứ tự hấp dẫn. Đó là cách các chức năng của bạn
ngắn
nên là! 3
2. Một công cụ kiểm tra đơn vị mã nguồn mở cho Java. www.junit.org
3. Tôi hỏi Kent liệu anh ấy có còn một bản sao không, nhưng anh ấy không thể tìm thấy. Tôi cũng đã tìm kiếm tất cả các máy tính cũ của mình, nhưng vô ích.
Tất cả những gì còn lại bây giờ là ký ức của tôi về chương trình đó.
www.it-ebooks.info

Trang 66
35
Làm một việc
Chức năng của bạn nên ngắn đến mức nào? Chúng thường phải ngắn hơn Liệt kê 3-2!
Thật vậy, Liệt kê 3-2 thực sự nên được rút ngắn thành Liệt kê 3-3.
Chặn và thụt lề
Điều này ngụ ý rằng các khối trong vòng nếu báo cáo, khác phát biểu, trong khi báo cáo, và
như vậy phải dài một dòng. Có lẽ dòng đó phải là một cuộc gọi hàm. Không chỉ
điều này giữ cho chức năng bao quanh nhỏ, nhưng nó cũng thêm giá trị tài liệu vì
hàm được gọi trong khối có thể có một tên mô tả độc đáo.
Điều này cũng ngụ ý rằng các hàm không nên đủ lớn để chứa các cấu trúc lồng nhau.
Do đó, mức thụt lề của một hàm không được lớn hơn một hoặc hai. Cái này, của
làm cho các chức năng dễ đọc và dễ hiểu hơn.
Làm một việc
Cần phải nói rõ rằng Liệt kê 3-1 đang có rất nhiều
nhiều hơn một điều. Nó tạo bộ đệm, tìm nạp
trang, tìm kiếm các trang kế thừa, đường dẫn hiển thị,
nối các chuỗi phức tạp và tạo HTML,
Trong số những thứ khác. Liệt kê 3-1 rất bận rộn làm
rất nhiều thứ khác nhau. Mặt khác, Liệt kê 3-3
đang làm một điều đơn giản. Nó bao gồm các thiết lập và
giọt nước mắt vào các trang thử nghiệm.
Lời khuyên sau đây đã xuất hiện ở một dạng
hoặc khác từ 30 năm trở lên.
CÁC CÔNG ĐOÀN F  NÊN LÀM MỘT ĐIỀU . T  ÔNG NÊN LÀM ĐÓ  .
T  HÃY CHỈ NÊN LÀM .
Vấn đề với tuyên bố này là khó có thể biết “một điều” là gì. Làm
Liệt kê 3-3 làm một điều? Thật dễ dàng để hiểu rằng nó đang làm ba điều:
1. Xác định xem trang có phải là trang thử nghiệm hay không.
2. Nếu vậy, bao gồm cả thiết lập và giọt nước mắt.
3. Kết xuất trang bằng HTML.
Liệt kê 3-3
HtmlUtil.java (đã cấu trúc lại)
public static String renderPageWithSetupsAndTeardown (
PageData pageData, boolean isSuite) ném Exception {
if (isTestPage (pageData))
includeSetupAndTeardownPages (pageData, isSuite);
trả về trangData.getHtml ();
}
www.it-ebooks.info

Trang 67
36
Chương 3: Chức năng
Vậy nó là gì? Hàm đang làm một việc hay ba việc? Lưu ý rằng ba
các bước của hàm là một mức trừu tượng bên dưới tên đã nêu của hàm. Chúng tôi
có thể mô tả chức năng bằng cách mô tả nó dưới dạng một đoạn văn TO  4 ngắn gọn :
ĐỂ RenderPageWithSetupsAndTeardown, chúng tôi kiểm tra xem trang đó có phải là trang thử nghiệm hay không
và nếu vậy, chúng tôi bao gồm các thiết lập và giọt nước mắt. Trong cả hai trường hợp, chúng tôi hiển thị trang trong
HTML.
Nếu một hàm chỉ thực hiện các bước đó thấp hơn một cấp so với tên đã nêu của
thì hàm đang làm một việc. Rốt cuộc, lý do chúng tôi viết các hàm là để
phân rã một khái niệm lớn hơn (nói cách khác, tên của hàm) thành một tập hợp các bước tại
cấp độ trừu tượng tiếp theo.
Cần rất rõ ràng rằng Liệt kê 3-1 chứa các bước ở nhiều cấp độ khác nhau của
sự trừu tượng. Vì vậy, nó rõ ràng đang làm nhiều hơn một điều. Ngay cả Liệt kê 3-2 cũng có hai cấp độ
sự trừu tượng, như đã được chứng minh bằng khả năng thu nhỏ nó lại. Nhưng nó sẽ rất khó để có nghĩa là-
vô tình thu nhỏ Liệt kê 3-3. Chúng ta có thể trích xuất các nếu tuyên bố vào một hàm có tên
includeSetupsAndTeardownIfTestPage , nhưng điều đó chỉ đơn giản là đặt lại mã mà không thay đổi
mức độ trừu tượng.
Vì vậy, một cách khác để biết rằng một hàm đang làm nhiều hơn "một việc" là nếu bạn có thể
trích xuất một chức năng khác từ nó với một cái tên không chỉ đơn thuần là sự tái hiện của nó-
sự cố vấn [G34].
Các phần trong Hàm
Nhìn vào Liệt kê 4-7 trên trang 71. Chú ý rằng GeneratePrimes chức năng được chia thành
các phần như khai báo , khởi tạo và sàng lọc . Đây là một triệu chứng rõ ràng của
làm nhiều hơn một việc. Các chức năng làm một việc không thể được phân chia hợp lý thành
các phần.
Một cấp độ trừu tượng cho mỗi hàm
Để đảm bảo các chức năng của chúng ta đang thực hiện “một việc”, chúng ta cần đảm bảo rằng
các câu lệnh trong hàm của chúng ta đều ở cùng một mức trừu tượng. Thật dễ dàng để xem làm thế nào
Liệt kê 3-1 vi phạm quy tắc này. Có những khái niệm trong đó ở cấp độ rất cao
trừu tượng, chẳng hạn như getHtml () ; những cái khác ở mức trừu tượng trung gian, chẳng hạn như
như: String pagePathName = PathParser.render (pagePath)  ; và vẫn còn những người khác được nhận xét-
mức thấp, chẳng hạn như: .append ("\ n") .
Việc trộn các mức độ trừu tượng trong một hàm luôn gây nhầm lẫn. Người đọc có thể không
có thể biết liệu một biểu thức cụ thể là một khái niệm thiết yếu hay một chi tiết. Tệ hơn,
4. Ngôn ngữ LOGO đã sử dụng từ khóa “TO” giống như cách mà Ruby và Python sử dụng “def.” Vì vậy, mọi chức năng bắt đầu với
từ "TO". Điều này có một ảnh hưởng thú vị đến cách các chức năng được thiết kế.
www.it-ebooks.info

Trang 68
37
Chuyển đổi câu lệnh
như cửa sổ bị vỡ, một khi các chi tiết bị trộn lẫn với các khái niệm cần thiết, ngày càng nhiều
các chi tiết có xu hướng tích tụ trong chức năng.
Đọc mã từ trên xuống dưới: Quy tắc bước xuống
Chúng tôi muốn mã đọc giống như một bản tường thuật từ trên xuống. 5 Chúng tôi muốn mọi chức năng trở thành
fol-
được hạ thấp bởi những người ở cấp độ trừu tượng tiếp theo để chúng ta có thể đọc chương trình, giảm dần
một mức trừu tượng tại một thời điểm khi chúng ta đọc xuống danh sách các hàm. Tôi gọi đây là Bước-
quy tắc xuống .
Nói cách khác, chúng tôi muốn có thể đọc chương trình như thể nó là một tập hợp
trong các đoạn văn TO , mỗi đoạn trong số đó mô tả mức độ trừu tượng hiện tại và tham chiếu-
mã hóa các đoạn TO tiếp theo ở cấp độ tiếp theo xuống.
Để bao gồm các thiết lập và chia nhỏ, chúng tôi bao gồm các thiết lập, sau đó chúng tôi đưa vào trang thử nghiệm-
lều, và sau đó chúng tôi bao gồm những giọt nước mắt.
Để bao gồm các thiết lập, chúng tôi bao gồm thiết lập bộ nếu đây là một bộ, sau đó chúng tôi bao gồm
thiết lập thường xuyên.
Để bao gồm thiết lập bộ, chúng tôi tìm kiếm phân cấp chính cho trang “SuiteSetUp”
và thêm câu lệnh bao gồm đường dẫn của trang đó.
Để tìm kiếm phụ huynh.  .  .
Hóa ra là rất khó cho các lập trình viên học cách tuân theo quy tắc này và viết
các hàm duy nhất ở mức trừu tượng. Nhưng học thủ thuật này cũng rất
quan trọng. Đó là chìa khóa để giữ cho các chức năng ngắn gọn và đảm bảo chúng thực hiện “một việc”.
Làm cho mã được đọc giống như một tập hợp các đoạn TO từ trên xuống là một kỹ thuật hiệu quả để
giữ mức trừu tượng nhất quán.
Hãy xem Liệt kê 3-7 ở cuối chương này. Nó cho thấy toàn bộ
Hàm testableHtml được cấu trúc lại theo các nguyên tắc được mô tả ở đây. Để ý
cách mỗi chức năng giới thiệu chức năng tiếp theo và mỗi chức năng vẫn ở mức nhất quán
của sự trừu tượng.
Chuyển đổi câu lệnh
Thật khó để đưa ra một tuyên bố chuyển đổi nhỏ . 6 Ngay cả một câu lệnh switch chỉ có hai trường hợp là
lớn hơn tôi muốn một khối hoặc một chức năng. Thật khó để thực hiện chuyển đổi trạng thái-
đề cập rằng làm một điều. Về bản chất, câu lệnh switch luôn thực hiện N việc. Không may-
tự nhiên, chúng ta không thể luôn tránh các câu lệnh chuyển đổi , nhưng chúng ta có thể đảm bảo rằng mỗi lần chuyển
đổi
câu lệnh được chôn trong một lớp cấp thấp và không bao giờ được lặp lại. Tất nhiên, chúng tôi làm điều này với
tính đa hình.
5. [KP78], tr. 37.
6. Và, tất nhiên, tôi bao gồm chuỗi if / else trong này.
www.it-ebooks.info

Trang 69
38
Chương 3: Chức năng
Xem xét Liệt kê 3-4. Nó chỉ hiển thị một trong các hoạt động có thể phụ thuộc vào
loại nhân viên.
Có một số vấn đề với chức năng này. Đầu tiên, nó lớn và khi mới
các loại nhân viên được thêm vào, nó sẽ phát triển. Thứ hai, nó rất rõ ràng làm được nhiều hơn một điều.
Thứ ba, nó vi phạm Nguyên tắc Trách nhiệm Đơn lẻ 7 (SRP) vì có nhiều hơn một
lý do để nó thay đổi. Thứ tư, nó vi phạm Nguyên tắc Đóng mở 8 (OCP) vì nó
phải thay đổi bất cứ khi nào các loại mới được thêm vào. Nhưng có thể vấn đề tồi tệ nhất với điều này
chức năng là có số lượng không giới hạn các chức năng khác sẽ có cùng
kết cấu. Ví dụ, chúng tôi có thể có
isPayday (Nhân viên e, Ngày tháng),
hoặc là
deliveryPay (Nhân viên e, Trả tiền),
hoặc một loạt những người khác. Tất cả chúng sẽ có cùng một cấu trúc có hại.
Giải pháp cho vấn đề này (xem Liệt kê 3-5) là chôn câu lệnh switch trong
tầng hầm của một A BSTRACT F actory , 9 và không bao giờ để bất cứ ai nhìn thấy nó. Nhà máy sẽ sử dụng
câu lệnh switch để tạo các bản sao thích hợp của các dẫn xuất của Employee và var-
chức năng ious, chẳng hạn như calculatePay , isPayday , và deliverPay , sẽ được cử poly-
biến hình thông qua giao diện Nhân viên .
Quy tắc chung của tôi cho các câu lệnh switch là chúng có thể được chấp nhận nếu chúng xuất hiện
chỉ một lần, được sử dụng để tạo các đối tượng đa hình và được ẩn sau một kế thừa
Liệt kê 3-4
Payroll.java
Tiền công tính Trả tiền (Nhân viên e)
ném không hợp lệ
chuyển đổi (e.type) {
trường hợp ĐƯỢC GIAO:
trả về tính toánCommissonedPay (e);
trường hợp HÀNG GIỜ:
trả về tính toánHourlyPay (e);
trường hợp SALARIED:
trả về tính toánSalariedPay (e);
mặc định:
ném ra InvalidErantyeeType mới (e.type);
}
}
7. a. http://en.wikipedia.org/wiki/Single_responsibility_principl e
b. http://www.objectmentor.com/resources/articles/srp.pdf
8. a. http://en.wikipedia.org/wiki/Open/closed_principle
b. http://www.objectmentor.com/resources/articles/ocp.pdf
9. [GOF].
www.it-ebooks.info

Trang 70
39
Sử dụng tên mô tả
để phần còn lại của hệ thống không thể nhìn thấy chúng [G23]. Tất nhiên mọi vòng
lập trường là duy nhất, và đôi khi tôi vi phạm một hoặc nhiều phần của quy tắc đó.
Sử dụng tên mô tả
Trong Liệt kê 3-7, tôi đã thay đổi tên của hàm mẫu của chúng ta từ testableHtml thành
SetupTeardownIncluder.render . Đây là một cái tên hay hơn nhiều vì nó mô tả tốt hơn những gì
chức năng không. Tôi cũng đặt cho mỗi phương thức riêng một cái tên mô tả như nhau
như isTestable hoặc includeSetupAndTeardownPages . Thật khó để đánh giá quá cao giá trị
của những cái tên hay. Hãy nhớ nguyên tắc của Ward: " Bạn biết bạn đang làm việc trên mã sạch
khi mỗi thói quen trở nên giống như những gì bạn mong đợi. ”Một nửa trận chiến tới
đạt được nguyên tắc đó là chọn những cái tên hay cho các chức năng nhỏ làm một việc.
Chức năng càng nhỏ và càng tập trung thì việc chọn mô tả càng dễ dàng
Tên.
Đừng ngại làm cho một tên tuổi dài. Tên mô tả dài sẽ tốt hơn tên ngắn
tên bí ẩn. Một tên mô tả dài sẽ tốt hơn một nhận xét mô tả dài. Sử dụng
quy ước đặt tên cho phép dễ dàng đọc nhiều từ trong tên hàm,
và sau đó sử dụng nhiều từ đó để đặt tên cho hàm nói lên điều gì
nó có.
Liệt kê 3-5
Nhân viên và Nhà máy
lớp trừu tượng công khai Nhân viên {
công khai trừu tượng boolean isPayday ();
công khai tóm tắt Tiền tính toánPay ();
public abstract void deliveryPay (Trả tiền);
}
-----------------
giao diện công khai EmployeeFactory {
public Employee makeE Employee (EmployeeRecord r) ném ra InvalidErantyeeType;
}
-----------------
public class EmployeeFactoryImpl triển khai EmployeeFactory {
public Employee makeE Employee (EmployeeRecord r) ném ra InvalidErantyeeType {
switch (r.type) {
trường hợp ĐƯỢC GIAO:
trả lại CommissonedEprisee mới (r);
trường hợp HÀNG GIỜ:
trả về HourlyE Employee mới (r);
trường hợp SALARIED:
trả về mới SalariedEmpletee (r);
mặc định:
ném ra InvalidEFasteeType mới (r.type);
}
}
}
www.it-ebooks.info

Trang 71
40
Chương 3: Chức năng
Đừng ngại tốn thời gian chọn tên. Thật vậy, bạn nên thử một số khác-
tên ent và đọc mã với mỗi tên tại chỗ. Các IDE hiện đại như Eclipse hoặc IntelliJ tạo ra
việc thay đổi tên là điều tầm thường. Sử dụng một trong các IDE đó và thử nghiệm với các tên khác
cho đến khi bạn tìm thấy một mô tả đúng như khả năng của bạn.
Chọn tên mô tả sẽ làm rõ thiết kế của mô-đun trong tâm trí bạn và
giúp bạn cải thiện nó. Việc săn lùng một cái tên hay dẫn đến kết quả là
sự tái cấu trúc thuận lợi của mã.
Hãy nhất quán trong tên của bạn. Sử dụng các cụm từ, danh từ và động từ giống nhau trong hàm
tên bạn chọn cho mô-đun của bạn. Ví dụ: hãy xem xét các tên bao gồmSetup-
AndTeardownPages , includeSetupPages , includeSuiteSetupPage , và includeSetupPage . Các
những cụm từ tương tự trong những cái tên đó cho phép trình tự kể một câu chuyện. Thật vậy, nếu tôi
chỉ cho bạn thấy trình tự ở trên, bạn sẽ tự hỏi mình: "Điều gì đã xảy ra với
includeTeardownPages , includeSuiteTeardownPage và includeTeardownPage ? ” Thế nào rồi
vì “. . . khá nhiều những gì bạn mong đợi . ”
Đối số hàm
Số đối số lý tưởng cho một hàm là
không (niladic). Tiếp theo là một (đơn nguyên), theo sau
gần bằng hai (dyadic). Ba đối số (bộ ba)
nên tránh nếu có thể. Hơn ba
(polyadic) yêu cầu sự biện minh rất đặc biệt — và
sau đó không nên được sử dụng anyway.
Lập luận thật khó. Họ mất rất nhiều ...
năng lượng tinh thể. Đó là lý do tại sao tôi đã loại bỏ gần như tất cả
chúng từ ví dụ. Ví dụ, hãy xem xét
StringBuffer trong ví dụ. Chúng ta có thể có
thông qua nó như một cuộc tranh cãi hơn là mak-
nhập vào nó một biến phiên bản, nhưng sau đó độc giả của chúng tôi
sẽ phải giải thích nó mỗi khi họ nhìn thấy
nó. Khi bạn đang đọc câu chuyện được kể bởi
module, includeSetupPage () dễ hiểu hơn includeSetupPageInto (newPage-
Nội dung) . Đối số ở cấp độ trừu tượng khác với tên hàm và
buộc bạn phải biết một chi tiết (nói cách khác, StringBuffer ) không đặc biệt quan trọng
tại thời điểm đó.
Lập luận thậm chí còn khó hơn từ quan điểm thử nghiệm. Hãy tưởng tượng khó khăn của
viết tất cả các trường hợp thử nghiệm để đảm bảo rằng tất cả các tổ hợp đối số khác nhau đều hoạt động
đúng cách. Nếu không có đối số, điều này là tầm thường. Nếu có một lập luận, nó không quá khó.
Với hai đối số, vấn đề trở nên khó khăn hơn một chút. Với hơn hai lập luận-
ments, thử nghiệm mọi kết hợp của các giá trị thích hợp có thể khó khăn.
www.it-ebooks.info

Trang 72
41
Đối số hàm
Đối số đầu ra khó hiểu hơn đối số đầu vào. Khi chúng ta đọc một
hàm, chúng ta đã quen với ý tưởng thông tin đi vào hàm thông qua các đối số
và ra ngoài thông qua giá trị trả về. Chúng tôi thường không mong đợi thông tin được đưa ra ngoài
thông qua các lập luận. Vì vậy, các đối số đầu ra thường khiến chúng ta thực hiện phép tính kép.
Một đối số đầu vào là điều tốt nhất tiếp theo để không có đối số. SetupTeardown-
Includeer.render (pageData) khá dễ hiểu. Rõ ràng chúng ta sẽ làm cho các
dữ liệu trong đối tượng pageData .
Các mẫu đơn nguyên chung
Có hai lý do rất phổ biến để truyền một đối số vào một hàm. Bạn có thể
đặt câu hỏi về đối số đó, như trong boolean fileExists (“MyFile”) . Hoặc bạn có thể
hoạt động trên đối số đó, chuyển đổi nó thành một thứ khác và trả lại nó . Đối với
ví dụ, InputStream fileOpen (“MyFile”) biến đổi chuỗi tên tệp thành chuỗi
Giá trị trả về InputStream . Hai cách sử dụng này là những gì người đọc mong đợi khi họ nhìn thấy một func-
sự. Bạn nên chọn những cái tên giúp phân biệt rõ ràng và luôn sử dụng cả hai
hình thành trong một bối cảnh nhất quán. (Xem Tách truy vấn lệnh bên dưới.)
Một dạng hơi ít phổ biến hơn, nhưng vẫn rất hữu ích cho một hàm đối số,
là một sự kiện . Trong biểu mẫu này có một đối số đầu vào nhưng không có đối số đầu ra. Tổng thể
chương trình có nghĩa là giải thích lời gọi hàm như một sự kiện và sử dụng đối số để thay đổi
trạng thái của hệ thống, ví dụ, void passwordAttemptFailedNtimes (int lần thử) . Sử dụng
biểu mẫu này một cách cẩn thận. Người đọc phải rất rõ ràng rằng đây là một sự kiện. Chọn
tên và ngữ cảnh cẩn thận.
Cố gắng tránh bất kỳ hàm đơn nguyên nào không tuân theo các biểu mẫu này, ví dụ: void
includeSetupPageInto (StringBuffer pageText) . Sử dụng một đối số đầu ra thay vì một
giá trị trả về cho một phép biến đổi là khó hiểu. Nếu một hàm sẽ chuyển đổi đầu vào của nó
đối số, phép biến đổi sẽ xuất hiện dưới dạng giá trị trả về. Thật vậy, StringBuffer
biến đổi (StringBuffer in) tốt hơn void biến đổi- (StringBuffer out) , ngay cả khi
việc triển khai trong trường hợp đầu tiên chỉ đơn giản là trả về đối số đầu vào. Ít nhất nó vẫn theo sau
dạng của một phép biến hình.
Cờ đối số
Đối số cờ là xấu. Chuyển một boolean vào một hàm là một việc làm thực sự khủng khiếp. Nó
ngay lập tức làm phức tạp chữ ký của phương pháp, lớn tiếng tuyên bố rằng chức năng này
làm nhiều hơn một điều. Nó thực hiện một việc nếu cờ đúng và một việc khác nếu cờ sai!
Trong Liệt kê 3-7, chúng tôi không có lựa chọn nào khác vì những người gọi đã chuyển cờ đó
trong và tôi muốn giới hạn phạm vi tái cấu trúc đối với hàm và bên dưới. Vẫn là
phương thức gọi render (true) dễ gây nhầm lẫn cho người đọc kém. Di chuyển qua cuộc gọi
và xem kết xuất (boolean isSuite) giúp một chút, nhưng không nhiều. Chúng ta nên có
chia hàm thành hai: renderForSuite () và renderForSingleTest () .
www.it-ebooks.info

Trang 73
42
Chương 3: Chức năng
Chức năng Dyadic
Một hàm có hai đối số khó hiểu hơn một hàm đơn nguyên. Cho kỳ thi-
ple, writeField (tên) dễ hiểu hơn writeField (output-Stream, tên) . 10
Mặc dù ý nghĩa của cả hai đều rõ ràng, nhưng cái đầu tiên lướt qua mắt, dễ dàng lắng đọng
Ý nghĩa. Thứ hai yêu cầu tạm dừng một thời gian ngắn cho đến khi chúng ta học cách bỏ qua tham số đầu tiên.
Và điều đó , tất nhiên, cuối cùng dẫn đến các vấn đề vì chúng ta không bao giờ được bỏ qua
một phần của mã. Những phần chúng tôi bỏ qua là nơi các lỗi sẽ ẩn.
Tất nhiên, có những lúc, hai đối số là thích hợp. Ví dụ,
Point p = new Point (0,0); là hoàn toàn hợp lý. Điểm Descartes đương nhiên mất hai
tranh luận. Thật vậy, chúng tôi sẽ rất ngạc nhiên khi thấy Điểm mới (0) . Tuy nhiên, cả hai cho rằng-
ments trong trường hợp này là các thành phần có thứ tự của một giá trị duy nhất! Trong khi đầu ra-Luồng và
tên không có sự gắn kết tự nhiên, cũng không có thứ tự tự nhiên.
Ngay cả các hàm dyadic rõ ràng như khẳng định Equals (dự kiến, thực tế) cũng có vấn đề.
Đã bao nhiêu lần bạn đặt thực tế ở vị trí mong đợi ? Hai người tranh luận-
ments không có thứ tự tự nhiên. Thứ tự dự kiến, thực tế là một quy ước
yêu cầu thực hành để học hỏi.
Dyads không xấu xa, và bạn chắc chắn sẽ phải viết chúng. Tuy nhiên, bạn nên
nhận thức được rằng chúng phải trả giá và nên tận dụng những gì mà thợ máy có thể
có sẵn cho bạn để chuyển đổi chúng thành monads. Ví dụ: bạn có thể làm cho
writeField phương thức một thành viên của outputStream để bạn có thể nói outputStream.
writeField (tên) . Hoặc bạn có thể đặt outputStream thành một biến thành viên của dòng
lớp để bạn không phải vượt qua nó. Hoặc bạn có thể trích xuất một lớp mới như FieldWriter
lấy outputStream trong hàm tạo của nó và có một phương thức ghi .
Bộ ba
Các hàm sử dụng ba đối số khó hiểu hơn đáng kể so với hàm hàm. Các
các vấn đề về đặt hàng, tạm dừng và bỏ qua tăng hơn gấp đôi. Tôi đề nghị bạn nghĩ rất
cẩn thận trước khi tạo bộ ba.
Ví dụ: hãy xem xét tình trạng quá tải phổ biến của khẳng địnhEquals cần đến ba đối số
ments: khẳng địnhEquals (tin nhắn, dự kiến, thực tế) . Bạn đã đọc bao nhiêu lần
tin nhắn và nghĩ rằng nó đã được mong đợi ? Tôi đã vấp ngã và tạm dừng về điều đó
bộ ba nhiều lần. Trên thực tế, mỗi khi tôi nhìn thấy nó, tôi thực hiện hai lần và sau đó học cách bỏ qua
thông điệp.
Mặt khác, đây là một bộ ba không quá xảo quyệt: khẳng địnhEquals (1,0,
số tiền, .001) . Mặc dù điều này vẫn yêu cầu thực hiện hai lần, nhưng đó là một trong những điều đáng để thực
hiện. Nó là
luôn luôn tốt khi được nhắc nhở rằng sự bình đẳng của các giá trị dấu chấm động là một điều tương đối.
10. Tôi vừa hoàn thành cấu trúc lại một mô-đun sử dụng biểu mẫu dyadic. Tôi đã có thể đặt outputStream thành một trường của lớp và
chuyển đổi tất cả các lệnh gọi writeField sang dạng đơn nguyên. Kết quả là sạch hơn nhiều.
www.it-ebooks.info
Trang 74
43
Đối số hàm
Đối tượng đối số
Khi một hàm dường như cần nhiều hơn hai hoặc ba đối số, có khả năng là một số
những đối số đó phải được gói thành một lớp của riêng chúng. Hãy xem xét, ví dụ,
sự khác biệt giữa hai khai báo sau:
Circle makeCircle (gấp đôi x, gấp đôi y, gấp đôi bán kính);
Circle makeCircle (Tâm điểm, bán kính gấp đôi);
Giảm số lượng đối số bằng cách tạo các đối tượng từ chúng có vẻ như
gian lận, nhưng nó không phải. Khi các nhóm biến được chuyển cùng nhau, cách x và
y trong ví dụ trên, chúng có thể là một phần của khái niệm xứng đáng với tên gọi của nó
sở hữu.
Danh sách đối số
Đôi khi chúng ta muốn truyền một số biến đối số vào một hàm. Xem xét cho
ví dụ, phương thức String.format :
String.format ("% s đã làm việc% .2f giờ.", Tên, giờ);
Nếu tất cả các đối số biến đều được xử lý giống nhau, như trong ví dụ trên, thì
chúng tương đương với một đối số duy nhất của kiểu Danh sách . Theo lý luận đó, String.format là
thực sự là loạn. Thật vậy, khai báo của String.format như hình dưới đây là rõ ràng
loạn luân.
định dạng chuỗi công khai (Định dạng chuỗi, Đối tượng ... args)
Vì vậy, tất cả các quy tắc tương tự được áp dụng. Các hàm nhận đối số thay đổi có thể là đơn nguyên,
dyads, hoặc thậm chí là bộ ba. Nhưng sẽ là sai lầm nếu đưa ra nhiều lý lẽ hơn là
cái đó.
void monad (Số nguyên ... args);
void dyad (Tên chuỗi, Số nguyên ... args);
void triad (String name, int count, Integer ... args);
Động từ và Từ khóa
Chọn những cái tên hay cho một chức năng có thể đi một chặng đường dài để giải thích mục đích của
chức năng và thứ tự và mục đích của các đối số. Trong trường hợp của một đơn nguyên,
chức năng và đối số nên tạo thành một cặp động từ / danh từ rất đẹp. Ví dụ,
viết (tên) là rất gợi. Dù thứ “tên” này là gì, nó đang được “viết”. An
thậm chí tên tốt hơn có thể là writeField (tên) , cho chúng ta biết rằng thứ “tên” là
"cánh đồng."
Cuối cùng này là một ví dụ về dạng từ khóa của một tên hàm. Sử dụng biểu mẫu này, chúng tôi
mã hóa tên của các đối số thành tên hàm. Ví dụ, khẳng định
tốt hơn có thể được viết dưới dạng khẳng định là SwiftEqualsActual (dự kiến, thực tế) . Điều này mạnh mẽ
giảm nhẹ vấn đề phải nhớ thứ tự của các đối số.
www.it-ebooks.info

Trang 75
44
Chương 3: Chức năng
Không có tác dụng phụ
Tác dụng phụ là dối trá. Hàm của bạn hứa hẹn thực hiện một việc, nhưng nó cũng thực hiện các chức năng ẩn khác
nhiều thứ. Đôi khi nó sẽ thực hiện những thay đổi bất ngờ đối với các biến của lớp riêng của nó.
Đôi khi nó sẽ biến chúng thành các tham số được truyền vào hàm hoặc hệ thống
bals. Trong cả hai trường hợp, chúng là những sai lầm quanh co và gây hại thường dẫn đến
khớp nối thời gian và phụ thuộc thứ tự.
Ví dụ, hãy xem xét hàm dường như vô hại trong Liệt kê 3-6. Chức năng này
sử dụng một thuật toán chuẩn để đối sánh userName với mật khẩu . Nó trả về true nếu chúng khớp
và sai nếu có gì sai. Nhưng nó cũng có một tác dụng phụ. Bạn có thể phát hiện ra nó không?
Tất nhiên, tác dụng phụ là lệnh gọi Session.initialize () . Các checkPassword func-
tion, theo tên của nó, nói rằng nó kiểm tra mật khẩu. Tên không ngụ ý rằng nó viết tắt-
izes phiên. Vì vậy, một người gọi tin vào những gì tên của hàm nói sẽ có nguy cơ
xóa dữ liệu phiên hiện có khi người đó quyết định kiểm tra tính hợp lệ của
người sử dụng.
Tác dụng phụ này tạo ra một khớp nối thời gian. Đó là, checkPassword chỉ có thể là
được gọi vào những thời điểm nhất định (nói cách khác, khi khởi tạo phiên là an toàn). Nếu nó là
được gọi không theo thứ tự, dữ liệu phiên có thể vô tình bị mất. Các khớp nối tạm thời là
hợp nhất, đặc biệt là khi ẩn dưới dạng tác dụng phụ. Nếu bạn phải có một khớp nối thời gian,
bạn nên làm cho nó rõ ràng trong tên của chức năng. Trong trường hợp này, chúng tôi có thể đổi tên
function checkPasswordAndInitializeSession , mặc dù điều đó chắc chắn vi phạm “Do one
Điều."
Liệt kê 3-6
UserValidator.java
lớp công khai UserValidator {
chuyên gia mật mã Cryptographer riêng;
public boolean checkPassword (String userName, String password) {
Người dùng người dùng = UserGateway.findByName (userName);
if (user! = User.NULL) {
String codedPhrase = user.getPhraseEncodedByPassword ();
Cụm từ chuỗi = cryptographer.decrypt (mã hóaPhrase, mật khẩu);
if ("Mật khẩu hợp lệ" .equals (cụm từ)) {
Session.initialize ();
trả về true;
}
}
trả về sai;
}
}
www.it-ebooks.info

Trang 76
45
Tách truy vấn lệnh
Đối số đầu ra
Các đối số được hiểu một cách tự nhiên nhất là các đầu vào cho một hàm. Nếu bạn đã từng là
trong hơn một vài năm, tôi chắc chắn rằng bạn đã thực hiện hai lần trong một cuộc tranh cãi
đó thực sự là một đầu ra hơn là một đầu vào. Ví dụ:
appendFooter (các);
Hàm này có nối s làm chân trang cho một cái gì đó không? Hay nó nối một số footer
đến s ? Là s một đầu vào hay đầu ra? Không mất nhiều thời gian để nhìn vào chữ ký hàm
và nhìn thấy:
public void appendFooter (báo cáo StringBuffer)
Điều này làm rõ vấn đề, nhưng chỉ ở chi phí kiểm tra khai báo của chức năng.
Bất cứ điều gì buộc bạn phải kiểm tra chữ ký hàm tương đương với việc thực hiện hai lần. nó là
một sự phá vỡ nhận thức và nên tránh.
Trong những ngày trước khi lập trình hướng đối tượng, đôi khi cần phải có
đối số đầu ra. Tuy nhiên, phần lớn nhu cầu đối số đầu ra biến mất trong OO lan-
guages vì này được dự định để hoạt động như một đối số đầu ra. Nói cách khác, nó sẽ là
tốt hơn để appendFooter được gọi là
report.appendFooter ();
Nói chung nên tránh các đối số đầu ra. Nếu chức năng của bạn phải thay đổi trạng thái
của một cái gì đó, có nó thay đổi trạng thái của đối tượng sở hữu của nó.
Tách truy vấn lệnh
Các hàm nên làm một cái gì đó hoặc trả lời một cái gì đó, nhưng không phải cả hai. Hoặc của bạn
hàm sẽ thay đổi trạng thái của một đối tượng hoặc nó sẽ trả về một số thông tin về
đối tượng đó. Làm cả hai thường dẫn đến nhầm lẫn. Ví dụ, hãy xem xét những điều sau
chức năng:
public boolean set (Thuộc tính chuỗi, Giá trị chuỗi);
Hàm này đặt giá trị của một thuộc tính đã đặt tên và trả về true nếu nó thành công và
sai nếu không có thuộc tính như vậy tồn tại. Điều này dẫn đến các tuyên bố kỳ quặc như thế này:
if (set ("username", "Unclebob")) ...
Hãy tưởng tượng điều này từ quan điểm của người đọc. Nó có nghĩa là gì? Nó đang hỏi liệu
các “ Tên truy nhập ” thuộc tính trước đó đã được thiết lập để “ unclebob ”? Hay nó đang hỏi liệu
“ Tên truy nhập ” thuộc tính đã được thiết lập thành công “ unclebob ”? Thật khó để suy ra ý nghĩa từ
cuộc gọi vì không rõ từ “ set ” là động từ hay tính từ.
Tác giả dự định thiết lập để trở thành một động từ, nhưng trong bối cảnh của nếu tuyên bố nó cảm thấy như
một tính từ. Vì vậy, câu lệnh đọc là "Nếu thuộc tính tên người dùng trước đó được đặt thành
Unclebob ”chứ không phải“ đặt thuộc tính tên người dùng thành Unclebob và nếu điều đó hoạt động sau đó. . . . ” Chúng
tôi
www.it-ebooks.info

Trang 77
46
Chương 3: Chức năng
có thể cố gắng giải quyết vấn đề này bằng cách đổi tên hàm set thành setAndCheckIfExists , nhưng điều đó
không giúp gì nhiều khả năng đọc của nếu tuyên bố. Giải pháp thực sự là tách
lệnh từ truy vấn để không thể xảy ra sự mơ hồ.
if (thuộc tínhExists ("tên người dùng")) {
setAttribute ("tên người dùng", "Unclebob");
...
}
Ưu tiên các trường hợp ngoại lệ trả lại mã lỗi
Trả lại mã lỗi từ các hàm lệnh là một vi phạm nhỏ đối với truy vấn lệnh
tách biệt. Nó thúc đẩy các lệnh được sử dụng như biểu thức trong các vị từ của nếu do nhà nước
ments.
if (deletePage (trang) == E_OK)
Điều này không gây nhầm lẫn động từ / tính từ nhưng dẫn đến cấu trúc được lồng vào nhau sâu sắc
chữa bệnh. Khi bạn trả về mã lỗi, bạn tạo ra sự cố mà người gọi phải giải quyết
lỗi ngay lập tức.
if (deletePage (trang) == E_OK) {
if (registry.deleteReference (page.name) == E_OK) {
if (configKeys.deleteKey (page.name.makeKey ()) == E_OK) {
logger.log ("trang đã bị xóa");
} khác {
logger.log ("configKey không bị xóa");
}
} khác {
logger.log ("deleteReference from registry failed");
}
} khác {
logger.log ("xóa không thành công");
trả về E_ERROR;
}
Mặt khác, nếu bạn sử dụng các ngoại lệ thay vì các mã lỗi trả về, thì lỗi
mã xử lý có thể được tách khỏi mã đường dẫn hạnh phúc và có thể được đơn giản hóa:
thử {
deletePage (trang);
registry.deleteReference (page.name);
configKeys.deleteKey (page.name.makeKey ());
}
bắt (Ngoại lệ e) {
logger.log (e.getMessage ());
}
Giải nén các khối Thử / Bắt
Các khối thử / bắt là xấu theo đúng nghĩa của chúng. Họ nhầm lẫn cấu trúc của mã và
trộn xử lý lỗi với xử lý thông thường. Vì vậy, tốt hơn là trích xuất các phần thân của thử
và bắt các khối thành các chức năng của riêng chúng.
www.it-ebooks.info

Trang 78
47
Ưu tiên các trường hợp ngoại lệ trả lại mã lỗi
public void delete (Trang page) {
thử {
deletePageAndAllRefferences (trang);
}
bắt (Ngoại lệ e) {
logError (e);
}
}
private void deletePageAndAllRefutions (Trang page) ném Exception {
deletePage (trang);
registry.deleteReference (page.name);
configKeys.deleteKey (page.name.makeKey ());
}
private void logError (Exception e) {
logger.log (e.getMessage ());
}
Ở trên, chức năng xóa là tất cả về xử lý lỗi. Nó rất dễ hiểu
và sau đó bỏ qua. Hàm deletePageAndAllRefferences là tất cả về các quy trình của
xóa hoàn toàn một trang . Xử lý lỗi có thể được bỏ qua. Điều này cung cấp một sự tách biệt tốt
làm cho mã dễ hiểu và dễ sửa đổi hơn.
Xử lý lỗi là một điều
Các hàm nên làm một việc. Giao lỗi là một chuyện. Do đó, một hàm xử lý
lỗi không nên làm gì khác. Điều này ngụ ý (như trong ví dụ trên) rằng nếu từ khóa
try tồn tại trong một hàm, nó phải là từ đầu tiên trong hàm và rằng ở đó
sẽ không có gì sau khối bắt / cuối cùng .
Nam châm phụ thuộc Error.java
Mã lỗi trả về thường ngụ ý rằng có một số lớp hoặc enum trong đó tất cả
mã lỗi được xác định.
public enum Error {
ĐỒNG Ý,
KHÔNG HỢP LỆ,
KHÔNG CÓ NHƯ VẬY,
ĐÃ KHÓA,
OUT_OF_RESOURCES,
CHỜ_ĐỢI_KẾT_ĐI;
}
Các lớp học như thế này là một nam châm phụ thuộc; nhiều lớp khác phải nhập và sử dụng
chúng. Do đó, khi Error enum thay đổi, tất cả các lớp khác cần được biên dịch lại
và triển khai lại. 11 Điều này gây áp lực tiêu cực lên lớp Lỗi . Lập trình viên không muốn
11. Những người cảm thấy rằng họ có thể thoát khỏi mà không cần biên dịch lại và sử dụng lại đã được tìm thấy — và bị xử lý.
www.it-ebooks.info

Trang 79
48
Chương 3: Chức năng
để thêm lỗi mới vì sau đó họ phải xây dựng lại và triển khai lại mọi thứ. Vì vậy, họ sử dụng lại
mã lỗi cũ thay vì thêm mã lỗi mới.
Khi bạn sử dụng các ngoại lệ thay vì mã lỗi, thì các ngoại lệ mới là các dẫn xuất của
lớp ngoại lệ. Chúng có thể được thêm vào mà không cần phải biên dịch lại hoặc triển khai lại. 12
Đừng lặp lại chính mình 13
Nhìn lại Liệt kê 3-1 một cách cẩn thận và bạn
sẽ nhận thấy rằng có một thuật toán
được lặp lại bốn lần, một lần cho mỗi
các Setup , SuiteSetUp , TearDown , và
Các trường hợp SuiteTearDown . Không dễ phát hiện
sự trùng lặp này bởi vì bốn trường hợp
được trộn lẫn với mã khác và không
nhân bản đồng nhất. Tuy nhiên, sự trùng lặp
là một vấn đề vì nó làm phồng mã và
sẽ yêu cầu sửa đổi gấp bốn lần nếu thuật toán phải thay đổi. Nó cũng là một
cơ hội gấp bốn lần cho một lỗi thiếu sót.
Sự trùng lặp này đã được khắc phục bằng phương thức include trong Liệt kê 3-7. Đọc qua
mã đó một lần nữa và lưu ý cách khả năng đọc của toàn bộ mô-đun được nâng cao bởi
giảm nhân đôi đó.
Sao chép có thể là gốc rễ của mọi điều xấu trong phần mềm. Nhiều nguyên tắc và thực hành có
được tạo ra với mục đích kiểm soát hoặc loại bỏ nó. Ví dụ, hãy xem xét rằng
tất cả các biểu mẫu bình thường của cơ sở dữ liệu Codd nhằm loại bỏ sự trùng lặp trong dữ liệu. Cũng xem xét
cách lập trình hướng đối tượng phục vụ để tập trung mã vào các lớp cơ sở sẽ
nếu không sẽ bị thừa. Lập trình có cấu trúc, Lập trình hướng theo khía cạnh, Compo-
nent Lập trình định hướng, một phần là tất cả các chiến lược để loại bỏ sự trùng lặp. Nó
sẽ xuất hiện rằng kể từ khi phát minh ra chương trình con, các đổi mới trong phần mềm phát triển-
đã là một nỗ lực không ngừng để loại bỏ sự trùng lặp khỏi mã nguồn của chúng tôi.
Lập trình có cấu trúc
Một số lập trình viên tuân theo quy tắc lập trình có cấu trúc của Edsger Dijkstra. 14 Dijkstra
nói rằng mọi hàm và mọi khối trong một hàm, nên có một mục nhập và một
lối ra. Tuân theo các quy tắc này có nghĩa là chỉ nên có một câu lệnh trả về trong một hàm-
tion, không có câu lệnh break hoặc continue trong một vòng lặp và không bao giờ, bất kỳ câu lệnh goto nào .
12. Đây là một ví dụ về Nguyên tắc đóng mở (OCP) [PPP02].
13. Nguyên tắc KHÔ. [PRAG].
14. [SP72].
www.it-ebooks.info

Trang 80
49
Phần kết luận
Mặc dù chúng tôi thông cảm với các mục tiêu và nguyên tắc của lập trình có cấu trúc,
những quy tắc đó phục vụ rất ít lợi ích khi các chức năng rất nhỏ. Nó chỉ ở các chức năng lớn hơn
rằng các quy tắc đó mang lại lợi ích đáng kể.
Vì vậy, nếu bạn giữ các chức năng của mình nhỏ, thì thỉnh thoảng sẽ có nhiều lần trả về , ngắt hoặc
tuyên bố tiếp tục không có hại và đôi khi thậm chí có thể biểu đạt hơn tội lỗi-
quy tắc gle-entry, single-exit. Mặt khác, goto chỉ có ý nghĩa trong các chức năng lớn, vì vậy
nó nên được tránh.
Làm thế nào để bạn viết các hàm như thế này?
Phần mềm viết cũng giống như bất kỳ loại văn bản nào khác. Khi bạn viết một bài báo hoặc một bài báo,
đầu tiên bạn giải quyết suy nghĩ của mình, sau đó bạn xoa bóp nó cho đến khi nó đọc tốt. Bản thảo đầu tiên
có thể vụng về và vô tổ chức, vì vậy bạn tạo ra từ ngữ và cấu trúc lại nó và tinh chỉnh cho đến khi
nó đọc theo cách bạn muốn nó đọc.
Khi tôi viết các hàm, chúng dài và phức tạp. Họ có rất nhiều
thụt lề và vòng lặp lồng nhau. Họ có danh sách đối số dài. Tên là tùy ý, và
có mã trùng lặp. Nhưng tôi cũng có một bộ các bài kiểm tra đơn vị bao gồm mọi
dòng mã vụng về.
Vì vậy, sau đó tôi xoa bóp và tinh chỉnh mã đó, tách các chức năng, đổi tên, elim-
không trùng lặp. Tôi thu nhỏ các phương thức và sắp xếp lại chúng. Đôi khi tôi phá vỡ toàn bộ
các lớp học, đồng thời giữ cho các bài kiểm tra trôi qua.
Cuối cùng, tôi kết thúc với các hàm tuân theo các quy tắc mà tôi đã trình bày trong chương này.
Tôi không viết chúng theo cách đó để bắt đầu. Tôi không nghĩ rằng bất cứ ai có thể.
Phần kết luận
Mọi hệ thống đều được xây dựng từ một ngôn ngữ dành riêng cho miền được các lập trình viên thiết kế để
mô tả hệ thống đó. Chức năng là động từ của ngôn ngữ đó, và các lớp là danh từ.
Đây không phải là một số quay ngược lại quan niệm cũ ghê tởm rằng các danh từ và động từ trong yêu cầu-
tài liệu ments là dự đoán đầu tiên về các lớp và chức năng của một hệ thống. Đúng hơn, đây là
một sự thật cũ hơn nhiều. Nghệ thuật lập trình và luôn luôn là nghệ thuật của ngôn ngữ
thiết kế.
Các lập trình viên bậc thầy nghĩ về hệ thống như những câu chuyện được kể hơn là những chương trình để
được viết. Họ sử dụng các cơ sở của ngôn ngữ lập trình đã chọn của họ để xây dựng
ngôn ngữ phong phú và biểu cảm hơn có thể được sử dụng để kể câu chuyện đó. Một phần của điều đó
ngôn ngữ miền cụ thể là hệ thống phân cấp các chức năng mô tả tất cả các hành động
diễn ra trong hệ thống đó. Trong một hành động đệ quy nghệ thuật, những hành động đó được ghi vào
sử dụng ngôn ngữ theo miền cụ thể mà họ xác định để nói phần nhỏ của họ về
câu chuyện.
Chương này đã nói về cơ chế viết hàm. Nếu bạn làm theo
các quy tắc ở đây, các chức năng của bạn sẽ ngắn gọn, được đặt tên tốt và được tổ chức độc đáo. Nhưng
www.it-ebooks.info

Trang 81
50
Chương 3: Chức năng
đừng bao giờ quên rằng mục tiêu thực sự của bạn là kể câu chuyện về hệ thống và các chức năng
bạn viết cần phải phù hợp rõ ràng với nhau thành một ngôn ngữ rõ ràng và chính xác để giúp bạn
kể rằng.
SetupTeardownIncluder
Liệt kê 3-7
SetupTeardownIncluder.java
gói fitnesse.html;
nhập fitnesse.responders.run.SuiteResponder;
nhập khẩu fitnesse.wiki. *;
public class SetupTeardownIncluder {
trang dữ liệu trang riêng tư dữ liệu trang;
boolean riêng isSuite;
thử nghiệm trang WikiPage riêng tư;
private StringBuffer newPageContent;
riêng PageCrawler pageCrawler;
public static String render (PageData pageData) ném Exception {
trả về kết xuất (pageData, false);
}
public static String render (PageData pageData, boolean isSuite)
ném Ngoại lệ {
trả về SetupTeardownIncluder (pageData) .render (isSuite) mới;
}
private SetupTeardownIncluder (PageData pageData) {
this.pageData = pageData;
testPage = pageData.getWikiPage ();
pageCrawler = testPage.getPageCrawler ();
newPageContent = new StringBuffer ();
}
private String render (boolean isSuite) ném Exception {
this.isSuite = isSuite;
if (isTestPage ())
includeSetupAndTeardownPages ();
trả về trangData.getHtml ();
}
private boolean isTestPage () ném Exception {
return pageData.hasAttribute ("Kiểm tra");
}
private void includeSetupAndTeardownPages () ném Exception {
includeSetupPages ();
includePageContent ();
includeTeardownPages ();
updatePageContent ();
}
www.it-ebooks.info

Trang 82
51
SetupTeardownIncluder
private void includeSetupPages () ném Exception {
if (isSuite)
includeSuiteSetupPage ();
includeSetupPage ();
}
private void includeSuiteSetupPage () ném Exception {
bao gồm (SuiteResponder.SUITE_SETUP_NAME, "-setup");
}
private void includeSetupPage () ném Exception {
bao gồm ("SetUp", "-setup");
}
private void includePageContent () ném Exception {
newPageContent.append (pageData.getContent ());
}
private void includeTeardownPages () ném Exception {
includeTeardownPage ();
if (isSuite)
includeSuiteTeardownPage ();
}
private void includeTeardownPage () ném Exception {
bao gồm ("TearDown", "-teardown");
}
private void includeSuiteTeardownPage () ném Exception {
bao gồm (SuiteResponder.SUITE_TEARDOWN_NAME, "-teardown");
}
private void updatePageContent () ném Exception {
pageData.setContent (newPageContent.toString ());
}
private void include (String pageName, String arg) ném Exception {
WikiPage inheritPage = findInheritedPage (tên trang);
if (inheritPage! = null) {
Chuỗi trangPathName = getPathNameForPage (Trang kế thừa);
buildIncludeDirective (pagePathName, arg);
}
}
WikiPage private findInheritedPage (String pageName) ném Exception {
return PageCrawlerImpl.getInheritedPage (pageName, testPage);
}
private String getPathNameForPage (trang WikiPage) ném Exception {
WikiPagePath pagePath = pageCrawler.getFullPath (trang);
return PathParser.render (pagePath);
}
private void buildIncludeDirective (String pagePathName, String arg) {
newPageContent
.append ("\ n! bao gồm")
Liệt kê 3-7 (tiếp theo)
SetupTeardownIncluder.java
www.it-ebooks.info

Trang 83
52
Chương 3: Chức năng
Thư mục
[KP78]: Kernighan và Plaugher, Các yếu tố của phong cách lập trình , 2d. ed., McGraw-
Hill, 1978.
[PPP02]: Robert C. Martin, Phát triển phần mềm Agile: Nguyên tắc, Mẫu và Thực tiễn-
tices , Prentice Hall, 2002.
[GOF]: Mẫu thiết kế: Các yếu tố của phần mềm hướng đối tượng có thể tái sử dụng , Gamma et al.,
Addison-Wesley, 1996.
[PRAG]: Lập trình viên thực dụng , Andrew Hunt, Dave Thomas, Addison-Wesley,
2000.
[SP72]: Lập trình có cấu trúc , O.-J. Dahl, EW Dijkstra, CAR Hoare, Học thuật
Báo chí, London, 1972.
.append (arg)
.append (".")
.append (pagePathName)
.append ("\ n");
}
}
Liệt kê 3-7 (tiếp theo)
SetupTeardownIncluder.java
www.it-ebooks.info

Trang 84
53

4
Bình luận
“Đừng nhận xét mã xấu — hãy viết lại nó.”
—Brian W. Kernighan và PJ Plaugher 1
Không có gì có thể khá hữu ích như một nhận xét đúng chỗ. Không gì có thể làm lộn xộn một mod-
tốt hơn là những bình luận giáo điều phù phiếm. Không có gì có thể gây tổn hại nhiều như đồ cũ
bình luận thô lỗ tuyên truyền dối trá và thông tin sai lệch.
Nhận xét không giống như Danh sách của Schindler. Chúng không phải là "tốt thuần túy." Thật vậy, nhận xét
tốt nhất là một điều ác cần thiết. Nếu ngôn ngữ lập trình của chúng tôi đủ biểu cảm hoặc nếu
1. [KP78], tr. 144.
www.it-ebooks.info

Trang 85
54
Chương 4: Nhận xét
chúng tôi đã có tài vận dụng những ngôn ngữ đó một cách tinh vi để thể hiện ý định của mình, chúng tôi sẽ không
cần
nhận xét rất nhiều — có lẽ không hề.
Việc sử dụng bình luận đúng cách là để bù đắp cho việc chúng ta không thể hiện được bản thân
mã. Lưu ý rằng tôi đã sử dụng từ thất bại . Ý tôi là nó. Nhận xét luôn là thất bại. Chúng ta phải
có chúng bởi vì chúng ta không thể luôn tìm ra cách thể hiện bản thân nếu không có chúng,
nhưng việc sử dụng chúng không phải là nguyên nhân để ăn mừng.
Vì vậy, khi bạn thấy mình ở vị trí cần viết bình luận, hãy nghĩ
thông qua và xem liệu không có cách nào để lật ngược tình thế và thể hiện bản thân
mã. Mỗi khi bạn thể hiện bản thân bằng mã, bạn nên tự vỗ về mình. Mỗi
khi bạn viết bình luận, bạn nên nhăn mặt và cảm thấy thất bại về khả năng của mình
biểu hiện.
Tại sao tôi rất thất vọng về các bình luận? Vì họ nói dối. Không phải luôn luôn và không cố ý,
nhưng quá thường xuyên. Nhận xét càng cũ và càng xa mã mà nó mô tả,
thì càng có nhiều khả năng nó chỉ là sai lầm rõ ràng. Lý do rất đơn giản. Lập trình viên không thể thực-
duy trì chúng một cách cẩn thận.
Mã thay đổi và phát triển. Từng mảng của nó di chuyển từ đây sang đó. Những khối đó-
và sinh sản và kết hợp lại với nhau để tạo thành chimeras. Thật không may là com-
không phải lúc nào cũng theo dõi họ— không phải lúc nào cũng có thể theo dõi họ. Và tất cả thường
các nhận xét bị tách khỏi mã mà chúng mô tả và trở thành những dòng mờ ảo vĩnh viễn-
độ chính xác giảm dần. Ví dụ: hãy xem điều gì đã xảy ra với nhận xét này và dòng nó
nhằm mô tả:
Yêu cầu MockRequest;
Chuỗi cuối cùng riêng tư HTTP_DATE_REGEXP =
"[SMTWF] [az] {2} \\, \\ s [0-9] {2} \\ s [JFMASOND] [az] {2} \\ s" +
"[0-9] {4} \\ s [0-9] {2} \\: [0-9] {2} \\: [0-9] {2} \\ sGMT";
phản hồi Response riêng tư;
ngữ cảnh FitNesseContext riêng tư;
người trả lời FileResponder riêng;
ngôn ngữ riêng saveLocale;
// Ví dụ: "Thứ ba, 02 tháng 4 năm 2003 22:18:49 GMT"
Các biến ví dụ khác có lẽ đã được thêm vào sau đó được xen kẽ giữa
Hằng số HTTP_DATE_REGEXP và đó là nhận xét giải thích.
Có thể đưa ra quan điểm rằng các lập trình viên phải đủ kỷ luật để
giữ cho các nhận xét ở trạng thái sửa chữa, phù hợp và chính xác cao. Tôi đồng ý, họ nên làm.
Nhưng tôi muốn năng lượng đó hướng tới việc làm cho mã rõ ràng và biểu đạt đến mức nó
không cần các ý kiến ngay từ đầu.
Nhận xét không chính xác còn tệ hơn nhiều so với không có bình luận nào. Họ lừa dối và lừa dối.
Họ đặt ra những kỳ vọng sẽ không bao giờ được thực hiện. Họ đặt ra các quy tắc cũ không cần thiết, hoặc
không nên, được theo dõi lâu hơn nữa.
Sự thật chỉ có thể được tìm thấy ở một nơi: mật mã. Chỉ có mã mới thực sự cho bạn biết những gì
nó có. Nó là nguồn thông tin thực sự chính xác duy nhất. Do đó, mặc dù nhận xét là
đôi khi cần thiết, chúng tôi sẽ tiêu tốn năng lượng đáng kể để giảm thiểu chúng.
www.it-ebooks.info

Trang 86
55
Nhận xét tốt
Nhận xét không bù đắp cho mã xấu
Một trong những động cơ phổ biến hơn để viết bình luận là mã xấu. Chúng tôi viết một mod-
ule và chúng tôi biết nó là khó hiểu và vô tổ chức. Chúng tôi biết đó là một mớ hỗn độn. Vì vậy, chúng tôi nói với-
bản thân, "Ồ, tôi nên bình luận điều đó!" Không! Tốt hơn bạn nên làm sạch nó!
Mã rõ ràng và rõ ràng với ít chú thích hơn hẳn so với mã lộn xộn và phức tạp
mã với rất nhiều bình luận. Thay vì dành thời gian viết các bình luận
giải thích về mớ hỗn độn bạn đã làm, dành nó để dọn dẹp đống lộn xộn đó.
Giải thích bản thân bằng mã
Chắc chắn có những lúc mã làm cho một chiếc xe kém để giải thích. Không may,
nhiều lập trình viên đã hiểu điều này có nghĩa là mã hiếm khi, nếu có, là một phương tiện tốt để
giải trình. Điều này là sai nghiêm trọng. Bạn muốn xem cái nào hơn? Điều này:
// Kiểm tra xem nhân viên có đủ điều kiện nhận đầy đủ quyền lợi hay không
if ((worker.flags & HOURLY_FLAG) &&
(nhân viên. trang> 65))
Hay cái này?
if (worker.isEnoughForFullBenefits ())
Chỉ mất vài giây suy nghĩ để giải thích hầu hết ý định của bạn trong mã. Trong nhiều
trường hợp đó chỉ đơn giản là vấn đề tạo ra một hàm nói điều tương tự như nhận xét
bạn muốn viết.
Nhận xét tốt
Một số nhận xét là cần thiết hoặc có lợi. Chúng tôi sẽ xem xét một số mà tôi cho là xứng đáng
các bit mà họ sử dụng. Tuy nhiên, hãy nhớ rằng nhận xét thực sự tốt duy nhất là
nhận xét bạn đã tìm thấy một cách để không viết.
Bình luận pháp lý
Đôi khi các tiêu chuẩn mã hóa công ty của chúng tôi buộc chúng tôi phải viết một số nhận xét nhất định cho hợp
pháp
lý do. Ví dụ: bản quyền và tuyên bố về quyền tác giả là cần thiết và hợp lý
những thứ cần đưa vào nhận xét ở đầu mỗi tệp nguồn.
Ví dụ: đây là tiêu đề nhận xét chuẩn mà chúng tôi đặt ở đầu
mọi tệp nguồn trong FitNesse. Tôi rất vui khi nói rằng IDE của chúng tôi ẩn nhận xét này khỏi hành động-
ing như lộn xộn bằng cách tự động thu gọn nó.
// Bản quyền (C) 2003,2004,2005 của Object Mentor, Inc. Mọi quyền được bảo lưu.
// Được phát hành theo các điều khoản của Giấy phép Công cộng GNU phiên bản 2 trở lên.
www.it-ebooks.info

Trang 87
56
Chương 4: Nhận xét
Những bình luận như thế này không nên là hợp đồng hoặc chủ đề pháp lý. Nếu có thể, hãy tham khảo stan-
giấy phép dard hoặc tài liệu bên ngoài khác thay vì đưa ra tất cả các điều khoản và điều kiện
vào bình luận.
Nhận xét thông tin
Đôi khi, việc cung cấp thông tin cơ bản kèm theo nhận xét sẽ rất hữu ích. Ví dụ, con-
sider nhận xét này giải thích giá trị trả về của một phương thức trừu tượng:
// Trả về một thể hiện của Trình phản hồi đang được kiểm tra.
được bảo vệ trừu tượng Responder responseerInstance ();
Một nhận xét như thế này đôi khi có thể hữu ích, nhưng tốt hơn là sử dụng tên của func-
tion để truyền đạt thông tin nếu có thể. Ví dụ, trong trường hợp này, nhận xét
có thể dư thừa bằng cách đổi tên hàm: responseerBeingTested .
Đây là một trường hợp tốt hơn một chút:
// định dạng phù hợp kk: mm: ss EEE, MMM dd, yyyy
Pattern timeMatcher = Pattern.compile (
"\\ d *: \\ d *: \\ d * \\ w *, \\ w * \\ d *, \\ d *");
Trong trường hợp này, nhận xét cho chúng tôi biết rằng biểu thức chính quy nhằm khớp với
thời gian và ngày tháng được định dạng bằng hàm SimpleDateFormat.format bằng cách sử dụng
chuỗi định dạng được chỉ định. Tuy nhiên, nó có thể tốt hơn và rõ ràng hơn, nếu mã này đã
đã chuyển sang một lớp đặc biệt đã chuyển đổi các định dạng ngày và giờ. Sau đó nhận xét
có thể đã được thừa.
Giải thích ý định
Đôi khi một nhận xét không chỉ là thông tin hữu ích về việc triển khai và
cung cấp ý định đằng sau một quyết định. Trong trường hợp sau, chúng tôi thấy một quyết định thú vị
được ghi lại bởi một bình luận. Khi so sánh hai đối tượng, tác giả quyết định rằng anh
muốn sắp xếp các đối tượng của lớp mình cao hơn các đối tượng của bất kỳ đối tượng nào khác.
public int so sánhTo (Đối tượng o)
{
if (o instanceof WikiPagePath)
{
WikiPagePath p = (WikiPagePath) o;
StringressedName = StringUtil.join (tên, "");
StringressedArgumentName = StringUtil.join (p.names, "");
trả về nénName.compareTo (Tên miền nén);
}
trả về 1; // chúng tôi lớn hơn vì chúng tôi là loại phù hợp.
}
Đây là một ví dụ thậm chí còn tốt hơn. Bạn có thể không đồng ý với giải pháp của lập trình viên để
vấn đề, nhưng ít nhất bạn biết anh ấy đang cố gắng làm gì.
public void testConcurrentAddWidgets () ném Exception {
WidgetBuilder widgetBuilder =
new WidgetBuilder (Lớp mới [] {BoldWidget.class});
www.it-ebooks.info
Trang 88
57
Nhận xét tốt
String text = "'' 'văn bản in đậm' ''";
ParentWidget parent =
new BoldWidget (MockWidgetRoot mới (), "'' 'văn bản in đậm' ''");
AtomicBoolean failFlag = new AtomicBoolean ();
failFlag.set (sai);
// Đây là nỗ lực tốt nhất của chúng tôi để đạt được điều kiện đua
// bằng cách tạo ra một số lượng lớn các chủ đề.
for (int i = 0; i <25000; i ++) {
WidgetBuilderThread widgetBuilderThread =
mới WidgetBuilderThread (widgetBuilder, text, cha, failFlag);
Chủ đề luồng = new Thread (widgetBuilderThread);
thread.start ();
}
khẳng địnhEquals (false, failFlag.get ());
}
Làm rõ
Đôi khi, việc dịch nghĩa của một lập luận hoặc câu trả lời khó hiểu nào đó chỉ hữu ích
giá trị thành một cái gì đó có thể đọc được. Nói chung, tốt hơn hết là bạn nên tìm cách giải quyết vấn đề đó-
ment hoặc trả về giá trị rõ ràng theo đúng nghĩa của nó; nhưng khi nó là một phần của thư viện chuẩn, hoặc trong
mã mà bạn không thể thay đổi, thì một nhận xét làm rõ hữu ích có thể hữu ích.
public void testCompareTo () ném Exception
{
WikiPagePath a = PathParser.parse ("TrangA");
WikiPagePath ab = PathParser.parse ("TrangA.PageB");
WikiPagePath b = PathParser.parse ("TrangB");
WikiPagePath aa = PathParser.parse ("TrangA.PageA");
WikiPagePath bb = PathParser.parse ("TrangB.PageB");
WikiPagePath ba = PathParser.parse ("TrangB.PageA");
khẳng địnhTrue (a.compareTo (a) == 0); // a == a
khẳng địnhTrue (a.compareTo (b)! = 0); // a! = b
khẳng địnhTrue (ab.compareTo (ab) == 0); // ab == ab
khẳng địnhTrue (a.compareTo (b) == -1); // a <b
khẳng địnhTrue (aa.compareTo (ab) == -1); // aa <ab
khẳng địnhTrue (ba.compareTo (bb) == -1); // ba <bb
khẳng địnhTrue (b.compareTo (a) == 1); // b> a
khẳng địnhTrue (ab.compareTo (aa) == 1); // ab> aa
khẳng địnhTrue (bb.compareTo (ba) == 1); // bb> ba
}
Tất nhiên, có một rủi ro đáng kể là một nhận xét làm rõ là không chính xác. Đi
thông qua ví dụ trước và thấy khó khăn như thế nào để xác minh rằng chúng là đúng. Điều này
giải thích cả lý do tại sao việc làm rõ là cần thiết và tại sao nó lại rủi ro. Vì vậy, trước khi viết com-
những người như thế này, hãy cẩn thận rằng không có cách nào tốt hơn, và sau đó hãy quan tâm hơn nữa rằng họ
là chính xác.
www.it-ebooks.info

Trang 89
58
Chương 4: Nhận xét
Cảnh báo hậu quả
Đôi khi, rất hữu ích khi cảnh báo các nhà cung cấp khác
grammers về hậu quả nhất định. Đối với
ví dụ, đây là một nhận xét giải thích
tại sao một trường hợp thử nghiệm cụ thể bị tắt:
// Không chạy trừ khi bạn
// có chút thời gian để giết.
public void _testWithReallyBigFile ()
{
writeLinesToFile (10000000);
response.setBody (testFile);
response.readyToSend (this);
Chuỗi phản hồiString = output.toString ();
khẳng địnhSubString ("Nội dung-Độ dài: 1000000000", responseString);
khẳng địnhTrue (byteSent> 1000000000);
}
Tất nhiên, ngày nay, chúng tôi sẽ tắt trường hợp thử nghiệm bằng cách sử dụng thuộc tính @Ignore với
chuỗi giải thích phù hợp. @Ignore ("Mất quá nhiều thời gian để chạy") . Nhưng trở lại trong những ngày
trước JUnit 4, đặt dấu gạch dưới trước tên phương thức là một vấn đề phổ biến-
sự. Nhận xét, trong khi không rõ ràng, làm cho quan điểm khá tốt.
Đây là một ví dụ khác, sâu sắc hơn:
public static SimpleDateFormat makeStandardHttpDateFormat ()
{
// SimpleDateFormat không an toàn cho chuỗi,
// vì vậy chúng ta cần tạo từng cá thể một cách độc lập.
SimpleDateFormat df = new SimpleDateFormat ("EEE, dd MMM yyyy HH: mm: ss z");
df.setTimeZone (TimeZone.getTimeZone ("GMT"));
trả về df;
}
Bạn có thể phàn nàn rằng có nhiều cách tốt hơn để giải quyết vấn đề này. Tôi có thể đồng ý với
bạn. Nhưng nhận xét, như được đưa ra ở đây, là hoàn toàn hợp lý. Nó sẽ ngăn chặn một số
lập trình viên háo hức từ việc sử dụng bộ khởi tạo tĩnh nhân danh hiệu quả.
TODO nhận xét
Đôi khi, việc để lại ghi chú "Việc cần làm" dưới dạng // nhận xét VIỆC LÀM . bên trong
trường hợp sau, nhận xét TODO giải thích tại sao hàm có triển khai suy biến
và tương lai của chức năng đó sẽ như thế nào.
// TODO-MdM những thứ này không cần thiết
// Chúng tôi hy vọng điều này sẽ biến mất khi chúng tôi thực hiện mô hình thanh toán
Phiên bản được bảo vệ makeVersion () ném Ngoại lệ
{
trả về null;
}
www.it-ebooks.info

Trang 90
59
Nhận xét xấu
CẦN LÀM là những công việc mà lập trình viên cho rằng nên làm, nhưng vì một số lý do
không thể làm vào lúc này. Nó có thể là lời nhắc xóa một tính năng không dùng nữa hoặc một
cầu xin người khác xem xét một vấn đề. Nó có thể là một yêu cầu người khác
nghĩ về một cái tên hay hơn hoặc một lời nhắc nhở để thực hiện thay đổi phụ thuộc vào
sự kiện dự kiến. Bất kể điều gì khác là VIỆC CẦN LÀM , nó không phải là cái cớ để để lại mã xấu trong
hệ thống.
Ngày nay, hầu hết các IDE tốt đều cung cấp các cử chỉ và tính năng đặc biệt để định vị tất cả các
TODO nhận xét, vì vậy không có khả năng họ sẽ bị lạc. Tuy nhiên, bạn không muốn mã của mình
được rải rác với TODO s. Vì vậy, hãy quét qua chúng thường xuyên và loại bỏ những cái bạn
có thể.
Khuếch đại
Một nhận xét có thể được sử dụng để khuếch đại tầm quan trọng của một cái gì đó có thể có vẻ khác
không đáng kể.
String listItemContent = match.group (3) .trim ();
// phần cắt là thực sự quan trọng. Nó loại bỏ sự bắt đầu
// dấu cách có thể khiến mục được nhận dạng
// như một danh sách khác.
new ListItemWidget (this, listItemContent, this.level + 1);
trả về buildList (text.substring (match.end ()));
Javadocs trong API công khai
Không có gì hữu ích và thỏa mãn như một API công khai được mô tả tốt. Java-
tài liệu cho thư viện Java tiêu chuẩn là một trường hợp điển hình. Rất khó, tốt nhất là viết
Các chương trình Java không có chúng.
Nếu bạn đang viết một API công khai, thì chắc chắn bạn nên viết javadocs tốt cho nó.
Nhưng hãy ghi nhớ phần còn lại của lời khuyên trong chương này. Javadocs cũng có thể gây hiểu lầm,
không địa phương, và không trung thực như bất kỳ loại nhận xét nào khác.
Nhận xét xấu
Hầu hết các bình luận đều thuộc thể loại này. Thông thường họ là nạng hoặc bào chữa cho mã kém
hoặc những lời biện minh cho những quyết định không đầy đủ, số lượng ít hơn lập trình viên
nói chuyện với chính mình.
Lầm bầm
Bắt đầu nhận xét chỉ vì bạn cảm thấy cần hoặc vì quá trình này yêu cầu nó,
là một vụ hack. Nếu bạn quyết định viết bình luận, hãy dành thời gian cần thiết để đảm bảo nó
là bình luận tốt nhất bạn có thể viết.
www.it-ebooks.info

Trang 91
60
Chương 4: Nhận xét
Ví dụ: đây là một trường hợp tôi tìm thấy trong FitNesse, nơi một nhận xét thực sự có thể có
hữu ích. Nhưng tác giả đã vội hay chỉ là không để ý lắm. Mẹ của anh ấy-
bling để lại một bí ẩn:
public void loadProperties ()
{
thử
{
Chuỗi thuộc tínhPath = propertiesLocation + "/" + PROPERTIES_FILE;
FileInputStream thuộc tínhStream = new FileInputStream (propertyPath);
loadProperties.load (propertyStream);
}
bắt (IOException e)
{
// Không có tệp thuộc tính nào có nghĩa là tất cả các giá trị mặc định đã được tải
}
}
Nhận xét đó trong khối bắt có nghĩa là gì? Rõ ràng nó có ý nghĩa với
tác giả, nhưng ý nghĩa không đi qua tất cả những điều đó. Rõ ràng, nếu chúng ta nhận được một
IOException , có nghĩa là không có tệp thuộc tính nào; và trong trường hợp đó, tất cả các giá trị mặc định là
nạp vào. Nhưng ai tải tất cả các giá trị mặc định? Chúng đã được tải trước khi cuộc gọi tới
loadProperties.load ? Hoặc loadProperties.load đã bắt ngoại lệ, tải các giá trị mặc định,
và sau đó chuyển ngoại lệ cho chúng tôi để bỏ qua? Hoặc loadProperties.load đã tải tất cả
mặc định trước khi cố gắng tải tệp? Có phải tác giả đang cố tự an ủi mình về
thực tế là anh ta đã để trống khối bắt ? Hoặc — và đây là khả năng đáng sợ —
là tác giả đang cố gắng tự nhủ rằng hãy quay lại đây sau và viết mã sẽ
tải các giá trị mặc định?
Cách duy nhất của chúng tôi là kiểm tra mã trong các phần khác của hệ thống để tìm ra
đang xảy ra. Bất kỳ nhận xét nào buộc bạn phải xem trong mô-đun khác để biết ý nghĩa của điều đó
nhận xét không liên lạc được với bạn và không đáng giá bằng số bit mà nó tiêu thụ.
Nhận xét dư thừa
Liệt kê 4-1 cho thấy một hàm đơn giản với chú thích tiêu đề là hoàn toàn dư thừa.
Nhận xét có thể mất nhiều thời gian hơn để đọc mã.
Liệt kê 4-1
waitForClose
// Phương thức tiện ích trả về khi this.closed là đúng. Ném một ngoại lệ
// nếu hết thời gian chờ.
công khai đồng bộ hóa void waitForClose (thời gian chờ dài cuối cùngMillis)
ném Ngoại lệ
{
nếu (! đã đóng)
{
chờ đợi (timeoutMillis);
nếu (! đã đóng)
ném ngoại lệ mới ("Không thể đóng MockResponseSender");
}
}
www.it-ebooks.info

Trang 92
61
Nhận xét xấu
Mục đích của bình luận này là gì? Nó chắc chắn không nhiều thông tin hơn
mã. Nó không biện minh cho mã hoặc cung cấp ý định hoặc lý do. Nó không dễ đọc hơn
mật mã. Thật vậy, nó kém chính xác hơn mã và lôi kéo người đọc chấp nhận rằng thiếu
sự chính xác thay cho sự hiểu biết thực sự. Nó giống như một người bán xe cũ vui vẻ niềm nở
đảm bảo với bạn rằng bạn không cần phải nhìn dưới mui xe.
Bây giờ hãy xem xét quân đoàn javadocs vô dụng và dư thừa trong Liệt kê 4-2 được lấy từ
Tomcat. Những bình luận này chỉ để làm lộn xộn và che khuất mã. Họ không cung cấp tài liệu-
mục đích cố vấn ở tất cả. Để làm cho vấn đề tồi tệ hơn, tôi chỉ cho bạn thấy một số đầu tiên. Có
nhiều hơn nữa trong mô-đun này.
Liệt kê 4-2
ContainerBase.java (Tomcat)
công khai lớp trừu tượng ContainerBase
thực hiện Thùng chứa, Vòng đời, Đường ống,
MBeanRegistration, Serializable {
/ **
* Độ trễ của bộ xử lý cho thành phần này.
*/
bảo vệ int backgroundProcessorDelay = -1;
/ **
* Hỗ trợ sự kiện vòng đời cho thành phần này.
*/
Vòng đời được bảo vệSupport =
new LifecycleSupport (this);
/ **
* Trình nghe sự kiện vùng chứa cho Vùng chứa này.
*/
bảo vệ người nghe ArrayList = new ArrayList ();
/ **
* Triển khai Trình tải mà Vùng chứa này là
* liên kết.
*/
Bộ nạp được bảo vệ Loader = null;
/ **
* Việc triển khai Logger mà Vùng chứa này là
* liên kết.
*/
Đã bảo vệ Log logger = null;
/ **
* Tên trình ghi liên quan.
*/
bảo vệ String logName = null;
www.it-ebooks.info

Trang 93
62
Chương 4: Nhận xét
/ **
* Triển khai Trình quản lý mà Vùng chứa này là
* liên kết.
*/
Người quản lý được bảo vệ Manager = null;
/ **
* Cụm liên kết với Vùng chứa này.
*/
cụm Cluster được bảo vệ = null;
/ **
* Tên có thể đọc được của con người của Vùng chứa này.
*/
tên String được bảo vệ = null;
/ **
* Vùng chứa mẹ mà Vùng chứa này là con.
*/
Vùng chứa được bảo vệ cha = null;
/ **
* Trình tải lớp cha được định cấu hình khi chúng tôi cài đặt
* Bộ nạp.
*/
được bảo vệ ClassLoader parentClassLoader = null;
/ **
* Đối tượng Đường ống mà Vùng chứa này là
* liên kết.
*/
đường ống bảo vệ Pipeline = new StandardPipeline (this);
/ **
* Vương quốc mà Vùng chứa này được liên kết với.
*/
Cảnh giới được bảo vệ = null;
/ **
* Đối tượng DirContext tài nguyên mà vùng chứa này
* được liên kết.
*/
tài nguyên DirContext được bảo vệ = null;
Liệt kê 4-2 (tiếp theo)
ContainerBase.java (Tomcat)
www.it-ebooks.info

Trang 94
63
Nhận xét xấu
Nhận xét gây hiểu lầm
Đôi khi, với tất cả ý định tốt nhất, một lập trình viên đưa ra tuyên bố trong nhận xét của mình
điều đó không đủ chính xác để chính xác. Hãy xem xét một lúc nữa điều tồi tệ thừa
nhưng cũng có nhận xét gây hiểu lầm một cách tinh vi mà chúng ta đã thấy trong Liệt kê 4-1.
Bạn có phát hiện ra nhận xét sai lệch như thế nào không? Phương thức không trả về
khi điều  này. đóng cửa trở thành sự thật . Nó trả về nếu  this.closed là đúng ; nếu không, nó chờ đợi một
thời gian chờ mù và sau đó ném một ngoại lệ nếu điều  này. đóng cửa vẫn không đúng .
Một chút thông tin sai lệch tinh vi này, được đưa vào một bình luận khó đọc hơn
phần thân của mã, có thể khiến một lập trình viên khác vô tình gọi hàm này trong
kỳ vọng rằng nó sẽ trở lại ngay sau khi điều này trở thành sự thật . Lập trình viên nghèo đó
sau đó sẽ thấy mình trong một phiên gỡ lỗi cố gắng tìm ra lý do tại sao mã của anh ấy được thực thi
thật chậm.
Nhận xét được ủy quyền
Thật là ngớ ngẩn khi có một quy tắc nói rằng mọi hàm phải có javadoc, hoặc
mọi biến phải có chú thích. Những bình luận như thế này chỉ làm lộn xộn mã, propa-
cửa ải dối trá, và cho mượn đến sự nhầm lẫn và vô tổ chức chung.
Ví dụ: javadocs bắt buộc cho mọi chức năng dẫn đến những điều ghê tởm như List-
ing 4-3. Sự lộn xộn này không thêm gì và chỉ phục vụ để làm xáo trộn mã và tạo
tiềm năng dối trá và định hướng sai.
Bình luận Tạp chí
Đôi khi mọi người thêm nhận xét vào đầu mô-đun mỗi khi họ chỉnh sửa nó. Những
các nhận xét tích lũy như một loại nhật ký, hoặc nhật ký, về mọi thay đổi đã từng
thực hiện. Tôi đã thấy một số mô-đun với hàng chục trang của các mục nhật ký đang chạy này.
Liệt kê 4-3
/ **
*
* @param title Tiêu đề của CD
* Tác giả @param Tác giả của CD
* @param bản nhạc Số lượng bản nhạc trên CD
* Thời lượng @paramInMinutes Thời lượng của đĩa CD tính bằng phút
*/
public void addCD (String title, String author,
int track, int timeInMinutes) {
CD cd = new CD ();
cd.title = title;
cd.author = tác giả;
cd.tracks = bài hát;
cd.duration = thời lượng;
cdList.add (cd);
}
www.it-ebooks.info

Trang 95
64
Chương 4: Nhận xét
Từ lâu, đã có lý do chính đáng để tạo và duy trì các mục nhật ký này khi bắt đầu
của mọi mô-đun. Chúng tôi không có hệ thống kiểm soát mã nguồn làm điều đó cho chúng tôi. Ngày nay,
tuy nhiên, những tạp chí dài này chỉ lộn xộn hơn để làm rối mắt mô-đun. Họ nên
bị loại bỏ hoàn toàn.
Nhận xét về tiếng ồn
Đôi khi bạn thấy những bình luận chẳng qua là tiếng ồn. Họ trình bày lại điều hiển nhiên và
không cung cấp thông tin mới.
/ **
* Nhà xây dựng mặc định.
*/
được bảo vệ hàng năm theo ngày tháng () {
}
Không, thực sự? Hoặc làm thế nào về điều này:
/ ** Ngày trong tháng. * /
private int dayOfMonth;
Và sau đó là mô hình dư thừa này:
/ **
* Trả về ngày trong tháng.
*
* @ quay trở lại ngày trong tháng.
*/
public int getDayOfMonth () {
trở lại ngàyOfMonth;
}
* Thay đổi (từ 11-10-2001)
* --------------------------
* 11-10-2001: Tổ chức lại lớp học và chuyển nó sang gói mới
*
com.jrefinery.date (DG);
* 05-11-2001: Thêm phương thức getDescription () và loại bỏ NotableDate
*
lớp (DG);
* 12-11-2001: IBD yêu cầu phương thức setDescription (), bây giờ là NotableDate
*
lớp học đã biến mất (DG); Đã thay đổi getPreviousDayOfWeek (),
*
getFollowingDayOfWeek () và getNearestDayOfWeek () để sửa
*
bọ (DG);
* 05-12-2001: Sửa lỗi trong lớp SpreadsheetDate (DG);
* 29 tháng 5 năm 2002: Chuyển các hằng số tháng vào một giao diện riêng
*
(Kết quả tháng) (DG);
* 27-8-2002: Sửa lỗi trong phương thức addMonths (), nhờ N ??? levka Petr (DG);
* 03-10-2002: Sửa các lỗi do Checkstyle (DG) báo cáo;
* Ngày 13 tháng 3 năm 2003: Thực hiện Serializable (DG);
* 29-05-2003: Sửa lỗi trong phương thức addMonths (DG);
* 04-09-2003: Thực hiện So sánh. Đã cập nhật javadocs isInRange (DG);
* 05-01-2005: Sửa lỗi trong phương thức addYears () (1096282) (DG);
www.it-ebooks.info

Trang 96
65
Nhận xét xấu
Những bình luận này quá ồn ào nên chúng tôi học cách bỏ qua chúng. Khi chúng tôi đọc qua mã,
mắt chỉ đơn giản là bỏ qua chúng. Cuối cùng, các nhận xét bắt đầu dối trá vì mã xung quanh chúng
những thay đổi.
Nhận xét đầu tiên trong Liệt kê 4-4 có vẻ phù hợp. 2 Nó giải thích tại sao khối bắt
đang bị bỏ qua. Nhưng nhận xét thứ hai là tiếng ồn thuần túy. Rõ ràng lập trình viên đã
chỉ là quá chán nản với việc viết các khối try / catch trong chức năng này mà anh ấy cần phải giải tỏa.
Thay vì trút vào một bình luận vô giá trị và ồn ào, lập trình viên nên có
nhận ra rằng sự thất vọng của anh ấy có thể được giải quyết bằng cách cải thiện cấu trúc mã của anh ấy.
Anh ta nên chuyển hướng năng lượng của mình để trích xuất khối thử / bắt cuối cùng đó thành một khối riêng biệt
, như được hiển thị trong Liệt kê 4-5.
2. Xu hướng hiện tại đối với các IDE để kiểm tra chính tả trong các nhận xét sẽ là một phương pháp điều trị cho những ai trong chúng ta, những người đọc nhiều mã.
Liệt kê 4-4
startSending
private void startSending ()
{
thử
{
doSending ();
}
bắt (SocketException e)
{
// bình thường. ai đó đã dừng yêu cầu.
}
bắt (Ngoại lệ e)
{
thử
{
response.add (ErrorResponder.makeExceptionString (e));
response.closeAll ();
}
bắt (Ngoại lệ e1)
{
//Hãy cho tôi một break!
}
}
}
Liệt kê 4-5
startSending (tái cấu trúc)
private void startSending ()
{
thử
{
doSending ();
}
www.it-ebooks.info
Trang 97
66
Chương 4: Nhận xét
Thay thế sự cám dỗ để tạo ra tiếng ồn bằng quyết tâm làm sạch mã của bạn. Bạn sẽ
thấy nó giúp bạn trở thành một lập trình viên tốt hơn và hạnh phúc hơn.
Tiếng ồn đáng sợ
Javadocs cũng có thể ồn ào. Mục đích của Javadocs sau đây (từ một
thư viện mã nguồn mở) phục vụ? Trả lời: không có gì. Chúng chỉ là những bình luận ồn ào dư thừa
được viết ra với mong muốn cung cấp tài liệu không đúng chỗ.
/** Tên. * /
tên chuỗi riêng;
/ ** Phiên bản. * /
phiên bản chuỗi riêng;
/ ** Tên chấy. * /
private String LicenceName;
/ ** Phiên bản. * /
thông tin chuỗi riêng tư;
Đọc lại những bình luận này cẩn thận hơn. Bạn có thấy lỗi cắt dán không? Nếu tác giả
không chú ý khi nhận xét được viết (hoặc dán), tại sao người đọc phải
dự kiến thu lợi nhuận từ chúng?
bắt (SocketException e)
{
// bình thường. ai đó đã dừng yêu cầu.
}
bắt (Ngoại lệ e)
{
addExceptionAndCloseResponse (e);
}
}
private void addExceptionAndCloseResponse (Exception e)
{
thử
{
response.add (ErrorResponder.makeExceptionString (e));
response.closeAll ();
}
bắt (Ngoại lệ e1)
{
}
}
Liệt kê 4-5 (tiếp theo)
startSending (tái cấu trúc)
www.it-ebooks.info

Trang 98
67
Nhận xét xấu
Không sử dụng nhận xét khi bạn có thể sử dụng một hàm hoặc một biến
Hãy xem xét đoạn mã sau:
// mô-đun từ danh sách chung <mod> có phụ thuộc vào
// hệ thống con mà chúng ta là một phần?
if (smodule.getDependSubsystems (). chứa (subSysMod.getSubSystem ()))
Điều này có thể được diễn đạt lại mà không có nhận xét như
ArrayList moduleDependees = smodule.getDependSubsystems ();
String ourSubSystem = subSysMod.getSubSystem ();
if (moduleDependees.contains (ourSubSystem))
Tác giả của mã gốc có thể đã viết nhận xét trước (không chắc) và sau đó
đã viết mã để hoàn thành nhận xét. Tuy nhiên, tác giả sau đó nên cấu trúc lại
như tôi đã làm, để có thể xóa nhận xét.
Điểm đánh dấu vị trí
Đôi khi các lập trình viên muốn đánh dấu một vị trí cụ thể trong một tệp nguồn. Ví dụ, tôi
gần đây đã tìm thấy điều này trong một chương trình mà tôi đã xem qua:
// Tác vụ ////////////////////////////////////
Hiếm khi có ý nghĩa khi tập hợp các chức năng nhất định lại với nhau bên dưới
biểu ngữ như thế này. Nhưng nhìn chung chúng rất lộn xộn cần được loại bỏ — đặc biệt là
chuyến tàu ồn ào của những cuộc chém giết ở cuối.
Nghĩ theo cách này. Một biểu ngữ thật đáng ngạc nhiên và hiển nhiên nếu bạn không nhìn thấy biểu ngữ
thường xuyên. Vì vậy, hãy sử dụng chúng thật tiết kiệm và chỉ khi lợi ích là đáng kể. Nếu bạn lạm dụng
biểu ngữ, chúng sẽ rơi vào tiếng ồn xung quanh và bị bỏ qua.
Đóng dấu ngoặc nhận xét
Đôi khi các lập trình viên sẽ đặt các nhận xét đặc biệt về việc đóng dấu ngoặc nhọn, như trong Liệt kê 4-6.
Mặc dù điều này có thể có ý nghĩa đối với các hàm dài có cấu trúc lồng nhau sâu, nhưng nó phục vụ
chỉ để lộn xộn các loại chức năng nhỏ và đóng gói mà chúng ta thích. Vì vậy, nếu bạn tìm thấy
bạn muốn đánh dấu kết thúc niềng răng của mình, thay vào đó hãy cố gắng rút ngắn các hàm của bạn.
Liệt kê 4-6
wc.java
lớp công cộng wc {
public static void main (String [] args) {
BufferedReader in = new BufferedReader (InputStreamReader mới (System.in));
Dòng chuỗi;
int lineCount = 0;
int charCount = 0;
int wordCount = 0;
thử {
www.it-ebooks.info

Trang 99
68
Chương 4: Nhận xét
Thuộc tính và Dòng
/ * Được thêm bởi Rick * /
Hệ thống kiểm soát mã nguồn rất tốt trong việc ghi nhớ ai đã thêm cái gì, khi nào.
Không cần phải làm ô nhiễm mã với các dòng nhỏ. Bạn có thể nghĩ rằng ...
ments sẽ hữu ích để giúp những người khác biết cần nói chuyện với ai về mã. Nhưng
thực tế là chúng có xu hướng tồn tại trong nhiều năm, ngày càng kém chính xác
và có liên quan.
Một lần nữa, hệ thống kiểm soát mã nguồn là nơi tốt hơn cho loại thông tin này.
Mã nhận xét ra
Rất ít thực hành đáng sợ như mã bình luận. Đừng làm điều này!
InputStreamResponse response = new InputStreamResponse ();
response.setBody (formatter.getResultStream (), formatter.getByteCount ());
// InputStream kết quảStream = formatter.getResultStream ();
// Trình đọc StreamReader = new StreamReader (resultsStream);
// response.setContent (reader.read (formatter.getByteCount ()));
Những người khác nhìn thấy mã đã nhận xét đó sẽ không có can đảm để xóa nó. Họ sẽ nghĩ
nó có lý do và quá quan trọng để xóa. Vì vậy, mã được nhận xét thu thập như
cặn ở đáy chai rượu dở.
Hãy xem xét điều này từ các commons apache:
this.bytePos = writeBytes (pngIdBytes, 0);
// hdrPos = bytePos;
writeHeader ();
writeResolution ();
// dataPos = bytePos;
if (writeImageData ()) {
writeEnd ();
this.pngBytes = resizeByteArray (this.pngBytes, this.maxPos);
}
while ((line = in.readLine ())! = null) {
lineCount ++;
charCount + = line.length ();
Chuỗi từ [] = line.split ("\\ W");
wordCount + = words.length;
} //trong khi
System.out.println ("wordCount =" + wordCount);
System.out.println ("lineCount =" + lineCount);
System.out.println ("charCount =" + charCount);
} // thử
bắt (IOException e) {
System.err.println ("Lỗi:" + e.getMessage ());
} //nắm lấy
} //chủ yếu
}
Liệt kê 4-6 (tiếp theo)
wc.java
www.it-ebooks.info

Trang 100
69
Nhận xét xấu
khác {
this.pngBytes = null;
}
trả về this.pngBytes;
Tại sao hai dòng mã đó được nhận xét? Chúng có quan trọng không? Họ đã rời đi như
nhắc nhở cho một số thay đổi sắp xảy ra? Hay họ chỉ là những điều tồi tệ mà ai đó đã nhận xét
nhiều năm trước và chỉ đơn giản là không bận tâm đến việc dọn dẹp.
Đã có lúc, vào những năm sáu mươi, khi mã bình luận có thể
hữu ích. Nhưng chúng ta đã có hệ thống kiểm soát mã nguồn tốt từ rất lâu rồi. Những, cái đó
hệ thống sẽ ghi nhớ mã cho chúng tôi. Chúng tôi không cần phải bình luận về nó nữa. Chỉ
xóa mã. Chúng tôi sẽ không mất nó. Lời hứa.
Nhận xét HTML
HTML trong nhận xét mã nguồn là một điều ghê tởm, như bạn có thể biết bằng cách đọc mã
phía dưới. Nó làm cho các nhận xét khó đọc ở một nơi mà chúng phải dễ dàng
đọc — trình soạn thảo / IDE. Nếu nhận xét sẽ được trích xuất bằng một số công cụ (như Javadoc) để
xuất hiện trong một trang Web, thì nó phải là trách nhiệm của công cụ đó, chứ không phải chương trình-
mer, để tô điểm cho các bình luận bằng HTML thích hợp.
/ **
* Nhiệm vụ chạy các bài kiểm tra phù hợp.
* Nhiệm vụ này chạy các bài kiểm tra sức khỏe và công bố kết quả.
* <p />
* <pre>
* Sử dụng:
* & lt; taskdef name = & quot; execute-fitnesse-tests & quot;
* classname = & quot; fitnesse.ant.ExecuteFitnesseTestsTask & quot;
* classpathref = & quot; classpath & quot; / & gt;
* HOẶC LÀ
* & lt; taskdef classpathref = & quot; classpath & quot;
*
resource = & quot; task.properties & quot; / & gt;
* <p />
* & lt; execute-fitnesse-tests
* suitepage = & quot; FitNesse.SuiteAcceptanceTests & quot;
* fitnesseport = & quot; 8082 & quot;
* resultsdir = & quot; $ {results.dir} & quot;
* resultshtmlpage = & quot; fit-results.html & quot;
* classpathref = & quot; classpath & quot; / & gt;
* </pre>
*/
Thông tin phi địa phương
Nếu bạn phải viết bình luận, hãy đảm bảo nó mô tả mã mà nó xuất hiện gần đó. Đừng
cung cấp thông tin toàn hệ thống trong bối cảnh của một bình luận địa phương. Ví dụ, hãy xem xét
bình luận javadoc bên dưới. Ngoài thực tế là nó dư thừa khủng khiếp, nó cũng cung cấp
thông tin về cổng mặc định. Tuy nhiên, chức năng hoàn toàn không kiểm soát
mặc định đó là gì. Nhận xét không mô tả chức năng, nhưng một số khác, khác xa
một phần của hệ thống. Tất nhiên không có gì đảm bảo rằng nhận xét này sẽ được thay đổi
khi mã chứa mặc định bị thay đổi.
www.it-ebooks.info

Trang 101
70
Chương 4: Nhận xét
/ **
* Cổng mà thể dục sẽ chạy. Mặc định là <b> 8082 </b>.
*
* @param fitnessePort
*/
public void setFitnessePort (int fitnessePort)
{
this.fitnessePort = fitnessePort;
}
Quá nhiều thông tin
Không đưa các cuộc thảo luận lịch sử thú vị hoặc các mô tả chi tiết không liên quan vào
bình luận. Nhận xét dưới đây được trích xuất từ một mô-đun được thiết kế để kiểm tra rằng một func-
tion có thể mã hóa và giải mã base64. Ngoài số RFC, ai đó đang đọc cái này
mã không cần thông tin phức tạp có trong nhận xét.
/*
RFC 2045 - Tiện ích mở rộng Thư Internet Đa năng (MIME)
Phần thứ nhất: Định dạng các phần tử tin nhắn Internet
mục 6.8. Mã hóa truyền nội dung Base64
Quá trình mã hóa biểu diễn các nhóm bit đầu vào 24 bit như đầu ra
chuỗi gồm 4 ký tự được mã hóa. Tiếp tục từ trái sang phải, a
Nhóm đầu vào 24-bit được hình thành bằng cách ghép 3 nhóm đầu vào 8-bit.
24 bit này sau đó được coi là 4 nhóm 6 bit ghép nối, mỗi nhóm
trong số đó được dịch thành một chữ số trong bảng chữ cái base64.
Khi mã hóa luồng bit thông qua mã hóa base64, luồng bit
phải được cho là được sắp xếp với bit quan trọng nhất trước.
Tức là, bit đầu tiên trong luồng sẽ là bit bậc cao trong
byte 8 bit đầu tiên và bit thứ tám sẽ là bit bậc thấp trong
byte 8 bit đầu tiên, v.v.
*/
Kết nối không rõ ràng
Mối liên hệ giữa một nhận xét và mã mà nó mô tả phải rõ ràng. Nếu bạn là
gặp khó khăn khi viết bình luận, thì ít nhất bạn muốn người đọc có thể
nhìn vào bình luận và mã và hiểu bình luận đang nói về điều gì.
Ví dụ, hãy xem xét nhận xét này được rút ra từ dấu phẩy apache:
/*
* bắt đầu với một mảng đủ lớn để chứa tất cả các pixel
* (cộng với các byte bộ lọc) và thêm 200 byte cho thông tin tiêu đề
*/
this.pngBytes = new byte [((this.width + 1) * this.height * 3) + 200];
Bộ lọc byte là gì? Nó có liên quan đến +1 không? Hay đến * 3? Cả hai? Một pixel có phải là một byte không? Tại
sao
200? Mục đích của nhận xét là giải thích mã không giải thích chính nó. Thật đáng tiếc
khi một bình luận cần giải thích riêng.
Tiêu đề hàm
Chức năng ngắn không cần mô tả nhiều. Một cái tên được lựa chọn tốt cho một chức năng nhỏ
một điều thường tốt hơn một tiêu đề bình luận.
www.it-ebooks.info

Trang 102
71
Nhận xét xấu
Javadocs trong Mã không công khai
Cũng hữu ích như javadocs dành cho các API công khai, chúng không phù hợp với mã không nhằm mục đích
cho tiêu dùng công cộng. Tạo các trang javadoc cho các lớp và hàm bên trong
hệ thống nói chung không hữu ích và tính hình thức quá lớn của các nhận xét javadoc
nhiều hơn một chút so với sự rắc rối và mất tập trung.
Thí dụ
Tôi đã viết mô-đun trong Liệt kê 4-7 cho XP Immersion đầu tiên . Nó được dự định là một
ví dụ về phong cách viết và bình luận xấu. Kent Beck sau đó đã cấu trúc lại mã này thành một
hình thức dễ chịu hơn nhiều trước mặt vài chục sinh viên nhiệt tình. Sau này tôi thích nghi
ví dụ cho cuốn sách Phát triển phần mềm Agile, Nguyên tắc, Mẫu và Thực tiễn của tôi
và là bài báo đầu tiên về Thợ thủ công của tôi được đăng trên tạp chí Phát triển phần mềm .
Điều tôi thấy thú vị về mô-đun này là đã có lúc nhiều người trong chúng ta
sẽ coi nó là "được ghi chép đầy đủ." Bây giờ chúng ta thấy nó như một mớ hỗn độn nhỏ. Xem cách
nhiều vấn đề bình luận khác nhau mà bạn có thể tìm thấy.
Liệt kê 4-7
GeneratePrimes.java
/ **
* Lớp này tạo các số nguyên tố lên đến một người dùng được chỉ định
* tối đa. Thuật toán được sử dụng là Sieve of Eratosthenes.
* <p>
* Eratosthenes của Cyrene, bc 276 TCN, Cyrene, Libya -
* dc 194, Alexandria. Người đầu tiên tính toán
* chu vi của Trái đất. Cũng được biết đến vì đã làm việc trên
* lịch có năm nhuận và điều hành thư viện ở Alexandria.
* <p>
* Thuật toán khá đơn giản. Cho một mảng các số nguyên
* bắt đầu từ 2. Gạch bỏ tất cả các bội của 2. Tìm tiếp theo
* số nguyên không bị gạch chéo và gạch bỏ tất cả các bội số của nó.
* Lặp lại cho đến khi bạn đã vượt qua căn bậc hai của tối đa
* giá trị.
*
* @author Alphonse
* @version 13 tháng 2, 2002 atp
*/
nhập java.util. *;
public class GeneratePrimes
{
/ **
* @param maxValue là giới hạn tạo.
*/
public static int [] createPrimes (int maxValue)
{
if (maxValue> = 2) // trường hợp hợp lệ duy nhất
{
// khai báo
int s = maxValue + 1; // kích thước của mảng
boolean [] f = new boolean [s];
int i;
www.it-ebooks.info

Trang 103
72
Chương 4: Nhận xét
Trong Liệt kê 4-8, bạn có thể thấy một phiên bản được cấu trúc lại của cùng một mô-đun. Lưu ý rằng việc sử dụng
bình luận bị hạn chế đáng kể. Chỉ có hai nhận xét trong toàn bộ mô-đun.
Cả hai ý kiến đều mang tính chất giải thích.
// khởi tạo mảng thành true.
cho (i = 0; i <s; i ++)
f [i] = true;
// loại bỏ các số không phải số nguyên tố đã biết
f [0] = f [1] = false;
// sàng
int j;
for (i = 2; i <Math.sqrt (s) + 1; i ++)
{
if (f [i]) // nếu tôi không được lai, hãy vượt qua bội số của nó.
{
cho (j = 2 * i; j <s; j + = i)
f [j] = false; // bội số không phải là số nguyên tố
}
}
// có bao nhiêu số nguyên tố?
int count = 0;
cho (i = 0; i <s; i ++)
{
nếu (f [i])
tính ++; // số lượng tăng.
}
int [] primes = new int [count];
// chuyển các số nguyên tố vào kết quả
cho (i = 0, j = 0; i <s; i ++)
{
nếu (f [i])
// nếu nguyên tố
số nguyên tố [j ++] = i;
}
trả về số nguyên tố; // trả về các số nguyên tố
}
else // maxValue <2
trả về new int [0]; // trả về mảng null nếu nhập sai.
}
}
Liệt kê 4-8
PrimeGenerator.java (đã cấu trúc lại)
/ **
* Lớp này tạo các số nguyên tố lên đến một người dùng được chỉ định
* tối đa. Thuật toán được sử dụng là Sieve of Eratosthenes.
* Cho một mảng các số nguyên bắt đầu từ 2:
* Tìm số nguyên chưa được gạch chéo đầu tiên và gạch bỏ tất cả
Liệt kê 4-7 (tiếp theo)
GeneratePrimes.java
www.it-ebooks.info

Trang 104
73
Nhận xét xấu
* bội số. Lặp lại cho đến khi không còn bội số
* trong mảng.
*/
lớp công cộng PrimeGenerator
{
private static boolean [] crossOut;
kết quả private static int [];
public static int [] createPrimes (int maxValue)
{
nếu (maxValue <2)
trả về new int [0];
khác
{
unrossIntegersUpTo (maxValue);
crossOutMultiples ();
putUncrossedIntegersIntoResult ();
trả về kết quả;
}
}
private static void uncrossIntegersUpTo (int maxValue)
{
crossOut = new boolean [maxValue + 1];
for (int i = 2; i <crossOut.length; i ++)
crossOut [i] = false;
}
private static void crossOutMultiples ()
{
int giới hạn = xác địnhIterationLimit ();
for (int i = 2; i <= limit; i ++)
if (notCrossed (i))
crossOutMultiplesOf (i);
}
private static int defineIterationLimit ()
{
// Mọi bội số trong mảng có một thừa số nguyên tố
// nhỏ hơn hoặc bằng giá trị gốc của kích thước mảng,
// để chúng ta không phải gạch bỏ các bội số
// lớn hơn gốc đó.
lặp képLimit = Math.sqrt (crossOut.length);
return (int) iterationLimit;
}
private static void crossOutMultiplesOf (int i)
{
for (int multiple = 2 * i;
nhiều <crossOut.length;
nhiều + = i)
crossOut [nhiều] = true;
}
Liệt kê 4-8 (tiếp theo)
PrimeGenerator.java (đã cấu trúc lại)
www.it-ebooks.info

Trang 105
74
Chương 4: Nhận xét
Dễ dàng cho rằng bình luận đầu tiên là thừa vì nó đọc rất thích
các GeneratePrimes chức năng riêng của mình. Tuy nhiên, tôi nghĩ nhận xét giúp người đọc dễ dàng hơn
thuật toán, vì vậy tôi có xu hướng bỏ nó.
Lập luận thứ hai gần như chắc chắn là cần thiết. Nó giải thích lý do đằng sau
việc sử dụng căn bậc hai làm giới hạn vòng lặp. Tôi không thể tìm thấy tên biến đơn giản nào, cũng không
cấu trúc mã hóa khác nhau đã làm rõ điểm này. Mặt khác, việc sử dụng
căn bậc hai có thể là một tự phụ. Tôi thực sự tiết kiệm được nhiều thời gian bằng cách hạn chế lặp lại
căn bậc hai? Việc tính căn bậc hai có thể mất nhiều thời gian hơn tôi tiết kiệm được không?
Nó đáng để suy nghĩ. Sử dụng căn bậc hai làm giới hạn lặp thỏa mãn C cũ
và hacker ngôn ngữ lắp ráp trong tôi, nhưng tôi không tin rằng điều đó xứng đáng với thời gian và nỗ lực
mà những người khác sẽ sử dụng để hiểu nó.
Thư mục
[KP78]: Kernighan và Plaugher, Các yếu tố của phong cách lập trình , 2d. ed., McGraw-
Hill, 1978.
private static boolean notCrossed (int i)
{
return crossOut [i] == false;
}
private static void putUncrossedIntegersIntoResult ()
{
result = new int [numberOfUncrossedIntegers ()];
for (int j = 0, i = 2; i <crossOut.length; i ++)
if (notCrossed (i))
kết quả [j ++] = i;
}
private static int numberOfUncrossedIntegers ()
{
int count = 0;
for (int i = 2; i <crossOut.length; i ++)
if (notCrossed (i))
tính ++;
số lần trả lại;
}
}
Liệt kê 4-8 (tiếp theo)
PrimeGenerator.java (đã cấu trúc lại)
www.it-ebooks.info

Trang 106
75

5
Định dạng
Khi mọi người nhìn dưới mui xe, chúng tôi muốn họ ấn tượng với sự gọn gàng,
sự hỗ trợ và chú ý đến từng chi tiết mà họ nhận thấy. Chúng tôi muốn họ bị tấn công bởi
trật tự. Chúng tôi muốn lông mày của họ nhướng lên khi họ cuộn qua các mô-đun. Chúng tôi muốn
họ nhận thức rằng các chuyên gia đã và đang làm việc. Nếu thay vào đó, họ thấy một
khối lượng mã trông giống như nó được viết bởi một nhóm thủy thủ say rượu, sau đó họ
có khả năng kết luận rằng sự không chú ý đến chi tiết giống nhau lan tỏa mọi khía cạnh khác của
dự án.
www.it-ebooks.info

Trang 107
76
Chương 5: Định dạng
Bạn nên chú ý rằng mã của bạn được định dạng độc đáo. Bạn nên chọn một bộ
các quy tắc đơn giản chi phối định dạng mã của bạn và sau đó bạn nên áp dụng một cách nhất quán
các quy tắc đó. Nếu bạn đang làm việc trong một nhóm, thì nhóm phải đồng ý với một nhóm
quy tắc định dạng và tất cả các thành viên nên tuân thủ. Thật hữu ích khi có một công cụ tự động
có thể áp dụng các quy tắc định dạng đó cho bạn.
Mục đích của định dạng
Trước hết, chúng ta hãy rõ ràng. Định dạng mã là quan trọng . Nó là quá quan trọng để bỏ qua và
nó là quá quan trọng để đối xử với tôn giáo. Định dạng mã là về giao tiếp và
giao tiếp là trình tự kinh doanh đầu tiên của nhà phát triển chuyên nghiệp.
Có lẽ bạn nghĩ rằng "làm cho nó hoạt động" là đơn đặt hàng đầu tiên của doanh nghiệp đối với
nhà phát triển chuyên nghiệp. Tuy nhiên, bây giờ tôi hy vọng rằng cuốn sách này đã khiến bạn không thể hiểu được
điều đó
ý tưởng. Chức năng bạn tạo hôm nay có cơ hội tốt để thay đổi trong lần tiếp theo
phát hành, nhưng khả năng đọc mã của bạn sẽ có ảnh hưởng sâu sắc đến tất cả các thay đổi
điều đó sẽ được thực hiện. Phong cách mã hóa và khả năng đọc thiết lập các tiền lệ tiếp tục
ảnh hưởng đến khả năng bảo trì và khả năng mở rộng lâu sau khi mã gốc đã được thay đổi
ngoài sự công nhận. Phong cách và kỷ luật của bạn vẫn tồn tại, ngay cả khi mã của bạn không.
Vậy các vấn đề về định dạng giúp chúng ta giao tiếp tốt nhất là gì?
Định dạng dọc
Hãy bắt đầu với kích thước dọc. Một tệp nguồn phải lớn như thế nào? Trong Java, kích thước tệp gần
liên quan đến quy mô lớp học. Chúng ta sẽ nói về quy mô lớp học khi chúng ta nói về các lớp học. Cho
thời điểm chúng ta chỉ cần xem xét kích thước tệp.
Hầu hết các tệp nguồn Java lớn như thế nào? Nó chỉ ra rằng có một loạt các kích thước và
một số khác biệt đáng chú ý trong phong cách. Hình 5-1 cho thấy một số khác biệt đó.
Bảy dự án khác nhau được mô tả. Junit, FitNesse, testNG, Thời gian và Tiền bạc,
JDepend, Ant và Tomcat. Các đường thông qua các hộp hiển thị tối thiểu và tối đa-
độ dài tệp mẹ trong mỗi dự án. Hộp hiển thị khoảng một phần ba (một tiêu chuẩn
độ lệch 1 ) của các tệp. Giữa hộp là giá trị trung bình. Vì vậy, kích thước tệp trung bình trong
Dự án FitNesse có khoảng 65 dòng và khoảng một phần ba số tệp nằm trong khoảng từ 40 đến
Hơn 100 dòng. Tệp lớn nhất trong FitNesse là khoảng 400 dòng và nhỏ nhất là 6 dòng.
Lưu ý rằng đây là thang đo nhật ký, do đó, sự khác biệt nhỏ về vị trí thẳng đứng ngụ ý
chênh lệch lớn về kích thước tuyệt đối.
1. Hộp hiển thị sigma / 2 trên và dưới trung bình. Có, tôi biết rằng phân phối độ dài tệp không bình thường và do đó, stan-
độ lệch dard không chính xác về mặt toán học. Nhưng chúng tôi không cố gắng cho độ chính xác ở đây. Chúng tôi chỉ đang cố gắng cảm nhận.
www.it-ebooks.info

Trang 108
77
Định dạng dọc
Junit, FitNesse và Time and Money bao gồm các tệp tương đối nhỏ. không ai
có trên 500 dòng và hầu hết các tệp đó đều dưới 200 dòng. Tomcat và Ant, trên
mặt khác, có một số tệp dài vài nghìn dòng và gần một nửa đã hết
200 dòng.
Điều đó có ý nghĩa gì với chúng ta? Dường như có thể xây dựng các hệ thống quan trọng
(FitNesse có gần 50.000 dòng) trong số các tệp thường dài 200 dòng, với
giới hạn trên 500. Mặc dù đây không phải là một quy tắc cứng và nhanh, nhưng nó nên được xem xét
rất mong muốn. Các tệp nhỏ thường dễ hiểu hơn các tệp lớn.
Ẩn dụ báo
Hãy nghĩ về một bài báo được viết tốt. Bạn đọc nó theo chiều dọc. Ở trên cùng, bạn mong đợi một
dòng tiêu đề sẽ cho bạn biết câu chuyện về nội dung gì và cho phép bạn quyết định xem nó có
một cái gì đó bạn muốn đọc. Đoạn đầu tiên cho bạn tóm tắt toàn bộ câu chuyện,
ẩn tất cả các chi tiết trong khi cung cấp cho bạn các khái niệm bàn chải rộng. Khi bạn tiếp tục đi xuống-
phường, các chi tiết tăng lên cho đến khi bạn có tất cả ngày tháng, tên, trích dẫn, xác nhận quyền sở hữu và các
vụn vặt.
Chúng tôi muốn một tập tin nguồn giống như một bài báo. Tên phải đơn giản
nhưng giải thích. Bản thân cái tên phải đủ để cho chúng ta biết liệu chúng ta có đang ở trong
đúng mô-đun hay không. Các phần trên cùng của tệp nguồn phải cung cấp mức cao
Hình 5-1
Phân bố độ dài tệp Thang đo LOG (chiều cao hộp = sigma)
www.it-ebooks.info

Trang 109
78
Chương 5: Định dạng
khái niệm và thuật toán. Chi tiết sẽ tăng lên khi chúng ta di chuyển xuống dưới, cho đến khi kết thúc
chúng tôi tìm thấy các chức năng và chi tiết cấp thấp nhất trong tệp nguồn.
Một tờ báo gồm nhiều bài báo; hầu hết đều rất nhỏ. Một số lớn hơn một chút.
Rất ít chứa nhiều văn bản như một trang có thể chứa. Điều này làm cho tờ báo có thể sử dụng được . Nếu
tờ báo chỉ là một câu chuyện dài có chứa sự kết hợp vô tổ chức của các sự kiện,
ngày tháng và tên, sau đó chúng tôi chỉ đơn giản là sẽ không đọc nó.
Sự cởi mở theo chiều dọc giữa các khái niệm
Gần như tất cả mã được đọc từ trái sang phải và từ trên xuống dưới. Mỗi dòng đại diện cho một biểu thức hoặc
một mệnh đề, và mỗi nhóm dòng đại diện cho một ý nghĩ hoàn chỉnh. Những suy nghĩ đó nên
ngăn cách nhau bằng dòng trống.
Ví dụ, hãy xem xét Liệt kê 5-1. Có các dòng trống ngăn cách gói
khai báo, (các) nhập và từng hàm. Quy tắc cực kỳ đơn giản này có một
ảnh hưởng đến bố cục trực quan của mã. Mỗi dòng trống là một dấu hiệu trực quan xác định
một khái niệm mới và riêng biệt. Khi bạn quét xuống danh sách, mắt bạn sẽ bị thu hút bởi
dòng sau một dòng trống.
Việc loại bỏ những dòng trống đó, như trong Liệt kê 5-2, có tác động che khuất đáng kể đối với
khả năng đọc của mã.
Liệt kê 5-1
BoldWidget.java
gói fitnesse.wikitext.widgets;
nhập java.util.regex. *;
lớp công khai BoldWidget mở rộng ParentWidget {
public static final String REGEXP = "'' '. +?' ''";
private static final Pattern pattern = Pattern.compile ("'' '(. +?)' ''",
Pattern.MULTILINE + Pattern.DOTALL
);
public BoldWidget (ParentWidget cha, String text) ném Exception {
super (cha mẹ);
Đối sánh khớp = pattern.matcher (văn bản);
match.find ();
addChildWidgets (match.group (1));
}
public String render () ném Exception {
StringBuffer html = new StringBuffer ("<b>");
html.append (childHtml ()). append ("</b>");
trả về html.toString ();
}
}
www.it-ebooks.info

Trang 110
79
Định dạng dọc
Hiệu ứng này thậm chí còn rõ ràng hơn khi bạn không tập trung vào mắt. Trong ví dụ đầu tiên
các nhóm đường khác nhau bật ra với bạn, trong khi ví dụ thứ hai trông giống như
lộn xộn. Sự khác biệt giữa hai danh sách này là một chút về độ mở dọc.
Mật độ dọc
Nếu tính mở phân tách các khái niệm, thì mật độ dọc ngụ ý sự liên kết chặt chẽ. Vì vậy, dòng
mã có liên quan chặt chẽ với nhau nên xuất hiện dày đặc theo chiều dọc. Chú ý cách vô dụng
các nhận xét trong Liệt kê 5-3 phá vỡ sự liên kết chặt chẽ của hai biến cá thể.
Liệt kê 5-4 dễ đọc hơn nhiều. Nó phù hợp với "mãn nhãn", hoặc ít nhất là nó phù hợp với tôi. Tôi
có thể nhìn vào nó và thấy rằng đây là một lớp có hai biến và một phương thức, mà không cần phải
cử động đầu hoặc mắt nhiều. Danh sách trước buộc tôi phải sử dụng mắt nhiều hơn và
chuyển động đầu để đạt được mức độ hiểu như nhau.
Liệt kê 5-2
BoldWidget.java
gói fitnesse.wikitext.widgets;
nhập java.util.regex. *;
lớp công khai BoldWidget mở rộng ParentWidget {
public static final String REGEXP = "'' '. +?' ''";
private static final Pattern pattern = Pattern.compile ("'' '(. +?)' ''",
Pattern.MULTILINE + Pattern.DOTALL);
public BoldWidget (ParentWidget cha, String text) ném Exception {
super (cha mẹ);
Đối sánh khớp = pattern.matcher (văn bản);
match.find ();
addChildWidgets (match.group (1));}
public String render () ném Exception {
StringBuffer html = new StringBuffer ("<b>");
html.append (childHtml ()). append ("</b>");
trả về html.toString ();
}
}
Liệt kê 5-3
lớp công chúng ReporterConfig {
/ **
* Tên lớp của người nghe báo cáo
*/
private String m_className;
/ **
* Thuộc tính của người nghe báo cáo
*/
Danh sách riêng tư <Thuộc tính> m_properties = new ArrayList <Thuộc tính> ();
public void addProperty (Thuộc tính tài sản) {
m_properties.add (thuộc tính);
}
www.it-ebooks.info

Trang 111
80
Chương 5: Định dạng
Khoảng cách dọc
Bạn đã bao giờ đuổi theo đuôi của mình qua một lớp, nhảy từ chức năng này sang chức năng tiếp theo,
cuộn lên và xuống tệp nguồn, cố gắng tìm hiểu cách các hàm liên quan và
hoạt động, chỉ để bị lạc trong một tổ chuột của sự nhầm lẫn? Bạn đã bao giờ săn tìm chuỗi
kế thừa cho định nghĩa của một biến hoặc hàm? Điều này thật khó chịu vì bạn
cố gắng hiểu những gì hệ thống làm, nhưng bạn đang dành thời gian và trí óc của mình
năng lượng vào việc cố gắng xác định vị trí và ghi nhớ vị trí của các mảnh.
Các khái niệm có liên quan chặt chẽ nên được giữ gần nhau theo chiều dọc [G10].
Rõ ràng quy tắc này không hoạt động đối với các khái niệm thuộc các tệp riêng biệt. Nhưng sau đó chặt chẽ
các khái niệm liên quan không nên được tách thành các tệp khác nhau trừ khi bạn có một
lý do. Thật vậy, đây là một trong những lý do mà các biến được bảo vệ nên tránh.
Đối với những khái niệm có liên quan chặt chẽ đến mức chúng thuộc cùng một tệp nguồn,
sự phân tách theo chiều dọc của chúng phải là thước đo mức độ quan trọng của mỗi loại đối với sự hiểu biết-
khả năng của người kia. Chúng tôi muốn tránh việc buộc người đọc phải xem qua nguồn của chúng tôi
tệp và lớp.
Khai báo biến. Các biến nên được khai báo càng gần với cách sử dụng của chúng thì có thể
chảy máu. Bởi vì các hàm của chúng ta rất ngắn, các biến cục bộ sẽ xuất hiện ở đầu mỗi
, như trong hàm lâu dài này từ Junit4.3.1.
private static void readPreferences () {
InputStream là = null;
thử {
is = new FileInputStream (getPreferencesFile ());
setPreferences (Thuộc tính mới (getPreferences ()));
getPreferences (). load (is);
} catch (IOException e) {
thử {
if (is! = null)
is.close ();
} catch (IOException e1) {
}
}
}
Các biến điều khiển cho vòng lặp thường phải được khai báo trong câu lệnh vòng lặp, như trong
chức năng nhỏ dễ thương từ cùng một nguồn.
Liệt kê 5-4
lớp công chúng ReporterConfig {
private String m_className;
Danh sách riêng tư <Thuộc tính> m_properties = new ArrayList <Thuộc tính> ();
public void addProperty (Thuộc tính tài sản) {
m_properties.add (thuộc tính);
}
www.it-ebooks.info

Trang 112
81
Định dạng dọc
public int countTestCases () {
int count = 0;
cho ( Kiểm tra từng : kiểm tra)
count + = each.countTestCases ();
số lần trả lại;
}
Trong một số trường hợp hiếm hoi, một biến có thể được khai báo ở đầu khối hoặc ngay trước vòng lặp trong
hàm lâu dài. Bạn có thể thấy một biến như vậy trong đoạn mã này trong khoảng thời gian rất dài
chức năng trong TestNG.
...
cho (Kiểm tra XmlTest: m_suite.getTests ()) {
TestRunner tr = m_runnerFactory.newTestRunner (this, test);
tr.addListener (m_textReporter);
m_testRunners.add (tr);
invoker = tr.getInvoker ();
cho (ITestNGMethod m: tr.getBeforeSuiteMethods ()) {
beforeSuiteMethods.put (m.getMethod (), m);
}
cho (ITestNGMethod m: tr.getAfterSuiteMethods ()) {
afterSuiteMethods.put (m.getMethod (), m);
}
}
...
Mặt khác , các biến cá thể nên được khai báo ở trên cùng của lớp. Điều này
không nên tăng khoảng cách theo chiều dọc của các biến này, bởi vì trong một
lớp, chúng được sử dụng bởi nhiều, nếu không phải tất cả, các phương thức của lớp.
Đã có nhiều cuộc tranh luận về việc các biến thể hiện nên đi đâu. Trong C ++ chúng tôi
thường thực hành cái gọi là quy tắc kéo , đặt tất cả các biến phiên bản tại
dưới cùng. Tuy nhiên, quy ước chung trong Java là đặt tất cả chúng ở đầu lớp.
Tôi không có lý do gì để tuân theo bất kỳ quy ước nào khác. Điều quan trọng là đối với biến thể ví dụ-
ables được công bố ở một nơi nổi tiếng. Mọi người nên biết nơi để xem
các khai báo.
Ví dụ, hãy xem xét trường hợp lạ của lớp TestSuite trong JUnit 4.3.1. Tôi có
làm suy yếu rất nhiều lớp này để làm cho điểm. Nếu bạn nhìn xuống một nửa danh sách,
bạn sẽ thấy hai biến thể hiện được khai báo ở đó. Thật khó để che giấu chúng một cách tốt hơn
địa điểm. Ai đó đọc mã này sẽ phải tình cờ xem các khai báo của acci-
vết lõm (như tôi đã làm).
lớp công khai TestSuite triển khai Kiểm tra {
static public Test createTest (Class <? expand TestCase> theClass,
Tên chuỗi) {
...
}
www.it-ebooks.info

Trang 113
82
Chương 5: Định dạng
public static Constructor <? mở rộng TestCase>
getTestConstructor (Lớp <? expand TestCase> theClass)
ném NoSuchMethodException {
...
}
cảnh báo thử nghiệm tĩnh công khai (thông báo chuỗi cuối cùng) {
...
}
private static String exceptionToString (Throwable t) {
...
}
private String fName;
private Vector <Test> fTests = new Vector <Test> (10);
public TestSuite () {
}
public TestSuite (Lớp cuối cùng <? expand TestCase> theClass) {
...
}
public TestSuite (Class <? expand TestCase> theClass, String name) {
...
}
... ... ... ... ...
}
Các chức năng phụ thuộc. Nếu một hàm gọi một hàm khác, chúng phải đóng theo chiều dọc,
và người gọi phải ở trên callee, nếu có thể. Điều này mang lại cho chương trình một sự tự nhiên
lưu lượng. Nếu quy ước được tuân thủ một cách đáng tin cậy, người đọc sẽ có thể tin tưởng rằng hàm định nghĩa-
tions sẽ theo sau ngay sau khi sử dụng. Ví dụ: hãy xem xét đoạn mã từ FitNesse
trong Liệt kê 5-5. Lưu ý cách hàm trên cùng gọi những hàm bên dưới nó và cách chúng lần lượt
gọi những người bên dưới họ. Điều này giúp bạn dễ dàng tìm thấy các hàm được gọi và cải thiện đáng kể
khả năng đọc của toàn bộ mô-đun.
Liệt kê 5-5
WikiPageResponder.java
lớp công khai WikiPageResponder triển khai SecureResponder {
trang WikiPage được bảo vệ;
được bảo vệ PageData pageData;
String pageTitle được bảo vệ;
Yêu cầu được bảo vệ Yêu cầu;
trình thu thập thông tin PageCrawler được bảo vệ;
phản hồi công khai makeResponse (ngữ cảnh FitNesseContext, Yêu cầu yêu cầu)
ném Ngoại lệ {
String pageName = getPageNameOrDefault (yêu cầu, "FrontPage");
www.it-ebooks.info

Trang 114
83
Định dạng dọc
Ngoài ra, đoạn mã này cung cấp một ví dụ hay về việc giữ các hằng số ở mức thích hợp-
đã ăn cấp [G35]. Các "FrontPage" liên tục có thể đã được chôn trong
hàm getPageNameOrDefault , nhưng điều đó sẽ ẩn một hàm nổi tiếng và được mong đợi
không đổi trong một chức năng cấp thấp không thích hợp. Tốt hơn là nên chuyển hằng số đó xuống
từ nơi có ý nghĩa khi biết nó đến nơi thực sự sử dụng nó.
loadPage (tên trang, ngữ cảnh);
if (trang == null)
trả về notFoundResponse (ngữ cảnh, yêu cầu);
khác
trả về makePageResponse (ngữ cảnh);
}
private String getPageNameOrDefault (Yêu cầu yêu cầu, String defaultPageName)
{
String pageName = request.getResource ();
if (StringUtil.isBlank (pageName))
pageName = defaultPageName;
tên trang trở lại;
}
void loadPage được bảo vệ (Tài nguyên chuỗi, ngữ cảnh FitNesseContext)
ném Ngoại lệ {
Đường dẫn WikiPagePath = PathParser.parse (tài nguyên);
creepler = context.root.getPageCrawler ();
crawller.setDeadEndStrategy (VirtualEnabledPageCrawler mới ());
page = crawller.getPage (context.root, path);
if (trang! = null)
pageData = page.getData ();
}
Phản hồi riêng tư notFoundResponse (ngữ cảnh FitNesseContext, Yêu cầu yêu cầu)
ném Ngoại lệ {
trả về NotFoundResponder mới (). makeResponse (ngữ cảnh, yêu cầu);
}
private SimpleResponse makePageResponse (ngữ cảnh FitNesseContext)
ném Ngoại lệ {
pageTitle = PathParser.render (crawller.getFullPath (trang));
Chuỗi html = makeHtml (ngữ cảnh);
Phản hồi SimpleResponse = new SimpleResponse ();
response.setMaxAge (0);
response.setContent (html);
trả lời phản hồi;
}
...
Liệt kê 5-5 (tiếp theo)
WikiPageResponder.java
www.it-ebooks.info

Trang 115
84
Chương 5: Định dạng
Mối quan hệ khái niệm. Một số đoạn mã nhất định muốn
gần các bit khác. Họ có một số
ái lực khái niệm. Mối quan hệ đó càng mạnh thì
khoảng cách dọc ít hơn nên có giữa
chúng.
Như chúng ta đã thấy, mối quan hệ này có thể dựa trên
dựa trên sự phụ thuộc trực tiếp, chẳng hạn như một lệnh gọi hàm-
ing khác, hoặc một hàm sử dụng một biến. Nhưng
có những nguyên nhân khác có thể gây ra ái lực. Sự giống nhau
có thể được gây ra bởi vì một nhóm chức năng
tạo thành một hoạt động tương tự. Hãy xem xét đoạn mã này của
mã từ Junit 4.3.1:
lớp công khai Khẳng định {
static public void khẳng địnhTrue (Thông báo chuỗi, điều kiện boolean) {
nếu (! điều kiện)
fail (tin nhắn);
}
static public void khẳng địnhTrue (điều kiện boolean) {
khẳng địnhTrue (null, điều kiện);
}
static public void khẳng địnhFalse (Thông báo chuỗi, điều kiện boolean) {
khẳng địnhTrue (thông báo,! điều kiện);
}
static public void khẳng địnhFalse (điều kiện boolean) {
khẳng địnhFalse (null, điều kiện);
}
...
Các hàm này có mối quan hệ khái niệm mạnh mẽ vì chúng có chung một cách đặt tên
lược đồ và thực hiện các biến thể của cùng một nhiệm vụ cơ bản. Thực tế là họ gọi nhau là
thứ hai. Ngay cả khi không làm vậy, họ vẫn muốn ở gần nhau.
Đặt hàng dọc
Nói chung, chúng tôi muốn các phụ thuộc của lời gọi hàm trỏ theo hướng đi xuống. Đó là,
một hàm được gọi phải nằm dưới một hàm thực hiện lệnh gọi. 2 Điều này tạo ra một
tốt đẹp dòng chảy xuống mô-đun mã nguồn từ cấp cao xuống cấp thấp.
Như trong các bài báo, chúng tôi mong đợi các khái niệm quan trọng nhất phải xuất hiện trước, và
chúng tôi mong muốn chúng được thể hiện với ít chi tiết gây ô nhiễm nhất. Chúng tôi mong đợi
chi tiết cấp thấp đến cuối cùng. Điều này cho phép chúng tôi đọc lướt các tệp nguồn, lấy ý chính từ
2. Điều này hoàn toàn ngược lại với các ngôn ngữ như Pascal, C và C ++ thực thi các hàm được định nghĩa hoặc ít nhất là được khai báo,
trước khi chúng được sử dụng.
www.it-ebooks.info

Trang 116
85
Định dạng ngang
một số chức năng đầu tiên, mà không cần phải đắm mình vào các chi tiết. Liệt kê 5-5 mới là
tổ chức theo cách này. Có lẽ các ví dụ tốt hơn nữa là Liệt kê 15-5 trên trang 263 và Danh sách-
ing 3-7 trên trang 50.
Định dạng ngang
Độ rộng của một dòng là bao nhiêu? Để trả lời điều đó, chúng ta hãy xem xét mức độ rộng của các đường
gam. Một lần nữa, chúng tôi xem xét bảy dự án khác nhau. Hình 5-2 cho thấy sự phân bố
độ dài dòng của tất cả bảy dự án. Sự đều đặn rất ấn tượng, đặc biệt là ngay xung quanh
45 ký tự. Thật vậy, mọi kích thước từ 20 đến 60 đại diện cho khoảng 1 phần trăm tổng số
số dòng. Đó là 40 phần trăm! Có lẽ 30 phần trăm khác ít hơn 10 ký tự
rộng. Hãy nhớ rằng đây là thang đo nhật ký, do đó, sự xuất hiện tuyến tính của thả xuống trên 80 ký tự
tác nhân thực sự rất quan trọng. Các lập trình viên rõ ràng thích các dòng ngắn.
Điều này cho thấy rằng chúng ta nên cố gắng giữ cho lời thoại của mình ngắn gọn. Giới hạn Hollerith cũ của
80 là một chút tùy tiện, và tôi không phản đối các dòng vượt ra ngoài 100 hoặc thậm chí 120. Nhưng
ngoài ra có lẽ chỉ là bất cẩn.
Tôi đã từng tuân theo quy tắc mà bạn không bao giờ được phải cuộn sang phải. Nhưng màn hình
quá rộng so với ngày nay và các lập trình viên trẻ hơn có thể thu nhỏ phông chữ quá nhỏ
Hình 5-2
Phân phối độ rộng dòng trong Java
www.it-ebooks.info

Trang 117
86
Chương 5: Định dạng
mà họ có thể nhận được 200 ký tự trên màn hình. Đừng làm vậy. Cá nhân tôi đặt giới hạn của mình
ở 120.
Mật độ và độ mở ngang
Chúng tôi sử dụng khoảng trắng theo chiều ngang để liên kết những thứ có liên quan chặt chẽ và tách rời
những thứ có liên quan yếu hơn. Hãy xem xét chức năng sau:
private void MeasureLine (Dòng chuỗi) {
lineCount ++;
int lineSize = line.length ();
totalChars + = lineSize;
lineWidthHistogram.addLine (lineSize, lineCount);
recordWidestLine (lineSize);
}
Tôi bao quanh các toán tử gán với khoảng trắng để làm nổi bật chúng. Chuyển nhượng
câu lệnh có hai yếu tố chính và khác biệt: vế trái và vế phải. Các
không gian làm cho sự tách biệt đó trở nên rõ ràng.
Mặt khác, tôi không đặt dấu cách giữa tên hàm và phần mở
dấu ngoặc đơn. Điều này là do hàm và các đối số của nó có liên quan chặt chẽ với nhau. Dấu phân cách-
ăn vào làm cho chúng có vẻ rời rạc thay vì dính liền. Tôi tách các đối số trong
dấu ngoặc gọi hàm để nhấn mạnh dấu phẩy và cho thấy rằng các đối số
tách rời.
Một cách sử dụng khác cho khoảng trắng là làm nổi bật mức độ ưu tiên của các toán tử.
public class bậc hai {
public static root kép1 (double a, double b, double c) {
định thức kép = định thức (a, b, c);
return (-b + Math.sqrt (định thức)) / (2 * a);
}
public static double root2 (int a, int b, int c) {
định thức kép = định thức (a, b, c);
return (-b - Math.sqrt (định thức)) / (2 * a);
}
định thức kép tĩnh private (double a, double b, double c) {
return b * b - 4 * a * c;
}
}
Chú ý cách đọc các phương trình độc đáo. Các yếu tố không có khoảng trắng giữa chúng
vì chúng được ưu tiên cao. Các thuật ngữ được phân tách bằng khoảng trắng vì add-
tion và phép trừ được ưu tiên thấp hơn.
Thật không may, hầu hết các công cụ để định dạng lại mã không được ưu tiên
toán tử và áp đặt cùng một khoảng cách xuyên suốt. Vì vậy, khoảng cách tinh tế như những
hiển thị ở trên có xu hướng bị mất sau khi bạn định dạng lại mã.
www.it-ebooks.info

Trang 118
87
Định dạng ngang
Căn chỉnh theo chiều ngang
Khi tôi là một lập trình viên hợp ngữ, 3 tôi đã sử dụng căn chỉnh ngang để làm nổi bật
cấu trúc nhất định. Khi tôi bắt đầu viết mã bằng C, C ++ và cuối cùng là Java, tôi tiếp tục thử
để xếp hàng tất cả các tên biến trong một tập hợp các khai báo hoặc tất cả các giá trị trong một tập hợp các phép
gán-
phát biểu đề cập. Mã của tôi có thể trông như thế này:
public class FitNesseExpediter triển khai ResponseSender
{
ổ cắm riêng
ổ cắm;
đầu vào InputStream riêng tư;
đầu ra OutputStream riêng;
Yêu cầu riêng tư
yêu cầu;
phản hồi riêng tư
phản ứng;
ngữ cảnh FitNesseContext riêng tư;
được bảo vệ lâu dài
requestParsingTimeLimit;
tư nhân dài
yêu cầu;
tư nhân dài
requestParsingDeadline;
boolean riêng
hasError;
public FitNesseExpediter (Socket
S,
Ngữ cảnh FitNesseContext) ném Exception
{
this.context =
bối cảnh;
ổ cắm =
S;
đầu vào =
s.getInputStream ();
đầu ra =
s.getOutputStream ();
requestParsingTimeLimit = 10000;
}
Tuy nhiên, tôi nhận thấy rằng kiểu căn chỉnh này không hữu ích. Sự liên kết dường như
nhấn mạnh những điều sai trái và dẫn mắt tôi ra khỏi ý định thực sự. Ví dụ, trong
danh sách khai báo ở trên, bạn muốn đọc danh sách tên biến với-
ra ngoài xem xét các loại của họ. Tương tự như vậy, trong danh sách các câu lệnh gán, bạn muốn
nhìn xuống danh sách các giá trị mà không bao giờ nhìn thấy toán tử gán. Để làm cho vấn đề
tệ hơn, các công cụ định dạng lại tự động thường loại bỏ loại căn chỉnh này.
Vì vậy, cuối cùng, tôi không làm loại chuyện này nữa. Ngày nay tôi thích không có dấu
các khai báo và nhiệm vụ, như được hiển thị bên dưới, vì chúng chỉ ra một điều quan trọng
hiệu lực. Nếu tôi có những danh sách dài cần được căn chỉnh, vấn đề là độ dài của danh sách , không phải
sự thiếu liên kết. Độ dài của danh sách khai báo trong FitNesseExpediter bên dưới
gợi ý rằng lớp này nên được tách ra.
public class FitNesseExpediter triển khai ResponseSender
{
ổ cắm Socket riêng;
đầu vào InputStream riêng tư;
đầu ra OutputStream riêng;
yêu cầu Yêu cầu riêng tư;
3. Tôi đang đùa ai vậy? Tôi vẫn là một lập trình viên hợp ngữ. Bạn có thể đưa cậu bé ra khỏi kim loại, nhưng bạn không thể
lấy kim loại ra khỏi cậu bé!
www.it-ebooks.info

Trang 119
88
Chương 5: Định dạng
phản hồi Response riêng tư;
ngữ cảnh FitNesseContext riêng tư;
request dài được bảo vệParsingTimeLimit;
yêu cầu dài riêng tư;
yêu cầu dài riêng tưParsingDeadline;
boolean riêng hasError;
public FitNesseExpediter (Socket s, FitNesseContext context) ném Exception
{
this.context = context;
ổ cắm = s;
input = s.getInputStream ();
output = s.getOutputStream ();
requestParsingTimeLimit = 10000;
}
Thụt lề
Tệp nguồn là một hệ thống phân cấp giống như một đường viền. Có thông tin liên quan đến
gửi một cách tổng thể, cho các lớp riêng lẻ trong tệp, cho các phương thức trong các lớp,
tới các khối trong các phương thức và đệ quy tới các khối bên trong các khối. Mỗi
cấp của cấu trúc phân cấp này là phạm vi mà các tên có thể được khai báo và trong đó khai báo-
tions và các câu lệnh thực thi được diễn giải.
Để hiển thị phân cấp phạm vi này, chúng tôi thụt lề các dòng mã nguồn trong pro-
phần vào vị trí của họ trong nghiên cứu. Các câu lệnh ở cấp độ của tệp, chẳng hạn như hầu hết
khai báo lớp, hoàn toàn không thụt lề. Các phương thức trong một lớp được thụt lề một cấp
ở bên phải của lớp. Việc triển khai các phương pháp đó được thực hiện ở một cấp độ để
quyền của khai báo phương thức. Triển khai khối được thực hiện một cấp
ở bên phải khối chứa của chúng, v.v.
Các lập trình viên chủ yếu dựa vào sơ đồ thụt lề này. Họ trực quan xếp hàng trên
bên trái để xem phạm vi họ xuất hiện. Điều này cho phép họ nhanh chóng nhảy qua các phạm vi,
chẳng hạn như triển khai các câu lệnh if hoặc while , không liên quan đến
tình hình. Họ quét bên trái để tìm các khai báo phương thức mới, các biến mới và thậm chí
các lớp học. Nếu không có thụt lề, các chương trình sẽ hầu như không thể đọc được bởi con người.
Hãy xem xét các chương trình sau giống hệt nhau về mặt cú pháp và ngữ nghĩa:
public class FitNesseServer triển khai SocketServer {private FitNesseContext
bối cảnh; public FitNesseServer (ngữ cảnh FitNesseContext) {this.context =
bối cảnh; } public void serve (Socket s) {serve (s, 10000); } khoảng trống công cộng
serve (Socket s, long requestTimeout) {try {FitNesseExpediter sender = new
FitNesseExpediter (s, ngữ cảnh);
sender.setRequestParsingTimeLimit (requestTimeout); sender.start (); }
bắt (Ngoại lệ e) {e.printStackTrace (); }}}
-----
public class FitNesseServer triển khai SocketServer {
ngữ cảnh FitNesseContext riêng tư;
www.it-ebooks.info

Trang 120
89
Định dạng ngang
public FitNesseServer (ngữ cảnh FitNesseContext) {
this.context = context;
}
public void serve (Socket s) {
phục vụ (s, 10000);
}
giao bóng trống công khai (Socket s, long requestTimeout) {
thử {
FitNesseExpediter sender = new FitNesseExpediter (s, context);
sender.setRequestParsingTimeLimit (requestTimeout);
sender.start ();
}
bắt (Ngoại lệ e) {
e.printStackTrace ();
}
}
}
Mắt của bạn có thể nhanh chóng nhận ra cấu trúc của tệp được thụt lề. Bạn gần như có thể ngay lập tức
xác định các biến, hàm tạo, trình truy cập và phương thức. Chỉ mất vài giây để thực-
ize rằng đây là một số loại giao diện người dùng đơn giản cho một ổ cắm, có thời gian chờ. Người không đồng ý
phiên bản, tuy nhiên, hầu như không thể xuyên thủng nếu không có nghiên cứu kỹ lưỡng.
Ngắt thụt lề. Đôi khi bạn muốn phá vỡ quy tắc thụt lề viết tắt
nếu báo cáo, viết tắt trong khi vòng, hoặc các chức năng ngắn. Bất cứ khi nào tôi không chịu nổi điều này
sự cám dỗ, tôi hầu như luôn quay lại và đặt thụt lề trở lại. Vì vậy, tôi tránh col-
phạm vi mất hiệu lực xuống một dòng như thế này:
public class CommentWidget mở rộng TextWidget
{
public static final String REGEXP = "^ # [^ \ r \ n] * (?: (?: \ r \ n) | \ n | \ r)?";
public CommentWidget (ParentWidget cha, String text) {super (parent, text);}
public String render () ném Exception {return ""; }
}
Thay vào đó, tôi thích mở rộng và thụt lề các phạm vi, như thế này:
public class CommentWidget mở rộng TextWidget {
public static final String REGEXP = "^ # [^ \ r \ n] * (?: (?: \ r \ n) | \ n | \ r)?";
public CommentWidget (ParentWidget cha, Chuỗi văn bản) {
super (cha, text);
}
public String render () ném Exception {
trở về "";
}
}
www.it-ebooks.info

Trang 121
90
Chương 5: Định dạng
Phạm vi giả
Đôi khi cơ thể của một trong khi hoặc cho tuyên bố là một hình nộm, như hình dưới đây. Tôi không thích
những loại cấu trúc này và cố gắng tránh chúng. Khi tôi không thể tránh chúng, tôi đảm bảo rằng
phần thân giả được thụt vào đúng cách và được bao quanh bởi các thanh giằng. Tôi không thể cho bạn biết làm thế
nào
nhiêu lần tôi đã bị lừa bởi một dấu chấm phẩy âm thầm ngồi ở phần cuối của một trong khi vòng lặp trên
cùng một dòng. Trừ khi bạn hiển thị dấu chấm phẩy đó bằng cách thụt lề trên dòng riêng, nếu không
quá khó để nhìn thấy.
while (dis.read (buf, 0, readBufferSize)! = -1)
;
Nội quy Đội
Tiêu đề của phần này là một vở kịch về
từ ngữ. Mỗi lập trình viên đều có
quy tắc định dạng yêu thích, nhưng nếu anh ta làm việc
trong một đội, sau đó là các quy tắc của đội.
Một nhóm các nhà phát triển nên đồng ý
dựa trên một kiểu định dạng duy nhất và sau đó
mọi thành viên của nhóm đó nên sử dụng
phong cách đó. Chúng tôi muốn phần mềm có một
phong cách nhất quán. Chúng tôi không muốn nó có vẻ đã được viết bởi một số người không đồng ý
các cá nhân.
Khi tôi bắt đầu dự án FitNesse vào năm 2002, tôi đã ngồi lại với nhóm để làm việc
tạo ra một phong cách mã hóa. Quá trình này mất khoảng 10 phút. Chúng tôi quyết định nơi chúng tôi sẽ niềng răng,
Kích thước thụt lề của chúng ta sẽ như thế nào, cách chúng ta đặt tên cho các lớp, biến và phương thức và
vân vân. Sau đó, chúng tôi mã hóa các quy tắc đó thành trình định dạng mã của IDE của chúng tôi và đã mắc kẹt
với họ kể từ đó. Đây không phải là những quy tắc mà tôi thích; chúng là những quy tắc được quyết định bởi
đội. Là một thành viên của nhóm đó, tôi đã theo dõi họ khi viết mã trong FitNesse
dự án.
Hãy nhớ rằng, một hệ thống phần mềm tốt bao gồm một tập hợp các tài liệu đọc
độc đáo. Họ cần phải có một phong cách nhất quán và trôi chảy. Người đọc cần có khả năng
tin tưởng rằng các cử chỉ định dạng mà họ đã thấy trong một tệp nguồn sẽ có nghĩa giống nhau
điều ở những người khác. Điều cuối cùng chúng tôi muốn làm là tăng thêm độ phức tạp cho mã nguồn bằng cách
viết nó trong một mớ hỗn độn của các phong cách cá nhân khác nhau.
Quy tắc định dạng của Uncle Bob
Cá nhân tôi sử dụng các quy tắc rất đơn giản và được minh họa bằng mã trong Liệt kê 5-6.
Hãy coi đây là một ví dụ về cách mã tạo ra tài liệu chuẩn mã hóa tốt nhất.
www.it-ebooks.info

Trang 122
91
Quy tắc định dạng của Uncle Bob
Liệt kê 5-6
CodeAnalyzer.java
public class CodeAnalyzer triển khai JavaFileAnalysis {
int lineCount riêng tư;
private int maxLineWidth;
private int widestLineNumber;
private LineWidthHistogram lineWidthHistogram;
private int totalChars;
public CodeAnalyzer () {
lineWidthHistogram = new LineWidthHistogram ();
}
public static List <File> findJavaFiles (File parentDirectory) {
Danh sách các tệp <Tập tin> = new ArrayList <Tập tin> ();
findJavaFiles (parentDirectory, các tệp);
trả lại tập tin;
}
private static void findJavaFiles (File parentDirectory, List <File> các tệp) {
for (Tệp tệp: parentDirectory.listFiles ()) {
if (file.getName (). endWith (". java"))
files.add (tập tin);
else if (file.isDirectory ())
findJavaFiles (tệp, các tệp);
}
}
public void analysisFile (File javaFile) ném Exception {
BufferedReader br = new BufferedReader (FileReader mới (javaFile));
Dòng chuỗi;
while ((line = br.readLine ())! = null)
MeasureLine (dòng);
}
private void MeasureLine (Dòng chuỗi) {
lineCount ++;
int lineSize = line.length ();
totalChars + = lineSize;
lineWidthHistogram.addLine (lineSize, lineCount);
recordWidestLine (lineSize);
}
private void recordWidestLine (int lineSize) {
if (lineSize> maxLineWidth) {
maxLineWidth = lineSize;
widestLineNumber = lineCount;
}
}
public int getLineCount () {
return lineCount;
}
public int getMaxLineWidth () {
trả về maxLineWidth;
}
www.it-ebooks.info

Trang 123
92
Chương 5: Định dạng
public int getWidestLineNumber () {
trả về widestLineNumber;
}
public LineWidthHistogram getLineWidthHistogram () {
trả về dòngWidthHistogram;
}
public double getMeanLineWidth () {
return (gấp đôi) totalChars / lineCount;
}
public int getMedianLineWidth () {
Integer [] sortedWidths = getSortedWidths ();
int cum cumlineCount = 0;
for (int width: sortedWidths) {
cum Tích lũyLineCount + = lineCountForWidth (chiều rộng);
if (cummentalLineCount> lineCount / 2)
chiều rộng trả về;
}
ném lỗi mới ("Không thể vào đây");
}
private int lineCountForWidth (int width) {
return lineWidthHistogram.getLinesforWidth (width) .size ();
}
private Integer [] getSortedWidths () {
Đặt <Integer> widths = lineWidthHistogram.getWidths ();
Integer [] sortedWidths = (widths.toArray (new Integer [0]));
Arrays.sort (sortedWidths);
trả về sortedWidths;
}
}
Liệt kê 5-6 (tiếp theo)
CodeAnalyzer.java
www.it-ebooks.info

Trang 124
93

6
Đối tượng và cấu trúc dữ liệu
Có một lý do mà chúng tôi giữ các biến của mình ở chế độ riêng tư. Chúng tôi không muốn bất cứ ai khác phụ thuộc
về họ. Chúng tôi muốn giữ quyền tự do thay đổi loại hoặc cách triển khai của họ theo ý thích
hoặc một sự thúc đẩy. Vậy thì tại sao nhiều lập trình viên lại tự động thêm getters và setters
đối với các đối tượng của họ, hiển thị các biến riêng tư của họ như thể chúng là công khai?
Trừu tượng dữ liệu
Hãy xem xét sự khác biệt giữa Liệt kê 6-1 và Liệt kê 6-2. Cả hai đều đại diện cho dữ liệu của một
điểm trên mặt phẳng Descartes. Tuy nhiên, một người tiết lộ việc triển khai nó và người kia
xin giấu nó đi.
www.it-ebooks.info

Trang 125
94
Chương 6: Đối tượng và cấu trúc dữ liệu
Điều tuyệt vời về Liệt kê 6-2 là không có cách nào bạn có thể biết liệu
thực hiện theo hình chữ nhật hoặc tọa độ cực. Nó có thể là không! Và tuy nhiên
giao diện vẫn đại diện cho một cấu trúc dữ liệu không thể nhầm lẫn.
Nhưng nó không chỉ đại diện cho một cấu trúc dữ liệu. Các phương thức thực thi quyền truy cập
chính sách. Bạn có thể đọc các tọa độ riêng lẻ một cách độc lập, nhưng bạn phải đặt coordi-
tự nhiên với nhau như một hoạt động nguyên tử.
Mặt khác, Liệt kê 6-1 được triển khai rất rõ ràng trong các tọa độ hình chữ nhật,
và nó buộc chúng ta phải thao tác các tọa độ đó một cách độc lập. Điều này làm lộ triển khai-
sự. Thật vậy, nó sẽ hiển thị việc triển khai ngay cả khi các biến là riêng tư và chúng tôi
đã sử dụng getters và setters biến đơn.
Ẩn triển khai không chỉ là vấn đề đặt một lớp chức năng giữa
các biến. Ẩn thực hiện là về sự trừu tượng! Một lớp học không chỉ đơn giản
đẩy các biến của nó ra thông qua getters và setters. Thay vào đó nó để lộ các giao diện trừu tượng
cho phép người dùng thao tác bản chất của dữ liệu mà không cần biết
thực hiện.
Xem xét Liệt kê 6-3 và Liệt kê 6-4. Đầu tiên sử dụng các thuật ngữ cụ thể để giao tiếp
mức nhiên liệu của một chiếc xe, trong khi mức thứ hai làm như vậy với phần trăm trừu tượng.
Trong trường hợp cụ thể, bạn có thể chắc chắn rằng đây chỉ là những người truy cập các biến. bên trong
trường hợp trừu tượng bạn không có manh mối nào về hình thức của dữ liệu.
Liệt kê 6-1
Điểm bê tông
lớp công cộng Điểm {
công kép x;
công đôi y;
}
Liệt kê 6-2
Điểm trừu tượng
giao diện công cộng Điểm {
kép getX ();
nhân đôi getY ();
void setCartesian (double x, double y);
kép getR ();
nhân đôi getTheta ();
void setPolar (double r, double theta);
}
Liệt kê 6-3
Xe bê tông
giao diện công cộng Phương tiện {
double getFuelTankCapacityInGallons ();
double getGallonsOfGasoline ();
}
www.it-ebooks.info

Trang 126
95
Chống đối xứng dữ liệu / đối tượng
Trong cả hai trường hợp trên, lựa chọn thứ hai là thích hợp hơn. Chúng tôi không muốn tiết lộ
chi tiết dữ liệu của chúng tôi. Thay vào đó chúng tôi muốn diễn đạt dữ liệu của mình bằng các thuật ngữ trừu
tượng. Đây không phải là
chỉ được thực hiện bằng cách sử dụng các giao diện và / hoặc bộ chuyển đổi và bộ định tuyến. Nhu cầu suy nghĩ
nghiêm túc
được đưa vào một cách tốt nhất để biểu diễn dữ liệu mà một đối tượng chứa. Lựa chọn tồi tệ nhất là
để thêm getters và setters.
Chống đối xứng dữ liệu / đối tượng
Hai ví dụ này cho thấy sự khác biệt giữa các đối tượng và cấu trúc dữ liệu. Đối tượng ẩn
dữ liệu của họ đằng sau các phần trừu tượng và hiển thị các hàm hoạt động trên dữ liệu đó. Chuỗi dữ liệu-
chắc chắn để lộ dữ liệu của họ và không có chức năng có ý nghĩa. Quay lại và đọc lại.
Lưu ý bản chất bổ sung của hai định nghĩa. Chúng là những mặt đối lập ảo. Điều này
sự khác biệt có vẻ tầm thường, nhưng nó có ý nghĩa sâu rộng.
Ví dụ, hãy xem xét ví dụ về hình dạng thủ tục trong Liệt kê 6-5. các Geometry
lớp hoạt động trên ba lớp hình dạng. Các lớp hình dạng là cấu trúc dữ liệu đơn giản
mà không có bất kỳ hành vi nào. Tất cả các hành vi nằm trong lớp Hình học.
Liệt kê 6-4
Xe trừu tượng
giao diện công cộng Phương tiện {
double getPercentFuelRemaining ();
}
Liệt kê 6-5
Hình dạng thủ tục
Hạng công cộng Quảng trường {
public Point topLeft;
công đôi bên;
}
lớp công cộng Rectangle {
public Point topLeft;
chiều cao đôi công;
chiều rộng đôi công cộng;
}
vòng kết nối lớp công khai {
trung tâm điểm công cộng;
bán kính kép công cộng;
}
Hình học lớp công cộng {
công kép PI = 3,141592653589793;
khu vực kép công cộng (Hình dạng đối tượng) ném NoSuchShapeException
{
if (shape instanceof Square) {
Hình vuông s = (Hình vuông);
return s.side * s.side;
}
www.it-ebooks.info

Trang 127
96
Chương 6: Đối tượng và cấu trúc dữ liệu
Các lập trình viên hướng đối tượng có thể nhăn mũi vì điều này và phàn nàn rằng nó
là thủ tục — và họ sẽ đúng. Nhưng những lời chế nhạo có thể không được bảo đảm. Cân nhắc những gì
sẽ xảy ra nếu một hàm perimeter () được thêm vào Geometry . Các lớp hình dạng sẽ
không bị ảnh hưởng! Bất kỳ lớp nào khác phụ thuộc vào các hình dạng cũng sẽ không bị ảnh hưởng!
Mặt khác, nếu tôi thêm một hình dạng mới, tôi phải thay đổi tất cả các chức năng trong Hình học thành
đối phó với nó. Một lần nữa, hãy đọc lại điều đó. Lưu ý rằng hai điều kiện là đường kính
phản đối.
Bây giờ hãy xem xét giải pháp hướng đối tượng trong Liệt kê 6-6. Đây là phương thức area ()
đa hình. Không cần lớp Hình học. Vì vậy, nếu tôi thêm một hình dạng mới, không có hình dạng nào hiện có
các chức năng bị ảnh hưởng, nhưng nếu tôi thêm một chức năng mới, tất cả các hình dạng phải được thay đổi! 1
else if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) hình dạng;
trả về r.height * r.width;
}
else if (shape instanceof Circle) {
Hình tròn c = (Hình tròn);
trả về PI * c.radius * c.radius;
}
ném NoSuchShapeException () mới;
}
}
Liệt kê 6-6
Hình dạng đa hình
public class Square triển khai Shape {
private Point topLeft;
hai mặt riêng tư;
khu vực đôi công cộng () {
return side * bên;
}
}
public class Rectangle thực hiện Shape {
private Point topLeft;
chiều cao gấp đôi tư nhân;
tư nhân đôi chiều rộng;
khu vực đôi công cộng () {
trả về chiều cao * chiều rộng;
}
}
1. Có nhiều cách để giải quyết vấn đề này mà các nhà thiết kế hướng đối tượng có kinh nghiệm đều biết: V ISITOR , hoặc dual- send , for
thí dụ. Nhưng các kỹ thuật này mang theo chi phí của riêng chúng và thường trả lại cấu trúc của một chương trình thủ tục.
Liệt kê 6-5 (tiếp theo)
Hình dạng thủ tục
www.it-ebooks.info

Trang 128
97
Định luật Demeter
Một lần nữa, chúng ta thấy bản chất bổ sung của hai định nghĩa này; họ là ảo
đối diện! Điều này cho thấy sự phân đôi cơ bản giữa các đối tượng và cấu trúc dữ liệu:
Mã thủ tục (mã sử dụng cấu trúc dữ liệu) giúp dễ dàng thêm các chức năng mới mà không cần
thay đổi cấu trúc dữ liệu hiện có. Mặt khác, mã OO giúp bạn dễ dàng thêm
các lớp mới mà không thay đổi các chức năng hiện có.
Phần bổ sung cũng đúng:
Mã thủ tục gây khó khăn cho việc thêm cấu trúc dữ liệu mới vì tất cả các hàm phải
thay đổi. Mã OO gây khó khăn cho việc thêm các chức năng mới vì tất cả các lớp phải thay đổi.
Vì vậy, những điều khó đối với OO dễ dàng về thủ tục, và những điều
thủ tục khó thì dễ OO!
Trong bất kỳ hệ thống phức tạp nào, sẽ có lúc chúng ta muốn thêm dữ liệu mới
thay vì các chức năng mới. Đối với những trường hợp này đối tượng và OO là thích hợp nhất. Trên
mặt khác, cũng sẽ có lúc chúng tôi muốn thêm các chức năng mới thay vì
sang các kiểu dữ liệu. Trong trường hợp đó mã thủ tục và cấu trúc dữ liệu sẽ thích hợp hơn.
Các lập trình viên trưởng thành biết rằng ý tưởng rằng mọi thứ là một đối tượng là một huyền thoại . Một số-
lần bạn thực sự làm muốn cấu trúc dữ liệu đơn giản với các thủ tục hoạt động trên chúng.
Định luật Demeter
Có một kinh nghiệm học nổi tiếng được gọi là Định luật Demeter  2 nói rằng một mô-đun không nên
biết về các phần bên trong của các đối tượng mà nó thao tác. Như chúng ta đã thấy trong phần trước, các đối tượng
ẩn dữ liệu của họ và để lộ hoạt động. Điều này có nghĩa là một đối tượng không nên để lộ
cấu trúc bên trong thông qua trình truy cập bởi vì làm như vậy là để lộ ra, thay vì ẩn,
cơ cấu nội bộ.
Chính xác hơn, Định luật Demeter nói rằng một phương thức f của lớp C chỉ nên gọi
các phương pháp này:
•C
• Một đối tượng được tạo bởi f
public class Circle triển khai Shape {
trung tâm điểm tư nhân;
bán kính đôi riêng;
công kép PI = 3,141592653589793;
khu vực đôi công cộng () {
trả về PI * radius * radius;
}
}
2. http://en.wikipedia.org/wiki/Law_of_Demete r
Liệt kê 6-6 (tiếp theo)
Hình dạng đa hình
www.it-ebooks.info

Trang 129
98
Chương 6: Đối tượng và cấu trúc dữ liệu
• Một đối tượng được truyền làm đối số cho f
• Một đối tượng được giữ trong một biến thể hiện của C
Phương thức không được gọi các phương thức trên các đối tượng được trả về bởi bất kỳ
các chức năng được phép. Nói cách khác, nói chuyện với bạn bè, không phải với người lạ.
Đoạn mã 3 sau đây dường như vi phạm Định luật Demeter (trong số những điều khác)
bởi vì nó gọi hàm getScratchDir () trên giá trị trả về của getOptions () và sau đó
gọi getAbsolutePath () trên giá trị trả về của getScratchDir () .
final String outputDir = ctxt.getOptions (). getScratchDir (). getAbsolutePath ();
Xác tàu bị đắm
Loại mã này thường được gọi là xác tàu vì nó trông giống như một đoàn tàu ghép lại
ô tô. Các chuỗi cuộc gọi như thế này thường được coi là phong cách cẩu thả và nên
tránh được [G36]. Tốt nhất nên chia chúng ra như sau:
Tùy chọn opts = ctxt.getOptions ();
Tệp xướcDir = opts.getScratchDir ();
final String outputDir = xướcDir.getAbsolutePath ();
Hai đoạn mã này có phải là viola-
tấn của Định luật Demeter? Chắc chắn
mô-đun chứa biết rằng
đối tượng ctxt chứa các tùy chọn,
tain một thư mục đầu, có một
con đường tuyệt đối. Đó là rất nhiều kiến thức
cho một chức năng cần biết. Cuộc gọi
chức năng biết cách điều hướng qua
rất nhiều đối tượng khác nhau.
Việc này có vi phạm Demeter hay không phụ thuộc vào việc có hay không ctxt , Options và
ScratchDir là các đối tượng hoặc cấu trúc dữ liệu. Nếu chúng là các đối tượng, thì cấu trúc bên trong của chúng
nên được giấu kín hơn là để lộ ra ngoài, và do đó, kiến thức về nội tạng của họ là một tiếng vĩ cầm rõ ràng
định luật Demeter. Mặt khác, nếu ctxt , Options và ScratchDir chỉ là
cấu trúc dữ liệu không có hành vi, sau đó chúng tự nhiên bộc lộ cấu trúc bên trong và
Demeter không áp dụng.
Việc sử dụng các chức năng của trình truy cập gây nhầm lẫn cho vấn đề. Nếu mã đã được viết là fol-
thấp, thì chúng tôi có thể sẽ không hỏi về các vi phạm của Demeter.
final String outputDir = ctxt.options.scratchDir.absolutePath;
Vấn đề này sẽ đỡ khó hiểu hơn rất nhiều nếu cấu trúc dữ liệu chỉ đơn giản là có các biến công khai
và không có hàm, trong khi các đối tượng có các biến riêng và hàm công khai. Tuy nhiên,
3. Tìm thấy ở đâu đó trong khuôn khổ apache.
www.it-ebooks.info

Trang 130
99
Định luật Demeter
có các khuôn khổ và tiêu chuẩn (ví dụ: "đậu") yêu cầu dữ liệu đơn giản
cấu trúc có bộ truy cập và bộ đột biến.
Con lai
Sự nhầm lẫn này đôi khi dẫn đến các cấu trúc lai không may là một nửa đối tượng và
nửa cấu trúc dữ liệu. Chúng có các chức năng thực hiện những việc quan trọng và chúng cũng có
các biến công khai hoặc các trình truy cập công khai và các biến đổi, cho tất cả các mục đích và mục đích, tạo ra
các biến private là public, dụ các hàm bên ngoài khác sử dụng các biến đó
cách một chương trình thủ tục sẽ sử dụng cấu trúc dữ liệu. 4
Sự lai tạp như vậy làm cho khó thêm các chức năng mới nhưng cũng khó thêm dữ liệu mới
cấu trúc. Họ là tệ nhất của cả hai thế giới. Tránh tạo ra chúng. Chúng là dấu hiệu của một
thiết kế lộn xộn mà tác giả không chắc chắn về — hoặc tệ hơn, không biết — liệu họ có cần
bảo vệ khỏi các chức năng hoặc loại.
Cấu trúc ẩn
Điều gì sẽ xảy ra nếu ctxt , options và xướcDir là các đối tượng có hành vi thực? Sau đó, bởi vì
các đối tượng phải ẩn cấu trúc bên trong của chúng, chúng tôi sẽ không thể điều hướng
thông qua họ. Làm thế nào sau đó chúng ta sẽ có được đường dẫn tuyệt đối của thư mục đầu?
ctxt.getAbsolutePathOfScratchDirectoryOption ();
hoặc là
ctx.getScratchDirectoryOption (). getAbsolutePath ()
Tùy chọn đầu tiên có thể dẫn đến sự bùng nổ các phương thức trong đối tượng ctxt. Trước thứ hai
sumes rằng getScratchDirectoryOption () trả về một cấu trúc dữ liệu, không phải là một đối tượng. Cũng không
tùy chọn cảm thấy tốt.
Nếu ctxt là một đối tượng, chúng ta nên yêu cầu nó làm điều gì đó; chúng ta không nên hỏi nó
về nội bộ của nó. Vậy tại sao chúng ta lại muốn đường dẫn tuyệt đối của thư mục đầu? Gì
chúng ta sẽ làm gì với nó? Hãy xem xét mã này từ (nhiều dòng xa hơn trong)
cùng một mô-đun:
String outFile = outputDir + "/" + className.replace ('.', '/') + ".Class";
FileOutputStream fout = new FileOutputStream (outFile);
BufferedOutputStream bos = new BufferedOutputStream (fout);
Sự kết hợp của các mức độ chi tiết khác nhau [G34] [G6] là một chút rắc rối. Dấu chấm, dấu gạch chéo,
phần mở rộng tệp và các đối tượng Tệp không được trộn một cách cẩu thả với nhau và trộn lẫn
với mã bao quanh. Tuy nhiên, bỏ qua điều đó, chúng ta thấy rằng mục đích của việc lấy
Đường dẫn lute của thư mục đầu là để tạo một tệp tin đầu có tên đã cho.
4. Điều này đôi khi được gọi là Feature Envy từ [Refactoring].
www.it-ebooks.info

Trang 131
100
Chương 6: Đối tượng và cấu trúc dữ liệu
Vì vậy, điều gì sẽ xảy ra nếu chúng ta yêu cầu đối tượng ctxt làm điều này?
BufferedOutputStream bos = ctxt.createScratchFileStream (classFileName);
Đó có vẻ như là một điều hợp lý cho một đối tượng để làm! Điều này cho phép ctxt ẩn
nội bộ và ngăn chức năng hiện tại không phải vi phạm Luật Demeter bằng cách
điều hướng qua các đối tượng mà nó không nên biết.
Đối tượng truyền dữ liệu
Dạng tinh túy của cấu trúc dữ liệu là một lớp có các biến công khai và không có hàm-
hàng tấn. Điều này đôi khi được gọi là đối tượng truyền dữ liệu, hoặc DTO. DTO rất hữu ích
sửa chữa, đặc biệt là khi giao tiếp với cơ sở dữ liệu hoặc phân tích cú pháp tin nhắn từ các socket,
và như thế. Họ thường trở thành người đầu tiên trong một loạt các giai đoạn dịch chuyển đổi dữ liệu thô
trong cơ sở dữ liệu thành các đối tượng trong mã ứng dụng.
Một chút phổ biến hơn là dạng "bean" được hiển thị trong Liệt kê 6-7. Đậu có tư
các biến được thao tác bởi getters và setters. Sự đóng gói gần như của đậu dường như
làm cho một số người theo chủ nghĩa OO cảm thấy tốt hơn nhưng thường không mang lại lợi ích nào khác.
Liệt kê 6-7
address.java
Địa chỉ lớp công khai {
phố tư nhân;
private String streetExtra;
thành phố chuỗi tư nhân;
trạng thái chuỗi tư nhân;
chuỗi zip riêng tư;
Địa chỉ công cộng (String street, String streetExtra,
Thành phố chuỗi, trạng thái chuỗi, chuỗi zip) {
this.street = đường phố;
this.streetExtra = streetExtra;
this.city = thành phố;
this.state = trạng thái;
this.zip = zip;
}
public String getStreet () {
đường trở về;
}
public String getStreetExtra () {
return streetExtra;
}
public String getCity () {
trở lại thành phố;
}
www.it-ebooks.info

Trang 132
101
Thư mục
Bản ghi hoạt động
Hồ sơ Hoạt động là các dạng DTO đặc biệt. Chúng là cấu trúc dữ liệu với public (hoặc bean-
truy cập) các biến; nhưng chúng thường có các phương pháp điều hướng như lưu và tìm . Typi-
cally các Bản ghi Hoạt động này là bản dịch trực tiếp từ các bảng cơ sở dữ liệu hoặc dữ liệu khác
các nguồn.
Thật không may, chúng tôi thường thấy rằng các nhà phát triển cố gắng xử lý các cấu trúc dữ liệu này như thể
họ là đối tượng bằng cách đưa các phương pháp quy tắc kinh doanh vào trong đó. Điều này thật khó xử vì nó
tạo ra sự kết hợp giữa cấu trúc dữ liệu và một đối tượng.
Tất nhiên, giải pháp là coi Active Record như một cấu trúc dữ liệu và tạo
các đối tượng riêng biệt có chứa các quy tắc kinh doanh và ẩn dữ liệu nội bộ của chúng (là
có lẽ chỉ là các phiên bản của Active Record).
Phần kết luận
Đối tượng phơi bày hành vi và ẩn dữ liệu. Điều này giúp dễ dàng thêm các loại đối tượng mới
mà không thay đổi các hành vi hiện có. Nó cũng gây khó khăn cho việc thêm các hành vi mới vào
các đối tượng. Cấu trúc dữ liệu làm lộ dữ liệu và không có hành vi đáng kể. Điều này giúp bạn dễ dàng
thêm các hành vi mới vào cấu trúc dữ liệu hiện có nhưng khó thêm cấu trúc dữ liệu mới
đến các chức năng hiện có.
Trong bất kỳ hệ thống nhất định nào, đôi khi chúng ta sẽ muốn có sự linh hoạt để thêm các kiểu dữ liệu mới và
vì vậy chúng tôi thích các đối tượng hơn cho phần đó của hệ thống. Những lần khác, chúng tôi sẽ muốn sự linh hoạt
thêm các hành vi mới, và do đó trong phần đó của hệ thống, chúng tôi thích các kiểu dữ liệu và thủ tục hơn.
Các nhà phát triển phần mềm giỏi hiểu những vấn đề này mà không thành kiến và chọn
cách tiếp cận tốt nhất cho công việc hiện tại.
Thư mục
[Refactoring]: Refactoring: Cải thiện thiết kế của mã hiện có , Martin Fowler và cộng sự,
Addison-Wesley, 1999.
public String getState () {
trở lại trạng thái;
}
public String getZip () {
trả lại mã zip;
}
}
Liệt kê 6-7 (tiếp theo)
address.java
www.it-ebooks.info

Trang 133
Trang này cố ý để trống
www.it-ebooks.info

Trang 134
103

7
Xử lý lỗi
bởi Michael Feathers
Có vẻ kỳ lạ khi có một phần về xử lý lỗi trong một cuốn sách về mã sạch. lỗi
xử lý chỉ là một trong những việc mà tất cả chúng ta phải làm khi lập trình. Đầu vào có thể là
bất thường và các thiết bị có thể bị lỗi. Nói tóm lại, mọi thứ có thể xảy ra sai lầm, và khi chúng xảy ra, chúng tôi với
tư cách là
grammers chịu trách nhiệm đảm bảo rằng mã của chúng tôi thực hiện những gì nó cần làm.
Tuy nhiên, kết nối với mã sạch phải rõ ràng. Nhiều cơ sở mã được com-
hoàn toàn bị chi phối bởi xử lý lỗi. Khi tôi nói bị chi phối, tôi không có nghĩa là lỗi đó han-
dling là tất cả những gì họ làm. Ý tôi là gần như không thể thấy mã làm gì
vì tất cả các xử lý lỗi rải rác. Xử lý lỗi là quan trọng, nhưng nếu nó
che khuất logic, nó sai .
Trong chương này, tôi sẽ trình bày một số kỹ thuật và cân nhắc mà bạn có thể sử dụng
để viết mã vừa rõ ràng vừa mạnh mẽ — mã xử lý lỗi một cách duyên dáng và phong cách.
www.it-ebooks.info

Trang 135
104
Chương 7: Xử lý lỗi
Sử dụng ngoại lệ thay vì trả lại mã
Trong quá khứ xa xôi, có rất nhiều ngôn ngữ không có ngoại lệ. Trong những
ngôn ngữ các kỹ thuật để xử lý và báo cáo lỗi còn hạn chế. Bạn có thể đặt một
cờ lỗi hoặc trả về mã lỗi mà người gọi có thể kiểm tra. Mã trong Liệt kê 7-1
minh họa những cách tiếp cận này.
Vấn đề với những cách tiếp cận này là chúng làm lộn xộn người gọi. Người gọi phải
kiểm tra lỗi ngay sau cuộc gọi. Thật không may, nó rất dễ quên. Đối với rea-
con trai tốt hơn là ném một ngoại lệ khi bạn gặp lỗi. Mã gọi là
sạch hơn. Logic của nó không bị che khuất bởi việc xử lý lỗi.
Liệt kê 7-2 hiển thị mã sau khi chúng tôi đã chọn ném ngoại lệ trong các phương thức
có thể phát hiện lỗi.
Liệt kê 7-1
DeviceController.java
lớp công khai DeviceController {
...
public void sendShutDown () {
Tay cầm DeviceHandle = getHandle (DEV1);
// Kiểm tra trạng thái của thiết bị
if (handle! = DeviceHandle.INVALID) {
// Lưu trạng thái thiết bị vào trường bản ghi
getDeviceRecord (xử lý);
// Nếu không bị treo, hãy tắt
if (record.getStatus ()! = DEVICE_SUSPENDED) {
pauseDevice (xử lý);
clearDeviceWorkQueue (xử lý);
closeDevice (xử lý);
} khác {
logger.log ("Thiết bị bị treo. Không thể tắt");
}
} khác {
logger.log ("Xử lý không hợp lệ cho:" + DEV1.toString ());
}
}
...
}
Liệt kê 7-2
DeviceController.java (có ngoại lệ)
lớp công khai DeviceController {
...
public void sendShutDown () {
thử {
tryToShutDown ();
} catch (DeviceShutDownError e) {
logger.log (e);
}
}
www.it-ebooks.info

Trang 136
105
Viết tuyên bố thử-bắt-cuối cùng của bạn trước
Để ý xem nó sạch hơn bao nhiêu. Đây không chỉ là vấn đề thẩm mỹ. Mã tốt hơn
bởi vì hai mối quan tâm bị rối, thuật toán tắt thiết bị và lỗi han-
dling, hiện đã được tách ra. Bạn có thể xem xét từng mối quan tâm đó và hiểu chúng
một cách độc lập.
Viết tuyên bố thử-bắt-cuối cùng của bạn trước
Một trong những điều thú vị nhất về các ngoại lệ là chúng xác định phạm vi trong
chương trình. Khi bạn thực thi mã trong phần thử của câu lệnh try - catch - last , bạn
đang nói rằng việc thực thi có thể hủy bỏ bất cứ lúc nào và sau đó tiếp tục tại điểm bắt .
Theo một cách nào đó, các khối thử giống như các giao dịch. Bắt của bạn phải rời khỏi chương trình của bạn trong
một
trạng thái nhất quán, không có vấn đề gì xảy ra trong lần thử . Vì lý do này, thực hành tốt là
bắt đầu bằng câu lệnh try - catch - last khi bạn đang viết mã có thể ném
các trường hợp ngoại lệ. Điều này giúp bạn xác định những gì người dùng mã đó sẽ mong đợi, bất kể điều gì
sai với mã được thực thi trong lần thử .
Hãy xem một ví dụ. Chúng tôi cần viết một số mã để truy cập tệp và đọc
một số đối tượng tuần tự.
Chúng tôi bắt đầu với thử nghiệm đơn vị cho thấy rằng chúng tôi sẽ nhận được một ngoại lệ khi tệp không tồn tại:
@Test (dự kiến = StorageException.class)
public void RetveSectionShouldThrowOnInvalidFileName () {
sectionStore.retrieveSection ("không hợp lệ - tệp");
}
Việc kiểm tra thúc đẩy chúng tôi tạo ra sơ khai này:
public List <RecordedGrip> getSection (String sectionName) {
// trả về giả cho đến khi chúng ta có một triển khai thực sự
trả về ArrayList mới <RecordedGrip> ();
}
private void tryToShutDown () ném DeviceShutDownError {
Tay cầm DeviceHandle = getHandle (DEV1);
Bản ghi DeviceRecord = truy xuấtDeviceRecord (xử lý);
pauseDevice (xử lý);
clearDeviceWorkQueue (xử lý);
closeDevice (xử lý);
}
private DeviceHandle getHandle (id DeviceID) {
...
ném DeviceShutDownError mới ("Xử lý không hợp lệ cho:" + id.toString ());
...
}
...
}
Liệt kê 7-2 (tiếp theo)
DeviceController.java (có ngoại lệ)
www.it-ebooks.info

Trang 137
106
Chương 7: Xử lý lỗi
Thử nghiệm của chúng tôi không thành công vì nó không đưa ra một ngoại lệ. Tiếp theo, chúng tôi thay đổi triển
khai của chúng tôi-
để nó cố gắng truy cập một tệp không hợp lệ. Thao tác này đưa ra một ngoại lệ:
public List <RecordedGrip> getSection (String sectionName) {
thử {
FileInputStream stream = new FileInputStream (sectionName)
} catch (Ngoại lệ e) {
ném StorageException mới ("lỗi truy xuất", e);
}
trả về ArrayList mới <RecordedGrip> ();
}
Bài kiểm tra của chúng tôi hiện đã vượt qua vì chúng tôi đã bắt được ngoại lệ. Tại thời điểm này, chúng ta có thể
tor. Chúng tôi có thể thu hẹp loại ngoại lệ mà chúng tôi bắt được để phù hợp với loại thực sự
được ném từ phương thức khởi tạo FileInputStream : FileNotFoundException :
public List <RecordedGrip> getSection (String sectionName) {
thử {
FileInputStream stream = new FileInputStream (sectionName);
stream.close ();
} catch (FileNotFoundException e) {
ném StorageException mới ("lỗi truy xuất", e);
}
trả về ArrayList mới <RecordedGrip> ();
}
Bây giờ chúng ta đã xác định phạm vi với cấu trúc try - catch , chúng ta có thể sử dụng TDD để xây dựng
phần còn lại của logic mà chúng ta cần. Logic đó sẽ được thêm vào giữa việc tạo ra
FileInputStream và kết thúc , và có thể giả vờ rằng không có gì sai.
Cố gắng viết các bài kiểm tra buộc các trường hợp ngoại lệ, sau đó thêm hành vi vào trình xử lý của bạn thành sat-
chuẩn hóa các bài kiểm tra của bạn. Điều này sẽ khiến bạn xây dựng phạm vi giao dịch của khối thử trước và
sẽ giúp bạn duy trì bản chất giao dịch của phạm vi đó.
Sử dụng các ngoại lệ không được chọn
Cuộc tranh luận kết thúc. Trong nhiều năm, các lập trình viên Java đã tranh luận về những lợi ích và liabili-
quan hệ của các ngoại lệ đã kiểm tra. Khi các ngoại lệ được kiểm tra được giới thiệu trong phiên bản đầu tiên
của Java, chúng dường như là một ý tưởng tuyệt vời. Chữ ký của mọi phương thức sẽ liệt kê tất cả các
ngoại lệ mà nó có thể chuyển cho người gọi của nó. Hơn nữa, những ngoại lệ này là một phần của loại
của phương pháp. Mã của bạn thực sự sẽ không biên dịch nếu chữ ký không khớp với những gì
mã có thể làm.
Vào thời điểm đó, chúng tôi nghĩ rằng các ngoại lệ được kiểm tra là một ý tưởng tuyệt vời; và vâng, họ có thể
mang lại một số lợi ích. Tuy nhiên, rõ ràng bây giờ chúng không cần thiết để sản xuất
phần mềm mạnh mẽ. C # không có các ngoại lệ được kiểm tra và mặc dù có những nỗ lực dũng cảm, C ++
cũng không. Python hay Ruby cũng vậy. Tuy nhiên, có thể viết phần mềm mạnh mẽ trong tất cả
của những ngôn ngữ này. Bởi vì đó là trường hợp, chúng tôi phải quyết định — thực sự — liệu đã kiểm tra
ngoại lệ có giá trị của họ.
www.it-ebooks.info

Trang 138
107
Xác định các loại ngoại lệ trong điều kiện nhu cầu của người gọi
Giá bao nhiêu? Giá của các trường hợp ngoại lệ đã kiểm tra là vi phạm Nguyên tắc Mở / Đóng 1 .
Nếu bạn ném một ngoại lệ đã kiểm tra từ một phương thức trong mã của bạn và bắt là ba cấp
ở trên, bạn phải khai báo ngoại lệ đó trong chữ ký của mỗi phương thức giữa bạn và
các  đánh bắt . Điều này có nghĩa là một thay đổi ở cấp độ thấp của phần mềm có thể buộc chữ ký
thay đổi trên nhiều cấp độ cao hơn. Các mô-đun đã thay đổi phải được xây dựng lại và triển khai lại,
mặc dù không có gì họ quan tâm thay đổi.
Xem xét hệ thống phân cấp gọi của một hệ thống lớn. Các chức năng ở các chức năng cuộc gọi hàng đầu
bên dưới chúng, gọi nhiều hàm hơn bên dưới chúng, ad infinitum. Bây giờ, hãy nói một trong những
các hàm cấp thấp nhất được sửa đổi theo cách mà nó phải ném ra một ngoại lệ. Nếu đó
ngoại lệ được kiểm tra, sau đó chữ ký hàm phải thêm một mệnh đề ném . Nhưng điều này
có nghĩa là mọi hàm gọi hàm đã sửa đổi của chúng tôi cũng phải được sửa đổi thành
bắt ngoại lệ mới hoặc để thêm mệnh đề ném thích hợp vào chữ ký của nó. Ad
vô sinh. Kết quả ròng là một loạt các thay đổi hoạt động theo cách của chúng từ các cấp thấp nhất
của phần mềm cao nhất! Quá trình đóng gói bị hỏng vì tất cả các hàm trong đường dẫn
của một cú ném phải biết về chi tiết của ngoại lệ cấp thấp đó. Cho rằng mục đích của
ngoại lệ là cho phép bạn xử lý lỗi ở khoảng cách xa, thật đáng tiếc khi đã kiểm tra ngoại lệ-
tions phá vỡ sự đóng gói theo cách này.
Các ngoại lệ được kiểm tra đôi khi có thể hữu ích nếu bạn đang viết một thư viện quan trọng: Bạn
phải bắt chúng. Nhưng trong phát triển ứng dụng nói chung, chi phí phụ thuộc lớn hơn
những lợi ích.
Cung cấp ngữ cảnh có ngoại lệ
Mỗi ngoại lệ mà bạn đưa ra phải cung cấp đủ ngữ cảnh để xác định nguồn và
vị trí của một lỗi. Trong Java, bạn có thể lấy dấu vết ngăn xếp từ bất kỳ ngoại lệ nào; tuy nhiên, một ngăn xếp
dấu vết không thể cho bạn biết mục đích của hoạt động không thành công.
Tạo thông báo lỗi đầy đủ thông tin và chuyển chúng cùng với các ngoại lệ của bạn. Đàn ông-
cho biết thao tác không thành công và loại lỗi. Nếu bạn đang đăng nhập vào ứng dụng của mình,
chuyển đủ thông tin để có thể ghi lại lỗi trong quá trình bắt của bạn .
Xác định các loại ngoại lệ trong điều kiện nhu cầu của người gọi
Có nhiều cách để phân loại lỗi. Chúng tôi có thể phân loại chúng theo nguồn của chúng:
đến từ thành phần này hay thành phần khác? Hoặc loại của họ: Có phải là lỗi thiết bị, mạng
lỗi, hoặc lỗi lập trình? Tuy nhiên, khi chúng ta xác định các lớp ngoại lệ trong một ứng dụng-
cation, mối quan tâm quan trọng nhất của chúng tôi là làm thế nào chúng được bắt .
1. [Martin].
www.it-ebooks.info

Trang 139
108
Chương 7: Xử lý lỗi
Hãy xem một ví dụ về phân loại ngoại lệ kém. Đây là một thử - nắm bắt - cuối cùng
cho lệnh gọi thư viện của bên thứ ba. Nó bao gồm tất cả các ngoại lệ mà các cuộc gọi có thể
phi:
Cổng ACMEPort = ACMEPort mới (12);
thử {
port.open ();
} catch (DeviceResponseException e) {
reportPortError (e);
logger.log ("Ngoại lệ phản hồi thiết bị", e);
} catch (ATM1212UnlockedException e) {
reportPortError (e);
logger.log ("Mở khóa ngoại lệ", e);
} catch (GMXError e) {
reportPortError (e);
logger.log ("Ngoại lệ phản hồi thiết bị");
} cuối cùng {

}
Tuyên bố đó chứa đựng rất nhiều sự trùng lặp, và chúng ta không nên ngạc nhiên. Ở hầu hết các
các tình huống xử lý ngoại lệ, công việc mà chúng tôi làm là tương đối chuẩn bất kể
nguyên nhân thực tế. Chúng tôi phải ghi lại một lỗi và đảm bảo rằng chúng tôi có thể tiếp tục.
Trong trường hợp này, bởi vì chúng ta biết rằng công việc chúng ta đang làm gần giống nhau
bất kể ngoại lệ là gì, chúng tôi có thể đơn giản hóa mã của mình đáng kể bằng cách gói API
mà chúng tôi đang gọi và đảm bảo rằng nó trả về một loại ngoại lệ phổ biến:
Cổng LocalPort = new LocalPort (12);
thử {
port.open ();
} catch (PortDeviceFailure e) {
reportError (e);
logger.log (e.getMessage (), e);
} cuối cùng {

}
Lớp LocalPort của chúng tôi chỉ là một trình bao bọc đơn giản giúp bắt và dịch các ngoại lệ
được ném bởi lớp ACMEPort :
lớp công khai LocalPort {
ACMEPort innerPort riêng tư;
public LocalPort (int portNumber) {
innerPort = new ACMEPort (portNumber);
}
public void open () {
thử {
innerPort.open ();
} catch (DeviceResponseException e) {
ném PortDeviceFailure mới (e);
} catch (ATM1212UnlockedException e) {
ném PortDeviceFailure mới (e);
} catch (GMXError e) {
www.it-ebooks.info

Trang 140
109
Xác định dòng chảy bình thường
ném PortDeviceFailure mới (e);
}
}

}
Các trình gói như chúng tôi đã xác định cho ACMEPort có thể rất hữu ích. Trên thực tế, gói
API của bên thứ ba là phương pháp hay nhất. Khi bạn bọc một API của bên thứ ba, bạn giảm thiểu
phụ thuộc vào nó: Bạn có thể chọn chuyển sang một thư viện khác trong tương lai mà không
phạt nhiều. Gói cũng giúp bạn dễ dàng bắt chước các cuộc gọi của bên thứ ba hơn khi bạn
kiểm tra mã của riêng bạn.
Một lợi thế cuối cùng của gói là bạn không bị ràng buộc với API của một nhà cung cấp cụ thể
lựa chọn thiết kế. Bạn có thể xác định một API mà bạn cảm thấy thoải mái. Trước
ví dụ, chúng tôi đã xác định một loại ngoại lệ duy nhất cho lỗi thiết bị cổng và nhận thấy rằng chúng tôi
có thể viết mã sạch hơn nhiều.
Thường thì một lớp ngoại lệ duy nhất là tốt cho một vùng mã cụ thể. Thông tin
được gửi với ngoại lệ có thể phân biệt các lỗi. Chỉ sử dụng các lớp khác nhau nếu có
khi bạn muốn bắt một ngoại lệ và cho phép ngoại lệ khác đi qua.
Xác định dòng chảy bình thường
Nếu bạn làm theo lời khuyên ở phần trước
phần, bạn sẽ kết thúc với một số tiền tốt
tách biệt giữa logic kinh doanh của bạn
và xử lý lỗi của bạn. Phần lớn của bạn
mã sẽ bắt đầu trông giống như một
thuật toán không trang trí. Tuy nhiên, pro-
ngừng làm việc này đẩy phát hiện lỗi
các cạnh của chương trình của bạn. Bạn quấn
các API bên ngoài để bạn có thể sử dụng
ngoại lệ riêng và bạn xác định một trình xử lý phía trên mã của mình để bạn có thể đối phó với bất kỳ
tính toán bị hủy bỏ. Hầu hết thời gian, đây là một cách tiếp cận tuyệt vời, nhưng có một số lần
khi bạn có thể không muốn phá thai.
Hãy xem một ví dụ. Dưới đây là một số mã khó hiểu tổng hợp các chi phí trong một
ứng dụng thanh toán:
thử {
MealExpenses chi = CostReportDAO.getMeals (worker.getID ());
m_total + = Cost.getTotal ();
} catch (MealExpensesNotFound e) {
m_total + = getMealPerDiem ();
}
Trong kinh doanh này, nếu các bữa ăn được tiêu tốn nhiều chi phí, chúng sẽ trở thành một phần của tổng số. Nếu
không,
người lao động nhận được một bữa ăn mỗi diem lượng cho ngày hôm đó. Ngoại lệ làm lộn xộn logic.
Sẽ tốt hơn nếu chúng ta không phải giải quyết trường hợp đặc biệt? Nếu không, mã của chúng tôi
sẽ trông đơn giản hơn nhiều. Nó sẽ trông như thế này:
MealExpenses chi = CostReportDAO.getMeals (worker.getID ());
m_total + = Cost.getTotal ();
www.it-ebooks.info

Trang 141
110
Chương 7: Xử lý lỗi
Chúng ta có thể làm cho mã đơn giản như vậy không? Nó chỉ ra rằng chúng tôi có thể. Chúng tôi có thể thay đổi
ExpenseReportDAO để nó luôn trả về một đối tượng MealExpense . Nếu không có bữa ăn
chi phí, nó trả về một đối tượng MealExpense trả về công tác phí là tổng của nó:
public class PerDiemMealExpenses triển khai MealExpenses {
public int getTotal () {
// trả về mặc định per diem
}
}
Đây được gọi là S PECIAL C ASE P ATTERN [Fowler]. Bạn tạo một lớp hoặc cấu hình một
để nó xử lý một trường hợp đặc biệt cho bạn. Khi bạn làm vậy, mã khách hàng không có
để đối phó với hành vi đặc biệt. Hành vi đó được gói gọn trong đối tượng trường hợp đặc biệt.
Đừng trả lại Null
Tôi nghĩ rằng bất kỳ cuộc thảo luận nào về việc xử lý lỗi nên bao gồm đề cập đến những điều chúng tôi
làm lỗi mời đó. Đầu tiên trong danh sách trả về null . Tôi không thể bắt đầu đếm số
trong số các ứng dụng mà tôi đã thấy trong đó gần như mọi dòng khác là kiểm tra giá trị rỗng . Đây là
một số mã ví dụ:
public void registerItem (Item item) {
if (item! = null) {
Mục đăng ký ItemRegistry = peristentStore.getItemRegistry ();
if (registry! = null) {
Mục hiện có = registry.getItem (item.getID ());
if (current.getBillingPeriod (). hasRetailOwner ()) {
hiện.register (mục);
}
}
}
}
Nếu bạn làm việc trong một cơ sở mã với mã như thế này, nó có thể không tệ đối với bạn, nhưng nó là
xấu! Khi chúng tôi trả về null , về cơ bản chúng tôi đang tạo ra công việc cho chính mình và củng cố
vấn đề với người gọi của chúng tôi. Tất cả chỉ cần một lần kiểm tra trống để gửi đơn đăng ký
quay ngoài tầm kiểm soát.
Bạn có nhận thấy thực tế là không có dấu kiểm rỗng trong dòng thứ hai của
nếu tuyên bố? Điều gì sẽ xảy ra trong thời gian chạy nếu kiên trìStore là null ? Chúng tôi
sẽ có một NullPointerException trong thời gian chạy và ai đó đang bắt
NullPointerException ở cấp cao nhất hoặc chúng không. Dù thế nào thì nó cũng tệ . Những gì chính xác
bạn nên làm gì để đáp ứng với một NullPointerException được ném từ sâu bên trong công cụ của bạn-
cation?
Thật dễ dàng để nói rằng vấn đề với mã ở trên là nó thiếu kiểm tra rỗng ,
nhưng trong thực tế, vấn đề là nó có quá nhiều . Nếu bạn muốn trả về null từ
thay vào đó , hãy xem xét việc ném một ngoại lệ hoặc trả về một đối tượng S PECIAL C ASE . Nếu
bạn đang gọi phương thức null -returning từ API của bên thứ ba, hãy xem xét gói lại
phương thức với một phương thức ném một ngoại lệ hoặc trả về một đối tượng trường hợp đặc biệt.
www.it-ebooks.info

Trang 142
111
Đừng vượt qua Null
Trong nhiều trường hợp, các đối tượng trường hợp đặc biệt là một biện pháp khắc phục dễ dàng. Hãy tưởng tượng
rằng bạn có mã
như thế này:
Liệt kê các nhân viên <E Jobee> = getEprisees ();
if (nhân viên! = null) {
cho (Nhân viên e: nhân viên) {
totalPay + = e.getPay ();
}
}
Ngay bây giờ, getEprisees có thể trả về null , nhưng nó có phải như vậy không? Nếu chúng ta thay đổi getE
Employee thì
rằng nó trả về một danh sách trống, chúng tôi có thể xóa mã:
Liệt kê các nhân viên <E Jobee> = getEprisees ();
cho (Nhân viên e: nhân viên) {
totalPay + = e.getPay ();
}
May mắn thay, Java có Collections.emptyList () và nó trả về một danh sách bất biến được xác định trước
mà chúng tôi có thể sử dụng cho mục đích này:
danh sách công khai <E Jobee> getEprisees () {
nếu (.. không có nhân viên ..)
return Collections.emptyList ();
}
Nếu bạn viết mã theo cách này, bạn sẽ giảm thiểu cơ hội xảy ra NullPointerExceptions và
mã sẽ sạch hơn.
Đừng vượt qua Null
Trả về null từ các phương thức là không tốt, nhưng chuyển null vào các phương thức thì tệ hơn. Trừ khi bạn
đang làm việc với một API yêu cầu bạn chuyển null , bạn nên tránh chuyển null vào
mã của bạn bất cứ khi nào có thể.
Hãy xem một ví dụ để xem tại sao. Đây là một phương pháp đơn giản để tính toán một met-
ric cho hai điểm:
Lớp công khai MetricsCalculator
{
public double xProjection (Point p1, Point p2) {
return (p2.x - p1.x) * 1.5;
}

}
Điều gì xảy ra khi ai đó chuyển null làm đối số?
Calculator.xProjection (null, new Point (12, 13));
Tất nhiên, chúng tôi sẽ nhận được một NullPointerException .
Làm thế nào chúng ta có thể sửa chữa nó? Chúng tôi có thể tạo một loại ngoại lệ mới và ném nó:
Lớp công khai MetricsCalculator
{
www.it-ebooks.info

Trang 143
112
Chương 7: Xử lý lỗi
public double xProjection (Point p1, Point p2) {
if (p1 == null || p2 == null) {
ném InvalidArgumentException (
"Đối số không hợp lệ cho MetricsCalculator.xProjection");
}
return (p2.x - p1.x) * 1.5;
}
}
Tốt hơn chưa? Nó có thể tốt hơn một chút so với ngoại lệ con trỏ null , nhưng hãy nhớ rằng, chúng tôi
phải xác định một trình xử lý cho InvalidArgumentException . Người xử lý phải làm gì? Là
có bất kỳ hướng hành động tốt nào không?
Có một sự thay thế khác. Chúng tôi có thể sử dụng một tập hợp các xác nhận:
Lớp công khai MetricsCalculator
{
public double xProjection (Point p1, Point p2) {
khẳng định p1! = null: "p1 không nên là null";
khẳng định p2! = null: "p2 không nên là null";
return (p2.x - p1.x) * 1.5;
}
}
Đó là tài liệu tốt, nhưng nó không giải quyết được vấn đề. Nếu ai đó vượt qua null , chúng tôi sẽ
vẫn có lỗi thời gian chạy.
Trong hầu hết các ngôn ngữ lập trình, không có cách nào tốt để đối phó với giá trị null đó là
đi ngang qua một người gọi vô tình. Bởi vì đây là trường hợp, cách tiếp cận hợp lý là cấm
truyền null theo mặc định. Khi bạn làm vậy, bạn có thể viết mã với kiến thức rằng giá trị null trong
danh sách đối số là một dấu hiệu của một vấn đề và kết thúc với ít sai lầm bất cẩn hơn nhiều.
Phần kết luận
Mã sạch có thể đọc được, nhưng nó cũng phải mạnh mẽ. Đây không phải là những mục tiêu xung đột. Chúng ta có
thể
viết mã sạch mạnh mẽ nếu chúng tôi thấy việc xử lý lỗi là một mối quan tâm riêng biệt, một cái gì đó
có thể xem độc lập với logic chính của chúng tôi. Ở mức độ mà chúng tôi có thể làm điều đó, chúng tôi có thể
lý luận về nó một cách độc lập và chúng tôi có thể đạt được những bước tiến lớn trong việc bảo trì
mã.
Thư mục
[Martin]: Phát triển phần mềm Agile: Nguyên tắc, Mẫu và Thực tiễn, Robert C.
Martin, Prentice Hall, 2002.
www.it-ebooks.info

Trang 144
113

số 8
Ranh giới
bởi James Grenning
Chúng tôi hiếm khi kiểm soát tất cả các phần mềm trong hệ thống của mình. Đôi khi chúng tôi mua gói của bên thứ
ba-
tuổi hoặc sử dụng mã nguồn mở. Những lần khác, chúng tôi phụ thuộc vào các đội trong công ty của chúng tôi để
sản xuất
các thành phần hoặc hệ thống con cho chúng tôi. Bằng cách nào đó, chúng ta phải tích hợp mã nước ngoài này một
cách rõ ràng
www.it-ebooks.info

Trang 145
114
Chương 8: Ranh giới
với của riêng chúng tôi. Trong chương này, chúng ta xem xét các thực hành và kỹ thuật để giữ các ranh giới của
phần mềm của chúng tôi sạch sẽ.
Sử dụng mã của bên thứ ba
Có một sự căng thẳng tự nhiên giữa nhà cung cấp giao diện và người sử dụng giao diện.
Các nhà cung cấp các gói và khuôn khổ của bên thứ ba cố gắng mang lại khả năng ứng dụng rộng rãi để họ
có thể hoạt động trong nhiều môi trường và thu hút được nhiều đối tượng. Mặt khác, người dùng
muốn có một giao diện tập trung vào các nhu cầu cụ thể của họ. Sự căng thẳng này có thể gây ra vấn đề
ở ranh giới của hệ thống của chúng tôi.
Hãy xem java.util.Map làm ví dụ. Như bạn có thể thấy bằng cách xem Hình 8-1,
Bản đồ có giao diện rất rộng với nhiều khả năng. Chắc chắn sức mạnh và sự linh hoạt này-
sự nhanh nhẹn là hữu ích, nhưng nó cũng có thể là một khoản nợ. Ví dụ: ứng dụng của chúng tôi có thể xây dựng
Bản đồ và vượt qua nó xung quanh. Ý định của chúng tôi có thể là không ai trong số những người nhận Bản đồ của
chúng tôi xóa
bất cứ thứ gì trong bản đồ. Nhưng ngay ở đầu danh sách là phương thức clear () . Bất kỳ người dùng nào của
các Map có sức mạnh để xóa nó. Hoặc có thể quy ước thiết kế của chúng tôi là chỉ cụ thể
các loại đối tượng có thể được lưu trữ trong Bản đồ , nhưng Bản đồ không hạn chế một cách đáng tin cậy các loại
các đối tượng được đặt bên trong chúng. Bất kỳ người dùng nào được xác định đều có thể thêm các mục thuộc bất
kỳ loại nào vào bất kỳ Bản đồ nào .
Nếu ứng dụng của chúng tôi cần Bản đồ các cảm biến , bạn có thể tìm thấy các cảm biến được thiết lập như sau:
Cảm biến bản đồ = new HashMap ();
• void clear () - Bản đồ
• boolean containsKey (Khóa đối tượng) - Bản đồ
• boolean containsValue (Giá trị đối tượng) - Bản đồ
• entrySet () Set - Bản đồ
• equals (Object o) boolean - Bản đồ
• get (Khóa đối tượng) Đối tượng - Bản đồ
• getClass () Lớp <? mở rộng Đối tượng> - Đối tượng
• hashCode () int - Bản đồ
• isEmpty () boolean - Bản đồ
• keySet () Set - Bản đồ
• thông báo () void - Đối tượng
• voidtifyAll () - Đối tượng
• đặt (Khóa đối tượng, Giá trị đối tượng) Đối tượng - Bản đồ
• void putAll (Map t) - Bản đồ
• loại bỏ (Phím đối tượng) Đối tượng - Bản đồ
• size () int - Bản đồ
• toString () Chuỗi - Đối tượng
• giá trị () Bộ sưu tập - Bản đồ
• wait () void - Đối tượng
• chờ đợi (thời gian chờ lâu) vô hiệu - Đối tượng
• chờ đợi (thời gian chờ lâu, int nanos) void - Đối tượng
Hình 8-1
Các phương pháp của bản đồ
www.it-ebooks.info

Trang 146
115
Sử dụng mã của bên thứ ba
Sau đó, khi một số phần khác của mã cần truy cập vào cảm biến, bạn sẽ thấy mã này:
Sensor s = (Cảm biến) sensor.get (sensorId);
Chúng ta không chỉ nhìn thấy nó một lần mà còn nhiều lần trong suốt đoạn mã. Khách hàng của cái này
mã có trách nhiệm lấy một Đối tượng từ Bản đồ và truyền nó sang bên phải
kiểu. Điều này hoạt động, nhưng nó không phải là mã sạch. Ngoài ra, mã này không kể câu chuyện của nó cũng như

có thể. Khả năng đọc của mã này có thể được cải thiện đáng kể bằng cách sử dụng generic, như được hiển thị
phía dưới:
Bản đồ <Sensor> sensor = new HashMap <Sensor> ();
...
Sensor s = sensor.get (sensorId);
Tuy nhiên, điều này không giải quyết được vấn đề mà Map <Sensor> cung cấp nhiều khả năng hơn chúng tôi
cần hoặc muốn.
Truyền một phiên bản của Map <Sensor> một cách tự do xung quanh hệ thống có nghĩa là sẽ có
có nhiều chỗ cần sửa nếu giao diện của Bản đồ thay đổi. Bạn có thể nghĩ rằng một sự thay đổi như vậy
khó xảy ra, nhưng hãy nhớ rằng nó đã thay đổi khi hỗ trợ generics được thêm vào Java 5.
Thật vậy, chúng tôi đã thấy các hệ thống bị hạn chế sử dụng thuốc generic vì
mức độ thay đổi cần thiết để bù đắp cho việc sử dụng tự do Bản đồ s.
Cách tốt hơn để sử dụng Bản đồ có thể giống như sau. Không người dùng Cảm biến nào quan tâm
một chút nếu generic được sử dụng hay không. Sự lựa chọn đó đã trở thành (và luôn luôn nên như vậy)
chi tiết thực hiện.
Bộ cảm biến lớp công cộng {
cảm biến bản đồ riêng = new HashMap ();
public Sensor getById (String id) {
return (Cảm biến) sensor.get (id);
}
// snip
}
Giao diện ở ranh giới ( Bản đồ ) bị ẩn. Nó có thể phát triển với rất ít tác động đến
phần còn lại của ứng dụng. Việc sử dụng generic không còn là một vấn đề lớn vì quá trình đúc
và quản lý kiểu được xử lý bên trong lớp Cảm biến .
Giao diện này cũng được điều chỉnh và hạn chế để đáp ứng nhu cầu của ứng dụng. Nó
dẫn đến mã dễ hiểu hơn và khó sử dụng sai hơn. Lớp Cảm biến có thể
thực thi các quy tắc thiết kế và kinh doanh.
Chúng tôi không gợi ý rằng mọi việc sử dụng Bản đồ được gói gọn trong hình thức này. Đúng hơn, chúng tôi
khuyên bạn không nên vượt qua Bản đồ (hoặc bất kỳ giao diện nào khác ở ranh giới) xung quanh
hệ thống. Nếu bạn sử dụng giao diện ranh giới như Bản đồ , hãy giữ nó bên trong lớp hoặc đóng gia đình
của các lớp, nơi nó được sử dụng. Tránh trả lại hoặc chấp nhận nó như một đối số để,
các API công khai.
www.it-ebooks.info

Trang 147
116
Chương 8: Ranh giới
Ranh giới Khám phá và Học hỏi
Mã của bên thứ ba giúp chúng tôi nhận được nhiều chức năng hơn được phân phối trong thời gian ngắn hơn. Chúng
ta bắt đầu từ đâu
khi chúng tôi muốn sử dụng một số gói của bên thứ ba? Công việc của chúng tôi không phải là kiểm tra bên thứ ba
nhưng chúng tôi có thể có lợi nhất khi viết thử nghiệm cho mã của bên thứ ba mà chúng tôi sử dụng.
Giả sử không rõ cách sử dụng thư viện bên thứ ba của chúng tôi. Chúng ta có thể dành một hoặc hai ngày
(hoặc nhiều hơn) đọc tài liệu và quyết định cách chúng tôi sẽ sử dụng tài liệu đó. Sau đó chúng tôi
có thể viết mã của chúng tôi để sử dụng mã của bên thứ ba và xem liệu nó có thực hiện những gì chúng tôi nghĩ hay
không. Chúng tôi
sẽ không ngạc nhiên khi thấy mình bị sa lầy trong các phiên gỡ lỗi dài cố gắng
tìm hiểu xem lỗi chúng tôi đang gặp phải là trong mã của chúng tôi hay của chúng.
Học mã của bên thứ ba thật khó. Việc tích hợp mã của bên thứ ba cũng khó.
Làm cả hai cùng một lúc khó gấp đôi. Điều gì sẽ xảy ra nếu chúng tôi thực hiện một cách tiếp cận khác? Thay thế
thử nghiệm và thử những thứ mới trong mã sản xuất của chúng tôi, chúng tôi có thể viết một số
kiểm tra để khám phá hiểu biết của chúng tôi về mã của bên thứ ba. Jim Newkirk gọi những bài kiểm tra như vậy
kiểm tra học tập.  1
Trong các bài kiểm tra học tập, chúng tôi gọi là API của bên thứ ba, vì chúng tôi mong muốn sử dụng nó trong ứng
dụng của mình.
Về cơ bản, chúng tôi đang thực hiện các thử nghiệm được kiểm soát để kiểm tra hiểu biết của chúng tôi về API đó.
Các bài kiểm tra tập trung vào những gì chúng tôi muốn từ API.
Học log4j
Giả sử chúng tôi muốn sử dụng gói apache log4j hơn là bản ghi được xây dựng tùy chỉnh của riêng chúng tôi-
mầm. Chúng tôi tải xuống và mở trang tài liệu giới thiệu. Không quá nhiều
đọc, chúng tôi viết trường hợp thử nghiệm đầu tiên của mình, mong đợi nó viết "xin chào" vào bảng điều khiển.
@Kiểm tra
public void testLogCreate () {
Logger logger = Logger.getLogger ("MyLogger");
logger.info ("xin chào");
}
Khi chúng tôi chạy nó, trình ghi nhật ký tạo ra một lỗi cho chúng tôi biết rằng chúng tôi cần một thứ gọi là
Người thẩm định . Sau khi đọc thêm một chút, chúng tôi thấy rằng có một ConsoleAppender . Vì vậy, chúng tôi tạo ra
một
ConsoleAppender và xem liệu chúng tôi đã mở khóa bí mật đăng nhập vào bảng điều khiển hay chưa.
@Kiểm tra
public void testLogAddAppender () {
Logger logger = Logger.getLogger ("MyLogger");
ConsoleAppender appender = new ConsoleAppender ();
logger.addAppender (ứng dụng);
logger.info ("xin chào");
}
1. [BeckTDD], trang 136–137.
www.it-ebooks.info

Trang 148
117
Học log4j
Lần này chúng tôi thấy rằng Appender không có luồng đầu ra. Kỳ lạ - có vẻ hợp lý khi nó sẽ
có một cái. Sau một chút trợ giúp từ Google, chúng tôi thử những cách sau:
@Kiểm tra
public void testLogAddAppender () {
Logger logger = Logger.getLogger ("MyLogger");
logger.removeAllAppenders ();
logger.addAppender (ConsoleAppender mới (
PatternLayout mới ("% p% t% m% n"),
ConsoleAppender.SYSTEM_OUT));
logger.info ("xin chào");
}
Điều đó đã hiệu quả; một thông báo nhật ký bao gồm “xin chào” xuất hiện trên bảng điều khiển! Nó có vẻ kỳ quặc
rằng chúng ta phải nói với ConsoleAppender rằng nó ghi vào bảng điều khiển.
Điều thú vị là khi chúng ta loại bỏ đối số ConsoleAppender.SystemOut , chúng ta
thấy rằng "xin chào" vẫn được in. Nhưng khi chúng tôi lấy ra PatternLayout , nó lại một lần nữa
đồng bằng về việc thiếu một dòng đầu ra. Đây là hành vi rất kỳ lạ.
Xem xét kỹ hơn một chút trong tài liệu, chúng tôi thấy rằng mặc định
Hàm tạo ConsoleAppender là "chưa được định cấu hình", có vẻ như không quá rõ ràng hoặc hữu ích.
Điều này giống như một lỗi, hoặc ít nhất là sự không nhất quán, trong log4j .
Googling, đọc và thử nghiệm nhiều hơn một chút, và cuối cùng chúng tôi kết thúc với Liệt kê 8-1.
Chúng tôi đã khám phá ra rất nhiều điều về cách log4j hoạt động và chúng tôi đã mã hóa
kiến thức thành một tập hợp các bài kiểm tra đơn vị đơn giản.
Liệt kê 8-1
LogTest.java
public class LogTest {
trình ghi nhật ký Logger riêng tư;
@Trước
public void khởi tạo () {
logger = Logger.getLogger ("trình ghi");
logger.removeAllAppenders ();
Logger.getRootLogger (). RemoveAllAppenders ();
}
@Kiểm tra
public void basicLogger () {
BasicConfigurator.configure ();
logger.info ("basicLogger");
}
@Kiểm tra
public void addAppenderWithStream () {
logger.addAppender (ConsoleAppender mới (
PatternLayout mới ("% p% t% m% n"),
ConsoleAppender.SYSTEM_OUT));
logger.info ("addAppenderWithStream");
}
www.it-ebooks.info

Trang 149
118
Chương 8: Ranh giới
Bây giờ chúng ta biết cách khởi tạo trình ghi nhật ký bảng điều khiển đơn giản và chúng ta có thể đóng gói
kiến thức đó vào lớp trình ghi nhật ký của riêng chúng tôi để phần còn lại của ứng dụng của chúng tôi được tách biệt
khỏi
các log4j giao diện ranh giới.
Kiểm tra học tập tốt hơn miễn phí
Các bài kiểm tra học tập kết thúc không tốn kém gì. Dù sao thì chúng tôi cũng phải học API và viết
những bài kiểm tra đó là một cách dễ dàng và riêng biệt để có được kiến thức đó. Các bài kiểm tra học tập đã
các thí nghiệm chính xác đã giúp nâng cao hiểu biết của chúng tôi.
Không chỉ miễn phí kiểm tra học tập, họ còn có lợi tức đầu tư tích cực. Khi ma co
là các bản phát hành mới của gói bên thứ ba, chúng tôi chạy các bài kiểm tra học tập để xem liệu có
là những khác biệt về hành vi.
Kiểm tra học tập xác minh rằng các gói của bên thứ ba mà chúng tôi đang sử dụng hoạt động theo cách chúng tôi
mong đợi họ. Sau khi được tích hợp, không có gì đảm bảo rằng mã của bên thứ ba sẽ tồn tại
tương thích với nhu cầu của chúng tôi. Các tác giả ban đầu sẽ có áp lực phải thay đổi mã của họ thành
đáp ứng nhu cầu mới của họ. Họ sẽ sửa lỗi và thêm các khả năng mới. Với mỗi
phát hành có rủi ro mới. Nếu gói của bên thứ ba thay đổi theo một cách nào đó không tương thích với
kiểm tra của chúng tôi, chúng tôi sẽ tìm hiểu ngay lập tức.
Dù bạn có cần kiến thức được cung cấp bởi các bài kiểm tra học tập hay không, một ranh giới rõ ràng
nên được hỗ trợ bởi một tập hợp các bài kiểm tra gửi đi thực hiện giao diện giống như cách
mã sản xuất nào. Nếu không có các thử nghiệm ranh giới này để dễ dàng di chuyển, chúng tôi có thể
bị cám dỗ ở lại với phiên bản cũ lâu hơn chúng ta nên làm.
Sử dụng mã chưa tồn tại
Có một loại ranh giới khác, một loại ranh giới ngăn cách cái đã biết và cái chưa biết. Đó
thường là những vị trí trong mã mà kiến thức của chúng ta dường như không có lợi. Đôi khi
những gì ở phía bên kia của ranh giới là không thể biết được (ít nhất là ngay bây giờ). Đôi khi
chúng tôi chọn không nhìn xa hơn ranh giới.
Một số năm trước, tôi là thành viên của nhóm phát triển phần mềm cho đài phát thanh
hệ thống thông tin liên lạc. Có một hệ thống con, "Máy phát", mà chúng tôi ít biết
về, và những người chịu trách nhiệm về hệ thống con đã không đến mức xác định
giao diện của họ. Chúng tôi không muốn bị chặn, vì vậy chúng tôi bắt đầu công việc của mình từ xa
phần không xác định của mã.
@Kiểm tra
public void addAppenderWithoutStream () {
logger.addAppender (ConsoleAppender mới (
new PatternLayout ("% p% t% m% n")));
logger.info ("addAppenderWithoutStream");
}
}
Liệt kê 8-1 (tiếp theo)
LogTest.java
www.it-ebooks.info

Trang 150
119
Sử dụng mã chưa tồn tại
Chúng tôi đã có một ý tưởng khá tốt về nơi thế giới của chúng tôi kết thúc và thế giới mới bắt đầu. Như chúng ta
đã làm việc, chúng tôi đôi khi va chạm với ranh giới này. Dù sương mù và những đám mây
sự thiếu hiểu biết đã che khuất tầm nhìn của chúng tôi vượt ra ngoài ranh giới, công việc của chúng tôi khiến chúng
tôi nhận thức được những gì chúng tôi
muốn giao diện ranh giới được. Chúng tôi muốn nói với người phát điều gì đó như thế này:
Khóa máy phát trên tần số được cung cấp và phát ra một biểu diễn tương tự của
dữ liệu đến từ luồng này.
Chúng tôi không biết điều đó sẽ được thực hiện như thế nào vì API chưa được thiết kế.
Vì vậy, chúng tôi quyết định làm việc chi tiết sau.
Để tránh bị chặn, chúng tôi đã xác định giao diện của riêng mình. Chúng tôi gọi nó là một cái gì đó
hấp dẫn, giống như Transmitter . Chúng tôi đã cung cấp cho nó một phương pháp gọi là truyền nhận tần số và
dòng dữ liệu. Đây là giao diện chúng tôi ước chúng tôi có.
Một điều tốt về việc viết giao diện mà chúng tôi ước chúng tôi có là nó nằm dưới
điều khiển. Điều này giúp giữ cho mã khách hàng dễ đọc hơn và tập trung vào những gì nó đang cố gắng
đạt được.
Trong Hình 8-2, bạn có thể thấy rằng chúng tôi đã cách ly các lớp CommunicationsController
từ API máy phát (nằm ngoài tầm kiểm soát của chúng tôi và không được xác định). Bằng cách sử dụng của riêng
chúng tôi
giao diện ứng dụng cụ thể, chúng tôi giữ cho mã CommunicationsController của chúng tôi sạch sẽ và
biểu cảm. Khi API bộ truyền được xác định, chúng tôi đã viết TransmitterAdapter cho
thu hẹp khoảng cách. A DAPTOR 2 đã đóng gói tương tác với API và cung cấp
nơi duy nhất để thay đổi khi API phát triển.
Hình 8-2
Dự đoán máy phát
Thiết kế này cũng cho chúng ta một đường may 3 rất tiện lợi trong việc kiểm tra mã. Sử dụng một
FakeTransmitter phù hợp , chúng ta có thể kiểm tra các lớp CommunicationsController . Chúng tôi cũng có thể
tạo các bài kiểm tra ranh giới khi chúng tôi có TransmitterAPI để đảm bảo rằng chúng tôi đang sử dụng
API chính xác.
2. Xem mẫu Bộ điều hợp trong [GOF].
3. Xem thêm về các đường nối trong [WELC].
www.it-ebooks.info

Trang 151
120
Chương 8: Ranh giới
Ranh giới sạch
Những điều thú vị xảy ra ở ranh giới. Thay đổi là một trong những điều đó. Phần mềm tốt
thiết kế thích ứng với sự thay đổi mà không cần đầu tư lớn và làm lại. Khi chúng tôi sử dụng mã
nằm ngoài tầm kiểm soát của chúng tôi, chúng tôi phải đặc biệt thận trọng để bảo vệ khoản đầu tư của chúng tôi và
chắc chắn thay đổi trong tương lai không quá tốn kém.
Mã tại các ranh giới cần có sự phân tách rõ ràng và các bài kiểm tra xác định các kỳ vọng. Chúng tôi
nên tránh để quá nhiều mã của chúng tôi biết về các chi tiết của bên thứ ba. Thế tốt hơn rồi
phụ thuộc vào thứ bạn kiểm soát hơn là phụ thuộc vào thứ bạn không kiểm soát, vì nó sẽ kết thúc
kiểm soát bạn.
Chúng tôi quản lý ranh giới của bên thứ ba bằng cách có rất ít vị trí trong mã đề cập đến
chúng. Chúng tôi có thể quấn chúng như đã làm với Bản đồ hoặc chúng tôi có thể sử dụng A DAPTOR để chuyển đổi
từ
giao diện hoàn hảo của chúng tôi với giao diện được cung cấp. Dù bằng cách nào thì mã của chúng tôi cũng nói với
chúng tôi tốt hơn,
thúc đẩy việc sử dụng nhất quán nội bộ trên toàn bộ ranh giới và ít bảo trì hơn
điểm khi mã của bên thứ ba thay đổi.
Thư mục
[BeckTDD]: Phát triển theo hướng thử nghiệm, Kent Beck, Addison-Wesley, 2003.
[GOF]: Mẫu thiết kế: Các yếu tố của phần mềm hướng đối tượng có thể tái sử dụng, Gamma et al.,
Addison-Wesley, 1996.
[WELC]: Làm việc hiệu quả với Mã kế thừa, Addison-Wesley, 2004.
www.it-ebooks.info

Trang 152
121

9
Kiểm tra đơn vị
Nghề của chúng tôi đã đi một chặng đường dài trong mười năm qua. Năm 1997 không ai nghe nói về
Hướng phát triển thử nghiệm. Đối với đại đa số chúng ta, các bài kiểm tra đơn vị chỉ là một đoạn ngắn
mã mà chúng tôi đã viết để đảm bảo các chương trình của chúng tôi “hoạt động”. Chúng tôi sẽ cẩn thận
viết các lớp và phương thức của chúng tôi, sau đó chúng tôi sẽ tạo một số mã đặc biệt để kiểm tra
chúng. Thông thường, điều này sẽ liên quan đến một số loại chương trình trình điều khiển đơn giản cho phép
chúng tôi tương tác thủ công với chương trình chúng tôi đã viết.
Tôi nhớ đã viết một chương trình C ++ cho một hệ thống thời gian thực được nhúng trong
giữa những năm 90. Chương trình là một bộ đếm thời gian đơn giản với chữ ký sau:
void Timer :: ScheduleCommand (Command * theCommand, int mili giây)
Ý tưởng rất đơn giản; các thực hiện phương pháp của lệnh sẽ được thực hiện trong một mới
luồng sau số mili giây được chỉ định. Vấn đề là, làm thế nào để kiểm tra nó.
www.it-ebooks.info

Trang 153
122
Chương 9: Kiểm tra đơn vị
Tôi đã tập hợp một chương trình điều khiển đơn giản để nghe bàn phím. Mỗi khi một
ký tự đã được nhập, nó sẽ lên lịch một lệnh sẽ nhập cùng một ký tự năm
giây sau. Sau đó, tôi gõ một giai điệu nhịp nhàng trên bàn phím và chờ đợi điều đó
giai điệu để phát lại trên màn hình năm giây sau.
"TÔI . . . muốn-một-cô-gái. . . chỉ . . . like-the-girl-who-marr. . . ied. . . kính thưa . . . cũ . . . cha."
Tôi thực sự đã hát giai điệu đó khi gõ “.” và sau đó tôi hát lại nó như là
dấu chấm xuất hiện trên màn hình.
Đó là thử nghiệm của tôi! Khi tôi thấy nó hoạt động và chứng minh nó cho đồng nghiệp của mình, tôi đã ném
mã kiểm tra đi.
Như tôi đã nói, nghề của chúng tôi đã trải qua một chặng đường dài. Ngày nay, tôi sẽ viết một bài kiểm tra
chắc chắn rằng mọi ngóc ngách của mã đó hoạt động như tôi mong đợi. Tôi sẽ cô lập của tôi
mã từ hệ điều hành thay vì chỉ gọi các chức năng định thời tiêu chuẩn. Tôi
sẽ mô phỏng các hàm thời gian đó để tôi có quyền kiểm soát tuyệt đối về thời gian. tôi sẽ
lập lịch các lệnh đặt cờ boolean và sau đó tôi sẽ chuyển thời gian về phía trước, xem
những lá cờ đó và đảm bảo rằng chúng chuyển từ sai thành đúng khi tôi thay đổi thời gian thành
đúng giá trị.
Sau khi tôi có một bộ bài kiểm tra để vượt qua, tôi sẽ đảm bảo rằng những bài kiểm tra đó thuận tiện
để chạy cho bất kỳ ai khác cần làm việc với mã. Tôi sẽ đảm bảo rằng các bài kiểm tra và
mã đã được đăng ký cùng nhau vào cùng một gói nguồn.
Vâng, chúng tôi đã đi một chặng đường dài; nhưng chúng ta phải đi xa hơn. Động thái Agile và TDD-
ments đã khuyến khích nhiều lập trình viên viết các bài kiểm tra đơn vị tự động và hơn thế nữa
gia nhập hàng ngũ của họ mỗi ngày. Nhưng trong cơn sốt điên cuồng muốn thêm thử nghiệm vào kỷ luật của chúng
tôi, nhiều
các lập trình viên đã bỏ lỡ một số điểm tinh tế và quan trọng hơn khi viết
các bài kiểm tra tốt.
Ba định luật TDD
Giờ thì mọi người đều biết rằng TDD yêu cầu chúng tôi viết các bài kiểm tra đơn vị trước, trước khi chúng tôi viết
sản phẩm-
mã tion. Nhưng quy luật đó chỉ là phần nổi của tảng băng chìm. Hãy xem xét ba luật sau: 1
Luật thứ nhất Bạn không được viết mã sản xuất cho đến khi bạn viết một bài kiểm tra đơn vị không đạt.
Luật thứ hai Bạn không được viết nhiều bài kiểm tra đơn vị hơn mức đủ để không đạt, và không
đóng cọc đang thất bại.
Luật thứ ba Bạn không được viết nhiều mã sản xuất hơn mức đủ để vượt qua giới hạn
thi trượt thuê.
1. Chủ nghĩa chuyên nghiệp và Phát triển theo hướng thử nghiệm , Robert C. Martin, Cố vấn đối tượng, Phần mềm IEEE, tháng 5 / tháng 6 năm 2007 (Quyển 24,
Số 3) trang 32–36
http://doi.ieeecomputersociety.org/10.1109/MS.2007.85
www.it-ebooks.info

Trang 154
123
Giữ sạch các bài kiểm tra
Ba luật này khóa bạn vào một chu kỳ có lẽ dài ba mươi giây. Các bài kiểm tra
và mã sản xuất được viết cùng nhau, với các bài kiểm tra chỉ vài giây trước
Mã sản xuất.
Nếu chúng tôi làm việc theo cách này, chúng tôi sẽ viết hàng chục bài kiểm tra mỗi ngày, hàng trăm bài kiểm tra
mỗi
hàng tháng và hàng nghìn bài kiểm tra mỗi năm. Nếu chúng tôi làm việc theo cách này, những bài kiểm tra đó sẽ
bao gồm
đồng minh của tất cả các mã sản xuất của chúng tôi. Phần lớn các thử nghiệm đó, có thể sánh ngang với kích thước
của
bản thân mã sản xuất, có thể đưa ra một vấn đề quản lý khó khăn.
Giữ sạch các bài kiểm tra
Vài năm trước, tôi được yêu cầu huấn luyện một đội đã quyết định rõ ràng rằng bài kiểm tra của họ
mã không được duy trì theo cùng tiêu chuẩn chất lượng với mã sản xuất của chúng.
Họ đã cho nhau giấy phép để phá vỡ các quy tắc trong các bài kiểm tra đơn vị của họ. "Nhanh và bẩn" là
từ khóa. Các biến của chúng không cần phải được đặt tên tốt, các hàm kiểm tra của chúng không
cần phải ngắn gọn và mang tính mô tả. Mã thử nghiệm của họ không cần được thiết kế tốt và
phân vùng chu đáo. Miễn là mã kiểm tra hoạt động và miễn là nó bao gồm chương trình
mã duction, nó đã đủ tốt.
Một số bạn đọc đến đây có thể thông cảm với quyết định đó. Có lẽ, lâu trong
trước đây, bạn đã viết các bài kiểm tra thuộc loại mà tôi đã viết cho lớp Timer đó . Đó là một bước tiến lớn so với
viết loại bài kiểm tra loại bỏ đó, để viết một bộ bài kiểm tra đơn vị tự động. Vì vậy, như
nhóm mà tôi đang huấn luyện, bạn có thể quyết định rằng có những bài kiểm tra bẩn sẽ tốt hơn là không có
các bài kiểm tra.
Điều mà nhóm này không nhận ra là việc có các bài kiểm tra bẩn tương đương với, nếu không muốn nói là tệ hơn
hơn, không có thử nghiệm. Vấn đề là các bài kiểm tra phải thay đổi như mã sản xuất
tiến hóa. Các bài kiểm tra càng bẩn, chúng càng khó thay đổi. Mã kiểm tra càng rối,
thì có nhiều khả năng là bạn sẽ dành nhiều thời gian để nhồi nhét các bài kiểm tra mới vào bộ hơn là nó
để viết mã sản xuất mới. Khi bạn sửa đổi mã sản xuất, các thử nghiệm cũ sẽ bắt đầu
thất bại và sự lộn xộn trong mã kiểm tra khiến khó có thể vượt qua các bài kiểm tra đó một lần nữa. Nên
kiểm tra trở nên được coi như một trách nhiệm ngày càng tăng.
Từ khi phát hành đến khi phát hành, chi phí duy trì bộ thử nghiệm của nhóm tôi đã tăng lên. Cuối cùng nó
đã trở thành đơn khiếu nại lớn nhất giữa các nhà phát triển. Khi các nhà quản lý hỏi tại sao
ước tính của họ ngày càng lớn, các nhà phát triển đã đổ lỗi cho các thử nghiệm. Cuối cùng họ đã
buộc phải loại bỏ hoàn toàn bộ thử nghiệm.
Tuy nhiên, nếu không có bộ thử nghiệm, họ sẽ mất khả năng đảm bảo rằng các thay đổi đối với mã của họ
cơ sở hoạt động như mong đợi. Nếu không có bộ thử nghiệm, họ không thể đảm bảo rằng thay đổi thành
một phần của hệ thống của họ không phá vỡ các phần khác của hệ thống của họ. Vì vậy, tỷ lệ sai sót của họ bắt đầu
tăng lên. Khi số lượng khiếm khuyết ngoài ý muốn tăng lên, họ bắt đầu sợ phải thay đổi. Họ
ngừng làm sạch mã sản xuất của họ vì họ sợ những thay đổi sẽ làm được nhiều hơn
hại nhiều hơn lợi. Mã sản xuất của họ bắt đầu thối rữa. Cuối cùng họ không có bài kiểm tra nào,
mã sản xuất rối và nhiều lỗi, khách hàng thất vọng và cảm giác rằng
nỗ lực thử nghiệm đã thất bại họ.
www.it-ebooks.info

Trang 155
124
Chương 9: Kiểm tra đơn vị
Theo một cách nào đó họ đã đúng. Nỗ lực thử nghiệm của họ đã thất bại. Nhưng đó là quyết định của họ
để cho phép các bài kiểm tra lộn xộn là mầm mống của thất bại đó. Họ có giữ các bài kiểm tra của họ không
sạch, nỗ lực thử nghiệm của họ sẽ không thất bại. Tôi có thể nói điều này một cách chắc chắn bởi vì
Tôi đã tham gia, và huấn luyện, nhiều đội bóng đã thành công với sạch đơn vị
các bài kiểm tra.
Đạo lý của câu chuyện rất đơn giản: Mã kiểm tra cũng quan trọng như mã sản xuất. Nó
không phải là công dân hạng hai. Nó đòi hỏi sự suy nghĩ, thiết kế và cẩn thận. Nó phải được giữ sạch sẽ
như mã sản xuất.
Kiểm tra Kích hoạt khả năng
Nếu bạn không giữ sạch các bài kiểm tra của mình, bạn sẽ mất chúng. Và nếu không có chúng, bạn sẽ mất đi
thứ giúp mã sản xuất của bạn linh hoạt. Vâng, bạn đã đọc dúng điều đó. Đó là các bài kiểm tra đơn vị
giúp mã của chúng tôi linh hoạt, có thể bảo trì và có thể sử dụng lại. Lý do rất đơn giản. Nếu bạn có
kiểm tra, bạn không sợ thay đổi mã! Không có thử nghiệm, mọi thay đổi đều có thể
bọ cánh cứng. Bất kể kiến trúc của bạn linh hoạt đến đâu, bất kể bạn được phân vùng độc đáo như thế nào
thiết kế, không có thử nghiệm, bạn sẽ miễn cưỡng thực hiện thay đổi vì sợ rằng bạn
sẽ giới thiệu các lỗi chưa được phát hiện.
Nhưng với những bài kiểm tra nỗi sợ hãi hầu như biến mất. Mức độ phù hợp thử nghiệm của bạn càng cao, càng ít
nỗi sợ của bạn. Bạn có thể thực hiện các thay đổi mà gần như không bị trừng phạt đối với mã có giá trị thấp hơn
kiến trúc và một thiết kế rối và mờ đục. Thật vậy, bạn có thể cải thiện kiến trúc đó
và thiết kế mà không sợ hãi!
Vì vậy, có một bộ kiểm tra đơn vị tự động bao gồm mã sản xuất là chìa khóa để
giữ cho thiết kế và kiến trúc của bạn sạch sẽ nhất có thể. Kiểm tra cho phép tất cả các khả năng,
bởi vì các thử nghiệm cho phép thay đổi .
Vì vậy, nếu các bài kiểm tra của bạn bị bẩn, thì khả năng thay đổi mã của bạn sẽ bị cản trở và bạn
bắt đầu mất khả năng cải thiện cấu trúc của mã đó. Các bài kiểm tra của bạn càng bẩn,
mã của bạn trở nên bẩn hơn. Cuối cùng, bạn mất các bài kiểm tra và mã của bạn bị thối rữa.
Kiểm tra sạch
Điều gì tạo nên một bài kiểm tra sạch? Ba thứ. Khả năng đọc, khả năng đọc và khả năng đọc. Đọc-
khả năng có lẽ còn quan trọng hơn trong các bài kiểm tra đơn vị hơn là trong mã sản xuất. Gì
làm cho các bài kiểm tra có thể đọc được? Điều tương tự làm cho tất cả mã có thể đọc được: rõ ràng, đơn giản,
và mật độ biểu hiện. Trong một bài kiểm tra, bạn muốn nói rất nhiều với ít cách diễn đạt như
khả thi.
Hãy xem xét mã từ FitNesse trong Liệt kê 9-1. Ba bài kiểm tra này khó
hiểu và chắc chắn có thể được cải thiện. Đầu tiên, có một lượng trùng lặp khủng khiếp
mã [G5] trong các lệnh gọi lặp lại tới addPage và khẳng địnhSubString . Quan trọng hơn, điều này
mã chỉ được tải với các chi tiết can thiệp vào sự biểu đạt của bài kiểm tra.
www.it-ebooks.info

Trang 156
125
Kiểm tra sạch
Liệt kê 9-1
SerializedPageResponderTest.java
public void testGetPageHieratchyAsXml () ném Exception
{
crawller.addPage (root, PathParser.parse ("PageOne"));
crawller.addPage (root, PathParser.parse ("PageOne.ChildOne"));
crawller.addPage (root, PathParser.parse ("PageTwo"));
request.setResource ("gốc");
request.addInput ("loại", "trang");
Người trả lời phản hồi = new SerializedPageResponder ();
Phản hồi của SimpleResponse =
(SimpleResponse) responseer.makeResponse (
new FitNesseContext (root), request);
Chuỗi xml = response.getContent ();
khẳng địnhEquals ("text / xml", response.getContentType ());
khẳng địnhSubString ("<name> PageOne </name>", xml);
khẳng địnhSubString ("<name> PageTwo </name>", xml);
khẳng địnhSubString ("<name> ChildOne </name>", xml);
}
public void testGetPageHieratchyAsXmlDoesntContainSymbolicLinks ()
ném Ngoại lệ
{
WikiPage pageOne = crawller.addPage (root, PathParser.parse ("PageOne"));
crawller.addPage (root, PathParser.parse ("PageOne.ChildOne"));
crawller.addPage (root, PathParser.parse ("PageTwo"));
Dữ liệu PageData = pageOne.getData ();
Thuộc tính WikiPageProperties = data.getProperties ();
WikiPageProperty symLinks = properties.set (SymbolicPage.PROPERTY_NAME);
symLinks.set ("SymPage", "PageTwo");
pageOne.commit (dữ liệu);
request.setResource ("gốc");
request.addInput ("loại", "trang");
Người trả lời phản hồi = new SerializedPageResponder ();
Phản hồi của SimpleResponse =
(SimpleResponse) responseer.makeResponse (
new FitNesseContext (root), request);
Chuỗi xml = response.getContent ();
khẳng địnhEquals ("text / xml", response.getContentType ());
khẳng địnhSubString ("<name> PageOne </name>", xml);
khẳng địnhSubString ("<name> PageTwo </name>", xml);
khẳng địnhSubString ("<name> ChildOne </name>", xml);
khẳng địnhNotSubString ("SymPage", xml);
}
public void testGetDataAsHtml () ném Exception
{
crawller.addPage (root, PathParser.parse ("TestPageOne"), "trang thử nghiệm");
request.setResource ("TestPageOne");
request.addInput ("type", "data");
www.it-ebooks.info

Trang 157
126
Chương 9: Kiểm tra đơn vị
Ví dụ, hãy xem các cuộc gọi PathParser . Họ chuyển đổi các chuỗi thành PagePath
các phiên bản được trình thu thập thông tin sử dụng. Sự biến đổi này hoàn toàn không liên quan đến thử nghiệm tại
và chỉ phục vụ để làm xáo trộn ý định. Các chi tiết xung quanh việc tạo ra
phản hồi và việc thu thập và đúc phản hồi cũng chỉ là tiếng ồn. Sau đó, có
cách ham muốn rằng URL yêu cầu được tạo từ một tài nguyên và một đối số. (Tôi đã giúp
viết mã này, vì vậy tôi cảm thấy thoải mái để chỉ trích nó.)
Cuối cùng, mã này không được thiết kế để đọc. Người đọc tội nghiệp bị ngập trong một
một loạt các chi tiết phải được hiểu trước khi các thử nghiệm có ý nghĩa thực sự.
Bây giờ, hãy xem xét các bài kiểm tra được cải tiến trong Liệt kê 9-2. Những bài kiểm tra này làm cùng một điều,
nhưng chúng đã được cấu trúc lại thành một hình thức dễ hiểu và dễ hiểu hơn nhiều.
Người trả lời phản hồi = new SerializedPageResponder ();
Phản hồi của SimpleResponse =
(SimpleResponse) responseer.makeResponse (
new FitNesseContext (root), request);
Chuỗi xml = response.getContent ();
khẳng địnhEquals ("text / xml", response.getContentType ());
khẳng địnhSubString ("trang thử nghiệm", xml);
khẳng địnhSubString ("<Thử nghiệm", xml);
}
Liệt kê 9-2
SerializedPageResponderTest.java (tái cấu trúc)
public void testGetPageHierarchyAsXml () ném Exception {
makePages ("PageOne", "PageOne.ChildOne", "PageTwo");
submitRequest ("root", "type: pages");
khẳng địnhResponseIsXML ();
khẳng địnhResponseContains (
"<name> PageOne </name>", "<name> PageTwo </name>", "<name> ChildOne </name>"
);
}
public void testSymbolicLinksAreNotInXmlPageHierarchy () ném Exception {
Trang WikiPage = makePage ("TrangOne");
makePages ("PageOne.ChildOne", "PageTwo");
addLinkTo (trang, "PageTwo", "SymPage");
submitRequest ("root", "type: pages");
khẳng địnhResponseIsXML ();
khẳng địnhResponseContains (
"<name> PageOne </name>", "<name> PageTwo </name>", "<name> ChildOne </name>"
);
khẳng địnhResponseDoesNotContain ("SymPage");
}
Liệt kê 9-1 (tiếp theo)
SerializedPageResponderTest.java
www.it-ebooks.info

Trang 158
127
Kiểm tra sạch
Mô hình B UILD -O PERATE -C HECK 2 được thể hiện rõ ràng bởi cấu trúc của các bài kiểm tra này.
Mỗi bài kiểm tra được chia thành ba phần rõ ràng. Phần đầu tiên xây dựng dữ liệu thử nghiệm,
phần thứ hai hoạt động trên dữ liệu thử nghiệm đó và phần thứ ba kiểm tra xem hoạt động có mang lại kết quả
không
kết quả mong đợi.
Lưu ý rằng phần lớn các chi tiết khó chịu đã được loại bỏ. Các bài kiểm tra nhận được
phù hợp với vấn đề và chỉ sử dụng các kiểu dữ liệu và chức năng mà họ thực sự cần. Bất kỳ ai
những người đọc các bài kiểm tra này sẽ có thể tìm ra những gì họ làm rất nhanh chóng, không bị
nhầm lẫn hoặc bị choáng ngợp bởi các chi tiết.
Ngôn ngữ thử nghiệm dành riêng cho miền
Các bài kiểm tra trong Liệt kê 9-2 chứng minh kỹ thuật xây dựng ngôn ngữ dành riêng cho miền
cho các bài kiểm tra của bạn. Thay vì sử dụng các API mà các lập trình viên sử dụng để thao tác với hệ thống-
tem, chúng tôi xây dựng một tập hợp các chức năng và tiện ích sử dụng các API đó và
làm cho các bài kiểm tra thuận tiện hơn để viết và dễ đọc hơn. Các chức năng và tiện ích này
trở thành một API chuyên biệt được sử dụng bởi các bài kiểm tra. Chúng là một ngôn ngữ thử nghiệm lập trình-
mers sử dụng để giúp bản thân viết các bài kiểm tra của họ và để giúp những người phải đọc
kiểm tra sau này.
API thử nghiệm này không được thiết kế trước; thay vào đó nó phát triển từ quá trình tiếp tục
mã kiểm tra đã quá bẩn do làm xáo trộn chi tiết. Cũng như bạn đã nhìn thấy tôi
refactor Liệt kê 9-1 thành Liệt kê 9-2, vì vậy các nhà phát triển có kỷ luật cũng sẽ cấu trúc lại thử nghiệm của họ
mã thành các hình thức ngắn gọn và biểu đạt hơn.
Một tiêu chuẩn kép
Theo một nghĩa nào đó, đội mà tôi đã đề cập ở đầu chương này đã làm đúng. Các
mã trong API thử nghiệm không có một bộ khác nhau của các tiêu chuẩn kỹ thuật hơn produc-
mã tion. Nó vẫn phải đơn giản, ngắn gọn và diễn đạt, nhưng nó không cần phải hiệu quả như
Mã sản xuất. Xét cho cùng, nó chạy trong môi trường thử nghiệm, không phải môi trường sản xuất và
hai môi trường đó có những nhu cầu rất khác nhau.
public void testGetDataAsXml () ném Exception {
makePageWithContent ("TestPageOne", "trang thử nghiệm");
submitRequest ("TestPageOne", "type: data");
khẳng địnhResponseIsXML ();
khẳng địnhResponseContains ("trang thử nghiệm", "<thử nghiệm");
}
2. http://fitnesse.org/FitNesse.AcceptanceTestPatterns
Liệt kê 9-2 (tiếp theo)
SerializedPageResponderTest.java (tái cấu trúc)
www.it-ebooks.info

Trang 159
128
Chương 9: Kiểm tra đơn vị
Hãy xem xét thử nghiệm trong Liệt kê 9-3. Tôi đã viết bài kiểm tra này như một phần của hệ thống kiểm soát môi
trường-
tem Tôi đang tạo mẫu. Nếu không đi vào chi tiết, bạn có thể nói rằng thử nghiệm này kiểm tra
cảnh báo nhiệt độ thấp, lò sưởi và quạt gió đều được bật khi nhiệt độ
chắc chắn là "quá lạnh."
Tất nhiên, có rất nhiều chi tiết ở đây. Ví dụ: chức năng tic đó là gì
trong khoảng? Trên thực tế, tôi muốn bạn không lo lắng về điều đó khi đọc bài kiểm tra này. Tôi muốn bạn chỉ
lo lắng về việc liệu bạn có đồng ý rằng trạng thái cuối của hệ thống phù hợp với tem-
nhiệt độ là "quá lạnh."
Lưu ý, khi bạn đọc bài kiểm tra, mắt bạn cần phải đảo qua lại giữa
tên của trạng thái đang được kiểm tra và cảm giác của trạng thái được kiểm tra. Bạn thấy đấy
hotState , và sau đó mắt bạn liếc sang trái để khẳng địnhTrue . Bạn thấy CoolState và của bạn
mắt phải theo dõi bên trái để khẳng địnhFalse . Điều này là tẻ nhạt và không đáng tin cậy. Nó làm cho bài kiểm tra khó
đọc.
Tôi đã cải thiện khả năng đọc của bài kiểm tra này rất nhiều bằng cách chuyển nó thành Liệt kê 9-4.
Tất nhiên tôi đã ẩn chi tiết của hàm tic bằng cách tạo một hàm wayTooCold . Nhưng
điều cần lưu ý là chuỗi kỳ lạ trong khẳng địnhEquals . Chữ hoa có nghĩa là “trên”, chữ thường
có nghĩa là “tắt” và các chữ cái luôn theo thứ tự sau: {lò sưởi, quạt gió, bộ làm mát,
hi-temp-alert, lo-temp-alert} .
Mặc dù điều này gần như vi phạm quy tắc về lập bản đồ tâm thần, 3 có vẻ như
thích hợp trong trường hợp này. Lưu ý, khi bạn biết ý nghĩa, mắt bạn lướt qua
Liệt kê 9-3
EnvironmentControllerTest.java
@Kiểm tra
public void turnOnLoTempAlarmAtThreashold () ném Exception {
hw.setTemp (WAY_TOO_COLD);
controller.tic ();
khẳng địnhTrue (hw.heaterState ());
khẳng địnhTrue (hw.blowerState ());
khẳng địnhFalse (hw.coolerState ());
khẳng địnhFalse (hw.hiTempAlarm ());
khẳng địnhTrue (hw.loTempAlarm ());
}
Liệt kê 9-4
EnvironmentControllerTest.java (tái cấu trúc)
@Kiểm tra
public void turnOnLoTempAlarmAtThreshold () ném Exception {
wayTooCold ();
khẳng địnhEquals ("HBchL", hw.getState ());
}
3. “Tránh lập bản đồ tinh thần” trên trang 25.
www.it-ebooks.info

Trang 160
129
Kiểm tra sạch
chuỗi đó và bạn có thể nhanh chóng giải thích kết quả. Đọc bài kiểm tra gần như trở thành một
vui lòng. Chỉ cần xem qua Liệt kê 9-5 và xem mức độ dễ hiểu của các thử nghiệm này.
Hàm getState được hiển thị trong Liệt kê 9-6. Lưu ý rằng điều này không hiệu quả lắm
mã. Để làm cho nó hiệu quả, có lẽ tôi nên sử dụng một StringBuffer .
StringBuffer s hơi xấu. Ngay cả trong mã sản xuất, tôi sẽ tránh chúng nếu chi phí
nhỏ; và bạn có thể tranh luận rằng chi phí của mã trong Liệt kê 9-6 là rất nhỏ. Tuy nhiên,
ứng dụng này rõ ràng là một hệ thống thời gian thực được nhúng và có khả năng là máy tính và
tài nguyên bộ nhớ rất hạn chế. Các bài kiểm tra môi trường, tuy nhiên, là không có khả năng
hạn chế ở tất cả.
Liệt kê 9-5
EnvironmentControllerTest.java (lựa chọn lớn hơn)
@Kiểm tra
public void turnOnCoolerAndBlowerIfTooHot () ném Exception {
quá nóng();
khẳng địnhEquals ("hBChl", hw.getState ());
}
@Kiểm tra
public void turnOnHeaterAndBlowerIfTooCold () ném Exception {
quá lạnh();
khẳng địnhEquals ("HBchl", hw.getState ());
}
@Kiểm tra
public void turnOnHiTempAlarmAtThreshold () ném Exception {
wayTooHot ();
khẳng địnhEquals ("hBCHl", hw.getState ());
}
@Kiểm tra
public void turnOnLoTempAlarmAtThreshold () ném Exception {
wayTooCold ();
khẳng địnhEquals ("HBchL", hw.getState ());
}
Liệt kê 9-6
MockControlHardware.java
public String getState () {
Trạng thái chuỗi = "";
trạng thái + = lò sưởi? "H": "h";
trạng thái + = quạt gió? "B": "b";
trạng thái + = mát hơn? "C": "c";
trạng thái + = hiTempAlarm? "H": "h";
trạng thái + = loTempAlarm? "L": "l";
trở lại trạng thái;
}
www.it-ebooks.info

Trang 161
130
Chương 9: Kiểm tra đơn vị
Đó là bản chất của tiêu chuẩn kép. Có những điều mà bạn có thể không bao giờ làm trong một
môi trường sản xuất hoàn toàn tốt trong môi trường thử nghiệm. Thông thường chúng liên quan đến
các vấn đề về bộ nhớ hoặc hiệu quả của CPU. Nhưng chúng không bao giờ liên quan đến vấn đề sạch sẽ.
Một xác nhận cho mỗi bài kiểm tra
Có một trường phái tư tưởng 4 nói rằng mọi hàm kiểm tra trong một bài kiểm tra JUnit nên có một
và chỉ có một câu khẳng định. Quy tắc này có vẻ hà khắc, nhưng có thể thấy lợi thế
trong Liệt kê 9-5. Những thử nghiệm đó đưa ra một kết luận duy nhất nhanh chóng và dễ hiểu.
Nhưng còn Liệt kê 9-2 thì sao? Có vẻ như không hợp lý khi bằng cách nào đó chúng ta có thể dễ dàng
hợp nhất xác nhận rằng đầu ra là XML và nó chứa các chuỗi con nhất định. Làm sao-
bao giờ hết, chúng tôi có thể chia thử nghiệm thành hai thử nghiệm riêng biệt, mỗi thử nghiệm có khẳng định cụ thể
riêng,
được hiển thị trong Liệt kê 9-7.
Lưu ý rằng tôi đã thay đổi tên của các hàm để sử dụng chung cho trước-khi-
thì 5 quy ước. Điều này làm cho các bài kiểm tra thậm chí còn dễ đọc hơn. Thật không may, tách các bài kiểm tra
như được hiển thị dẫn đến rất nhiều mã trùng lặp.
Chúng ta có thể loại bỏ sự trùng lặp bằng T EMPLATE M ETHOD 6 mô hình và đưa
các phần đã cho / khi trong lớp cơ sở và các phần sau đó trong các dẫn xuất khác nhau. Hoặc chúng tôi có thể
tạo ra một lớp thử nghiệm hoàn toàn riêng biệt và đưa cho và khi bộ phận trong @Before func-
tion và các phần khi nào trong mỗi hàm @Test . Nhưng điều này có vẻ như quá nhiều cơ chế cho
một vấn đề nhỏ như vậy. Cuối cùng, tôi thích nhiều xác nhận trong Liệt kê 9-2 hơn.
4. Xem mục blog của Dave Astel: http://www.artima.com/weblogs/viewpost.jsp?thread=35578
Liệt kê 9-7
SerializedPageResponderTest.java (Một khẳng định)
public void testGetPageHierarchyAsXml () ném Exception {
GivenPages ("PageOne", "PageOne.ChildOne", "PageTwo");
whenRequestIsIssued ("root", "type: pages");
thenResponseShouldBeXML ();
}
public void testGetPageHierarchyHasRightTags () ném Exception {
GivenPages ("PageOne", "PageOne.ChildOne", "PageTwo");
whenRequestIsIssued ("root", "type: pages");
thenResponseShouldContain (
"<name> PageOne </name>", "<name> PageTwo </name>", "<name> ChildOne </name>"
);
}
5. [RSpec].
6. [GOF].
www.it-ebooks.info

Trang 162
131
Một xác nhận cho mỗi bài kiểm tra
Tôi nghĩ rằng quy tắc khẳng định duy nhất là một hướng dẫn tốt. 7 Tôi thường cố gắng tạo một miền-
ngôn ngữ thử nghiệm cụ thể hỗ trợ nó, như trong Liệt kê 9-5. Nhưng tôi không ngại đặt
nhiều hơn một khẳng định trong một bài kiểm tra. Tôi nghĩ điều tốt nhất chúng ta có thể nói là số lượng
khẳng định trong một thử nghiệm phải được giảm thiểu.
Khái niệm đơn cho mỗi thử nghiệm
Có lẽ một quy tắc tốt hơn là chúng ta muốn kiểm tra một khái niệm duy nhất trong mỗi hàm kiểm tra. Chúng tôi
không
muốn các chức năng thử nghiệm dài đi thử nghiệm hết thứ này đến thứ khác. Liệt kê 9-8
là một ví dụ về một thử nghiệm như vậy. Bài kiểm tra này nên được chia thành ba bài kiểm tra độc lập
bởi vì nó kiểm tra ba thứ độc lập. Hợp nhất tất cả chúng lại với nhau thành cùng một chức năng
buộc người đọc phải tìm ra lý do tại sao mỗi phần ở đó và điều gì đang được kiểm tra bởi phần đó
phần.
Ba chức năng kiểm tra có thể phải như thế này:
• Cho ngày cuối cùng của tháng có 31 ngày (như tháng 5):
1. Khi bạn thêm một tháng, sao cho ngày cuối cùng của tháng đó là ngày 30
(như tháng 6), thì ngày đó phải là ngày 30 của tháng đó, không phải ngày 31.
2. Khi bạn thêm hai tháng vào ngày đó, sao cho tháng cuối cùng có 31 ngày,
thì ngày sẽ là ngày 31.
7. "Giữ mã!"
Liệt kê 9-8
/ **
* Các bài kiểm tra khác cho phương thức addMonths ().
*/
public void testAddMonths () {
SerialDate d1 = SerialDate.createInstance (31, 5, 2004);
SerialDate d2 = SerialDate.addMonths (1, d1);
khẳng địnhEquals (30, d2.getDayOfMonth ());
khẳng địnhEquals (6, d2.getMonth ());
khẳng địnhEquals (2004, d2.getYYYY ());
SerialDate d3 = SerialDate.addMonths (2, d1);
khẳng địnhEquals (31, d3.getDayOfMonth ());
khẳng địnhEquals (7, d3.getMonth ());
khẳng địnhEquals (2004, d3.getYYYY ());
SerialDate d4 = SerialDate.addMonths (1, SerialDate.addMonths (1, d1));
khẳng địnhEquals (30, d4.getDayOfMonth ());
khẳng địnhEquals (7, d4.getMonth ());
khẳng địnhEquals (2004, d4.getYYYY ());
}
www.it-ebooks.info

Trang 163
132
Chương 9: Kiểm tra đơn vị
• Cho ngày cuối cùng của tháng với 30 ngày trong đó (như tháng 6):
1. Khi bạn thêm một tháng như vậy mà ngày cuối cùng của tháng đó có 31 ngày, sau đó các
ngày phải là ngày 30, không phải ngày 31.
Nói như thế này, bạn có thể thấy rằng có một quy tắc chung ẩn giữa sự sai lầm-
các bài kiểm tra neous. Khi bạn tăng tháng, ngày không được lớn hơn ngày cuối cùng của
tháng. Điều này ngụ ý rằng tăng tháng vào ngày 28 tháng 2 sẽ mang lại tháng 3
Ngày 28. Bài kiểm tra đó bị thiếu và sẽ là một bài kiểm tra hữu ích để viết.
Vì vậy, không phải nhiều xác nhận trong mỗi phần của Liệt kê 9-8 gây ra vấn đề.
Đúng hơn là thực tế là có nhiều hơn một khái niệm đang được thử nghiệm. Vì vậy, có lẽ là tốt nhất
quy tắc là bạn nên giảm thiểu số lượng xác nhận cho mỗi khái niệm và chỉ kiểm tra một
cept cho mỗi chức năng kiểm tra.
8 ĐẦU TIÊN
Kiểm tra sạch tuân theo năm quy tắc khác tạo thành từ viết tắt ở trên:
Kiểm tra nhanh phải nhanh chóng. Họ nên chạy thật nhanh. Khi các bài kiểm tra chạy chậm, bạn sẽ không muốn
để chạy chúng thường xuyên. Nếu bạn không chạy chúng thường xuyên, bạn sẽ không sớm phát hiện ra vấn đề
đủ để sửa chữa chúng một cách dễ dàng. Bạn sẽ không cảm thấy thoải mái khi xóa mã. Cuối cùng thì mã
sẽ bắt đầu thối rữa.
Các bài kiểm tra độc lập không nên phụ thuộc vào nhau. Một thử nghiệm không nên thiết lập điều kiện-
tions cho bài kiểm tra tiếp theo. Bạn sẽ có thể chạy từng bài kiểm tra một cách độc lập và chạy các bài kiểm tra
trong
bất kỳ thứ tự nào bạn thích. Khi các bài kiểm tra phụ thuộc vào nhau, thì cái đầu tiên không thành công sẽ gây ra
cade của các lỗi hạ lưu, làm cho chẩn đoán khó khăn và che giấu các khuyết tật ở hạ lưu.
Lặp lại thử nghiệm nên được lặp lại trong bất kỳ môi trường. Bạn sẽ có thể chạy
kiểm tra trong môi trường sản xuất, trong môi trường QA và trên máy tính xách tay của bạn trong khi
đi tàu về nhà mà không có mạng. Nếu các bài kiểm tra của bạn không thể lặp lại trong bất kỳ môi trường nào
nhắc nhở, sau đó bạn sẽ luôn có một cái cớ cho lý do tại sao họ thất bại. Bạn cũng sẽ thấy mình không thể
để chạy các bài kiểm tra khi môi trường không khả dụng.
Tự xác thực Các bài kiểm tra phải có đầu ra boolean. Họ đạt hoặc không đạt. Bạn
không cần phải đọc qua tệp nhật ký để biết liệu các bài kiểm tra có vượt qua hay không. Bạn không nên có
để so sánh thủ công hai tệp văn bản khác nhau để xem liệu các bài kiểm tra có vượt qua hay không. Nếu các bài
kiểm tra không
tự xác thực, sau đó thất bại có thể trở nên chủ quan và việc chạy thử nghiệm có thể đòi hỏi một thời gian dài
đánh giá thủ công.
8. Tài liệu Đào tạo Cố vấn Đối tượng.
www.it-ebooks.info

Trang 164
133
Thư mục
Đúng lúc Các bài kiểm tra cần được viết đúng thời hạn. Đơn vị xét nghiệm nên được viết chỉ
trước mã sản xuất khiến chúng vượt qua. Nếu bạn viết các bài kiểm tra sau khi sản xuất
thì bạn có thể thấy mã sản xuất khó kiểm tra. Bạn có thể quyết định rằng một số
mã sản xuất quá khó để kiểm tra. Bạn không thể thiết kế mã sản xuất để có thể kiểm tra được.
Phần kết luận
Chúng tôi hầu như không làm xước bề mặt của chủ đề này. Thật vậy, tôi nghĩ rằng cả một cuốn sách có thể
viết về các bài kiểm tra sạch . Các bài kiểm tra cũng quan trọng đối với sức khỏe của một dự án như quá trình sản
xuất
mã là. Có lẽ chúng thậm chí còn quan trọng hơn, bởi vì các bài kiểm tra bảo tồn và nâng cao
tính linh hoạt, khả năng bảo trì và khả năng tái sử dụng của mã sản xuất. Vì vậy, hãy duy trì các bài kiểm tra của
bạn-
sạch sẽ nghiêm ngặt. Làm việc để làm cho chúng diễn đạt và ngắn gọn. API thử nghiệm phát minh hoạt động như
ngôn ngữ miền cụ thể giúp bạn viết các bài kiểm tra.
Nếu bạn để các bài kiểm tra bị mục, thì mã của bạn cũng sẽ bị mục. Giữ các bài kiểm tra của bạn sạch sẽ.
Thư mục
[RSpec]: RSpec: Phát triển theo hướng hành vi cho các lập trình viên Ruby ,
Aslak Hellesøy, David Chelimsky, Giá sách thực dụng, 2008.
[GOF]: Mẫu thiết kế: Các yếu tố của phần mềm hướng đối tượng có thể tái sử dụng , Gamma et al.,
Addison-Wesley, 1996.
www.it-ebooks.info

Trang 165
Trang này cố ý để trống
www.it-ebooks.info

Trang 166
135

10
Các lớp học
với Jeff Langr
Cho đến nay trong cuốn sách này, chúng tôi đã tập trung vào cách viết tốt các dòng và khối mã. Chúng ta có
đi sâu vào thành phần thích hợp của các chức năng và cách chúng tương tác với nhau. Nhưng đối với tất cả sự suy
giảm-
liên quan đến tính biểu đạt của các câu lệnh mã và các chức năng mà chúng bao gồm, chúng tôi vẫn
không có mã sạch cho đến khi chúng tôi chú ý đến các cấp tổ chức mã cao hơn. Hãy
nói về lớp học sạch sẽ.
www.it-ebooks.info

Trang 167
136
Chương 10: Lớp học
Tổ chức lớp học
Theo quy ước Java tiêu chuẩn, một lớp phải bắt đầu bằng một danh sách các biến. Quán rượu-
Các hằng số tĩnh lic, nếu có, nên xuất hiện trước. Sau đó, các biến tĩnh riêng, tiếp theo là pri-
các biến thể hiện vate. Ít khi có lý do chính đáng để có một biến công khai.
Các hàm công khai nên tuân theo danh sách các biến. Chúng tôi muốn đưa các tiện ích riêng
được gọi bởi một hàm công cộng ngay sau chính hàm công cộng. Điều này theo sau bước xuống
quy tắc và giúp chương trình đọc giống như một bài báo.
Đóng gói
Chúng tôi muốn giữ các biến và các hàm tiện ích của mình ở chế độ riêng tư, nhưng chúng tôi không quá cuồng tín
về nó.
Đôi khi chúng ta cần bảo vệ một biến hoặc một hàm tiện ích để nó có thể
được truy cập bằng một bài kiểm tra. Đối với chúng tôi, quy tắc kiểm tra. Nếu một bài kiểm tra trong cùng một gói
cần gọi một hàm
hoặc truy cập một biến, chúng tôi sẽ làm cho nó được bảo vệ hoặc phạm vi gói. Tuy nhiên, trước tiên chúng tôi sẽ
tìm kiếm
một cách để duy trì sự riêng tư. Nới lỏng sự đóng gói luôn là phương sách cuối cùng.
Lớp học nên nhỏ!
Quy tắc đầu tiên của các lớp là chúng phải nhỏ. Quy tắc thứ hai của các lớp là chúng
nên nhỏ hơn thế. Không, chúng tôi sẽ không lặp lại cùng một văn bản từ
Chương chức năng . Nhưng cũng như với các hàm, nhỏ hơn là quy tắc chính khi nói đến
thiết kế các lớp học. Đối với các chức năng, câu hỏi trước mắt của chúng tôi luôn là "Nhỏ như thế nào?"
Với các chức năng, chúng tôi đo kích thước bằng cách đếm đường vật lý. Với các lớp học, chúng tôi sử dụng
biện pháp khác nhau. Chúng tôi tính trách nhiệm.  1
Liệt kê 10-1 phác thảo một lớp, SuperDashboard , hiển thị khoảng 70 phương thức công khai.
Hầu hết các nhà phát triển sẽ đồng ý rằng nó có kích thước hơi quá lớn. Một số nhà phát triển có thể tham khảo
lên SuperDashboard như một “đẳng cấp của Chúa”.
1. [RDD].
Liệt kê 10-1
Quá nhiều trách nhiệm
public class SuperDashboard mở rộng JFrame triển khai MetaDataUser
public String getCustomizerLanguagePath ()
public void setSystemConfigPath (String systemConfigPath)
public String getSystemConfigDocument ()
public void setSystemConfigDocument (Chuỗi hệ thốngConfigDocument)
public boolean getGuruState ()
public boolean getNoviceState ()
public boolean getOpenSourceState ()
public void showObject (đối tượng MetaObject)
public void showProgress (Chuỗi s)
www.it-ebooks.info

Trang 168
137
Lớp học nên nhỏ!
public boolean isMetadataDirty ()
public void setIsMetadataDirty (boolean isMetadataDirty)
public Component getLastFocusedComponent ()
public void setLastFocused (Thành phần lastFocused)
public void setMouseSelectState (boolean isMouseSelected)
public boolean isMouseSelected ()
public LanguageManager getLanguageManager ()
dự án công cộng getProject ()
public Project getFirstProject ()
public Project getLastProject ()
public String getNewProjectName ()
public void setComponentSizes (Dimension dim)
public String getCurrentDir ()
public void setCurrentDir (String newDir)
public void updateStatus (int dotPos, int markPos)
public Class [] getDataBaseClasses ()
public MetadataFeeder getMetadataFeeder ()
public void addProject (Dự án dự án)
public boolean setCurrentProject (Dự án dự án)
public boolean removeProject (Dự án dự án)
public MetaProjectHeader getProgramMetadata ()
public void resetDashboard ()
public Project loadProject (String fileName, String projectName)
public void setCanSaveMetadata (boolean canSave)
public MetaObject getSelectedObject ()
public void deselectObjects ()
public void setProject (Dự án dự án)
public void editorAction (String actionName, ActionEvent event)
public void setMode (int mode)
public FileManager getFileManager ()
public void setFileManager (FileManager fileManager)
public ConfigManager getConfigManager ()
public void setConfigManager (ConfigManager configManager)
public ClassLoader getClassLoader ()
public void setClassLoader (ClassLoader classLoader)
Thuộc tính công cộng getProps ()
public String getUserHome ()
public String getBaseDir ()
public int getMajorVersionNumber ()
public int getMinorVersionNumber ()
public int getBuildNumber ()
dán MetaObject công khai (
Mục tiêu MetaObject, MetaObject đã dán, Dự án MetaProject)
public void processMenuItems (MetaObject metaObject)
public void processMenuSeparators (MetaObject metaObject)
public void processTabPages (MetaObject metaObject)
public void processPlacement (đối tượng MetaObject)
public void processCreateLayout (đối tượng MetaObject)
public void updateDisplayLayer (đối tượng MetaObject, int layerIndex)
public void propertyEditedRepaint (đối tượng MetaObject)
public void processDeleteObject (đối tượng MetaObject)
public boolean getAttachedToDesigner ()
public void processProjectChangedState (boolean hasProjectChanged)
public void processObjectNameChanged (đối tượng MetaObject)
public void runProject ()
Liệt kê 10-1 (tiếp theo)
Quá nhiều trách nhiệm
www.it-ebooks.info

Trang 169
138
Chương 10: Lớp học
Nhưng điều gì sẽ xảy ra nếu SuperDashboard chỉ chứa các phương thức được hiển thị trong Liệt kê 10-2?
Năm phương pháp không phải là quá nhiều, phải không? Trong trường hợp này là vì mặc dù số lượng nhỏ
trong số các phương thức, SuperDashboard có quá nhiều trách nhiệm .
Tên của một lớp phải mô tả những trách nhiệm mà nó thực hiện. Trên thực tế, việc đặt tên
có lẽ là cách đầu tiên giúp xác định quy mô lớp học. Nếu chúng ta không thể rút ra một cách ngắn gọn
tên cho một lớp, thì nó có thể quá lớn. Tên lớp càng mơ hồ thì càng
có khả năng nó có quá nhiều trách nhiệm. Ví dụ: tên lớp bao gồm các từ chồn
như bộ xử lý hoặc quản lý hoặc siêu thường gợi ý tại tập hợp bất hạnh của
trách nhiệm.
Chúng ta cũng có thể viết một mô tả ngắn gọn về lớp học trong khoảng 25 từ,
mà không sử dụng các từ “nếu”, “và” “hoặc” hoặc “nhưng”. Làm thế nào chúng tôi sẽ mô tả
SuperDashboard ? “ SuperDashboard cung cấp quyền truy cập vào thành phần cuối cùng giữ
tập trung, và nó cũng cho phép chúng tôi theo dõi phiên bản và xây dựng số lượng. ” Chữ “và” đầu tiên là
gợi ý rằng SuperDashboard có quá nhiều trách nhiệm.
Nguyên tắc trách nhiệm duy nhất
Nguyên tắc Trách nhiệm Đơn lẻ (SRP) 2 tuyên bố rằng một lớp hoặc mô-đun phải có một,
và chỉ một, lý do để thay đổi . Nguyên tắc này cho chúng ta cả định nghĩa về trách nhiệm,
và hướng dẫn về quy mô lớp học. Các lớp nên có một trách nhiệm — một lý do để
thay đổi.
public void setAçowDragging (boolean allowDragging)
public boolean allowDragging ()
public boolean isCustomizing ()
public void setTitle (String title)
public IdeMenuBar getIdeMenuBar ()
public void showHelper (MetaObject metaObject, String propertyName)
// ... nhiều phương thức không công khai theo sau ...
}
Liệt kê 10-2
Đủ nhỏ?
public class SuperDashboard mở rộng JFrame triển khai MetaDataUser
public Component getLastFocusedComponent ()
public void setLastFocused (Thành phần lastFocused)
public int getMajorVersionNumber ()
public int getMinorVersionNumber ()
public int getBuildNumber ()
}
2. Bạn có thể đọc thêm về nguyên tắc này trong [PPP].
Liệt kê 10-1 (tiếp theo)
Quá nhiều trách nhiệm
www.it-ebooks.info
Trang 170
139
Lớp học nên nhỏ!
Lớp SuperDashboard có vẻ nhỏ trong Liệt kê 10-2 có hai lý do để thay đổi.
Đầu tiên, nó theo dõi thông tin về phiên bản dường như cần được cập nhật mỗi khi
phần mềm được vận chuyển. Thứ hai, nó quản lý các thành phần Java Swing (nó là một dẫn xuất của
JFrame , đại diện Swing của cửa sổ GUI cấp cao nhất). Không nghi ngờ gì nữa, chúng tôi sẽ muốn
cập nhật số phiên bản nếu chúng tôi thay đổi bất kỳ mã Swing nào, nhưng trò chuyện không bị liệt vào danh sách-
hoàn toàn đúng: Chúng tôi có thể thay đổi thông tin phiên bản dựa trên những thay đổi đối với mã khác trong
hệ thống.
Cố gắng xác định trách nhiệm (lý do để thay đổi) thường giúp chúng ta nhận ra và
tạo sự trừu tượng tốt hơn trong mã của chúng tôi. Chúng tôi có thể dễ dàng giải nén cả ba SuperDashboard
các phương thức xử lý thông tin phiên bản thành một lớp riêng biệt có tên Phiên bản. (Xem
Liệt kê 10-3.) Lớp Phiên bản là một cấu trúc có tiềm năng cao để sử dụng lại trong
các ứng dụng!
SRP là một trong những khái niệm quan trọng hơn trong thiết kế OO. Nó cũng là một trong những đơn giản
các khái niệm cần hiểu và tuân thủ. Tuy nhiên, điều kỳ lạ là SRP thường là thiết kế lớp bị lạm dụng nhiều nhất
nguyên tắc. Chúng tôi thường xuyên gặp phải các lớp học làm quá nhiều thứ. Tại sao?
Làm cho phần mềm hoạt động và làm cho phần mềm sạch là hai hoạt động rất khác nhau.
Hầu hết chúng ta đều có giới hạn trong đầu, vì vậy chúng ta tập trung vào việc mã của mình hoạt động nhiều hơn
hơn tổ chức và sạch sẽ. Điều này hoàn toàn phù hợp. Duy trì sự tách biệt của
mối quan tâm cũng quan trọng trong các hoạt động lập trình của chúng tôi cũng như trong các chương trình của
chúng tôi.
Vấn đề là có quá nhiều người trong chúng ta nghĩ rằng chúng ta đã hoàn thành một khi chương trình hoạt động.
Chúng tôi không thể chuyển sang mối quan tâm khác là tổ chức và sự sạch sẽ. Chúng tôi chuyển sang
vấn đề tiếp theo thay vì quay lại và chia nhỏ các lớp được nhồi quá nhiều thành
đơn vị chịu trách nhiệm đơn lẻ.
Đồng thời, nhiều nhà phát triển lo ngại rằng một số lượng lớn các công ty nhỏ, mục đích duy nhất
các lớp làm cho việc hiểu bức tranh lớn hơn trở nên khó khăn hơn. Họ lo ngại rằng
họ phải điều hướng từ lớp này sang lớp khác để tìm ra cách một phần công việc lớn hơn được
đã hoàn thành.
Tuy nhiên, một hệ thống có nhiều lớp nhỏ không có nhiều bộ phận chuyển động hơn một hệ thống
với một vài lớp học lớn. Có nhiều thứ để học trong hệ thống với một số
các lớp học. Vì vậy, câu hỏi đặt ra là: Bạn có muốn các công cụ của mình được tổ chức thành các hộp công cụ với
nhiều
mỗi ngăn kéo nhỏ chứa các thành phần được xác định rõ ràng và được dán nhãn rõ ràng? Hay bạn muốn
một vài ngăn kéo mà bạn chỉ cần ném mọi thứ vào?
Mỗi hệ thống lớn sẽ chứa một lượng lớn logic và phức tạp. Phần đầu
Mục tiêu quan trọng trong việc quản lý sự phức tạp đó là tổ chức nó sao cho nhà phát triển biết
Liệt kê 10-3
Một lớp trách nhiệm duy nhất
Phiên bản lớp công khai {
public int getMajorVersionNumber ()
public int getMinorVersionNumber ()
public int getBuildNumber ()
}
www.it-ebooks.info

Trang 171
140
Chương 10: Lớp học
nhìn để tìm mọi thứ và chỉ cần hiểu sự phức tạp bị ảnh hưởng trực tiếp ở bất kỳ
thời gian nhất định. Ngược lại, một hệ thống có các lớp đa năng lớn hơn luôn cản trở chúng ta bởi
nhấn mạnh rằng chúng tôi đã vượt qua rất nhiều điều mà chúng tôi không cần biết ngay bây giờ.
Nhắc lại những điểm trước đây để nhấn mạnh: Chúng tôi muốn hệ thống của mình bao gồm
nhiều lớp nhỏ, không ít lớp lớn. Mỗi lớp nhỏ đóng gói một phản hồi duy nhất
nhanh nhẹn, có một lý do duy nhất để thay đổi và cộng tác với một số người khác để đạt được
các hành vi hệ thống mong muốn.
Sự gắn kết
Các lớp nên có một số lượng nhỏ các biến cá thể. Mỗi phương thức của một lớp
nên thao tác với một hoặc nhiều biến đó. Nói chung, một phương pháp càng nhiều biến
thao túng phương thức đó càng gắn kết với lớp của nó. Một lớp trong đó mỗi biến là
được sử dụng bởi mỗi phương pháp là tối đa gắn kết.
Nói chung, không nên và cũng không thể tạo ra sự gắn kết tối đa như vậy
các lớp học; mặt khác, chúng tôi muốn sự gắn kết cao. Khi sự gắn kết cao, nó
có nghĩa là các phương thức và biến của lớp là đồng phụ thuộc và gắn liền với nhau dưới dạng
logic toàn bộ.
Xem xét việc triển khai một Ngăn xếp trong Liệt kê 10-4. Đây là một lớp rất gắn kết.
Trong ba phương thức, chỉ có size () không sử dụng được cả hai biến.
Chiến lược giữ các chức năng nhỏ và giữ cho danh sách tham số ngắn có thể-
dẫn đến sự gia tăng của các biến cá thể được sử dụng bởi một tập hợp con các phương thức.
Khi điều này xảy ra, hầu như luôn có nghĩa là có ít nhất một lớp khác đang cố gắng
Liệt kê 10-4
Stack.java Một lớp cố kết.
lớp công khai Stack {
private int topOfStack = 0;
Liệt kê các phần tử <Integer> = new LinkedList <Integer> ();
public int size () {
trả về topOfStack;
}
public void push (int element) {
topOfStack ++;
Elements.add (phần tử);
}
public int pop () ném PoppedWhenEmpty {
if (topOfStack == 0)
ném PoppedWhenEmpty () mới;
int element = Elements.get (- topOfStack);
Elements.remove (topOfStack);
phần tử trả về;
}
}
www.it-ebooks.info

Trang 172
141
Lớp học nên nhỏ!
ra khỏi lớp lớn hơn. Bạn nên cố gắng tách các biến và phương thức thành hai hoặc
nhiều lớp hơn để các lớp mới gắn kết hơn.
Duy trì kết quả gắn kết trong nhiều lớp nhỏ
Chỉ cần hành động phá vỡ các chức năng lớn thành các chức năng nhỏ hơn gây ra sự gia tăng
các lớp học. Hãy xem xét một hàm lớn với nhiều biến được khai báo bên trong nó. Hãy nói rằng bạn
muốn trích xuất một phần nhỏ của hàm đó thành một hàm riêng biệt. Tuy nhiên, mã
bạn muốn trích xuất sử dụng bốn trong số các biến được khai báo trong hàm. Bạn phải vượt qua tất cả
bốn trong số các biến đó vào hàm mới dưới dạng đối số?
Không có gì! Nếu chúng ta đã thăng cấp bốn biến đó thành các biến phiên bản của lớp, thì
chúng tôi có thể trích xuất mã mà không cần chuyển bất kỳ biến nào . Nó sẽ rất dễ bị phá vỡ
chức năng thành từng phần nhỏ.
Thật không may, điều này cũng có nghĩa là các lớp học của chúng ta mất đi sự gắn kết vì chúng tích tụ
ngày càng có nhiều biến thể hiện chỉ tồn tại để cho phép một vài hàm chia sẻ chúng.
Nhưng đợi đã! Nếu có một vài hàm muốn chia sẻ các biến nhất định, điều đó không
làm cho họ trở thành một lớp học theo đúng nghĩa của họ? Tất nhiên là thế. Khi các lớp mất gắn kết, chia tách
chúng!
Vì vậy, việc chia một chức năng lớn thành nhiều chức năng nhỏ hơn thường cho chúng ta cơ hội-
thuận tiện để chia một số lớp nhỏ hơn. Điều này mang lại cho chương trình của chúng tôi một tổ chức tốt hơn nhiều-
nization và một cấu trúc minh bạch hơn.
Để minh chứng cho điều tôi muốn nói, hãy sử dụng một ví dụ về thời gian được lấy từ
Cuốn sách tuyệt vời của Knuth's Literate Programming.  3 Liệt kê 10-5 hiển thị bản dịch sang Java
của chương trình PrintPrimes của Knuth . Công bằng mà nói với Knuth, đây không phải là chương trình như anh ấy đã
viết
nhưng đúng hơn là nó được xuất ra bởi công cụ WEB của anh ấy. Tôi đang sử dụng nó vì nó tạo ra một khởi đầu
tuyệt vời
nơi để chia nhỏ một hàm lớn thành nhiều hàm và lớp nhỏ hơn.
3. [Knuth92].
Liệt kê 10-5
PrintPrimes.java
gói literatePrimes;
lớp công khai PrintPrimes {
public static void main (String [] args) {
int cuối cùng M = 1000;
cuối cùng int RR = 50;
cuối cùng int CC = 4;
cuối cùng int WW = 10;
cuối cùng int ORDMAX = 30;
int P [] = new int [M + 1];
int PAGENUMBER;
int PAGEOFFSET;
int ROWOFFSET;
int C;
www.it-ebooks.info

Trang 173
142
Chương 10: Lớp học
int J;
int K;
boolean JPRIME;
int ORD;
int SQUARE;
int N;
int MULT [] = new int [ORDMAX + 1];
J = 1;
K = 1;
P [1] = 2;
ORD = 2;
VUÔNG = 9;
trong khi (K <M) {
làm {
J = J + 2;
if (J == SQUARE) {
ORD = ORD + 1;
SQUARE = P [ORD] * P [ORD];
MULT [ORD - 1] = J;
}
N = 2;
JPRIME = true;
trong khi (N <ORD && JPRIME) {
trong khi (MULT [N] <J)
MULT [N] = MULT [N] + P [N] + P [N];
nếu (MULT [N] == J)
JPRIME = sai;
N = N + 1;
}
} while (! JPRIME);
K = K + 1;
P [K] = J;
}
{
PAGENUMBER = 1;
PAGEOFFSET = 1;
trong khi (PAGEOFFSET <= M) {
System.out.println ("Đầu tiên" + M +
"Số nguyên tố --- Trang" + PAGENUMBER);
System.out.println ("");
cho (ROWOFFSET = PAGEOFFSET; ROWOFFSET <PAGEOFFSET + RR; ROWOFFSET ++) {
cho (C = 0; C <CC; C ++)
nếu (ROWOFFSET + C * RR <= M)
System.out.format ("% 10d", P [ROWOFFSET + C * RR]);
System.out.println ("");
}
System.out.println ("\ f");
PAGENUMBER = PAGENUMBER + 1;
PAGEOFFSET = PAGEOFFSET + RR * CC;
}
}
}
}
Liệt kê 10-5 (tiếp theo)
PrintPrimes.java
www.it-ebooks.info

Trang 174
143
Lớp học nên nhỏ!
Chương trình này, được viết dưới dạng một hàm duy nhất, là một mớ hỗn độn. Nó có một struc-
chắc chắn, rất nhiều biến số kỳ quặc và một cấu trúc được kết hợp chặt chẽ. Ít nhất, một
chức năng lớn nên được chia thành một vài chức năng nhỏ hơn.
Liệt kê 10-6 đến Liệt kê 10-8 hiển thị kết quả của việc tách mã trong Liệt kê 10-5
thành các lớp và hàm nhỏ hơn và chọn tên có ý nghĩa cho các lớp đó, func-
tions và các biến.
Liệt kê 10-6
PrimePrinter.java (đã cấu trúc lại)
gói literatePrimes;
lớp công cộng PrimePrinter {
public static void main (String [] args) {
int cuối cùng NUMBER_OF_PRIMES = 1000;
int [] primes = PrimeGenerator.generate (NUMBER_OF_PRIMES);
int cuối cùng ROWS_PER_PAGE = 50;
cuối cùng int COLUMNS_PER_PAGE = 4;
RowColumnPagePrinter tablePrinter =
RowColumnPagePrinter mới (ROWS_PER_PAGE,
COLUMNS_PER_PAGE,
"Đầu tiên" + NUMBER_OF_PRIMES +
" Số nguyên tố");
tablePrinter.print (số nguyên tố);
}
}
Liệt kê 10-7
RowColumnPagePrinter.java
gói literatePrimes;
nhập java.io.PrintStream;
public class RowColumnPagePrinter {
int private rowPerPage;
private int columnPerPage;
int sốPerPage riêng tư;
trang String privateHeader;
private PrintStream printStream;
public RowColumnPagePrinter (int rowPerPage,
int columnPerPage,
String pageHeader) {
this.rowsPerPage = rowPerPage;
this.columnsPerPage = cộtPerPage;
this.pageHeader = pageHeader;
numberPerPage = rowPerPage * cộtPerPage;
printStream = System.out;
}
www.it-ebooks.info

Trang 175
144
Chương 10: Lớp học
public void print (int data []) {
int pageNumber = 1;
for (int firstIndexOnPage = 0;
firstIndexOnPage <data.length;
firstIndexOnPage + = numberPerPage) {
int lastIndexOnPage =
Math.min (firstIndexOnPage + numberPerPage - 1,
data.length - 1);
printPageHeader (pageHeader, pageNumber);
printPage (firstIndexOnPage, lastIndexOnPage, data);
printStream.println ("\ f");
pageNumber ++;
}
}
private void printPage (int firstIndexOnPage,
int lastIndexOnPage,
int [] dữ liệu) {
int firstIndexOfLastRowOnPage =
firstIndexOnPage + rowPerPage - 1;
for (int firstIndexInRow = firstIndexOnPage;
firstIndexInRow <= firstIndexOfLastRowOnPage;
firstIndexInRow ++) {
printRow (firstIndexInRow, lastIndexOnPage, data);
printStream.println ("");
}
}
private void printRow (int firstIndexInRow,
int lastIndexOnPage,
int [] dữ liệu) {
for (int column = 0; column <columnPerPage; column ++) {
int index = firstIndexInRow + column * rowPerPage;
if (index <= lastIndexOnPage)
printStream.format ("% 10d", data [index]);
}
}
private void printPageHeader (String pageHeader,
int pageNumber) {
printStream.println (pageHeader + "--- Trang" + pageNumber);
printStream.println ("");
}
public void setOutput (PrintStream printStream) {
this.printStream = printStream;
}
}
Liệt kê 10-7 (tiếp theo)
RowColumnPagePrinter.java
www.it-ebooks.info

Trang 176
145
Lớp học nên nhỏ!
Liệt kê 10-8
PrimeGenerator.java
gói literatePrimes;
nhập java.util.ArrayList;
public class PrimeGenerator {
private static int [] số nguyên tố;
private static ArrayList <Integer> MultilesOfPrimeFactors;
static int [] create (int n) {được bảo vệ
số nguyên tố = new int [n];
multilesOfPrimeFactors = new ArrayList <Integer> ();
set2AsFirstPrime ();
checkOddNumbersForSubsequentPrimes ();
trả về số nguyên tố;
}
private static void set2AsFirstPrime () {
số nguyên tố [0] = 2;
MultilesOfPrimeFactors.add (2);
}
private static void checkOddNumbersForSubsequentPrimes () {
int primeIndex = 1;
for (int ứng viên = 3;
primeIndex <primes.length;
ứng cử viên + = 2) {
if (isPrime (ứng cử viên))
số nguyên tố [primeIndex ++] = ứng cử viên;
}
}
private static boolean isPrime (int application) {
if (isLeastRelevantMultipleOfNextLargerPrimeFactor (ứng cử viên)) {
MultilesOfPrimeFactors.add (ứng cử viên);
trả về sai;
}
return isNotMultipleOfAnyPreviousPrimeFactor (ứng cử viên);
}
boolean tĩnh riêng tư
isLeastRelevantMultipleOfNextLargerPrimeFactor (ứng viên int) {
int nextLargerPrimeFactor = primes [multiplelesOfPrimeFactors.size ()];
int lessRelevantMultiple = nextLargerPrimeFactor * nextLargerPrimeFactor;
ứng cử viên trả về == ít nhấtRelevantMultiple;
}
boolean tĩnh riêng tư
isNotMultipleOfAnyPreviousPrimeFactor (ứng viên int) {
for (int n = 1; n <multiplelesOfPrimeFactors.size (); n ++) {
if (isMultipleOfNthPrimeFactor (ứng viên, n))
trả về sai;
}
www.it-ebooks.info

Trang 177
146
Chương 10: Lớp học
Điều đầu tiên bạn có thể nhận thấy là chương trình dài hơn rất nhiều. Nó đến từ một
độ dài từ một trang đến gần ba trang. Cái này có một vài nguyên nhân
sự phát triển. Đầu tiên, chương trình được tái cấu trúc sử dụng các tên biến mô tả dài hơn.
Thứ hai, chương trình được tái cấu trúc sử dụng khai báo hàm và lớp như một cách để thêm
bình luận cho mã. Thứ ba, chúng tôi đã sử dụng các kỹ thuật định dạng và khoảng trắng để giữ
chương trình có thể đọc được.
Lưu ý rằng chương trình đã được chia thành ba trách nhiệm chính như thế nào. Chính
chương trình được chứa trong lớp PrimePrinter . Trách nhiệm của nó là xử lý
môi trường thực thi. Nó sẽ thay đổi nếu phương pháp gọi thay đổi. Đối với
ví dụ: nếu chương trình này được chuyển đổi thành dịch vụ SOAP, thì đây là lớp sẽ
bị ảnh hưởng.
Các RowColumnPagePrinter biết tất cả về làm thế nào để định dạng một danh sách các số vào
các trang với một số hàng và cột nhất định. Nếu cần định dạng đầu ra
thay đổi, thì đây là lớp sẽ bị ảnh hưởng.
Lớp PrimeGenerator biết cách tạo một danh sách các số nguyên tố. Chú ý rằng nó
không có nghĩa là được khởi tạo như một đối tượng. Lớp chỉ là một phạm vi hữu ích trong đó
các biến của nó có thể được khai báo và ẩn. Lớp này sẽ thay đổi nếu thuật toán cho
tính toán các số nguyên tố thay đổi.
Đây không phải là một bài viết lại! Chúng tôi đã không bắt đầu lại từ đầu và viết chương trình lại
lần nữa. Thật vậy, nếu bạn quan sát kỹ hai chương trình khác nhau, bạn sẽ thấy rằng chúng sử dụng
cùng một thuật toán và cơ chế để hoàn thành công việc của họ.
Thay đổi được thực hiện bằng cách viết một bộ thử nghiệm xác minh hành vi chính xác của
chương trình đầu tiên. Sau đó, vô số thay đổi nhỏ được thực hiện, mỗi lần một thay đổi. Sau mỗi cái
thay đổi chương trình đã được thực hiện để đảm bảo rằng hành vi không thay đổi. Một tí
bước này sang bước khác, chương trình đầu tiên được dọn dẹp và chuyển thành chương trình thứ hai.
trả về true;
}
boolean tĩnh riêng tư
isMultipleOfNthPrimeFactor (int ứng viên, int n) {
trở về
ứng viên == smallOddNthMultipleNotLessThanCandidate (ứng viên, n);
}
int tĩnh riêng
smallOddNthMultipleNotLessThanCandidate (int application, int n) {
int multiple = MultilesOfPrimeFactors.get (n);
while (nhiều <ứng cử viên)
bội số + = 2 * số nguyên tố [n];
MultilesOfPrimeFactors.set (n, nhiều);
trả về nhiều;
}
}
Liệt kê 10-8 (tiếp theo)
PrimeGenerator.java
www.it-ebooks.info

Trang 178
147
Tổ chức để thay đổi
Tổ chức để thay đổi
Đối với hầu hết các hệ thống, sự thay đổi là liên tục. Mọi thay đổi đều khiến chúng ta có nguy cơ
phần còn lại của hệ thống không còn hoạt động như dự kiến. Trong một hệ thống sạch sẽ, chúng tôi tổ chức
các lớp để giảm rủi ro thay đổi.
Lớp Sql trong Liệt kê 10-9 được sử dụng để tạo các chuỗi SQL được định dạng đúng
siêu dữ liệu thích hợp. Đó là một công việc đang được tiến hành và do đó, chưa hỗ trợ SQL func-
tionality như câu lệnh cập nhật . Khi đến lúc lớp Sql hỗ trợ
cập nhật tuyên bố, chúng tôi sẽ phải "mở" lớp này để thực hiện các sửa đổi. Vấn đề
với việc mở một lớp học là nó dẫn đến rủi ro. Bất kỳ sửa đổi nào đối với lớp đều có
tiềm năng phá vỡ mã khác trong lớp. Nó phải được kiểm tra lại hoàn toàn.
Lớp Sql phải thay đổi khi chúng ta thêm một kiểu câu lệnh mới. Nó cũng phải thay đổi
khi chúng tôi thay đổi các chi tiết của một loại câu lệnh đơn lẻ — ví dụ: nếu chúng tôi cần sửa đổi
chức năng lựa chọn để hỗ trợ các lựa chọn con . Hai lý do để thay đổi này có nghĩa là
Lớp Sql vi phạm SRP.
Chúng tôi có thể phát hiện vi phạm SRP này từ quan điểm tổ chức đơn giản. Phương pháp
phác thảo của Sql cho thấy rằng có các phương thức riêng, chẳng hạn như selectWithCriteria ,
dường như chỉ liên quan đến các câu lệnh được chọn .
Hành vi phương thức riêng chỉ áp dụng cho một tập hợp con nhỏ của một lớp có thể hữu ích
heuristic để phát hiện các lĩnh vực tiềm năng để cải thiện. Tuy nhiên, động lực chính cho tak-
hành động nhập phải được hệ thống tự thay đổi. Nếu lớp Sql được coi là hoàn thành về mặt logic,
thì chúng ta không cần lo lắng về việc tách bạch trách nhiệm. Nếu chúng tôi không cần cập nhật
chức năng cho tương lai gần, sau đó chúng ta nên để Sql một mình. Nhưng ngay khi chúng tôi
thấy mình đang mở một lớp học, chúng ta nên xem xét việc sửa chữa thiết kế của mình.
Điều gì sẽ xảy ra nếu chúng ta xem xét một giải pháp như vậy trong Liệt kê 10-10? Mỗi giao diện công khai
phương thức được định nghĩa trong Sql trước đó từ Liệt kê 10-9 được cấu trúc lại thành dẫn xuất của chính nó
của lớp Sql . Lưu ý rằng các phương thức riêng tư, chẳng hạn như danh sách giá trị , di chuyển trực tiếp đến nơi
Liệt kê 10-9
Một lớp học phải được mở để thay đổi
lớp công khai Sql {
public Sql (Bảng chuỗi, các cột Column [])
public String create ()
public String insert (Object [] fields)
public String selectAll ()
public String findByKey (String keyColumn, String keyValue)
công khai Chọn chuỗi (Cột cột, Mẫu chuỗi)
chọn chuỗi công khai (Tiêu chí tiêu chí)
public String readyInsert ()
private String columnList (Column [] cột)
private String giá trịList (các trường Object [], các cột Column [] cuối cùng)
private String selectWithCriteria (Tiêu chí chuỗi)
private String placeholderList (Column [] cột)
}
www.it-ebooks.info

Trang 179
148
Chương 10: Lớp học
chúng là cần thiết. Hành vi riêng tư chung được tách biệt với một cặp lớp tiện ích, Ở đâu
và ColumnList .
Liệt kê 10-10
Một tập hợp các lớp đóng
lớp công khai trừu tượng Sql {
public Sql (Bảng chuỗi, các cột Column [])
trừu tượng public String create ();
}
public class CreateSql mở rộng Sql {
public CreateSql (Bảng chuỗi, các cột Column [])
@Override public String create ()
}
public class SelectSql mở rộng Sql {
public SelectSql (Bảng chuỗi, các cột Column [])
@Override public String create ()
}
public class InsertSql mở rộng Sql {
public InsertSql (Bảng chuỗi, các cột Column [], các trường Đối tượng [])
@Override public String create ()
private String giá trịList (các trường Object [], các cột Column [] cuối cùng)
}
public class SelectWithCriteriaSql mở rộng Sql {
public SelectWithCriteriaSql (
Bảng chuỗi, Cột [] cột, Tiêu chí tiêu chí)
@Override public String create ()
}
public class SelectWithMatchSql mở rộng Sql {
public SelectWithMatchSql (
Bảng chuỗi, Cột [] cột, Cột cột, Mẫu chuỗi)
@Override public String create ()
}
public class FindByKeySql mở rộng Sql
public FindByKeySql (
Bảng chuỗi, Cột [] cột, Chuỗi keyColumn, String keyValue)
@Override public String create ()
}
public class PreparedInsertSql mở rộng Sql {
public PreparedInsertSql (Bảng chuỗi, các cột Column [])
@Override public String create () {
private String placeholderList (Column [] cột)
}
lớp công chúng ở đâu {
public Where (Tiêu chí chuỗi)
công khai chuỗi tạo ()
}
www.it-ebooks.info

Trang 180
149
Tổ chức để thay đổi
Mã trong mỗi lớp trở nên cực kỳ đơn giản. Hiểu yêu cầu của chúng tôi
thời gian để hiểu bất kỳ lớp nào giảm xuống gần như không có. Rủi ro mà một chức năng có thể
phá vỡ khác trở nên biến mất nhỏ. Từ quan điểm thử nghiệm, nó trở nên dễ dàng hơn
nhiệm vụ chứng minh tất cả các bit logic trong giải pháp này, vì tất cả các lớp đều được tách biệt khỏi một
khác.
Quan trọng không kém, khi đã đến lúc thêm các câu lệnh cập nhật , không có câu lệnh nào hiện có
các lớp học cần thay đổi! Chúng tôi viết mã logic để xây dựng các câu lệnh cập nhật trong một lớp con mới của Sql
có tên là UpdateSql . Không có mã nào khác trong hệ thống sẽ bị hỏng do thay đổi này.
Logic Sql được cấu trúc lại của chúng tôi đại diện cho điều tốt nhất của tất cả các thế giới. Nó hỗ trợ SRP. Nó cũng
hỗ trợ một nguyên tắc thiết kế lớp OO quan trọng khác được gọi là Nguyên tắc Đóng mở, hoặc
OCP: 4 Các lớp nên được mở để gia hạn nhưng bị đóng để sửa đổi. Của chúng tôi đã tái cấu trúc
Lớp Sql được mở để cho phép chức năng mới thông qua lớp con, nhưng chúng tôi có thể thực hiện thay đổi này
trong khi vẫn đóng cửa mọi lớp khác. Chúng tôi chỉ cần thả lớp UpdateSql của chúng tôi tại chỗ.
Chúng tôi muốn cấu trúc hệ thống của mình để chúng tôi sử dụng ít nhất có thể khi chúng tôi
cập nhật chúng với các tính năng mới hoặc thay đổi. Trong một hệ thống lý tưởng, chúng tôi kết hợp công nghệ mới
sửa chữa bằng cách mở rộng hệ thống, không phải bằng cách sửa đổi mã hiện có.
Cách ly khỏi sự thay đổi
Nhu cầu sẽ thay đổi, do đó mã sẽ thay đổi. Chúng tôi đã học được trong OO 101 rằng có những
các lớp crete, chứa các chi tiết triển khai (mã) và các lớp trừu tượng,
chỉ đại diện cho các khái niệm. Một lớp khách hàng phụ thuộc vào các chi tiết cụ thể sẽ gặp rủi ro khi
những chi tiết đó thay đổi. Chúng tôi có thể giới thiệu các giao diện và các lớp trừu tượng để giúp tách biệt
tác động của các chi tiết đó.
Sự phụ thuộc vào các chi tiết cụ thể tạo ra thách thức cho việc kiểm tra hệ thống của chúng tôi. Nếu chúng ta
xây dựng một lớp Danh mục đầu tư và nó phụ thuộc vào API TokyoStockExchange bên ngoài để
lấy giá trị của danh mục đầu tư, các trường hợp thử nghiệm của chúng tôi bị ảnh hưởng bởi sự biến động của việc tra
cứu như vậy.
Thật khó để viết một bài kiểm tra khi cứ năm phút chúng ta lại nhận được một câu trả lời khác nhau!
Thay vì thiết kế Danh mục đầu tư để nó phụ thuộc trực tiếp vào TokyoStockExchange ,
chúng tôi tạo một giao diện, StockExchange , khai báo một phương thức duy nhất:
giao diện chung StockExchange {
Money currentPrice (Ký hiệu chuỗi);
}
lớp công khai ColumnList {
công khai ColumnList (Cột [] cột)
công khai chuỗi tạo ()
}
4. [PPP].
Liệt kê 10-10 (tiếp theo)
Một tập hợp các lớp đóng
www.it-ebooks.info

Trang 181
150
Chương 10: Lớp học
Chúng tôi thiết kế TokyoStockExchange để thực hiện giao diện này. Chúng tôi cũng đảm bảo rằng
constructor của Portfolio lấy tham chiếu StockExchange làm đối số:
danh mục đầu tư công cộng {
sàn giao dịch StockExchange tư nhân;
Danh mục đầu tư công cộng (sàn giao dịch StockExchange) {
this.exchange = trao đổi;
}
// ...
}
Bây giờ thử nghiệm của chúng tôi có thể tạo ra một triển khai có thể kiểm tra được của giao diện StockExchange
mô phỏng TokyoStockExchange . Việc triển khai thử nghiệm này sẽ cố định giá trị hiện tại cho
bất kỳ ký hiệu nào chúng tôi sử dụng trong thử nghiệm. Nếu thử nghiệm của chúng tôi cho thấy việc mua năm cổ
phiếu của Microsoft
cho danh mục đầu tư của mình, chúng tôi viết mã việc triển khai thử nghiệm để luôn trả lại 100 đô la cho mỗi cổ
phiếu
Microsoft. Việc triển khai thử nghiệm giao diện StockExchange của chúng tôi giảm xuống mức đơn giản
tra cứu bảng. Sau đó, chúng tôi có thể viết một bài kiểm tra dự kiến $ 500 cho giá trị danh mục đầu tư tổng thể của
chúng tôi.
PortfolioTest hạng công khai {
trao đổi FixedStockExchangeStub riêng tư;
danh mục đầu tư tư nhân;
@Trước
bảo vệ void setUp () ném Ngoại lệ {
Exchange = new FixedStockExchangeStub ();
Exchange.fix ("MSFT", 100);
portfolio = Danh mục đầu tư mới (trao đổi);
}
@Kiểm tra
public void GivenFiveMSFTTotalShouldBe500 () ném Exception {
portfolio.add (5, "MSFT");
Assert.assertEquals (500, portfolio.value ());
}
}
Nếu một hệ thống được tách đủ để được kiểm tra theo cách này, nó cũng sẽ linh hoạt hơn
và thúc đẩy tái sử dụng nhiều hơn. Việc thiếu khớp nối có nghĩa là các phần tử của hệ thống của chúng tôi
tốt hơn cách ly với nhau và khỏi sự thay đổi. Sự cô lập này làm cho nó dễ dàng hơn
đứng từng phần tử của hệ thống.
Bằng cách giảm thiểu sự ghép nối theo cách này, các lớp của chúng tôi tuân thủ các nguyên tắc thiết kế lớp khác-
được gọi là Nguyên tắc Đảo ngược Phụ thuộc (DIP). 5 Về bản chất, DIP nói rằng
các lớp nên phụ thuộc vào sự trừu tượng, không phụ thuộc vào các chi tiết cụ thể.
Thay vì phụ thuộc vào các chi tiết triển khai của TokyoStock-
Lớp Exchange , lớp Danh mục đầu tư của chúng tôi hiện phụ thuộc vào giao diện StockExchange .
Các sàn giao dịch chứng khoán giao diện đại diện cho các khái niệm trừu tượng của yêu cầu mức giá hiện tại
của một biểu tượng. Sự trừu tượng này cô lập tất cả các chi tiết cụ thể để có được mức giá như vậy,
kể cả từ nơi mà giá đó được lấy.
5. [PPP].
www.it-ebooks.info

Trang 182
151
Thư mục
Thư mục
[RDD]: Thiết kế đối tượng: Vai trò, Trách nhiệm và Cộng tác , Rebecca Wirfs-
Brock và cộng sự, Addison-Wesley, 2002.
[PPP]: Phát triển phần mềm Agile: Nguyên tắc, Mẫu và Thực tiễn , Robert C. Martin,
Prentice Hall, 2002.
[Knuth92]: Lập trình Literate, Donald E. Knuth, Trung tâm Nghiên cứu ngôn ngữ
và Thông tin, Đại học Leland Stanford Junior, 1992.
www.it-ebooks.info

Trang 183
Trang này cố ý để trống
www.it-ebooks.info

Trang 184
153

11
Hệ thống
bởi Tiến sĩ Kevin Dean Wampler
“Sự phức tạp giết chết. Nó hút hết cuộc sống của các nhà phát triển,
nó làm cho các sản phẩm khó lập kế hoạch, xây dựng và thử nghiệm. ”
—Ray Ozzie, CTO, Microsoft Corporation
www.it-ebooks.info

Trang 185
154
Chương 11: Hệ thống
Bạn sẽ xây dựng một thành phố như thế nào?
Bạn có thể tự quản lý tất cả các chi tiết? Chắc là không. Thậm chí quản lý một thành phố hiện có
là quá nhiều cho một người. Tuy nhiên, các thành phố hoạt động (hầu hết thời gian). Họ làm việc vì các thành phố
có các nhóm người quản lý các khu vực cụ thể của thành phố, hệ thống nước, điện
hệ thống, giao thông, thực thi pháp luật, quy tắc xây dựng, v.v. Một số người trong số đó là
chịu trách nhiệm về bức tranh lớn , trong khi những người khác tập trung vào các chi tiết.
Các thành phố cũng hoạt động vì chúng đã phát triển các cấp độ trừu tượng và mô thức phù hợp
tính hợp lý giúp các cá nhân và “thành phần” mà họ quản lý hoạt động
hiệu quả, ngay cả khi không hiểu bức tranh lớn.
Mặc dù các nhóm phần mềm cũng thường được tổ chức như vậy, nhưng hệ thống mà họ làm việc
thường không có sự phân tách các mối quan tâm và mức độ trừu tượng giống nhau. Mã sạch
giúp chúng tôi đạt được điều này ở cấp độ trừu tượng thấp hơn. Trong chương này, chúng ta hãy xem xét cách
để giữ sạch sẽ ở cấp độ trừu tượng cao hơn, cấp độ hệ thống .
Tách biệt việc xây dựng một hệ thống khỏi việc sử dụng nó
Đầu tiên, hãy xem xét rằng xây dựng là một quá trình rất khác với sử dụng . Khi tôi viết điều này,
có một khách sạn mới đang được xây dựng mà tôi nhìn thấy qua cửa sổ của mình ở Chicago. Hôm nay nó là
một hộp bê tông trần với một cần trục xây dựng và thang máy được bắt vít bên ngoài. Các
những người bận rộn ở đó đều đội mũ cứng và mặc quần áo làm việc. Trong một năm hoặc lâu hơn, khách sạn sẽ
đã kết thúc. Cần trục và thang máy sẽ không còn nữa. Tòa nhà sẽ sạch sẽ, được bao bọc trong
tường cửa sổ kính và sơn hấp dẫn. Những người làm việc và ở đó sẽ nhìn
khác rất nhiều.
Hệ thống phần mềm nên tách biệt quá trình khởi động, khi các đối tượng ứng dụng
được xây dựng và các phụ thuộc được "kết nối" với nhau, từ logic thời gian chạy
sau khi khởi động.
Quá trình khởi động là một mối quan tâm mà bất kỳ ứng dụng nào cũng phải giải quyết. Đó là lần đầu tiên con-
CERN mà chúng ta sẽ xem xét trong chương này. Sự tách biệt các mối quan tâm là một trong những điều lâu đời
nhất
và các kỹ thuật thiết kế quan trọng nhất trong nghề của chúng tôi.
Thật không may, hầu hết các ứng dụng không tách rời mối quan tâm này. Mã khởi động
quy trình là đặc biệt và nó được trộn lẫn với logic thời gian chạy. Đây là một ví dụ điển hình:
dịch vụ công cộng getService () {
if (service == null)
service = new MyServiceImpl (...); // Mặc định đủ tốt cho hầu hết các trường hợp?
dịch vụ trả hàng;
}
Đây là thành ngữ LAZY INITIALIZATION / EVALUATION , và nó có một số điểm đáng khen. Chúng tôi
không phải chịu chi phí xây dựng trừ khi chúng tôi thực sự sử dụng đối tượng và khởi động của chúng tôi
kết quả là thời gian có thể nhanh hơn. Chúng tôi cũng đảm bảo rằng null không bao giờ được trả lại.
www.it-ebooks.info

Trang 186
155
Tách biệt việc xây dựng một hệ thống khỏi việc sử dụng nó
Tuy nhiên, bây giờ chúng ta có một sự phụ thuộc được mã hóa cứng vào MyServiceImpl và mọi thứ
hàm tạo yêu cầu (mà tôi đã giải thích). Chúng tôi không thể biên dịch mà không giải quyết những
phụ thuộc, ngay cả khi chúng ta không bao giờ thực sự sử dụng một đối tượng kiểu này trong thời gian chạy!
Kiểm tra có thể là một vấn đề. Nếu MyServiceImpl là một đối tượng nặng, chúng ta sẽ cần
hãy chắc chắn rằng một thích hợp T EST D Gấp đôi 1 hoặc M Ock O BJECT được gán cho ser-
trường phó trước khi phương thức này được gọi trong quá trình thử nghiệm đơn vị. Vì chúng tôi thi công
logic trộn lẫn với xử lý thời gian chạy bình thường, chúng tôi nên kiểm tra tất cả các đường dẫn thực thi (đối với
ví dụ, thử nghiệm null và khối của nó). Có cả hai trách nhiệm này có nghĩa là
phương pháp đang làm nhiều hơn một việc, vì vậy chúng tôi đang phá vỡ Nguyên tắc Trách nhiệm Đơn lẻ
theo một cách nhỏ.
Có lẽ tệ nhất là chúng tôi không biết liệu MyServiceImpl có phải là đối tượng phù hợp trong tất cả các
các trường hợp. Tôi ngụ ý nhiều như trong bình luận. Tại sao lớp với phương thức này phải
biết bối cảnh toàn cầu? Có thể chúng ta bao giờ thực sự biết đối tượng quyền sử dụng ở đây? Nó thậm chí
có thể cho một loại để phù hợp với tất cả các ngữ cảnh có thể?
Tất nhiên, một lần xuất hiện LAZY - BAN ĐẦU không phải là một vấn đề nghiêm trọng. Tuy nhiên,
thường có nhiều trường hợp các thành ngữ cài đặt nhỏ như thế này trong các ứng dụng. Vì thế,
chiến lược thiết lập toàn cầu (nếu có) nằm rải rác trên ứng dụng, với rất ít
mô đun và thường trùng lặp đáng kể.
Nếu chúng ta siêng năng xây dựng các hệ thống được hình thành tốt và mạnh mẽ, chúng ta đừng bao giờ để
thành ngữ ít, tiện lợi dẫn đến phá vỡ mô-đun. Quá trình khởi động của đối tượng
struction và wiring cũng không ngoại lệ. Chúng ta nên mô-đun hóa quá trình này một cách riêng biệt với
logic thời gian chạy bình thường và chúng ta nên đảm bảo rằng chúng ta có một chiến lược toàn cầu, nhất quán
egy để giải quyết các phụ thuộc chính của chúng tôi.
Tách chính
Một cách để tách công trình xây dựng khỏi việc sử dụng đơn giản là chuyển tất cả các khía cạnh của công trình xây
dựng sang
chính hoặc mô-đun được gọi bởi chính và để thiết kế phần còn lại của hệ thống, giả sử rằng tất cả
các đối tượng đã được xây dựng và lên dây phù hợp. (Xem Hình 11-1.)
Quy trình kiểm soát dễ dàng theo dõi. Các chính chức năng xây dựng các đối tượng cần thiết
cho hệ thống, sau đó chuyển chúng đến ứng dụng, ứng dụng chỉ cần sử dụng chúng. Chú ý
hướng của các mũi tên phụ thuộc vượt qua rào cản giữa chính và ứng dụng.
Tất cả đều đi về một hướng, hướng ra xa chính. Điều này có nghĩa là ứng dụng không có
kiến thức về chính hoặc về quá trình xây dựng. Nó chỉ đơn giản mong đợi rằng mọi thứ đều có
được xây dựng đúng cách.
Nhà máy
Tất nhiên, đôi khi chúng ta cần làm cho ứng dụng chịu trách nhiệm về thời điểm một đối tượng
tạo. Ví dụ: trong hệ thống xử lý đơn đặt hàng, ứng dụng phải tạo
1. [Mezzaros07].
www.it-ebooks.info
Trang 187
156
Chương 11: Hệ thống
Các phiên bản LineItem để thêm vào Đơn hàng . Trong trường hợp này chúng ta có thể sử dụng A BSTRACT F actory 2
để cung cấp cho ứng dụng quyền kiểm soát thời điểm xây dựng các LineItems , nhưng vẫn giữ các chi tiết
của cấu trúc đó tách biệt với mã ứng dụng. (Xem Hình 11-2.)
Một lần nữa lưu ý rằng tất cả các phụ thuộc đều trỏ từ main đến OrderProcessing
ứng dụng. Điều này có nghĩa là ứng dụng được tách ra khỏi các chi tiết về cách
xây dựng một LineItem . Khả năng đó được giữ trong LineItemFactoryImplementation ,
nằm ở phía chính của dòng. Và ứng dụng hoàn toàn kiểm soát khi nào
các cá thể LineItem được xây dựng và thậm chí có thể cung cấp hàm tạo dành riêng cho ứng dụng
tranh luận.
Hình 11-1
Tách cấu trúc trong main ()
2. [GOF].
Hình 11-2
Xây dựng ngăn cách với nhà máy
www.it-ebooks.info

Trang 188
157
Mở rộng quy mô
Tiêm phụ thuộc
Một cơ chế mạnh mẽ để tách việc xây dựng khỏi việc sử dụng là Dependency Injection (DI),
ứng dụng của Inversion of Control (IoC) để quản lý sự phụ thuộc. 3 Đảo ngược của
Quyền kiểm soát chuyển các trách nhiệm phụ từ một đối tượng sang các đối tượng khác được dành riêng
với mục đích, do đó hỗ trợ Nguyên tắc Trách nhiệm Đơn lẻ. Trong ngữ cảnh của
quản lý phụ thuộc, một đối tượng không nên chịu trách nhiệm về việc khởi tạo depen-
chính nó. Thay vào đó, nó nên chuyển trách nhiệm này cho một cơ quan “có thẩm quyền” khác-
nism, do đó đảo ngược điều khiển. Bởi vì thiết lập là mối quan tâm toàn cầu, điều này có thẩm quyền
cơ chế thường sẽ là quy trình “chính” hoặc một thùng chứa có mục đích đặc biệt .
Tra cứu JNDI là một triển khai "một phần" của DI, trong đó một đối tượng hỏi một thư mục
máy chủ để cung cấp “dịch vụ” phù hợp với một tên cụ thể.
MyService myService = (MyService) (jndiContext.lookup (“NameOfMyService”));
Đối tượng đang gọi không kiểm soát loại đối tượng nào thực sự được trả về (miễn là nó
triển khai giao diện thích hợp, tất nhiên), nhưng đối tượng gọi vẫn tích cực
giải quyết sự phụ thuộc.
True Dependency Injection tiến thêm một bước nữa. Lớp học không có bước trực tiếp để
giải quyết các phụ thuộc của nó; nó hoàn toàn thụ động. Thay vào đó, nó cung cấp các phương thức setter hoặc
đối số phương thức khởi tạo (hoặc cả hai) được sử dụng để đưa vào các phụ thuộc. Trong quá trình
quá trình struction, vùng chứa DI khởi tạo các đối tượng cần thiết (thường là theo yêu cầu)
và sử dụng các đối số của phương thức khởi tạo hoặc phương thức setter được cung cấp để kết nối depen-
các yếu tố. Đối tượng phụ thuộc nào thực sự được sử dụng được chỉ định thông qua một cấu hình
tập tin hoặc lập trình trong mô-đun xây dựng có mục đích đặc biệt.
Spring Framework cung cấp vùng chứa DI nổi tiếng nhất cho Java. 4 Bạn xác định
những đối tượng nào để kết nối với nhau trong một tệp cấu hình XML, sau đó bạn yêu cầu cụ thể
các đối tượng theo tên trong mã Java. Chúng ta sẽ xem xét một ví dụ ngay sau đây.
Nhưng còn đức tính của LÃO HÓA - BAN ĐẦU ? Thành ngữ này đôi khi vẫn
hữu ích với DI. Đầu tiên, hầu hết các vùng chứa DI sẽ không tạo một đối tượng cho đến khi cần thiết. Thứ hai,
nhiều trong số các thùng chứa này cung cấp các cơ chế để gọi nhà máy hoặc xây dựng
proxy, có thể được sử dụng cho LAZY - EVALUATION và các tối ưu hóa tương tự .  5
Mở rộng quy mô
Các thành phố phát triển từ các thị trấn, phát triển từ các khu định cư. Lúc đầu, đường hẹp và
thực tế không tồn tại, sau đó chúng được lát, sau đó được mở rộng theo thời gian. Các tòa nhà nhỏ và
3. Xem, ví dụ, [Fowler].
4. Xem [Mùa xuân]. Ngoài ra còn có một khuôn khổ Spring.NET.
5. Đừng quên rằng việc khởi tạo / đánh giá lười biếng chỉ là một sự tối ưu hóa và có thể là quá sớm!
www.it-ebooks.info

Trang 189
158
Chương 11: Hệ thống
các mảnh đất trống được lấp đầy bởi các tòa nhà lớn hơn, một số trong số đó cuối cùng sẽ được thay thế
với những tòa nhà chọc trời.
Lúc đầu, không có các dịch vụ như điện, nước, thoát nước và Internet (thở hổn hển!). Những
các dịch vụ cũng được bổ sung khi dân số và mật độ xây dựng tăng lên.
Sự phát triển này không phải là không đau. Đã bao nhiêu lần bạn lái xe, hết lần này đến lần khác
thông qua một dự án "cải thiện" con đường và tự hỏi bản thân, "Tại sao họ không xây dựng nó rộng rãi
đủ lần đầu tiên !? ”
Nhưng nó không thể xảy ra theo bất kỳ cách nào khác. Ai có thể biện minh cho chi phí của sáu-
làn đường cao tốc thông qua giữa một thị trấn nhỏ đón đầu sự phát triển? Ai sẽ
muốn một con đường như vậy qua thị trấn của họ?
Thật là hoang đường khi chúng ta có thể có được hệ thống “ngay lần đầu tiên”. Thay vào đó, chúng ta nên ...
chỉ đề cập đến những câu chuyện của ngày hôm nay , sau đó tái cấu trúc và mở rộng hệ thống để triển khai những
câu chuyện mới
Ngày mai. Đây là bản chất của sự nhanh nhẹn lặp đi lặp lại và gia tăng. Phát triển theo hướng kiểm tra-
cố vấn, tái cấu trúc và mã sạch mà họ tạo ra làm cho việc này hoạt động ở cấp mã.
Nhưng ở cấp độ hệ thống thì sao? Kiến trúc hệ thống không yêu cầu lập kế hoạch trước-
ning? Chắc chắn, nó không thể phát triển từng bước từ đơn giản đến phức tạp , có thể nó?
Hệ thống phần mềm là duy nhất so với hệ thống vật lý.  Kiến trúc của họ có thể phát triển
tăng dần,  nếu  chúng ta duy trì sự phân tách các mối quan tâm thích hợp.
Bản chất phù du của các hệ thống phần mềm làm cho điều này trở nên khả thi, như chúng ta sẽ thấy. Hãy để chúng
tôi đầu tiên
hãy xem xét một ví dụ ngược lại của một kiến trúc không phân tách các mối quan tâm một cách thỏa đáng.
Các kiến trúc EJB1 và EJB2 ban đầu không phân tách các mối quan tâm một cách thích hợp và
do đó áp đặt các rào cản không cần thiết đối với tăng trưởng hữu cơ. Xem xét một thực thể Bean cho một
lớp Ngân hàng bền bỉ . Đậu thực thể là một biểu diễn trong bộ nhớ của dữ liệu quan hệ, trong
nói cách khác, một hàng bảng.
Đầu tiên, bạn phải xác định giao diện cục bộ (đang trong quá trình xử lý) hoặc giao diện từ xa (JVM riêng biệt),
khách hàng sẽ sử dụng. Liệt kê 11-1 cho thấy một giao diện cục bộ có thể có:
Liệt kê 11-1
Giao diện cục bộ EJB2 cho EJB ngân hàng
gói com.example.banking;
nhập java.util.Collections;
nhập javax.ejb. *;
giao diện công khai BankLocal mở rộng java.ejb.EJBLocalObject {
Chuỗi getStreetAddr1 () ném EJBException;
Chuỗi getStreetAddr2 () ném EJBException;
Chuỗi getCity () ném EJBException;
Chuỗi getState () ném EJBException;
Chuỗi getZipCode () ném EJBException;
void setStreetAddr1 (String street1) ném EJBException;
void setStreetAddr2 (String street2) ném EJBException;
void setCity (String city) ném EJBException;
void setState (Trạng thái chuỗi) ném EJBException;
www.it-ebooks.info

Trang 190
159
Mở rộng quy mô
Tôi đã hiển thị một số thuộc tính cho địa chỉ Ngân hàng và một tập hợp các tài khoản
ngân hàng sở hữu, mỗi ngân hàng sẽ được xử lý dữ liệu bởi một Tài khoản EJB riêng biệt .
Liệt kê 11-2 cho thấy lớp triển khai tương ứng cho Bank bean.
void setZipCode (String zip) ném EJBException;
Collection getAccounts () ném EJBException;
void setAccounts (Tài khoản tập hợp) ném EJBException;
void addAccount (AccountDTO accountDTO) ném EJBException;
}
Liệt kê 11-2
Triển khai Bean thực thể EJB2 tương ứng
gói com.example.banking;
nhập java.util.Collections;
nhập javax.ejb. *;
public abstract class Bank triển khai javax.ejb.EntityBean {
// Logic kinh doanh ...
public abstract String getStreetAddr1 ();
public abstract String getStreetAddr2 ();
public abstract String getCity ();
public abstract String getState ();
public abstract String getZipCode ();
public abstract void setStreetAddr1 (String street1);
public abstract void setStreetAddr2 (String street2);
public abstract void setCity (Thành phố chuỗi);
public abstract void setState (Trạng thái chuỗi);
public abstract void setZipCode (String zip);
public abstract Collection getAccounts ();
public abstract void setAccounts (Tài khoản tập hợp);
public void addAccount (AccountDTO accountDTO) {
InitialContext context = new InitialContext ();
AccountHomeLocal accountHome = context.lookup ("AccountHomeLocal");
Tài khoản AccountLocal = accountHome.create (accountDTO);
Tài khoản bộ sưu tập = getAccounts ();
Account.add (tài khoản);
}
// Lôgic vùng chứa EJB
public abstract void setId (Integer id);
public abstract Integer getId ();
public Integer ejbCreate (Số nguyên id) {...}
public void ejbPostCreate (Integer id) {...}
// Phần còn lại phải được triển khai nhưng thường trống:
public void setEntityContext (EntityContext ctx) {}
public void unsetEntityContext () {}
public void ejbActivate () {}
public void ejbPassivate () {}
public void ejbLoad () {}
public void ejbStore () {}
public void ejbRemove () {}
}
Liệt kê 11-1 (tiếp theo)
Giao diện cục bộ EJB2 cho EJB ngân hàng
www.it-ebooks.info

Trang 191
160
Chương 11: Hệ thống
Tôi chưa hiển thị giao diện LocalHome tương ứng , về cơ bản là một nhà máy được sử dụng để
tạo các đối tượng, cũng như bất kỳ phương thức tìm Ngân hàng (truy vấn) nào mà bạn có thể thêm vào.
Cuối cùng, bạn phải viết một hoặc nhiều bộ mô tả triển khai XML chỉ định
ánh xạ quan hệ đối tượng chi tiết đến một kho lưu trữ liên tục, hành vi giao dịch mong muốn,
hạn chế bảo mật, và vân vân .
Logic nghiệp vụ được kết hợp chặt chẽ với “vùng chứa” ứng dụng EJB2. Bạn phải
các loại vùng chứa lớp con và bạn phải cung cấp nhiều phương thức vòng đời được yêu cầu
bằng thùng chứa.
Do khớp nối này với thùng chứa nặng, việc kiểm tra đơn vị biệt lập rất khó khăn.
Cần phải mô phỏng vùng chứa, điều này khó hoặc tốn nhiều thời gian triển khai
EJB và các thử nghiệm đối với một máy chủ thực. Việc tái sử dụng bên ngoài kiến trúc EJB2 có hiệu quả-
sible, do khớp nối chặt chẽ.
Cuối cùng, ngay cả lập trình hướng đối tượng cũng bị hủy hoại. Một hạt đậu không thể kế thừa
từ một loại đậu khác. Lưu ý logic để thêm tài khoản mới. Nó thường gặp ở đậu EJB2
để xác định “đối tượng truyền dữ liệu” (DTO) về cơ bản là “cấu trúc” không có hành vi.
Điều này thường dẫn đến các loại dự phòng về cơ bản giữ cùng một dữ liệu và nó yêu cầu
mã boilerplate để sao chép dữ liệu từ đối tượng này sang đối tượng khác.
Mối quan tâm xuyên suốt
Kiến trúc EJB2 tiến gần đến sự tách biệt thực sự của các mối quan tâm trong một số lĩnh vực. Đối với
ví dụ, giao dịch mong muốn, bảo mật và một số hành vi liên tục là
được khai báo trong bộ mô tả triển khai, độc lập với mã nguồn.
Lưu ý rằng các mối quan tâm như sự bền bỉ có xu hướng cắt ngang ranh giới đối tượng tự nhiên của
một miền. Bạn muốn duy trì tất cả các đối tượng của mình nói chung sử dụng cùng một chiến lược, để kiểm tra-
xin vui lòng , sử dụng DBMS 6 cụ thể so với các tệp phẳng, tuân theo các quy ước đặt tên nhất định cho
bảng và cột, sử dụng ngữ nghĩa giao dịch phù hợp, và vân vân .
Về nguyên tắc, bạn có thể lập luận về chiến lược bền bỉ của mình trong một mô-đun, gói gọn
đường. Tuy nhiên, trong thực tế, bạn phải phổ biến về cơ bản cùng một đoạn mã triển khai tính năng-
chiến lược tence trên nhiều đối tượng. Chúng tôi sử dụng thuật ngữ mối quan tâm xuyên suốt cho các mối quan tâm
như
những cái này. Một lần nữa, khung kiên trì có thể là mô-đun và logic miền của chúng tôi, trong isola-
tion, có thể là mô-đun. Vấn đề là sự giao nhau giữa các miền này.
Trên thực tế, cách kiến trúc EJB xử lý tính bền bỉ, bảo mật và giao dịch ,
Lập trình hướng khía cạnh "dự đoán" (AOP), 7 là cách tiếp cận có mục đích chung
để khôi phục mô-đun cho các mối quan tâm xuyên suốt.
Trong AOP, các cấu trúc mô-đun được gọi là các khía cạnh chỉ định điểm nào trong hệ thống nên
đã sửa đổi hành vi của họ theo một số cách nhất quán để hỗ trợ một mối quan tâm cụ thể. Điều này
đặc điểm kỹ thuật được thực hiện bằng cách sử dụng cơ chế khai báo hoặc lập trình ngắn gọn.
6. Hệ quản trị cơ sở dữ liệu.
7. Xem [AOSD] để biết thông tin chung về các khía cạnh và [AspectJ]] và [Colyer] để biết thông tin cụ thể về AspectJ.
www.it-ebooks.info

Trang 192
161
Java Proxy
Sử dụng tính kiên trì làm ví dụ, bạn sẽ khai báo các đối tượng và thuộc tính nào (hoặc
các mẫu của chúng) nên được duy trì và sau đó ủy quyền các nhiệm vụ liên tục cho cá nhân của bạn-
khuôn khổ tence. Các sửa đổi hành vi được thực hiện không xâm lấn  8 đến mã đích
theo khuôn khổ AOP. Chúng ta hãy xem xét ba khía cạnh hoặc các cơ chế giống như khía cạnh trong Java.
Java Proxy
Các proxy Java phù hợp với các tình huống đơn giản, chẳng hạn như gói các lệnh gọi phương thức riêng lẻ
các đối tượng hoặc các lớp. Tuy nhiên, các proxy động được cung cấp trong JDK chỉ hoạt động với
các giao diện. Đối với các lớp proxy, bạn phải sử dụng thư viện thao tác mã byte, chẳng hạn như
CGLIB, ASM hoặc Javassist. 9
Liệt kê 11-3 hiển thị khung cho proxy JDK để cung cấp hỗ trợ liên tục cho
ứng dụng Ngân hàng của chúng tôi , chỉ bao gồm các phương pháp lấy và thiết lập danh sách tài khoản.
8. Có nghĩa là không cần chỉnh sửa thủ công mã nguồn đích.
9. Xem [CGLIB], [ASM] và [Javassist].
Liệt kê 11-3
Ví dụ về proxy JDK
// Bank.java (chặn tên gói ...)
nhập java.utils. *;
// Sự trừu tượng của một ngân hàng.
giao diện công cộng Ngân hàng {
Bộ sưu tập <Tài khoản> getAccounts ();
void setAccounts (Bộ sưu tập tài khoản <Tài khoản>);
}
// BankImpl.java
nhập java.utils. *;
// “Đối tượng Java cũ thuần túy” (POJO) thực hiện phần trừu tượng.
lớp công khai BankImpl triển khai Ngân hàng {
các tài khoản <Account> danh sách riêng tư;
Bộ sưu tập công khai <Account> getAccounts () {
trả lại tài khoản;
}
public void setAccounts (Collection <Account> tài khoản) {
this.accounts = new ArrayList <Tài khoản> ();
cho (Tài khoản tài khoản: các tài khoản) {
this.accounts.add (tài khoản);
}
}
}
// BankProxyHandler.java
nhập java.lang.reflect. *;
nhập java.util. *;
www.it-ebooks.info

Trang 193
162
Chương 11: Hệ thống
Chúng tôi định nghĩa một giao diện Ngân hàng , mà sẽ được bao bọc bởi các proxy, và một Plain-Cũ
Đối tượng Java (POJO), BankImpl , thực hiện logic nghiệp vụ. (Chúng tôi sẽ truy cập lại các POJO
trong thời gian ngắn.)
API Proxy yêu cầu một đối tượng InvocationHandler mà nó gọi để triển khai bất kỳ
Các cuộc gọi phương thức ngân hàng được thực hiện tới proxy. BankProxyHandler của chúng tôi sử dụng phản chiếu
Java
API để ánh xạ các lệnh gọi phương thức chung với các phương thức tương ứng trong BankImpl ,
và như thế.
Có rất nhiều mã ở đây và nó tương đối phức tạp, ngay cả đối với trường hợp đơn giản này. 10
Việc sử dụng một trong các thư viện thao tác byte cũng tương tự như vậy. Mã này "khối lượng"
// “InvocationHandler” theo yêu cầu của API proxy.
public class BankProxyHandler triển khai InvocationHandler {
ngân hàng tư nhân Bank;
public BankHandler (Ngân hàng ngân hàng) {
this.bank = ngân hàng;
}
// Phương thức được định nghĩa trong InvocationHandler
Gọi đối tượng công khai (proxy đối tượng, phương thức phương thức, đối tượng [] args)
ném Có thể ném {
String methodName = method.getName ();
if (methodName.equals ("getAccounts")) {
bank.setAccounts (getAccountsFromDatabase ());
return bank.getAccounts ();
} else if (methodName.equals ("setAccounts")) {
bank.setAccounts ((Bộ sưu tập <Tài khoản>) args [0]);
setAccountsToDatabase (bank.getAccounts ());
trả về null;
} khác {
...
}
}
// Rất nhiều chi tiết ở đây:
Bộ sưu tập được bảo vệ <Account> getAccountsFromDatabase () {...}
được bảo vệ void setAccountsToDatabase (Collection <Account> tài khoản) {...}
}
// Ở một nơi khác ...
Ngân hàng ngân hàng = (Ngân hàng) Proxy.newProxyInstance (
Bank.class.getClassLoader (),
Lớp mới [] {Bank.class},
BankProxyHandler mới (BankImpl ())) mới;
10. Để biết thêm các ví dụ chi tiết về API Proxy và các ví dụ về việc sử dụng nó, hãy xem, ví dụ: [Goetz].
Liệt kê 11-3 (tiếp theo)
Ví dụ về proxy JDK
www.it-ebooks.info

Trang 194
163
Khung AOP thuần Java
và sự phức tạp là hai trong số những hạn chế của proxy. Họ khó tạo ra sự sạch sẽ
mã! Ngoài ra, proxy không cung cấp cơ chế chỉ định thực thi trên toàn hệ thống
"Điểm" quan tâm, cần thiết cho một giải pháp AOP thực sự. 11
Khung AOP thuần Java
May mắn thay, hầu hết các proxy boilerplate có thể được xử lý tự động bằng các công cụ. Proxy
được sử dụng nội bộ trong một số khuôn khổ Java, chẳng hạn như Spring AOP và JBoss AOP,
để triển khai các khía cạnh trong Java thuần túy. 12 Vào mùa xuân, bạn viết logic kinh doanh của mình là Plain-Old
Đối tượng Java . POJO hoàn toàn tập trung vào miền của họ. Họ không phụ thuộc vào
khuôn khổ doanh nghiệp (hoặc bất kỳ miền nào khác). Do đó, chúng đơn giản hơn về mặt khái niệm và
lái thử dễ dàng hơn. Sự đơn giản tương đối giúp dễ dàng đảm bảo rằng bạn đang-
đề cập đến các câu chuyện của người dùng tương ứng một cách chính xác và để duy trì và phát triển mã cho
những câu chuyện trong tương lai.
Bạn kết hợp cơ sở hạ tầng ứng dụng cần thiết, bao gồm cả kết nối xuyên suốt
các chứng chỉ như tính bền bỉ, giao dịch, bảo mật, bộ nhớ đệm, chuyển đổi dự phòng, v.v. bằng cách sử dụng khai
báo-
tệp cấu hình tive hoặc API. Trong nhiều trường hợp, bạn đang thực sự chỉ định Spring hoặc
Các khía cạnh thư viện JBoss, nơi khung công tác xử lý cơ chế sử dụng proxy Java
hoặc các thư viện mã byte cho người dùng một cách minh bạch. Các khai báo này thúc đẩy sự phụ thuộc
hộp chứa tiêm (DI), khởi tạo các đối tượng chính và kết nối chúng với nhau trên
nhu cầu .
Liệt kê 11-4 hiển thị một đoạn điển hình của tệp cấu hình Spring V2.5, app.xml 13 :
11. AOP đôi khi bị nhầm lẫn với các kỹ thuật được sử dụng để triển khai nó, chẳng hạn như đánh chặn phương pháp và "gói" thông qua
proxy. Giá trị thực của một hệ thống AOP là khả năng chỉ định các hành vi hệ thống một cách ngắn gọn và mô đun.
12. Xem [Spring] và [JBoss]. "Pure Java" có nghĩa là không sử dụng AspectJ.
Liệt kê 11-4
Tệp cấu hình Spring 2.X
<beans>
...
<bean id = "appDataSource"
class = "org.apache.commons.dbcp.BasicDataSource"
kill-method = "close"
p: driverClassName = "com.mysql.jdbc.Driver"
p: url = "jdbc: mysql: // localhost: 3306 / mydb"
p: username = "me" />
<bean id = "bankDataAccessObject"
class = "com.example.banking.persistence.BankDataAccessObject"
p: dataSource-ref = "appDataSource" />
<bean id = "bank"
13. Phỏng theo http : //www.theserverside.com/tt/articles/article.tss? L = IntrotoSpring25
www.it-ebooks.info

Trang 195
164
Chương 11: Hệ thống
Mỗi "hạt đậu" giống như một phần của "búp bê Nga" lồng vào nhau, với một đối tượng miền cho
Ngân hàng được ủy quyền (được bao bọc) bởi một đối tượng truy cập dữ liệu (DAO), chính nó được ủy quyền bởi một
Nguồn dữ liệu trình điều khiển JDBC. (Xem Hình 11-3.)
Khách hàng tin rằng nó đang gọi getAccounts () trên một đối tượng Bank , nhưng nó thực sự đang nói-
vào ngoài cùng của tập hợp các đối tượng D ECORATOR 14 lồng nhau để mở rộng hành vi cơ bản
của Ngân hàng POJO. Chúng ta có thể thêm trang trí khác cho các giao dịch, bộ nhớ đệm, và vân vân .
Trong ứng dụng, cần một vài dòng để yêu cầu vùng chứa DI cho cấp cao nhất
các đối tượng trong hệ thống, như được chỉ định trong tệp XML.
XmlBeanFactory bf =
mới XmlBeanFactory (ClassPathResource mới ("app.xml", getClass ()));
Ngân hàng ngân hàng = (Ngân hàng) bf.getBean ("ngân hàng");
Do yêu cầu quá ít dòng mã Java dành riêng cho Spring, ứng dụng gần như
hoàn toàn tách rời khỏi Spring , loại bỏ tất cả các vấn đề khớp nối chặt chẽ của hệ thống
như EJB2.
Mặc dù XML có thể dài dòng và khó đọc, 15 “chính sách” được chỉ định trong
các tệp hình tượng đơn giản hơn so với proxy phức tạp và logic khía cạnh bị ẩn khỏi
xem và tạo tự động. Kiểu kiến trúc này hấp dẫn đến nỗi khung-
hoạt động giống như Spring dẫn đến việc đại tu hoàn toàn tiêu chuẩn EJB cho phiên bản 3. EJB3
class = "com.example.banking.model.Bank"
p: dataAccessObject-ref = "bankDataAccessObject" />
...
</beans>
Hình 11-3
"Búp bê Nga" của các nhà trang trí
14. [GOF].
15. Ví dụ có thể được đơn giản hóa bằng cách sử dụng các cơ chế khai thác quy ước trên cấu hình và chú thích Java 5 để giảm
yêu cầu số lượng logic "dây" rõ ràng.
Liệt kê 11-4 (tiếp theo)
Tệp cấu hình Spring 2.X
www.it-ebooks.info

Trang 196
165
Khung AOP thuần Java
phần lớn tuân theo mô hình Spring hỗ trợ một cách rõ ràng các mối quan tâm xuyên suốt bằng cách sử dụng
Tệp cấu hình XML và / hoặc chú thích Java 5.
Liệt kê 11-5 cho thấy đối tượng Ngân hàng của chúng tôi được viết lại trong EJB3 16 .
Liệt kê 11-5
Ngân hàng EBJ3 EJB
gói com.example.banking.model;
nhập javax.persistence. *;
nhập java.util.ArrayList;
nhập java.util.Collection;
@Entity
@Table (name = "BANKS")
public class Bank triển khai java.io.Serializable {
@Id @GeneratedValue (chiến lược = GenerationType.AUTO)
id riêng tư;
@Embeddable // Một đối tượng "nội tuyến" trong hàng DB của Bank
Địa chỉ lớp công khai {
bảo vệ String streetAddr1;
String streetAddr2 được bảo vệ;
thành phố String được bảo vệ;
Trạng thái chuỗi được bảo vệ;
String zipCode được bảo vệ;
}
@Embedded
địa chỉ Địa chỉ riêng;
@OneToMany (thác nước = CascadeType.ALL, tìm nạp = FetchType.EAGER,
mappedBy = "ngân hàng")
private Collection <Tài khoản> tài khoản = new ArrayList <Tài khoản> ();
public int getId () {
trả về id;
}
public void setId (int id) {
this.id = id;
}
public void addAccount (Tài khoản tài khoản) {
account.setBank (this);
Account.add (tài khoản);
}
Bộ sưu tập công khai <Account> getAccounts () {
trả lại tài khoản;
}
16. Phỏng theo http://www.onjava.com/pub/a/onjava/2006/05/17/standardizing-with-ejb3-java-persistence-api.html
www.it-ebooks.info

Trang 197
166
Chương 11: Hệ thống
Mã này sạch hơn nhiều so với mã EJB2 gốc. Một số chi tiết thực thể là
vẫn ở đây, được chứa trong các chú thích. Tuy nhiên, vì không có thông tin nào được đưa ra ngoài-
bên cạnh chú thích, mã rõ ràng, sạch sẽ và do đó dễ dàng kiểm tra lái xe, bảo trì và
Sớm.
Một số hoặc tất cả thông tin liên tục trong các chú thích có thể được chuyển sang XML
các bộ mô tả triển khai, nếu muốn, để lại một POJO thực sự tinh khiết. Nếu ánh xạ liên tục
các chi tiết sẽ không thay đổi thường xuyên, nhiều nhóm có thể chọn giữ các chú thích, nhưng với
ít hạn chế có hại hơn nhiều so với tính xâm nhập của EJB2.
AspectJ Các khía cạnh
Cuối cùng, công cụ đầy đủ tính năng nhất để phân tách mối quan tâm qua các khía cạnh là AspectJ
ngôn ngữ, 17 một phần mở rộng của Java cung cấp hỗ trợ "hạng nhất" cho các khía cạnh như mô-đun-
cấu trúc ity. Các phương pháp tiếp cận Java thuần túy được cung cấp bởi Spring AOP và JBoss AOP là suf-
đủ cho 80–90 phần trăm các trường hợp mà các khía cạnh hữu ích nhất. Tuy nhiên, AspectJ
cung cấp một bộ công cụ rất phong phú và mạnh mẽ để phân tách các mối quan tâm. Hạn chế của
AspectJ cần áp dụng một số công cụ mới và học các cấu trúc ngôn ngữ mới và
cách sử dụng thành ngữ.
Các vấn đề về việc áp dụng đã được giảm thiểu một phần nhờ một “annota-
tion form ”của AspectJ, trong đó các chú thích Java 5 được sử dụng để xác định các khía cạnh bằng cách sử dụng
Java thuần túy
mã. Ngoài ra, Spring Framework có một số tính năng giúp kết hợp
các khía cạnh dựa trên chú thích dễ dàng hơn nhiều đối với một nhóm có kinh nghiệm AspectJ hạn chế.
Một cuộc thảo luận đầy đủ về AspectJ nằm ngoài phạm vi của cuốn sách này. Xem [AspectJ], [Colyer],
và [Spring] để biết thêm thông tin.
Chạy thử kiến trúc hệ thống
Không thể phóng đại sức mạnh của việc phân tách mối quan tâm thông qua các phương pháp tiếp cận theo khía
cạnh. Nếu
bạn có thể viết lôgic tên miền của ứng dụng bằng POJO, được tách ra khỏi bất kỳ tệp nén nào-
đảm bảo các mối quan tâm ở cấp mã, sau đó có thể thực sự kiểm tra kiến trúc của bạn. Bạn
có thể phát triển nó từ đơn giản đến phức tạp, nếu cần, bằng cách áp dụng các công nghệ mới trên
public void setAccounts (Collection <Account> tài khoản) {
this.accounts = tài khoản;
}
}
17. Xem [AspectJ] và [Colyer].
Liệt kê 11-5 (tiếp theo)
Ngân hàng EBJ3 EJB
www.it-ebooks.info

Trang 198
167
Tối ưu hóa việc ra quyết định
nhu cầu. Không nhất thiết phải thực hiện Big Design Up Front  18 (BDUF). Trên thực tế, BDUF thậm chí còn
có hại vì nó kìm hãm sự thích nghi với sự thay đổi, do tâm lý chống lại sự bất
thực hiện nỗ lực trước và do cách lựa chọn kiến trúc ảnh hưởng đến
suy nghĩ về thiết kế.
Các kiến trúc sư xây dựng phải thực hiện BDUF vì không khả thi để tạo ra các kiến trúc cấp tiến-
kiến trúc thay đổi thành một cấu trúc vật chất lớn khi quá trình xây dựng được tiến hành tốt. 19
Mặc dù phần mềm có riêng của mình vật lý , 20 nó là khả thi về mặt kinh tế để thực hiện triệt để
thay đổi, nếu cấu trúc của phần mềm phân tách các mối quan tâm của nó một cách hiệu quả.
Điều này có nghĩa là chúng ta có thể bắt đầu một dự án phần mềm với một cách trang trí "đơn giản" nhưng độc đáo
cam kết kiến trúc, cung cấp câu chuyện người dùng hoạt động nhanh chóng, sau đó bổ sung thêm cơ sở hạ tầng
khi chúng tôi mở rộng quy mô. Một số trang Web lớn nhất thế giới đã đạt được tính khả dụng rất cao
và hiệu suất, sử dụng bộ nhớ đệm dữ liệu phức tạp, bảo mật, ảo hóa, v.v.,
tất cả đều được thực hiện một cách hiệu quả và linh hoạt vì các thiết kế được ghép nối tối thiểu là phù hợp
đơn giản ở mỗi cấp độ trừu tượng và phạm vi.
Tất nhiên, điều này không có nghĩa là chúng ta đi vào một dự án “không có bánh lái”. Chúng tôi có một số
kỳ vọng về phạm vi, mục tiêu và lịch trình chung cho dự án, cũng như
cấu trúc eral của hệ thống kết quả. Tuy nhiên, chúng ta phải duy trì khả năng thay đổi
khóa học để đáp ứng với hoàn cảnh phát triển.
Kiến trúc EJB ban đầu nhưng là một trong số nhiều API nổi tiếng đang sử dụng quá nhiều
gần và thỏa hiệp đó tách biệt các mối quan tâm. Ngay cả các API được thiết kế tốt cũng có thể
giết khi chúng không thực sự cần thiết. Một API tốt phần lớn sẽ biến mất khỏi chế độ xem hầu hết
theo thời gian, vì vậy nhóm dành phần lớn nỗ lực sáng tạo của mình tập trung vào người dùng-
ries đang được thực hiện. Nếu không, thì các ràng buộc về kiến trúc sẽ hạn chế hiệu quả
cung cấp giá trị tối ưu cho khách hàng.
Để tóm tắt lại cuộc thảo luận dài này,
Một kiến trúc hệ thống tối ưu bao gồm các lĩnh vực được mô-đun hóa quan tâm, mỗi miền trong số đó
được triển khai với Đối tượng Java cũ (hoặc các đối tượng khác). Các miền khác nhau là tương
được mài với nhau bằng các Công cụ tương tự hoặc các khía cạnh xâm lấn tối thiểu.  Kiến trúc này có thể
được hướng dẫn thử nghiệm, giống như mã.
Tối ưu hóa việc ra quyết định
Tính mô-đun hóa và tách biệt các mối quan tâm giúp cho việc quản lý và quyết định phi tập trung
làm cho có thể. Trong một hệ thống đủ lớn, cho dù đó là một thành phố hay một dự án phần mềm, không
một người có thể đưa ra tất cả các quyết định.
18. Đừng nhầm lẫn với thực hành tốt của thiết kế từ trước, BDUF là thực hành thiết kế mọi thứ từ trước
thực hiện bất cứ điều gì ở tất cả.
19. Vẫn còn một lượng lớn các cuộc thăm dò lặp đi lặp lại và thảo luận về các chi tiết, ngay cả sau khi bắt đầu xây dựng.
20. Thuật ngữ vật lý phần mềm lần đầu tiên được sử dụng bởi [K bạo lực].
www.it-ebooks.info

Trang 199
168
Chương 11: Hệ thống
Tất cả chúng ta đều biết tốt nhất nên giao trách nhiệm cho những người có năng lực nhất. Chúng tôi thường
quên rằng tốt nhất là nên trì hoãn các quyết định cho đến thời điểm cuối cùng có thể . Đây không phải là
lười biếng hoặc vô trách nhiệm; nó cho phép chúng tôi đưa ra những lựa chọn sáng suốt với thông tin tốt nhất có thể.
Một quyết định sớm là một quyết định được đưa ra với kiến thức chưa tối ưu. Chúng tôi sẽ có điều đó
phản hồi của khách hàng ít hơn nhiều, phản ánh tinh thần về dự án và trải nghiệm với
lựa chọn triển khai nếu chúng ta quyết định quá sớm.
Sự linh hoạt được cung cấp bởi hệ thống POJO với các mối quan tâm được mô-đun hóa cho phép chúng tôi thực hiện tối ưu hóa
nam, quyết định đúng lúc, dựa trên kiến thức gần đây nhất. Sự phức tạp của những
các quyết định cũng được giảm bớt.
Sử dụng các tiêu chuẩn một cách khôn ngoan, khi chúng thêm giá trị có
thể chứng minh
Việc xây dựng tòa nhà là một điều kỳ diệu để xem vì tốc độ xây dựng các tòa nhà mới
được xây dựng (ngay cả trong mùa đông chết chóc) và vì những thiết kế đặc biệt có khả năng
với công nghệ ngày nay. Xây dựng là một ngành công nghiệp trưởng thành với các bộ phận được tối ưu hóa cao,
các phương pháp và tiêu chuẩn đã phát triển dưới áp lực trong nhiều thế kỷ.
Nhiều đội đã sử dụng kiến trúc EJB2 vì nó là một tiêu chuẩn, ngay cả khi nhẹ hơn-
trọng lượng và các thiết kế đơn giản hơn sẽ là đủ. Tôi đã thấy các đội
bị ám ảnh bởi các tiêu chuẩn được thổi phồng mạnh mẽ khác nhau và mất tập trung vào việc thực hiện
giá trị cho khách hàng của họ.
Các tiêu chuẩn giúp việc sử dụng lại các ý tưởng và thành phần trở nên dễ dàng hơn, tuyển dụng những người có kinh nghiệm phù hợp
rience, gói gọn các ý tưởng hay và các thành phần dây với nhau. Tuy nhiên, quá trình
việc tạo ra các tiêu chuẩn đôi khi có thể mất quá nhiều thời gian để ngành công nghiệp chờ đợi và một số tiêu chuẩn
mất liên lạc với nhu cầu thực sự của người dùng mà họ muốn phục vụ.
Hệ thống cần ngôn ngữ dành riêng cho miền
Xây dựng tòa nhà, giống như hầu hết các lĩnh vực, đã phát triển một ngôn ngữ phong phú với vocabu-
lary, thành ngữ và mẫu 21 truyền tải thông tin cần thiết một cách rõ ràng và ngắn gọn. Trong mềm-
, gần đây đã có sự quan tâm mới đến việc tạo Ngôn ngữ dành riêng cho miền
(DSL), 22 là các ngôn ngữ kịch bản nhỏ, riêng biệt hoặc các API trong các ngôn ngữ tiêu chuẩn
cho phép mã được viết để nó đọc giống như một dạng văn xuôi có cấu trúc mà một miền
chuyên gia có thể viết.
Một DSL tốt sẽ giảm thiểu “khoảng cách giao tiếp” giữa khái niệm miền và
mã triển khai nó, cũng giống như các phương pháp linh hoạt tối ưu hóa thông tin liên lạc trong
nhóm và với các bên liên quan của dự án. Nếu bạn đang triển khai logic miền trong
21. Công việc của [Alexander] đã có ảnh hưởng đặc biệt đến cộng đồng phần mềm.
22. Ví dụ, xem [DSL]. [JMock] là một ví dụ điển hình về API Java tạo DSL.
www.it-ebooks.info

Trang 200
169
Thư mục
cùng ngôn ngữ mà chuyên gia tên miền sử dụng, ít có nguy cơ bạn chuyển đổi sai
cuối miền vào việc thực hiện.
DSL, khi được sử dụng hiệu quả, nâng cao mức trừu tượng lên trên các thành ngữ và thiết kế mã
các mẫu. Chúng cho phép nhà phát triển tiết lộ ý định của mã ở cấp độ thích hợp
của sự trừu tượng.
Ngôn ngữ dành riêng cho miền cho phép tất cả các cấp độ trừu tượng và tất cả các miền trong ứng dụng-
được thể hiện dưới dạng POJO, từ chính sách cấp cao đến chi tiết cấp thấp.
Phần kết luận
Hệ thống cũng phải sạch. Một kiến trúc xâm lấn lấn át logic miền và
tác động đến sự nhanh nhẹn. Khi logic miền bị che khuất, chất lượng sẽ bị ảnh hưởng vì lỗi tìm thấy nó
dễ che giấu hơn và câu chuyện trở nên khó thực hiện hơn. Nếu sự nhanh nhẹn bị xâm phạm, sản xuất-
sức ép bị ảnh hưởng và lợi ích của TDD bị mất.
Ở tất cả các cấp độ trừu tượng, ý định phải rõ ràng. Điều này sẽ chỉ xảy ra nếu bạn
viết POJO và bạn sử dụng các cơ chế giống khía cạnh để kết hợp các triển khai khác
mối quan tâm không xâm lấn.
Cho dù bạn đang thiết kế hệ thống hay mô-đun riêng lẻ, đừng bao giờ quên sử dụng
điều đơn giản nhất có thể hoạt động .
Thư mục
[Alexander]: Christopher Alexander, Con đường xây dựng vượt thời gian, Đại học Oxford
Báo chí, New York, 1979.
[AOSD]: Cổng phát triển phần mềm hướng theo khía cạnh, http://aosd.net
[ASM]: Trang chủ ASM, http : //asm.objectweb.org/
[AspectJ]: http://eclipse.org/aspectj
[CGLIB]: Thư viện tạo mã, http://cglib.sourceforge.net/
[Colyer]: Adrian Colyer, Andy Clement, George Hurley, Mathew Webster, Eclipse
AspectJ, Person Education, Inc., Upper Saddle River, NJ, 2005.
[DSL]: Ngôn ngữ lập trình dành riêng cho miền, http : //en.wikipedia.org/wiki/Domain-
specific_programming_language
[Fowler]: Đảo ngược vùng chứa điều khiển và mẫu tiêm phụ thuộc,
http://martinfowler.com/articles/injection.html
www.it-ebooks.info

Trang 201
170
Chương 11: Hệ thống
[Goetz]: Brian Goetz, Lý thuyết và thực hành Java: Trang trí với Dynamic Proxie s,
http://www.ibm.com/developerworks/java/library/j-jtp08305.html
[Javassist]: Trang chủ Javassist, http://www.csg.is.titech.ac.jp/~chiba/javassist/
[JBoss]: Trang chủ JBoss, http://jboss.org
[JMock]: JMock — Thư viện đối tượng mô phỏng nhẹ cho Java, http://jmock.org
[K bạo lực]: Kenneth W. K bạo lực, Vật lý phần mềm và đo lường hiệu suất máy tính-
ments, Kỷ yếu hội nghị thường niên ACM — Tập 2 , Boston, Massachusetts,
trang 1024–1040, 1972.
[Spring]: Spring Framework , http://www.springframework.org
[Mezzaros07]: Mẫu XUnit , Gerard Mezzaros, Addison-Wesley, 2007.
[GOF]: Mẫu thiết kế: Các yếu tố của phần mềm hướng đối tượng có thể tái sử dụng , Gamma et al.,
Addison-Wesley, 1996.
www.it-ebooks.info

Trang 202
171

12
Sự xuất hiện
bởi Jeff Langr
Làm sạch thông qua thiết kế nổi bật
Điều gì sẽ xảy ra nếu có bốn quy tắc đơn giản mà bạn có thể tuân theo sẽ giúp bạn tạo ra
thiết kế như bạn đã làm việc? Điều gì sẽ xảy ra nếu bằng cách tuân theo các quy tắc này, bạn đã hiểu rõ hơn về cấu
trúc-
kiểm tra và thiết kế mã của bạn, giúp việc áp dụng các nguyên tắc như SRP và DIP dễ dàng hơn?
Điều gì sẽ xảy ra nếu bốn quy tắc này tạo điều kiện cho sự xuất hiện của các thiết kế tốt?
Nhiều người trong chúng ta cảm thấy rằng bốn quy tắc Thiết kế Đơn giản  1 của Kent Beck giúp ích đáng kể trong
việc
tạo phần mềm được thiết kế tốt.
1. [XPE].
www.it-ebooks.info

Trang 203
172
Chương 12: Sự xuất hiện
Theo Kent, một thiết kế là "đơn giản" nếu nó tuân theo các quy tắc sau:
• Chạy tất cả các bài kiểm tra
• Không chứa trùng lặp
• Thể hiện ý định của người lập trình
• Giảm thiểu số lượng các lớp và phương thức
Các quy tắc được đưa ra theo thứ tự quan trọng.
Quy tắc thiết kế đơn giản 1: Chạy tất cả các bài kiểm tra
Đầu tiên và quan trọng nhất, một thiết kế phải tạo ra một hệ thống hoạt động như dự định. Một hệ thống có thể
có một thiết kế hoàn hảo trên giấy, nhưng nếu không có cách đơn giản nào để xác minh rằng hệ thống hoạt động-
ally hoạt động như dự định, sau đó tất cả các nỗ lực giấy là nghi vấn.
Một hệ thống được kiểm tra toàn diện và luôn vượt qua tất cả các bài kiểm tra của nó là một bài kiểm tra-
hệ thống có thể. Đó là một tuyên bố hiển nhiên, nhưng là một điều quan trọng. Hệ thống không thể kiểm tra
không thể kiểm chứng. Có thể cho rằng, một hệ thống không thể được xác minh sẽ không bao giờ được triển khai.
May mắn thay, việc làm cho hệ thống của chúng tôi có thể kiểm tra được sẽ thúc đẩy chúng tôi hướng tới một thiết
kế mà các lớp của chúng tôi
là mục đích nhỏ và duy nhất. Việc kiểm tra các lớp tuân theo SRP chỉ dễ dàng hơn. Các
chúng tôi viết nhiều bài kiểm tra hơn, chúng tôi sẽ tiếp tục đẩy mạnh những thứ đơn giản hơn để kiểm tra.
Vì vậy, đảm bảo hệ thống của chúng tôi hoàn toàn có thể kiểm tra được sẽ giúp chúng tôi tạo ra các thiết kế tốt hơn.
Khớp nối chặt chẽ gây khó khăn cho việc viết bài kiểm tra. Vì vậy, tương tự, chúng tôi viết càng nhiều bài kiểm tra,
chúng tôi càng sử dụng các nguyên tắc như DIP và các công cụ như chèn phụ thuộc, giao diện và
trừu tượng để giảm thiểu sự ghép nối. Thiết kế của chúng tôi cải thiện nhiều hơn nữa.
Đáng chú ý, tuân theo một quy tắc đơn giản và rõ ràng nói rằng chúng ta cần phải có các bài kiểm tra và
chạy chúng liên tục sẽ ảnh hưởng đến việc hệ thống của chúng tôi tuân thủ các mục tiêu OO chính là thấp
khớp nối và tính liên kết cao. Bài kiểm tra viết dẫn đến thiết kế tốt hơn.
Quy tắc thiết kế đơn giản 2–4: Tái cấu trúc
Khi chúng tôi có các bài kiểm tra, chúng tôi được trao quyền để giữ cho mã và các lớp của chúng tôi sạch sẽ. Chúng
tôi làm điều này bằng cách
từng bước cấu trúc lại mã. Đối với mỗi dòng mã chúng tôi thêm vào, chúng tôi tạm dừng và phản ánh
trên thiết kế mới. Có phải chúng ta vừa làm suy giảm nó? Nếu vậy, chúng tôi dọn dẹp nó và chạy các bài kiểm tra
của chúng tôi cho quỷ-
chiến lược rằng chúng tôi đã không phá vỡ bất cứ điều gì. Việc chúng tôi có những bài kiểm tra này giúp loại bỏ nỗi
sợ hãi
rằng việc dọn dẹp mã sẽ phá vỡ nó!
Trong bước tái cấu trúc này, chúng ta có thể áp dụng mọi thứ từ toàn bộ khối kiến thức
về thiết kế phần mềm tốt. Chúng ta có thể tăng sự gắn kết, giảm sự ghép nối, tách rời
chứng nhận, mô-đun hóa mối quan tâm của hệ thống, thu nhỏ các chức năng và lớp của chúng tôi, chọn tên tốt hơn,
và như thế. Đây cũng là nơi chúng tôi áp dụng ba quy tắc cuối cùng của thiết kế đơn giản: Loại bỏ
trùng lặp, đảm bảo tính biểu cảm, hạn chế tối đa số lớp, phương thức.
www.it-ebooks.info

Trang 204
173
Không trùng lặp
Không trùng lặp
Sao chép là kẻ thù chính của một hệ thống được thiết kế tốt. Nó thể hiện bổ sung
công việc, rủi ro bổ sung và phức tạp không cần thiết bổ sung. Biểu hiện trùng lặp
chính nó dưới nhiều hình thức. Tất nhiên, các dòng mã trông hoàn toàn giống nhau sẽ bị trùng lặp.
Các dòng mã giống nhau thường có thể được xoa bóp để trông giống nhau hơn nữa để
chúng có thể được cấu trúc lại dễ dàng hơn. Và sự trùng lặp có thể tồn tại ở các dạng khác như
thực hiện trùng lặp. Ví dụ: chúng ta có thể có hai phương thức trong một bộ sưu tập
lớp học:
int size () {}
boolean isEmpty () {}
Chúng tôi có thể có các triển khai riêng biệt cho từng phương pháp. Các isEmpty phương pháp có thể theo dõi
boolean, trong khi kích thước có thể theo dõi bộ đếm. Hoặc, chúng tôi có thể loại bỏ sự trùng lặp này bằng cách buộc
isEmpty cho định nghĩa về kích thước :
boolean isEmpty () {
trả về 0 == size ();
}
Tạo ra một hệ thống sạch sẽ đòi hỏi ý chí loại bỏ sự trùng lặp, thậm chí chỉ trong một vài
dòng mã. Ví dụ, hãy xem xét đoạn mã sau:
public void scaleToOneDimension (
float mong muốnDimension, float imageDimension) {
if (Math.abs (mong muốnDimension - imageDimension) <errorThreshold)
trở về;
float scalingFactor = wishDimension / imageDimension;
scalingFactor = (float) (Math.floor (scalingFactor * 100) * 0.01f);
RenderOp newImage = ImageUtilities.getScaledImage (
hình ảnh, scalingFactor, scalingFactor);
image.dispose ();
Hệ thống.gc ();
hình ảnh = newImage;
}
công khai void xoay (int độ) {
RenderOp newImage = ImageUtilities.getRotatedImage (
hình ảnh, độ);
image.dispose ();
Hệ thống.gc ();
hình ảnh = newImage;
}
Để giữ cho hệ thống này sạch sẽ, chúng ta nên loại bỏ một lượng nhỏ trùng lặp giữa
các scaleToOneDimension và xoay các phương pháp:
public void scaleToOneDimension (
float mong muốnDimension, float imageDimension) {
if (Math.abs (mong muốnDimension - imageDimension) <errorThreshold)
trở về;
float scalingFactor = wishDimension / imageDimension;
scalingFactor = (float) (Math.floor (scalingFactor * 100) * 0.01f);
www.it-ebooks.info

Trang 205
174
Chương 12: Sự xuất hiện
ReplaceImage (ImageUtilities.getScaledImage (
hình ảnh, scalingFactor, scalingFactor));
}
công khai void xoay (int độ) {
ReplaceImage (ImageUtilities.getRotatedImage (hình ảnh, độ));
}
private void ReplaceImage (RenderOp newImage) {
image.dispose ();
Hệ thống.gc ();
hình ảnh = newImage;
}
Khi chúng tôi trích xuất điểm chung ở cấp độ rất nhỏ này, chúng tôi bắt đầu nhận ra các vi phạm SRP.
Vì vậy, chúng ta có thể chuyển một phương thức mới được trích xuất sang một lớp khác. Điều đó nâng cao khả năng
hiển thị của nó.
Ai đó khác trong nhóm có thể nhận ra cơ hội để tóm tắt thêm
và sử dụng lại nó trong một ngữ cảnh khác. Việc "tái sử dụng trong phạm vi nhỏ" này có thể gây ra lỗi hệ thống
độ khó co rút đáng kể. Hiểu cách sử dụng lại trong quy mô nhỏ là điều cần thiết
để đạt được mức tái sử dụng lớn.
T EMPLATE M ETHOD 2 mô hình là một kỹ thuật phổ biến để loại bỏ cấp cao
sự trùng lặp. Ví dụ:
Kỳ nghỉ hạng công cộng Chính sách {
public void accrueUSDivisionVacation () {
// mã để tính kỳ nghỉ dựa trên số giờ làm việc cho đến nay
// ...
// mã để đảm bảo kỳ nghỉ đáp ứng mức tối thiểu của Hoa Kỳ
// ...
// mã áp dụng vị trí tuyển dụng vào hồ sơ trả lương
// ...
}
public void accrueEUDivisionVacation () {
// mã để tính kỳ nghỉ dựa trên số giờ làm việc cho đến nay
// ...
// mã để đảm bảo kỳ nghỉ đáp ứng mức tối thiểu của EU
// ...
// mã áp dụng vị trí tuyển dụng vào hồ sơ trả lương
// ...
}
}
Mã trên tích lũyUSDivisionVacation và tích lũyEuropeanDivisionVacation phần lớn là
giống nhau, ngoại trừ việc tính toán mức tối thiểu hợp pháp. Đó là bit của thuật toán
thay đổi dựa trên loại nhân viên.
Chúng ta có thể loại bỏ sự trùng lặp rõ ràng bằng cách áp dụng T EMPLATE M ETHOD mẫu.
trừu tượng lớp học công cộng VacationPolicy {
public void accrueVacation () {
tính toánBaseVacationHours ();
2. [GOF].
www.it-ebooks.info

Trang 206
175
Biểu cảm
AlterForLegalMinimums ();
applyToPayroll ();
}
riêng void tínhBaseVacationHours () {/ * ... * /};
trừu tượng được bảo vệ void alterForLegalMinimums ();
private void applyToPayroll () {/ * ... * /};
}
public class USVacationPolicy mở rộng VacationPolicy {
@Override được bảo vệ void alterForLegalMinimums () {
// Logic cụ thể của Hoa Kỳ
}
}
lớp công khai EUVacationPolicy mở rộng VacationPolicy {
@Override được bảo vệ void alterForLegalMinimums () {
// logic cụ thể của EU
}
}
Các lớp con điền vào "lỗ hổng" trong thuật toán accrueVacation , cung cấp các bit duy nhất của
thông tin không bị trùng lặp.
Biểu cảm
Hầu hết chúng ta đều đã có kinh nghiệm làm việc trên mã phức tạp. Nhiều người trong chúng ta có
tự tạo ra một số mã phức tạp. Thật dễ dàng để viết mã mà chúng tôi hiểu, bởi vì
tại thời điểm chúng tôi viết nó, chúng tôi đã hiểu sâu sắc về vấn đề mà chúng tôi đang cố gắng giải quyết.
Những người bảo trì mã khác sẽ không có hiểu biết sâu sắc như vậy.
Phần lớn chi phí của một dự án phần mềm là để bảo trì dài hạn. Để mà
giảm thiểu khả năng xảy ra các khiếm khuyết khi chúng tôi đưa ra thay đổi, điều quan trọng là chúng tôi có thể
hiểu những gì một hệ thống làm. Khi các hệ thống trở nên phức tạp hơn, chúng mất nhiều hơn và
có nhiều thời gian hơn để nhà phát triển hiểu và có cơ hội lớn hơn bao giờ hết cho một
sự hiểu biết. Do đó, mã phải thể hiện rõ ràng ý định của tác giả của nó. Càng rõ ràng
tác giả có thể tạo ra mã, những người khác sẽ phải dành ít thời gian hơn để hiểu nó. Điều này
sẽ giảm thiểu khuyết tật và giảm chi phí bảo trì.
Bạn có thể thể hiện bản thân bằng cách chọn những cái tên hay. Chúng tôi muốn có thể nghe một lớp học
hoặc tên chức năng và không ngạc nhiên khi chúng ta phát hiện ra các trách nhiệm của nó.
Bạn cũng có thể thể hiện bản thân bằng cách giữ cho các hàm và lớp của mình nhỏ lại. Nhỏ
các lớp và hàm thường dễ đặt tên, dễ viết và dễ hiểu.
Bạn cũng có thể thể hiện bản thân bằng cách sử dụng danh pháp chuẩn. Mẫu thiết kế, dành cho
ví dụ, phần lớn là về giao tiếp và biểu cảm. Bằng cách sử dụng tiêu chuẩn
tên mẫu, chẳng hạn như C OMMAND hoặc V ISITOR , trong tên của các lớp đóng vai trò-
đề cập đến những mẫu đó, bạn có thể mô tả ngắn gọn thiết kế của mình cho các nhà phát triển khác.
Các bài kiểm tra đơn vị được viết tốt cũng mang tính biểu cảm. Mục tiêu chính của các bài kiểm tra là hoạt động
như một tài liệu-
đề cập bằng ví dụ. Ai đó đọc các bài kiểm tra của chúng tôi sẽ có thể nhanh chóng nhận được
vị thế của một lớp là tất cả về.
www.it-ebooks.info

Trang 207
176
Chương 12: Sự xuất hiện
Nhưng cách quan trọng nhất để thể hiện là cố gắng . Chúng tôi thường xuyên nhận được mã của mình
làm việc và sau đó chuyển sang vấn đề tiếp theo mà không cần suy nghĩ đầy đủ để thực hiện
mã đó dễ dàng cho người tiếp theo đọc. Hãy nhớ rằng người tiếp theo có nhiều khả năng sẽ đọc
mã sẽ là bạn.
Vì vậy, hãy tự hào một chút về tay nghề của bạn. Hãy dành một chút thời gian cho mỗi chức năng của bạn-
tions và lớp học. Chọn tên tốt hơn, chia các chức năng lớn thành các chức năng nhỏ hơn và
thường chỉ quan tâm đến những gì bạn đã tạo. Chăm sóc là một nguồn tài nguyên quý giá.
Các lớp và phương pháp tối thiểu
Ngay cả những khái niệm cơ bản như loại bỏ sự trùng lặp, tính biểu cảm của mã và
SRP có thể được đưa quá xa. Trong nỗ lực làm cho các lớp và phương thức của chúng ta nhỏ lại, chúng ta có thể
tạo quá nhiều lớp và phương thức nhỏ. Vì vậy, quy tắc này gợi ý rằng chúng ta cũng nên giữ chức năng của mình-
tion và số lượng lớp học thấp.
Số lượng phương pháp và đẳng cấp cao đôi khi là kết quả của chủ nghĩa giáo điều vô nghĩa. Con-
sider, ví dụ, một tiêu chuẩn mã hóa đòi hỏi phải tạo ra một giao diện cho mỗi và
mỗi lớp. Hoặc xem xét các nhà phát triển khẳng định rằng các trường và hành vi phải luôn luôn được tách biệt
được xếp hạng thành các lớp dữ liệu và lớp hành vi. Những giáo điều như vậy cần được chống lại và
cách tiếp cận thực dụng được thông qua.
Mục tiêu của chúng tôi là giữ cho hệ thống tổng thể của chúng tôi nhỏ gọn trong khi chúng tôi cũng giữ các chức
năng của mình
và các lớp học nhỏ. Tuy nhiên, hãy nhớ rằng quy tắc này là mức ưu tiên thấp nhất trong bốn quy tắc
thiết kế đơn giản. Vì vậy, mặc dù điều quan trọng là giữ cho số lớp và hàm ở mức thấp, nhưng
quan trọng hơn là có các bài kiểm tra, loại bỏ sự trùng lặp và thể hiện bản thân.
Phần kết luận
Có một tập hợp các thực hành đơn giản có thể thay thế kinh nghiệm không? Rõ ràng không. Mặt khác
tay, các thực hành được mô tả trong chương này và trong cuốn sách này là một dạng kết tinh của
nhiều thập kỷ kinh nghiệm của các tác giả. Sau khi thực hành đơn giản
thiết kế có thể khuyến khích và cho phép các nhà phát triển tuân thủ các nguyên tắc tốt và
những mẫu mà nếu không thì phải mất nhiều năm để học.
Thư mục
[XPE]: Giải thích về lập trình cực đoan: Nắm lấy thay đổi, Kent Beck, Addison-
Wesley, 1999.
[GOF]: Mẫu thiết kế: Các yếu tố của phần mềm hướng đối tượng có thể tái sử dụng, Gamma et al.,
Addison-Wesley, 1996.
www.it-ebooks.info

Trang 208
177

13
Đồng tiền
bởi Brett L. Schuchert
“Đối tượng là sự trừu tượng của quá trình xử lý. Chủ đề là sự trừu tượng của lịch trình ”.
—James O. Coplien 1
1. Thư tín riêng.
www.it-ebooks.info

Trang 209
178
Chương 13: Đồng thời
Viết các chương trình đồng thời rõ ràng là rất khó — rất khó. Viết mã dễ dàng hơn nhiều
thực thi trong một luồng duy nhất. Nó cũng dễ dàng để viết mã đa luồng trông đẹp trên
bề mặt nhưng bị phá vỡ ở mức độ sâu hơn. Mã như vậy hoạt động tốt cho đến khi hệ thống được đặt
căng thẳng.
Trong chương này, chúng ta thảo luận về sự cần thiết của lập trình đồng thời và những khó khăn
nó trình bày. Sau đó, chúng tôi đưa ra một số khuyến nghị để giải quyết những khó khăn đó,
và viết mã đồng thời sạch sẽ. Cuối cùng, chúng tôi kết luận với các vấn đề liên quan đến thử nghiệm
mã đồng thời.
Clean Concurrency là một chủ đề phức tạp, tự nó xứng đáng là một cuốn sách. Chiến lược của chúng tôi trong việc
này
cuốn sách là để trình bày tổng quan ở đây và cung cấp một hướng dẫn chi tiết hơn trong "Concurrency II"
ở trang 317. Nếu bạn chỉ tò mò về đồng thời, thì chương này sẽ đủ cho bạn
hiện nay. Nếu bạn có nhu cầu hiểu về đồng thời ở mức độ sâu hơn, thì bạn nên đọc
thông qua hướng dẫn là tốt.
Tại sao lại sử dụng đồng thời?
Đồng thời là một chiến lược tách rời. Nó giúp chúng tôi phân tách những gì được hoàn thành từ khi nó
Được thực hiện. Trong các ứng dụng đơn luồng, điều gì và khi nào được kết hợp chặt chẽ đến mức
trạng thái của toàn bộ ứng dụng thường có thể được xác định bằng cách nhìn vào dấu vết ngăn xếp. A
lập trình viên gỡ lỗi một hệ thống như vậy có thể đặt một điểm ngắt hoặc một chuỗi các điểm ngắt,
và biết trạng thái của hệ thống mà các điểm ngắt bị tấn công.
Việc tách những gì từ khi nào có thể cải thiện đáng kể cả thông lượng và cấu trúc
kiểm tra của một ứng dụng. Từ quan điểm cấu trúc, ứng dụng trông giống như nhiều
hợp tác giữa các máy tính hơn là một vòng lặp chính lớn. Điều này có thể làm cho hệ thống dễ dàng hơn
để hiểu và đưa ra một số cách hiệu quả để tách biệt các mối quan tâm.
Ví dụ, hãy xem xét mô hình “Servlet” tiêu chuẩn của các ứng dụng Web. Những hệ thống này-
các tems chạy dưới sự bảo trợ của vùng chứa Web hoặc EJB quản lý một phần đồng thời-
rency cho bạn. Các servlet được thực thi một cách không đồng bộ bất cứ khi nào các yêu cầu Web đến.
Lập trình viên servlet không phải quản lý tất cả các yêu cầu đến. Về nguyên tắc,
mỗi lần thực thi servlet sống trong thế giới nhỏ của riêng nó và được tách biệt khỏi tất cả các servlet khác-
để thực hiện.
Tất nhiên nếu nó dễ dàng như vậy, chương này sẽ không cần thiết. Trên thực tế, decou-
pling được cung cấp bởi các vùng chứa Web kém hoàn hảo hơn nhiều. Lập trình viên Servlet phải
rất ý thức, và rất cẩn thận, để đảm bảo các chương trình đồng thời của họ là chính xác. Vẫn là
lợi ích cấu trúc của mô hình servlet là đáng kể.
Nhưng cấu trúc không phải là động cơ duy nhất để áp dụng đồng thời. Một số hệ thống có
các hạn chế về thời gian phản hồi và thông lượng yêu cầu các giải pháp đồng thời được mã hóa thủ công.
Ví dụ: hãy xem xét một trình tổng hợp thông tin đơn luồng thu thập thông tin
từ nhiều trang Web khác nhau và kết hợp thông tin đó thành một bản tóm tắt hàng ngày. Bởi vì
www.it-ebooks.info

Trang 210
179
Tại sao lại sử dụng đồng thời?
hệ thống này là một luồng, nó lần lượt truy cập vào từng trang Web, luôn kết thúc trước đó
bắt đầu tiếp theo. Quá trình chạy hàng ngày cần thực hiện trong vòng chưa đầy 24 giờ. Tuy nhiên, càng
và nhiều trang Web được thêm vào, thời gian tăng lên cho đến khi mất hơn 24 giờ để thu thập
tất cả dữ liệu. Chuỗi đơn liên quan đến rất nhiều chờ đợi tại các ổ cắm Web để I / O hoàn thành.
Chúng tôi có thể cải thiện hiệu suất bằng cách sử dụng thuật toán đa luồng đạt được nhiều hơn
một trang web tại một thời điểm.
Hoặc xem xét một hệ thống xử lý một người dùng tại một thời điểm và chỉ yêu cầu một giây
thời gian cho mỗi người dùng. Hệ thống này khá đáp ứng cho một số ít người dùng, nhưng số lượng
người dùng tăng, thời gian phản hồi của hệ thống tăng lên. Không người dùng nào muốn xếp hàng
đằng sau 150 người khác! Chúng tôi có thể cải thiện thời gian phản hồi của hệ thống này bằng cách xử lý
nhiều người dùng đồng thời.
Hoặc xem xét một hệ thống diễn giải các tập dữ liệu lớn nhưng chỉ có thể đưa ra một giải pháp hoàn chỉnh-
tion sau khi xử lý tất cả chúng. Có lẽ mỗi tập dữ liệu có thể được xử lý trên một
máy tính để nhiều tập dữ liệu đang được xử lý song song.
Những lầm tưởng và quan niệm sai lầm
Và do đó, có những lý do thuyết phục để áp dụng đồng thời. Tuy nhiên, như chúng tôi đã nói trước đây,
đồng thời là khó . Nếu không cẩn thận, bạn có thể tạo ra một số tình huống rất khó chịu.
Hãy xem xét những lầm tưởng và quan niệm sai lầm phổ biến này:
• Đồng thời luôn cải thiện hiệu suất.
Đồng thời đôi khi có thể cải thiện hiệu suất, nhưng chỉ khi phải chờ đợi nhiều
thời gian có thể được chia sẻ giữa nhiều luồng hoặc nhiều bộ xử lý. Cả situ-
ation là tầm thường.
• Thiết kế không thay đổi khi viết chương trình đồng thời.
Trên thực tế, thiết kế của một thuật toán đồng thời có thể khác biệt đáng kể so với
thiết kế của một hệ thống đơn luồng. Việc phân tách những gì từ khi nào thường có
ảnh hưởng rất lớn đến cấu trúc của hệ thống.
• Hiểu các vấn đề đồng thời không quan trọng khi làm việc với vùng chứa
chẳng hạn như vùng chứa Web hoặc EJB.
Trên thực tế, bạn nên biết rõ hơn vùng chứa của mình đang làm gì và cách đề phòng
các vấn đề về cập nhật đồng thời và bế tắc được mô tả sau trong chương này.
Dưới đây là một số điểm cân bằng âm thanh khác liên quan đến việc viết phần mềm đồng thời:
• Sự đồng thời phát sinh một số chi phí, cả về hiệu suất cũng như việc viết bổ sung
mã.
• Tính đồng thời đúng là phức tạp, ngay cả đối với các bài toán đơn giản.
www.it-ebooks.info

Trang 211
180
Chương 13: Đồng thời
• Các lỗi đồng thời thường không thể lặp lại, vì vậy chúng thường bị bỏ qua như một lần 2
thay vì những khiếm khuyết thực sự mà chúng đang có.
• Đồng thời thường đòi hỏi một sự thay đổi cơ bản trong chiến lược thiết kế .
Thách thức
Điều gì làm cho việc lập trình đồng thời trở nên khó khăn như vậy? Hãy xem xét lớp tầm thường sau:
lớp công khai X {
private int lastIdUsed;
public int getNextId () {
return ++ lastIdUsed;
}
}
Giả sử chúng ta tạo một phiên bản của X , đặt trường lastIdUsed thành 42, sau đó chia sẻ
ví dụ giữa hai luồng. Bây giờ, giả sử rằng cả hai luồng đó đều gọi phương thức
getNextId () ; có ba kết quả có thể xảy ra:
• Luồng một nhận giá trị 43, luồng hai nhận giá trị 44, lastIdUsed là 44.
• Luồng một nhận giá trị 44, luồng hai nhận giá trị 43, lastIdUsed là 44.
• Luồng một nhận giá trị 43, luồng hai nhận giá trị 43, lastIdUsed là 43.
Kết quả thứ ba đáng ngạc nhiên 3 xảy ra khi hai chủ đề dẫm lên nhau. Hap này-
bút bởi vì có nhiều con đường khả dĩ mà hai luồng có thể đi qua con đường đó
dòng mã Java và một số đường dẫn đó tạo ra kết quả không chính xác. Có bao nhiêu khác nhau
có những con đường? Để thực sự trả lời câu hỏi đó, chúng ta cần hiểu Just-In-
Trình biên dịch thời gian thực hiện với mã byte được tạo và hiểu bộ nhớ Java
mô hình được coi là nguyên tử.
Một câu trả lời nhanh, chỉ hoạt động với mã byte được tạo, là có 12.870
các đường dẫn thực thi khác nhau có thể có 4 cho hai luồng đó đang thực thi trong getNextId
phương pháp. Nếu loại lastIdUsed được thay đổi từ int thành long , số lượng có thể
đường dẫn tăng lên 2,704,156. Tất nhiên hầu hết các đường dẫn đó đều tạo ra kết quả hợp lệ. Các
vấn đề là một số trong số họ không .
Nguyên tắc phòng thủ đồng thời
Sau đây là một loạt các nguyên tắc và kỹ thuật để bảo vệ hệ thống của bạn khỏi
các vấn đề về mã đồng thời.
2. Tia vũ trụ, trục trặc, v.v.
3. Xem “Đào sâu hơn” trên trang 323.
4. Xem “Các con đường thực hiện có thể xảy ra” trên trang 321.
www.it-ebooks.info

Trang 212
181
Nguyên tắc phòng thủ đồng thời
Nguyên tắc trách nhiệm đơn lẻ
SRP 5 tuyên bố rằng một phương thức / lớp / thành phần nhất định phải có một lý do duy nhất để
thay đổi. Thiết kế đồng thời đủ phức tạp để trở thành một lý do để thay đổi theo đúng nghĩa của nó
và do đó xứng đáng được tách khỏi phần còn lại của mã. Thật không may, tất cả đều quá
chung cho các chi tiết triển khai đồng thời được nhúng trực tiếp vào các chương trình khác
mã duction. Đây là một vài thứ đáng xem xét:
• Mã liên quan đến đồng tiền có vòng đời phát triển, thay đổi và điều chỉnh riêng.
• Mã liên quan đến đồng tiền có những thách thức riêng, khác với và thường
khó hơn so với mã không liên quan đến tiền tệ.
• Số cách mà mã dựa trên đồng tiền bị viết sai có thể không thành công
đủ thách thức mà không có thêm gánh nặng về mã ứng dụng xung quanh.
Đề xuất : Giữ mã liên quan đến đồng thời của bạn tách biệt với mã khác . 6
Hệ quả: Giới hạn phạm vi dữ liệu
Như chúng ta đã thấy, hai luồng sửa đổi cùng một trường của một đối tượng được chia sẻ có thể can thiệp vào
khác, gây ra hành vi không mong muốn. Một giải pháp là sử dụng từ khóa được đồng bộ hóa để
bảo vệ một phần quan trọng trong mã sử dụng đối tượng được chia sẻ. Điều quan trọng là phải hạn chế
số lượng các phần quan trọng như vậy. Dữ liệu được chia sẻ càng nhiều địa điểm có thể được cập nhật,
nhiều khả năng:
• Bạn sẽ quên bảo vệ một hoặc nhiều nơi đó — phá vỡ mọi mã một cách hiệu quả
sửa đổi dữ liệu được chia sẻ đó.
• Sẽ có nhiều nỗ lực cần thiết để đảm bảo mọi thứ đều hiệu quả
được bảo vệ (vi phạm DRY 7 ).
• Sẽ rất khó để xác định nguồn gốc của lỗi, vốn đã đủ khó
để tìm.
Khuyến nghị : Hãy quan tâm đến việc đóng gói dữ liệu; giới hạn nghiêm ngặt quyền truy cập của bất kỳ
dữ liệu có thể được chia sẻ.
Hệ quả: Sử dụng bản sao dữ liệu
Một cách tốt để tránh chia sẻ dữ liệu là tránh chia sẻ dữ liệu ngay từ đầu. Trong một số ngồi-
uation có thể sao chép các đối tượng và coi chúng là chỉ đọc. Trong các trường hợp khác, nó có thể là
có thể sao chép các đối tượng, thu thập kết quả từ nhiều chuỗi trong các bản sao này và sau đó
hợp nhất các kết quả trong một chủ đề duy nhất.
5. [PPP]
6. Xem “Ví dụ về Máy khách / Máy chủ” trên trang 317.
7. [PRAG].
www.it-ebooks.info

Trang 213
182
Chương 13: Đồng thời
Nếu có một cách dễ dàng để tránh chia sẻ các đối tượng, mã kết quả sẽ ít có khả năng
để gây ra vấn đề. Bạn có thể lo lắng về chi phí của tất cả việc tạo thêm đối tượng. Nó là
đáng để thử nghiệm để tìm hiểu xem điều này có thực sự là một vấn đề hay không. Tuy nhiên, nếu sử dụng bản sao
của
các đối tượng cho phép mã tránh đồng bộ hóa, việc tiết kiệm trong việc tránh khóa nội tại sẽ
có thể bù đắp cho chi phí tạo và thu gom rác bổ sung.
Hệ quả: Chủ đề càng độc lập càng tốt
Cân nhắc viết mã luồng của bạn để mỗi luồng tồn tại trong thế giới riêng của nó, chia sẻ
không có dữ liệu với bất kỳ chủ đề nào khác. Mỗi luồng xử lý một yêu cầu của khách hàng, với tất cả
dữ liệu bắt buộc đến từ nguồn không được chia sẻ và được lưu trữ dưới dạng các biến cục bộ. Điều này làm cho
mỗi chuỗi hoạt động như thể nó là chuỗi duy nhất trên thế giới và không có
yêu cầu đồng bộ hóa.
Ví dụ: các lớp mà lớp con từ HttpServlet nhận tất cả thông tin của chúng
dưới dạng các tham số được truyền vào phương thức doGet và doPost . Điều này làm cho mỗi Servlet hành động
như thể nó có máy riêng. Miễn là mã trong Servlet chỉ sử dụng các biến cục bộ,
không có khả năng Servlet sẽ gây ra sự cố đồng bộ hóa. Tất nhiên,
hầu hết các ứng dụng sử dụng Servlet cuối cùng chạy vào các tài nguyên được chia sẻ như cơ sở dữ liệu
kết nối.
Khuyến nghị : Cố gắng phân vùng dữ liệu thành các tập con độc lập hơn mức có thể
được vận hành bởi các luồng độc lập, có thể trong các bộ xử lý khác nhau.
Biết thư viện của bạn
Java 5 cung cấp nhiều cải tiến để phát triển đồng thời so với các phiên bản trước. Đó
là một số điều cần xem xét khi viết mã luồng trong Java 5:
• Sử dụng các bộ sưu tập an toàn theo chuỗi được cung cấp.
• Sử dụng khung thực thi để thực thi các tác vụ không liên quan.
• Sử dụng các giải pháp không chặn khi có thể.
• Một số lớp thư viện không an toàn cho luồng.
Bộ sưu tập an toàn theo chuỗi
Khi Java còn trẻ, Doug Lea đã viết cuốn sách 8 Lập trình đồng thời trong
Java . Cùng với cuốn sách, ông đã phát triển một số bộ sưu tập an toàn theo chuỗi, sau này
đã trở thành một phần của JDK trong gói java.util.concurrent . Các bộ sưu tập trong gói đó-
độ tuổi an toàn cho các tình huống đa luồng và chúng hoạt động tốt. Trên thực tế
8. [Lea99].
www.it-ebooks.info

Trang 214
183
Biết các mô hình thực thi của bạn
Triển khai ConcurrentHashMap hoạt động tốt hơn HashMap trong hầu hết các tình huống. Nó
cũng cho phép đọc và ghi đồng thời đồng thời và nó có các phương pháp hỗ trợ
các hoạt động tổng hợp thông thường không an toàn cho luồng. Nếu Java 5 là triển khai-
môi trường cố vấn, bắt đầu với ConcurrentHashMap .
Có một số loại lớp khác được thêm vào để hỗ trợ tính đồng thời nâng cao
thiết kế. Đây là vài ví dụ:
Khuyến nghị : Xem lại các lớp học có sẵn cho bạn. Trong trường hợp của Java, trở thành
quen thuộc với java.util.concurrent, java.util.concurrent.atomic, java.util.concurrent.locks.
Biết các mô hình thực thi của bạn
Có một số cách khác nhau để phân vùng hành vi trong một ứng dụng đồng thời. Để loại bỏ
cuss chúng chúng ta cần hiểu một số định nghĩa cơ bản.
Với những định nghĩa này, bây giờ chúng ta có thể thảo luận về các mô hình thực thi khác nhau được sử dụng trong
lập trình đồng thời.
ReentrantLock
Một khóa có thể được mua trong một phương pháp và được phát hành trong một phương pháp khác.
Semaphore
Một triển khai của semaphore cổ điển, một khóa có số đếm.
CountDownLatch
Một khóa chờ một số sự kiện trước khi giải phóng tất cả
chủ đề chờ đợi trên đó. Điều này cho phép tất cả các chủ đề có cơ hội công bằng
bắt đầu vào cùng một thời điểm.
Tài nguyên ràng buộc
Tài nguyên có kích thước hoặc số lượng cố định được sử dụng trong môi trường đồng thời
cố vấn. Ví dụ bao gồm kết nối cơ sở dữ liệu và đọc /
ghi bộ đệm.
Loại trừ lẫn nhau
Chỉ một chuỗi có thể truy cập dữ liệu được chia sẻ hoặc tài nguyên được chia sẻ tại một
thời gian.
Chết đói
Một chủ đề hoặc một nhóm các chủ đề bị cấm tiếp tục
trong một thời gian dài quá mức hoặc mãi mãi. Ví dụ, luôn luôn để-
ting các luồng chạy nhanh thông qua đầu tiên có thể bị đói khi chạy lâu hơn-
ning luồng nếu không có kết thúc cho các luồng chạy nhanh.
Bế tắc
Hai hoặc nhiều chủ đề chờ nhau kết thúc. Mỗi chủ đề
có một tài nguyên mà luồng khác yêu cầu và cả hai đều không thể hoàn thành
cho đến khi nó nhận được tài nguyên khác.
Livelock
Các chủ đề trong bước khóa, mỗi người cố gắng làm việc nhưng lại tìm thấy một công việc khác
"Trên đường đi." Do cộng hưởng, các chuỗi tiếp tục cố gắng
đạt được tiến bộ nhưng không thể trong một thời gian quá dài—
hoặc mãi mãi.
www.it-ebooks.info

Trang 215
184
Chương 13: Đồng thời
Nhà sản xuất-Người tiêu dùng 9
Một hoặc nhiều luồng trình sản xuất tạo ra một số công việc và đặt nó vào bộ đệm hoặc hàng đợi. Một hoặc
nhiều chủ đề người tiêu dùng có được công việc đó từ hàng đợi và hoàn thành nó. Hàng đợi
giữa người sản xuất và người tiêu dùng là một nguồn lực ràng buộc . Điều này có nghĩa là nhà sản xuất phải
đợi không gian trống trong hàng đợi trước khi viết và người tiêu dùng phải đợi cho đến khi có
thứ gì đó trong hàng đợi để tiêu thụ. Phối hợp giữa người sản xuất và người tiêu dùng
thông qua hàng đợi liên quan đến người sản xuất và người tiêu dùng báo hiệu cho nhau. Các nhà sản xuất viết
vào hàng đợi và báo hiệu rằng hàng đợi không còn trống. Người tiêu dùng đọc từ hàng đợi
và báo hiệu rằng hàng đợi không còn đầy nữa. Cả hai đều có khả năng chờ được thông báo khi họ
có thể tiếp tục.
Người đọc-Nhà văn 10
Khi bạn có một tài nguyên được chia sẻ chủ yếu đóng vai trò là nguồn thông tin để đọc-
ers, nhưng đôi khi được cập nhật bởi các nhà văn, thông lượng là một vấn đề. Nhấn mạnh
thông lượng có thể gây ra đói và tích lũy thông tin cũ. Cho phép
cập nhật có thể ảnh hưởng đến thông lượng. Phối hợp người đọc để họ không đọc cái gì đó
nhà văn đang cập nhật và ngược lại là một hành động cân bằng khó khăn. Các nhà văn có xu hướng chặn nhiều lượt
đọc-
trong một thời gian dài, do đó gây ra các vấn đề về thông lượng.
Thách thức là cân bằng nhu cầu của cả người đọc và người viết để đáp ứng đúng
hoạt động, cung cấp thông lượng hợp lý và tránh chết đói. Một chiến lược đơn giản
khiến người viết phải đợi cho đến khi không có độc giả trước khi cho phép người viết thực hiện
cập nhật. Tuy nhiên, nếu có người đọc liên tục, người viết sẽ bị bỏ đói. Mặt khác
tay, nếu thường xuyên có người viết và họ được ưu tiên, thông lượng sẽ bị ảnh hưởng. Tìm thấy-
sự cân bằng đó và tránh các vấn đề cập nhật đồng thời là những gì vấn đề giải quyết.
Triết gia ăn uống 11
Hãy tưởng tượng một số triết gia đang ngồi quanh một chiếc bàn tròn. Một cái nĩa được đặt vào
trái của mỗi triết gia. Có một bát mì Ý lớn ở giữa bàn. Các
các triết gia dành thời gian để suy nghĩ trừ khi họ thấy đói. Khi đói, họ chọn
lên dĩa ở hai bên và ăn. Một triết gia không thể ăn trừ khi anh ta đang cầm
hai cái nĩa. Nếu nhà triết học ở bên phải hoặc bên trái của anh ta đã sử dụng một trong những cái nĩa, anh ta
cần, anh ta phải đợi cho đến khi nhà triết học đó ăn xong và đặt nĩa xuống.
Khi một nhà triết học ăn, anh ta đặt cả hai chiếc nĩa của mình xuống bàn và đợi cho đến khi anh ta
lại đói.
Thay thế các nhà triết học bằng các luồng và nhánh bằng các tài nguyên và vấn đề này là
dành cho nhiều ứng dụng doanh nghiệp trong đó các quy trình cạnh tranh về tài nguyên. Trừ khi cẩn thận-
được thiết kế đầy đủ, các hệ thống cạnh tranh theo cách này có thể gặp phải tình trạng bế tắc, sống động,
thông lượng và suy giảm hiệu quả.
9. http://en.wikipedia.org/wiki/Productioner-consumer
10. http://en.wikipedia.org/wiki/Readers-writers_problem
11. http://en.wikipedia.org/wiki/Dining_phiosystemhers_problem
www.it-ebooks.info

Trang 216
185
Giữ cho các phần được đồng bộ hóa nhỏ
Hầu hết các vấn đề đồng thời mà bạn có thể gặp phải sẽ là một số biến thể của những
ba vấn đề. Nghiên cứu các thuật toán này và viết các giải pháp bằng cách sử dụng chúng của riêng bạn để
rằng khi bạn gặp phải các vấn đề đồng thời, bạn sẽ sẵn sàng hơn để giải quyết
vấn đề.
Khuyến nghị : Tìm hiểu các thuật toán cơ bản này và hiểu các giải pháp của chúng.
Hãy coi chừng sự phụ thuộc giữa các phương thức được đồng bộ hóa
Sự phụ thuộc giữa các phương thức được đồng bộ hóa gây ra các lỗi nhỏ trong mã đồng thời. Các
Ngôn ngữ Java có khái niệm đồng bộ hóa , bảo vệ một phương thức riêng lẻ. Làm sao-
bao giờ hết, nếu có nhiều hơn một phương thức được đồng bộ hóa trên cùng một lớp được chia sẻ, thì
hệ thống có thể được viết sai. 12
Khuyến nghị : Tránh sử dụng nhiều hơn một phương pháp trên một đối tượng được chia sẻ.
Sẽ có lúc bạn phải sử dụng nhiều phương thức trên một đối tượng dùng chung.
Trong trường hợp này, có ba cách để làm cho mã chính xác:
• Khóa dựa trên máy khách —Làm máy khách khóa máy chủ trước khi gọi máy đầu tiên
và đảm bảo mức độ của khóa bao gồm mã gọi phương thức cuối cùng.
• Khóa dựa trên máy chủ —Trong khi máy chủ tạo một phương thức khóa máy chủ, các cuộc gọi
tất cả các phương pháp, và sau đó mở khóa. Yêu cầu khách hàng gọi phương thức mới.
• Máy chủ thích ứng —tạo một trung gian thực hiện việc khóa. Đây là một kỳ thi-
tất cả các khóa dựa trên máy chủ, trong đó máy chủ gốc không thể thay đổi.
Giữ cho các phần được đồng bộ hóa nhỏ
Các đồng bộ từ khóa giới thiệu một khóa. Tất cả các phần mã được bảo vệ bởi
cùng một khóa được đảm bảo chỉ có một luồng thực thi qua chúng tại bất kỳ
thời gian. Khóa rất tốn kém vì chúng tạo ra sự chậm trễ và thêm chi phí. Vì vậy, chúng tôi không
muốn phân phối mã của chúng tôi với các câu lệnh được đồng bộ hóa . Mặt khác, phần quan trọng
tions 13 phải được bảo vệ. Vì vậy, chúng tôi muốn thiết kế mã của mình với ít phần quan trọng như
khả thi.
Một số lập trình viên ngây thơ cố gắng đạt được điều này bằng cách làm cho các phần quan trọng của họ rất
lớn. Tuy nhiên, việc mở rộng đồng bộ hóa ngoài phần quan trọng tối thiểu sẽ làm tăng
tranh chấp và làm giảm hiệu suất. 14
Đề xuất : Giữ các phần được đồng bộ hóa của bạn càng nhỏ càng tốt.
12. Xem “Sự phụ thuộc giữa các phương pháp có thể phá vỡ mã đồng thời” trên trang 329.
13. Phần quan trọng là bất kỳ phần mã nào phải được bảo vệ khỏi việc sử dụng đồng thời để chương trình hoạt động chính xác.
14. Xem “Tăng thông lượng” trên trang 333.
www.it-ebooks.info

Trang 217
186
Chương 13: Đồng thời
Viết đúng mã tắt máy rất khó
Viết một hệ thống có nghĩa là tồn tại và chạy mãi mãi khác với việc viết một số-
thứ hoạt động trong một thời gian và sau đó tắt một cách duyên dáng.
Việc tắt máy có thể khó chính xác. Các vấn đề thường gặp liên quan đến bế tắc, 15
với các chủ đề đang chờ một tín hiệu để tiếp tục mà không bao giờ đến.
Ví dụ: hãy tưởng tượng một hệ thống với một chuỗi mẹ tạo ra một số chuỗi con
và sau đó đợi tất cả chúng hoàn thành trước khi nó giải phóng tài nguyên và tắt. Chuyện gì xảy ra nếu
một trong những chủ đề sinh ra bị bế tắc? Phụ huynh sẽ đợi mãi mãi, và hệ thống
sẽ không bao giờ tắt.
Hoặc xem xét một hệ thống tương tự đã được hướng dẫn để tắt. Cha mẹ kể tất cả
những đứa trẻ sinh ra để từ bỏ nhiệm vụ của họ và hoàn thành. Nhưng nếu hai trong số những đứa trẻ
đã hoạt động như một cặp nhà sản xuất / người tiêu dùng. Giả sử nhà sản xuất nhận được tín hiệu
từ chính và nhanh chóng tắt. Người tiêu dùng có thể đã mong đợi một
sage từ nhà sản xuất và bị chặn ở trạng thái không thể nhận được dấu hiệu tắt máy
nal. Nó có thể bị kẹt khi chờ nhà sản xuất và không bao giờ kết thúc, ngăn cản phụ huynh
hoàn thiện là tốt.
Những tình huống như thế này không phải là hiếm. Vì vậy, nếu bạn phải viết mã đồng thời
liên quan đến việc tắt một cách duyên dáng, hy vọng sẽ dành nhiều thời gian của bạn để tắt-
xuống để xảy ra một cách chính xác.
Khuyến nghị : Hãy suy nghĩ về việc đóng cửa sớm và làm cho nó hoạt động sớm. Nó sẽ
mất nhiều thời gian hơn bạn mong đợi. Xem lại các thuật toán hiện có vì điều này có thể khó hơn
hơn bạn nghĩ.
Kiểm tra mã luồng
Việc chứng minh mã đó là đúng là không thực tế. Thử nghiệm không đảm bảo tính đúng đắn. Làm sao-
bao giờ hết, thử nghiệm tốt có thể giảm thiểu rủi ro. Tất cả đều đúng trong một giải pháp đơn luồng. Ngay sau
vì có hai hoặc nhiều chuỗi sử dụng cùng một mã và làm việc với dữ liệu được chia sẻ, mọi thứ
phức tạp hơn đáng kể.
Đề xuất : Viết các bài kiểm tra có khả năng làm lộ ra các vấn đề và sau đó
chạy chúng thường xuyên, với các cấu hình lập trình và cấu hình hệ thống khác nhau
và tải. Nếu các thử nghiệm không thành công, hãy theo dõi lỗi đó. Đừng bỏ qua một thất bại chỉ vì
kiểm tra vượt qua trong lần chạy tiếp theo.
Đó là rất nhiều điều cần xem xét. Đây là một số chi tiết nhỏ hơn
khuyến nghị:
• Xử lý các lỗi giả như các vấn đề phân luồng ứng viên.
• Làm cho mã chưa đọc của bạn hoạt động trước.
15. Xem “Bế tắc” trên trang 335.
www.it-ebooks.info

Trang 218
187
Kiểm tra mã luồng
• Làm cho mã luồng của bạn có thể cắm được.
• Làm cho mã luồng của bạn có thể điều chỉnh được.
• Chạy với nhiều luồng hơn bộ xử lý.
• Chạy trên các nền tảng khác nhau.
• Công cụ mã của bạn để thử và buộc các lỗi.
Xử lý các lỗi giả mạo là vấn đề phân luồng ứng viên
Mã phân luồng gây ra những thứ không thành công mà "đơn giản là không thể thất bại." Hầu hết các nhà phát triển
không có
cảm giác trực quan về cách luồng tương tác với mã khác (bao gồm cả tác giả). Lỗi trong
mã luồng có thể hiển thị các triệu chứng của chúng một lần trong một nghìn hoặc một triệu lần thực thi.
Nỗ lực lặp lại hệ thống có thể gây khó chịu. Điều này thường khiến các nhà phát triển viết tắt
sự thất bại như một tia vũ trụ, một trục trặc phần cứng hoặc một số loại "một lần" khác. Nó là tốt nhất để
giả định rằng một lần không tồn tại. Các "một lần" này bị bỏ qua càng lâu, thì càng có nhiều mã
được xây dựng trên phương pháp tiếp cận có thể bị lỗi.
Khuyến nghị : Đừng bỏ qua lỗi hệ thống như một lần duy nhất.
Lấy mã chưa đọc của bạn làm việc trước
Điều này có vẻ hiển nhiên, nhưng củng cố nó không có hại gì. Đảm bảo mã hoạt động bên ngoài
sử dụng nó trong chủ đề. Nói chung, điều này có nghĩa là tạo POJO được gọi bởi các chuỗi của bạn.
Các POJO không nhận biết luồng, và do đó có thể được kiểm tra bên ngoài môi trường luồng luồng
ronment. Bạn có thể đặt càng nhiều hệ thống của mình trong các POJO như vậy thì càng tốt.
Khuyến nghị : Đừng cố gắng đuổi theo lỗi không đọc được và lỗi phân luồng
đồng thời. Đảm bảo mã của bạn hoạt động bên ngoài chuỗi .
Làm cho mã luồng của bạn trở nên dễ hiểu
Viết mã hỗ trợ đồng thời để nó có thể chạy trong một số cấu hình:
• Một luồng, nhiều luồng, thay đổi khi nó thực thi
• Mã luồng tương tác với thứ gì đó có thể là mã thực hoặc mã thử nghiệm kép.
• Thực hiện với các bộ đôi thử nghiệm chạy nhanh, chậm, thay đổi.
• Định cấu hình các bài kiểm tra để chúng có thể chạy trong một số lần lặp lại.
Đề xuất : Làm cho mã dựa trên chuỗi của bạn đặc biệt có thể cắm được để bạn
có thể chạy nó trong nhiều cấu hình khác nhau.
Làm cho mã luồng của bạn có thể điều chỉnh được
Để có được số dư phù hợp của các chủ đề thường yêu cầu thử một lỗi. Ngay từ sớm, hãy tìm cách
thời gian hiệu suất của hệ thống của bạn theo các cấu hình khác nhau. Cho phép số lượng
www.it-ebooks.info

Trang 219
188
Chương 13: Đồng thời
để dễ dàng điều chỉnh. Cân nhắc cho phép nó thay đổi trong khi hệ thống đang chạy.
Cân nhắc cho phép tự điều chỉnh dựa trên thông lượng và việc sử dụng hệ thống.
Chạy với nhiều luồng hơn bộ xử lý
Mọi thứ xảy ra khi hệ thống chuyển đổi giữa các tác vụ. Để khuyến khích hoán đổi nhiệm vụ, hãy chạy
với nhiều luồng hơn bộ xử lý hoặc lõi. Các nhiệm vụ của bạn trao đổi thường xuyên hơn, thì
có thể bạn sẽ gặp phải mã thiếu phần quan trọng hoặc gây ra bế tắc.
Chạy trên các nền tảng khác nhau
Vào giữa năm 2007, chúng tôi đã phát triển một khóa học về lập trình đồng thời. Khóa học
sự phát triển chủ yếu sau đó của OS X. Lớp học này được trình bày bằng Windows XP
chạy dưới máy ảo. Các bài kiểm tra được viết để chứng minh các điều kiện thất bại không thất bại như miễn phí
trong môi trường XP như chúng đã chạy trên OS X.
Trong mọi trường hợp, mã được kiểm tra được biết là không chính xác. Điều này chỉ củng cố một thực tế
rằng các hệ điều hành khác nhau có các chính sách phân luồng khác nhau, mỗi chính sách tác động đến
việc thực thi mã. Mã đa luồng hoạt động khác nhau trong các môi trường khác nhau. 16
Bạn nên chạy thử nghiệm của mình trong mọi môi trường triển khai tiềm năng.
Đề xuất : Chạy mã luồng của bạn trên tất cả các nền tảng mục tiêu sớm và thường xuyên.
Công cụ mã của bạn để thử và buộc thất bại
Việc ẩn các sai sót trong mã đồng thời là điều bình thường. Các bài kiểm tra đơn giản thường không cho thấy chúng.
Thật vậy, chúng thường ẩn trong quá trình xử lý bình thường. Họ có thể xuất hiện một lần sau mỗi vài
giờ, hoặc ngày, hoặc tuần!
Lý do mà các lỗi phân luồng có thể không thường xuyên, rời rạc và khó lặp lại, đó là
chỉ một số rất ít con đường trong số hàng ngàn con đường có thể có thông qua một
phần nerable thực sự không thành công. Vì vậy, xác suất mà một con đường thất bại được thực hiện có thể là sao-
thấp tí hon. Điều này làm cho việc phát hiện và gỡ lỗi rất khó khăn.
Làm thế nào bạn có thể tăng cơ hội bắt gặp những trường hợp hiếm gặp như vậy? Bạn có thể
công cụ mã của bạn và buộc nó chạy trong các chuỗi khác nhau bằng cách thêm lệnh gọi vào các phương thức
như Object.wait () , Object.sleep () , Object.yield () và Object.priasty () .
Mỗi phương pháp này có thể ảnh hưởng đến thứ tự thực hiện, do đó làm tăng tỷ lệ cược
phát hiện một lỗ hổng. Sẽ tốt hơn khi mã bị hỏng không thành công càng sớm và thường xuyên càng tốt.
Có hai tùy chọn cho thiết bị đo mã:
• Mã hóa bằng tay
• Tự động
16. Bạn có biết rằng mô hình phân luồng trong Java không đảm bảo phân luồng trước? Ưu tiên hỗ trợ Modern OS
phân luồng, vì vậy bạn nhận được điều đó "miễn phí." Mặc dù vậy, nó không được đảm bảo bởi JVM.
www.it-ebooks.info

Trang 220
189
Kiểm tra mã luồng
Mã hóa bằng tay
Bạn có thể chèn các cuộc gọi chờ () , ngủ () , nhường () và ưu tiên () vào mã của mình bằng tay. Nó
có thể chỉ là điều cần làm khi bạn đang thử nghiệm một đoạn mã đặc biệt khó khăn.
Đây là một ví dụ về việc đó:
được đồng bộ hóa công khai String nextUrlOrNull () {
if (hasNext ()) {
String url = urlGenerator.next ();
Thread.yield (); // được chèn để thử nghiệm.
updateHasNext ();
url trả về;
}
trả về null;
}
Lệnh gọi để nhường () được chèn sẽ thay đổi các đường dẫn thực thi được thực hiện bởi mã và
có thể khiến mã bị lỗi ở nơi mà nó không bị lỗi trước đó. Nếu mã không bị phá vỡ, nó là
không phải vì bạn đã thêm lệnh gọi nhường () . 17 Đúng hơn, mã của bạn đã bị hỏng và điều này đơn giản
đã làm cho sự thất bại trở nên rõ ràng.
Có nhiều vấn đề với cách tiếp cận này:
• Bạn phải tự tìm những nơi thích hợp để thực hiện việc này.
• Làm thế nào để bạn biết nơi để thực hiện cuộc gọi và loại cuộc gọi để sử dụng?
• Để mã như vậy trong môi trường sản xuất làm chậm mã một cách không cần thiết.
• Đó là một cách tiếp cận súng ngắn. Bạn có thể có hoặc không tìm thấy sai sót. Thật vậy, tỷ lệ cược không phải với
bạn.
Những gì chúng ta cần là một cách để thực hiện điều này trong quá trình thử nghiệm nhưng không phải trong quá
trình sản xuất. Chúng ta cũng cần
dễ dàng kết hợp các cấu hình giữa các lần chạy khác nhau, dẫn đến tăng cơ hội
tìm lỗi trong tổng hợp.
Rõ ràng, nếu chúng ta chia hệ thống của mình thành các POJO không biết gì về luồng và
các lớp kiểm soát luồng, sẽ dễ dàng tìm thấy các vị trí thích hợp để thiết bị
mật mã. Hơn nữa, chúng tôi có thể tạo nhiều đồ gá kiểm tra khác nhau gọi các POJO trong
các chế độ gọi đi ngủ , năng suất , v.v. khác nhau.
Tự động
Bạn có thể sử dụng các công cụ như Khung định hướng theo khía cạnh, CGLIB hoặc ASM để lập trình-
ically công cụ mã của bạn. Ví dụ: bạn có thể sử dụng một lớp với một phương thức:
lớp công khai ThreadJigglePoint {
public static void jiggle () {
}
}
17. Đây không phải là trường hợp nghiêm trọng. Vì JVM không đảm bảo phân luồng trước, một thuật toán cụ thể có thể luôn
hoạt động trên hệ điều hành không chặn trước các luồng. Điều ngược lại cũng có thể xảy ra nhưng vì những lý do khác nhau.
www.it-ebooks.info

Trang 221
190
Chương 13: Đồng thời
Bạn có thể thêm các cuộc gọi đến điều này ở nhiều nơi khác nhau trong mã của mình:
được đồng bộ hóa công khai String nextUrlOrNull () {
if (hasNext ()) {
ThreadJiglePoint.jiggle ();
String url = urlGenerator.next ();
ThreadJiglePoint.jiggle ();
updateHasNext ();
ThreadJiglePoint.jiggle ();
url trả về;
}
trả về null;
}
Bây giờ, bạn sử dụng một khía cạnh đơn giản để chọn ngẫu nhiên trong số không làm gì, ngủ hoặc
năng suất.
Hoặc tưởng tượng rằng lớp ThreadJigglePoint có hai cách triển khai. Bước đầu tiên-
ments lắc lư để không làm gì cả và được sử dụng trong sản xuất. Thứ hai tạo ra một ngẫu nhiên
số để lựa chọn giữa ngủ, năng suất, hoặc chỉ ngủ. Nếu bạn chạy thử nghiệm của mình
hàng nghìn lần với sự lắc lư ngẫu nhiên, bạn có thể giải quyết tận gốc một số sai sót. Nếu các bài kiểm tra vượt qua,
tại
ít nhất bạn có thể nói rằng bạn đã thực hiện thẩm định. Mặc dù hơi đơn giản, đây có thể là một
tùy chọn sonable thay cho một công cụ phức tạp hơn.
Có một công cụ được gọi là ConTest, 18 do IBM phát triển có thể thực hiện một công việc tương tự, nhưng nó
làm như vậy với một chút tinh vi hơn.
Vấn đề là phải lắc mã để các chuỗi chạy trong các chuỗi khác nhau ở các
lần. Sự kết hợp giữa các bài kiểm tra được viết tốt và lắc lư có thể làm tăng đáng kể
cơ hội tìm ra lỗi.
Khuyến nghị : Sử dụng chiến lược lắc lư để loại bỏ lỗi.
Phần kết luận
Rất khó để có được mã đồng thời. Mã dễ làm theo có thể trở thành đêm-
lỗi khi nhiều luồng và dữ liệu được chia sẻ vào hỗn hợp. Nếu bạn đang phải đối mặt với
khi nhập mã đồng thời, bạn cần phải viết mã rõ ràng với sự chặt chẽ nếu không sẽ phải đối mặt với
thất bại không thường xuyên.
Trước hết, hãy tuân theo Nguyên tắc Trách nhiệm Đơn lẻ. Phá vỡ hệ thống của bạn thành
POJO tách mã nhận biết luồng khỏi mã không biết luồng. Đảm bảo khi bạn
đang kiểm tra mã nhận biết luồng của bạn, bạn chỉ đang kiểm tra nó và không có gì khác. Những gợi ý
rằng mã nhận biết luồng của bạn phải nhỏ và tập trung.
Biết các nguồn có thể xảy ra vấn đề đồng thời: nhiều luồng hoạt động trên
dữ liệu được chia sẻ hoặc sử dụng nhóm tài nguyên chung. Các trường hợp ranh giới, chẳng hạn như tắt máy
một cách sạch sẽ hoặc kết thúc quá trình lặp lại của một vòng lặp, có thể đặc biệt khó khăn.
18. http://www.alphaworks.ibm.com/tech/contest
www.it-ebooks.info

Trang 222
191
Thư mục
Tìm hiểu thư viện của bạn và biết các thuật toán cơ bản. Hiểu cách một số
các tính năng do thư viện cung cấp hỗ trợ giải quyết các vấn đề tương tự như
các thuật toán.
Tìm hiểu cách tìm các vùng mã phải được khóa và khóa chúng. Không khóa
vùng mã không cần khóa. Tránh gọi một phần bị khóa từ
khác. Điều này đòi hỏi sự hiểu biết sâu sắc về việc một thứ gì đó được hay không được chia sẻ. Giữ
lượng đối tượng được chia sẻ và phạm vi chia sẻ càng thu hẹp càng tốt. Thay đổi
thiết kế của các đối tượng với dữ liệu được chia sẻ để phù hợp với khách hàng thay vì ép buộc khách hàng
để quản lý trạng thái được chia sẻ.
Các vấn đề sẽ xuất hiện. Những cái không thu hoạch sớm thường được viết tắt là một-
thời gian xảy ra. Những cái gọi là một lần này thường chỉ xảy ra dưới tải hoặc dường như-
thời gian ngẫu nhiên. Do đó, bạn cần có thể chạy mã liên quan đến chuỗi của mình trong
nhiều cấu hình trên nhiều nền tảng lặp đi lặp lại và liên tục. Khả năng kiểm tra, mà
tự nhiên xuất phát từ việc tuân theo Ba Luật TDD, ngụ ý một số cấp độ khả năng của trình cắm,
cung cấp sự hỗ trợ cần thiết để chạy mã trong nhiều loại cấu hình hơn.
Bạn sẽ cải thiện đáng kể cơ hội tìm thấy mã sai nếu bạn dành thời gian
để thiết lập mã của bạn. Bạn có thể làm như vậy bằng tay hoặc sử dụng một số loại tự động
Công nghệ. Đầu tư vào điều này sớm. Bạn muốn chạy mã dựa trên chuỗi của mình miễn là
có thể trước khi bạn đưa nó vào sản xuất.
Nếu bạn thực hiện một cách tiếp cận rõ ràng, cơ hội bạn làm đúng sẽ tăng lên đáng kể.
Thư mục
[Lea99]: Lập trình đồng thời trong Java: Nguyên tắc và mẫu thiết kế , 2d. ed.,
Doug Lea, Prentice Hall, 1999.
[PPP]: Phát triển phần mềm Agile: Nguyên tắc, Mẫu và Thực tiễn , Robert C. Martin,
Prentice Hall, 2002.
[PRAG]: Lập trình viên thực dụng , Andrew Hunt, Dave Thomas, Addison-Wesley,
2000.
www.it-ebooks.info

Trang 223
Trang này cố ý để trống
www.it-ebooks.info

Trang 224
193

14
Sàng lọc thành công
Nghiên cứu điển hình về trình phân tích cú pháp đối số dòng lệnh
Chương này là một nghiên cứu điển hình trong quá trình sàng lọc liên tiếp. Bạn sẽ thấy một mô-đun đã bắt đầu
tốt nhưng không mở rộng. Sau đó, bạn sẽ thấy mô-đun đã được cấu trúc lại và làm sạch như thế nào.
Hầu hết chúng ta thỉnh thoảng phải phân tích cú pháp các đối số dòng lệnh. Nếu chúng ta
không có một tiện ích thuận tiện, sau đó chúng tôi chỉ cần đi bộ dãy các chuỗi được chuyển
vào chức năng chính . Có một số tiện ích tốt có sẵn từ nhiều nguồn khác nhau,
www.it-ebooks.info

Trang 225
194
Chương 14: Sàng lọc thành công
nhưng không ai trong số họ làm chính xác những gì tôi muốn. Vì vậy, tất nhiên, tôi quyết định viết của riêng
mình. Tôi gọi
nó: Args .
Args rất đơn giản để sử dụng. Bạn chỉ cần xây dựng lớp Args với đầu vào là
ments và một chuỗi định dạng, rồi truy vấn phiên bản Args để biết các giá trị của đối số-
ments. Hãy xem xét ví dụ đơn giản sau:
Bạn có thể thấy điều này đơn giản như thế nào. Chúng tôi chỉ tạo một thể hiện của lớp Args với hai
thông số. Tham số đầu tiên là định dạng hoặc lược đồ, chuỗi: "l, p #, d * . " Nó xác định ba
đối số dòng lệnh. Đầu tiên, –l , là một đối số boolean. Thứ hai, -p , là một số nguyên
tranh luận. Thứ ba, -d , là một đối số chuỗi. Tham số thứ hai cho hàm tạo Args
chỉ đơn giản là mảng đối số dòng lệnh được truyền vào main .
Nếu hàm tạo trả về mà không ném một ArgsException , thì
dòng lệnh đã được phân tích cú pháp và cá thể Args đã sẵn sàng để được truy vấn. Các phương pháp như
getBoolean , getInteger và getString cho phép chúng ta truy cập các giá trị của các đối số bằng
tên của họ.
Nếu có sự cố, trong chuỗi định dạng hoặc trong các đối số dòng lệnh
chính nó, một ArgsException sẽ được ném ra. Một mô tả thuận tiện về những gì đã xảy ra
sai có thể được truy xuất từ phương thức errorMessage của ngoại lệ.
Thực hiện Args
Liệt kê 14-2 là sự triển khai của lớp Args . Hãy đọc nó rất cẩn thận. tôi đã làm việc
chăm chỉ về phong cách và cấu trúc và hy vọng nó đáng để mô phỏng.
Liệt kê 14-1
Sử dụng Args đơn giản
public static void main (String [] args) {
thử {
Args arg = new Args ("l, p #, d *", args);
boolean logging = arg.getBoolean ('l');
int port = arg.getInt ('p');
Thư mục chuỗi = arg.getString ('d');
executeApplication (ghi nhật ký, cổng, thư mục);
} catch (ArgsException e) {
System.out.printf ("Lỗi đối số:% s \ n", e.errorMessage ());
}
}
Liệt kê 14-2
Args.java
gói com.objectmentor.utilities.args;
nhập tĩnh com.objectmentor.utilities.args.ArgsException.ErrorCode. *;
nhập java.util. *;
public class Args {
bản đồ tư nhân <Character, ArgumentMarshaler> điều chế;
www.it-ebooks.info

Trang 226
195
Thực hiện Args
private Set <Character> argsFound;
private ListIterator <String> currentArgument;
public Args (String schema, String [] args) ném ArgsException {
marshalers = new HashMap <Character, ArgumentMarshaler> ();
argsFound = new HashSet <Ký tự> ();
parseSchema (giản đồ);
parseArgumentStrings (Arrays.asList (args));
}
private void parseSchema (String schema) ném ArgsException {
for (Phần tử chuỗi: schema.split (","))
if (element.length ()> 0)
parseSchemaElement (element.trim ());
}
private void parseSchemaElement (String element) ném ArgsException {
char elementId = element.charAt (0);
String elementTail = element.substring (1);
validateSchemaElementId (elementId);
if (elementTail.length () == 0)
marshalers.put (elementId, new BooleanArgumentMarshaler ());
else if (elementTail.equals ("*"))
marshalers.put (elementId, new StringArgumentMarshaler ());
else if (elementTail.equals ("#"))
marshalers.put (elementId, new IntegerArgumentMarshaler ());
else if (elementTail.equals ("##"))
marshalers.put (elementId, new DoubleArgumentMarshaler ());
else if (elementTail.equals ("[*]"))
marshalers.put (elementId, new StringArrayArgumentMarshaler ());
khác
ném ArgsException mới (INVALID_ARGUMENT_FORMAT, elementId, elementTail);
}
private void validateSchemaElementId (char elementId) ném ArgsException {
if (! Character.isLetter (elementId))
ném ArgsException mới (INVALID_ARGUMENT_NAME, elementId, null);
}
private void parseArgumentStrings (List <String> argsList) ném ArgsException
{
for (currentArgument = argsList.listIterator (); currentArgument.hasNext ();)
{
String argString = currentArgument.next ();
if (argString.startsWith ("-")) {
parseArgumentCharacters (argString.substring (1));
} khác {
currentArgument.previous ();
phá vỡ;
}
}
}
Liệt kê 14-2 (tiếp theo)
Args.java
www.it-ebooks.info

Trang 227
196
Chương 14: Sàng lọc thành công
Lưu ý rằng bạn có thể đọc mã này từ trên xuống dưới mà không cần phải nhảy nhiều
xung quanh hoặc nhìn về phía trước. Một điều bạn có thể đã phải nhìn trước là xác định-
ý nghĩa của ArgumentMarshaler , mà tôi đã cố ý loại bỏ. Sau khi đọc kỹ mã này,
private void parseArgumentCharacters (String argChars) ném ArgsException {
for (int i = 0; i <argChars.length (); i ++)
parseArgumentCharacter (argChars.charAt (i));
}
private void parseArgumentCharacter (char argChar) ném ArgsException {
ArgumentMarshaler m = marshalers.get (argChar);
if (m == null) {
ném ArgsException mới (UNEXPECTED_ARGUMENT, argChar, null);
} khác {
argsFound.add (argChar);
thử {
m.set (currentArgument);
} catch (ArgsException e) {
e.setErrorArgumentId (argChar);
ném e;
}
}
}
public boolean has (char arg) {
trả về argsFound.contains (arg);
}
public int nextArgument () {
trả về currentArgument.nextIndex ();
}
public boolean getBoolean (char arg) {
return BooleanArgumentMarshaler.getValue (marshalers.get (arg));
}
public String getString (char arg) {
return StringArgumentMarshaler.getValue (marshalers.get (arg));
}
public int getInt (char arg) {
return IntegerArgumentMarshaler.getValue (marshalers.get (arg));
}
public double getDouble (char arg) {
return DoubleArgumentMarshaler.getValue (marshalers.get (arg));
}
public String [] getStringArray (char arg) {
return StringArrayArgumentMarshaler.getValue (marshalers.get (arg));
}
}
Liệt kê 14-2 (tiếp theo)
Args.java
www.it-ebooks.info

Trang 228
197
Thực hiện Args
bạn nên hiểu giao diện ArgumentMarshaler là gì và các công cụ phái sinh của nó.
Tôi sẽ hiển thị một vài trong số chúng cho bạn ngay bây giờ (Liệt kê 14-3 đến Liệt kê 14-6).
Liệt kê 14-3
ArgumentMarshaler.java
giao diện công khai ArgumentMarshaler {
void set (Iterator <String> currentArgument) ném ArgsException;
}
Liệt kê 14-4
BooleanArgumentMarshaler.java
public class BooleanArgumentMarshaler triển khai ArgumentMarshaler {
boolean riêng booleanValue = false;
public void set (Iterator <String> currentArgument) ném ArgsException {
booleanValue = true;
}
public static boolean getValue (ArgumentMarshaler am) {
if (am! = null && am instanceof BooleanArgumentMarshaler)
return ((BooleanArgumentMarshaler) am) .booleanValue;
khác
trả về sai;
}
}
Liệt kê 14-5
StringArgumentMarshaler.java
nhập tĩnh com.objectmentor.utilities.args.ArgsException.ErrorCode. *;
public class StringArgumentMarshaler triển khai ArgumentMarshaler {
private String stringValue = "";
public void set (Iterator <String> currentArgument) ném ArgsException {
thử {
stringValue = currentArgument.next ();
} catch (NoSuchElementException e) {
ném ArgsException mới (MISSING_STRING);
}
}
public static String getValue (ArgumentMarshaler am) {
if (am! = null && am instanceof StringArgumentMarshaler)
return ((StringArgumentMarshaler) am) .stringValue;
khác
trở về "";
}
}
www.it-ebooks.info

Trang 229
198
Chương 14: Sàng lọc thành công
Các dẫn xuất ArgumentMarshaler khác chỉ cần sao chép mô hình này để nhân đôi và
Các mảng chuỗi và sẽ làm lộn xộn chương này. Tôi sẽ để chúng cho bạn như một bài tập.
Một chút thông tin khác có thể gây rắc rối cho bạn: định nghĩa mã lỗi
các hằng số. Chúng nằm trong lớp ArgsException (Liệt kê 14-7).
Liệt kê 14-6
IntegerArgumentMarshaler.java
nhập tĩnh com.objectmentor.utilities.args.ArgsException.ErrorCode. *;
public class IntegerArgumentMarshaler triển khai ArgumentMarshaler {
private int intValue = 0;
public void set (Iterator <String> currentArgument) ném ArgsException {
Tham số chuỗi = null;
thử {
tham số = currentArgument.next ();
intValue = Integer.parseInt (tham số);
} catch (NoSuchElementException e) {
ném ArgsException mới (MISSING_INTEGER);
} catch (NumberFormatException e) {
ném ArgsException mới (INVALID_INTEGER, tham số);
}
}
public static int getValue (ArgumentMarshaler am) {
if (am! = null && am instanceof IntegerArgumentMarshaler)
return ((IntegerArgumentMarshaler) am) .intValue;
khác
trả về 0;
}
}
Liệt kê 14-7
ArgsException.java
nhập tĩnh com.objectmentor.utilities.args.ArgsException.ErrorCode. *;
public class ArgsException mở rộng Exception {
char errorArgumentId = '\ 0';
private String errorParameter = null;
private ErrorCode errorCode = OK;
public ArgsException () {}
public ArgsException (String message) {super (message);}
public ArgsException (ErrorCode errorCode) {
this.errorCode = errorCode;
}
public ArgsException (ErrorCode errorCode, String errorParameter) {
this.errorCode = errorCode;
this.errorParameter = errorParameter;
}
www.it-ebooks.info

Trang 230
199
Thực hiện Args
public ArgsException (ErrorCode errorCode,
char errorArgumentId, String errorParameter) {
this.errorCode = errorCode;
this.errorParameter = errorParameter;
this.errorArgumentId = errorArgumentId;
}
public char getErrorArgumentId () {
trả về errorArgumentId;
}
public void setErrorArgumentId (char errorArgumentId) {
this.errorArgumentId = errorArgumentId;
}
public String getErrorParameter () {
trả về errorParameter;
}
public void setErrorParameter (String errorParameter) {
this.errorParameter = errorParameter;
}
public ErrorCode getErrorCode () {
trả về mã lỗi;
}
public void setErrorCode (ErrorCode errorCode) {
this.errorCode = errorCode;
}
public String errorMessage () {
switch (errorCode) {
trường hợp OK:
return "TILT: Không nên đến đây.";
trường hợp UNEXPECTED_ARGUMENT:
return String.format ("Đối số -% c không mong đợi.", errorArgumentId);
trường hợp MISSING_STRING:
return String.format ("Không thể tìm thấy tham số chuỗi cho -% c.",
errorArgumentId);
trường hợp INVALID_INTEGER:
return String.format ("Đối số -% c mong đợi một số nguyên nhưng là '% s'.",
errorArgumentId, errorParameter);
trường hợp MISSING_INTEGER:
return String.format ("Không thể tìm thấy tham số số nguyên cho -% c.",
errorArgumentId);
trường hợp INVALID_DOUBLE:
return String.format ("Đối số -% c mong đợi nhân đôi nhưng là '% s'.",
errorArgumentId, errorParameter);
trường hợp MISSING_DOUBLE:
return String.format ("Không thể tìm thấy tham số kép cho -% c.",
errorArgumentId);
trường hợp INVALID_ARGUMENT_NAME:
return String.format ("'% c' không phải là tên đối số hợp lệ.",
errorArgumentId);
Liệt kê 14-7 (tiếp theo)
ArgsException.java
www.it-ebooks.info

Trang 231
200
Chương 14: Sàng lọc thành công
Điều đáng chú ý là cần bao nhiêu mã để làm rõ các chi tiết của cấu trúc đơn giản này-
cept. Một trong những lý do cho điều này là chúng ta đang sử dụng một ngôn ngữ đặc biệt nhiều chữ. Java,
là một ngôn ngữ được nhập tĩnh, cần rất nhiều từ để đáp ứng kiểu sys-
tem. Trong một ngôn ngữ như Ruby, Python hoặc Smalltalk, chương trình này nhỏ hơn nhiều. 1
Vui lòng đọc mã hơn một lần nữa. Đặc biệt chú ý đến cách mọi thứ
được đặt tên, kích thước của các chức năng và định dạng của mã. Nếu bạn là một người có kinh nghiệm
lập trình viên, bạn có thể có một số câu đố ở đây và ở đó với các phần khác nhau của phong cách hoặc
kết cấu. Tuy nhiên, nhìn chung, tôi hy vọng bạn kết luận rằng chương trình này được viết rất hay và
có cấu trúc sạch sẽ.
Ví dụ: phải rõ ràng là bạn sẽ thêm kiểu đối số mới như thế nào, chẳng hạn như
đối số ngày tháng hoặc đối số số phức và việc bổ sung như vậy sẽ yêu cầu
lượng nỗ lực tầm thường. Tóm lại, nó chỉ đơn giản là yêu cầu một dẫn xuất mới của Argument-
Marshaler , một mới getXXX chức năng, và một tuyên bố trường hợp mới trong parseSchemaElement
chức năng. Cũng có thể có một ArgsException.ErrorCode mới và một lỗi mới
thông điệp.
Tôi đã làm điều này như thế nào?
Hãy để tôi đặt tâm trí của bạn vào phần còn lại. Tôi không chỉ đơn giản viết chương trình này từ đầu đến cuối trong
hình thức hiện tại của nó. Quan trọng hơn, tôi không mong đợi bạn có thể viết sạch và
chương trình thanh lịch trong một lần vượt qua. Nếu chúng ta đã học được bất cứ điều gì trong vài thập kỷ qua,
đó là lập trình là một thủ công nhiều hơn nó là một khoa học. Để viết mã sạch, bạn phải
đầu tiên viết mã bẩn và sau đó làm sạch nó .
Điều này sẽ không gây ngạc nhiên cho bạn. Chúng tôi đã học được sự thật này ở trường lớp khi
giáo viên đã cố gắng (thường là vô ích) để giúp chúng tôi viết những bản nháp thô cho các sáng tác của chúng
tôi. Các
họ nói với chúng tôi rằng chúng tôi nên viết một bản nháp thô, sau đó là một bản nháp thứ hai, rồi đến
eral các bản thảo tiếp theo cho đến khi chúng tôi có phiên bản cuối cùng. Viết sáng tác sạch, họ
đã cố gắng nói với chúng tôi, là một vấn đề của sự sàng lọc liên tiếp.
trường hợp INVALID_ARGUMENT_FORMAT:
return String.format ("'% s' không phải là định dạng đối số hợp lệ.",
errorParameter);
}
trở về "";
}
public enum ErrorCode {
Được, INVALID_ARGUMENT_FORMAT, UNEXPECTED_ARGUMENT, INVALID_ARGUMENT_NAME,
MISSING_STRING,
MISSING_INTEGER, INVALID_INTEGER,
MISSING_DOUBLE, INVALID_DOUBLE}
}
1. Gần đây tôi đã viết lại mô-đun này bằng Ruby. Nó có kích thước bằng 1/7 và có cấu trúc tốt hơn một cách tinh tế.
Liệt kê 14-7 (tiếp theo)
ArgsException.java
www.it-ebooks.info

Trang 232
201
Args: Bản nháp thô
Hầu hết các lập trình viên năm nhất (như hầu hết các học sinh trung học) không tuân theo lời khuyên này
ticularly tốt. Họ tin rằng mục tiêu chính là làm cho chương trình hoạt động. Một khi nó
"Đang làm việc", họ chuyển sang nhiệm vụ tiếp theo, để chương trình "đang làm việc" ở bất kỳ trạng thái nào
cuối cùng họ đã có nó để "hoạt động." Hầu hết các lập trình viên dày dạn kinh nghiệm đều biết rằng điều này là
chuyên nghiệp
tự tử.
Args: Bản nháp thô
Liệt kê 14-8 cho thấy một phiên bản cũ hơn của lớp Args . Nó "hoạt động." Và nó lộn xộn.
Liệt kê 14-8
Args.java (bản nháp đầu tiên)
nhập java.text.ParseException;
nhập java.util. *;
public class Args {
lược đồ chuỗi riêng;
private String [] args;
boolean riêng hợp lệ = true;
private Set <Character> inheritArguments = new TreeSet <Character> ();
bản đồ riêng tư <Character, Boolean> booleanArgs =
new HashMap <Character, Boolean> ();
private Map <Character, String> stringArgs = new HashMap <Character, String> ();
private Map <Character, Integer> intArgs = new HashMap <Character, Integer> ();
private Set <Character> argsFound = new HashSet <Character> ();
private int currentArgument;
char errorArgumentId = '\ 0';
private String errorParameter = "TILT";
riêng ErrorCode errorCode = ErrorCode.OK;
private enum ErrorCode {
OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT}
public Args (String schema, String [] args) ném ParseException {
this.schema = schema;
this.args = args;
hợp lệ = phân tích cú pháp ();
}
private boolean parse () ném ParseException {
if (schema.length () == 0 && args.length == 0)
trả về true;
parseSchema ();
thử {
parseArguments ();
} catch (ArgsException e) {
}
trở lại hợp lệ;
}
private boolean parseSchema () ném ParseException {
for (Phần tử chuỗi: schema.split (",")) {
www.it-ebooks.info

Trang 233
202
Chương 14: Sàng lọc thành công
if (element.length ()> 0) {
String trimmedElement = element.trim ();
parseSchemaElement (trimmedElement);
}
}
trả về true;
}
private void parseSchemaElement (String element) ném ParseException {
char elementId = element.charAt (0);
String elementTail = element.substring (1);
validateSchemaElementId (elementId);
if (isBooleanSchemaElement (elementTail))
parseBooleanSchemaElement (elementId);
else if (isStringSchemaElement (elementTail))
parseStringSchemaElement (elementId);
else if (isIntegerSchemaElement (elementTail)) {
parseIntegerSchemaElement (elementId);
} khác {
ném ParseException mới (
String.format ("Đối số:% c có định dạng không hợp lệ:% s.",
elementId, elementTail), 0);
}
}
private void validateSchemaElementId (char elementId) ném ParseException {
if (! Character.isLetter (elementId)) {
ném ParseException mới (
"Bad character:" + elementId + "ở định dạng Args:" + schema, 0);
}
}
private void parseBooleanSchemaElement (char elementId) {
booleanArgs.put (elementId, false);
}
private void parseIntegerSchemaElement (char elementId) {
intArgs.put (elementId, 0);
}
private void parseStringSchemaElement (char elementId) {
stringArgs.put (elementId, "");
}
private boolean isStringSchemaElement (String elementTail) {
return elementTail.equals ("*");
}
private boolean isBooleanSchemaElement (String elementTail) {
return elementTail.length () == 0;
}
private boolean isIntegerSchemaElement (String elementTail) {
return elementTail.equals ("#");
}
Liệt kê 14-8 (tiếp theo)
Args.java (bản nháp đầu tiên)
www.it-ebooks.info

Trang 234
203
Args: Bản nháp thô
private boolean parseArguments () ném ArgsException {
cho (currentArgument = 0; currentArgument <args.length; currentArgument ++)
{
String arg = args [currentArgument];
parseArgument (arg);
}
trả về true;
}
private void parseArgument (String arg) ném ArgsException {
if (arg.startsWith ("-"))
parseElements (arg);
}
private void parseElements (String arg) ném ArgsException {
for (int i = 1; i <arg.length (); i ++)
parseElement (arg.charAt (i));
}
private void parseElement (char argChar) ném ArgsException {
if (setArgument (argChar))
argsFound.add (argChar);
khác {
bất ngờArguments.add (argChar);
errorCode = ErrorCode.UNEXPECTED_ARGUMENT;
hợp lệ = sai;
}
}
private boolean setArgument (char argChar) ném ArgsException {
if (isBooleanArg (argChar))
setBooleanArg (argChar, true);
else if (isStringArg (argChar))
setStringArg (argChar);
else if (isIntArg (argChar))
setIntArg (argChar);
khác
trả về sai;
trả về true;
}
private boolean isIntArg (char argChar) {return intArgs.containsKey (argChar);}
private void setIntArg (char argChar) ném ArgsException {
currentArgument ++;
Tham số chuỗi = null;
thử {
tham số = args [currentArgument];
intArgs.put (argChar, new Integer (tham số));
} catch (ArrayIndexOutOfBoundsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
errorCode = ErrorCode.MISSING_INTEGER;
Liệt kê 14-8 (tiếp theo)
Args.java (bản nháp đầu tiên)
www.it-ebooks.info

Trang 235
204
Chương 14: Sàng lọc thành công
ném ArgsException mới ();
} catch (NumberFormatException e) {
hợp lệ = sai;
errorArgumentId = argChar;
errorParameter = tham số;
errorCode = ErrorCode.INVALID_INTEGER;
ném ArgsException mới ();
}
}
private void setStringArg (char argChar) ném ArgsException {
currentArgument ++;
thử {
stringArgs.put (argChar, args [currentArgument]);
} catch (ArrayIndexOutOfBoundsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
errorCode = ErrorCode.MISSING_STRING;
ném ArgsException mới ();
}
}
private boolean isStringArg (char argChar) {
trả về stringArgs.containsKey (argChar);
}
private void setBooleanArg (char argChar, boolean value) {
booleanArgs.put (argChar, giá trị);
}
boolean riêng isBooleanArg (char argChar) {
trả về booleanArgs.containsKey (argChar);
}
public int cardinality () {
trả về argsFound.size ();
}
sử dụng chuỗi công khai () {
nếu (schema.length ()> 0)
return "- [" + schema + "]";
khác
trở về "";
}
public String errorMessage () ném Exception {
switch (errorCode) {
trường hợp OK:
ném ngoại lệ mới ("TILT: Không nên đến đây.");
trường hợp UNEXPECTED_ARGUMENT:
trả về bất ngờArgumentMessage ();
trường hợp MISSING_STRING:
return String.format ("Không thể tìm thấy tham số chuỗi cho -% c.",
errorArgumentId);
Liệt kê 14-8 (tiếp theo)
Args.java (bản nháp đầu tiên)
www.it-ebooks.info

Trang 236
205
Args: Bản nháp thô
trường hợp INVALID_INTEGER:
return String.format ("Đối số -% c mong đợi một số nguyên nhưng là '% s'.",
errorArgumentId, errorParameter);
trường hợp MISSING_INTEGER:
return String.format ("Không thể tìm thấy tham số số nguyên cho -% c.",
errorArgumentId);
}
trở về "";
}
chuỗi riêng tư bất ngờArgumentMessage () {
Thông báo StringBuffer = new StringBuffer ("(Các) đối số -");
cho (char c: bất ngờArguments) {
message.append (c);
}
message.append ("bất ngờ.");
trả về message.toString ();
}
boolean riêng falseIfNull (Boolean b) {
trả về b! = null && b;
}
private int zeroIfNull (Integer i) {
trả về i == null? 0: i;
}
private String blankIfNull (Các chuỗi) {
trả về s == null? "": s;
}
public String getString (char arg) {
trả về blankIfNull (stringArgs.get (arg));
}
public int getInt (char arg) {
return zeroIfNull (intArgs.get (arg));
}
public boolean getBoolean (char arg) {
trả về falseIfNull (booleanArgs.get (arg));
}
public boolean has (char arg) {
trả về argsFound.contains (arg);
}
public boolean isValid () {
trở lại hợp lệ;
}
private class ArgsException mở rộng Exception {
}
}
Liệt kê 14-8 (tiếp theo)
Args.java (bản nháp đầu tiên)
www.it-ebooks.info

Trang 237
206
Chương 14: Sàng lọc thành công
Tôi hy vọng phản ứng ban đầu của bạn đối với khối lượng mã này là "Tôi chắc chắn rất vui vì anh ấy đã không bỏ

như thế! ” Nếu bạn cảm thấy như thế này, thì hãy nhớ đó là cách người khác sẽ cảm thấy
về mã mà bạn để lại ở dạng bản nháp thô.
Trên thực tế, "bản nháp thô" có lẽ là điều tốt nhất bạn có thể nói về mã này. nó là
rõ ràng là một công việc đang được tiến hành. Số lượng tuyệt đối của các biến cá thể là một điều đáng lo ngại. Số lẻ
chuỗi như “TILT , ” các HashSets và TreeSets , và try-catch-catch khối tất cả thêm lên đến
một đống mưng mủ.
Tôi không muốn viết một đống mưng mủ. Thật vậy, tôi đã cố gắng giữ mọi thứ theo lý do-
ably tổ chức tốt. Bạn có thể biết điều đó từ sự lựa chọn của tôi về hàm và biến
tên và thực tế là có một cấu trúc thô cho chương trình. Nhưng, rõ ràng, tôi đã để
vấn đề tránh xa tôi.
Sự lộn xộn xây dựng dần dần. Các phiên bản trước gần như không quá tệ. Ví dụ,
Liệt kê 14-9 cho thấy một phiên bản cũ hơn, trong đó chỉ có các đối số Boolean đang hoạt động.
Liệt kê 14-9
Args.java (chỉ Boolean)
gói com.objectmentor.utilities.getopts;
nhập java.util. *;
public class Args {
lược đồ chuỗi riêng;
private String [] args;
boolean riêng hợp lệ;
private Set <Character> inheritArguments = new TreeSet <Character> ();
bản đồ riêng tư <Character, Boolean> booleanArgs =
new HashMap <Character, Boolean> ();
private int numberOfArguments = 0;
public Args (String schema, String [] args) {
this.schema = schema;
this.args = args;
hợp lệ = phân tích cú pháp ();
}
public boolean isValid () {
trở lại hợp lệ;
}
phân tích cú pháp boolean riêng () {
if (schema.length () == 0 && args.length == 0)
trả về true;
parseSchema ();
parseArguments ();
trả về bất ngờArguments.size () == 0;
}
private boolean parseSchema () {
for (Phần tử chuỗi: schema.split (",")) {
parseSchemaElement (phần tử);
}
www.it-ebooks.info

Trang 238
207
Args: Bản nháp thô
trả về true;
}
private void parseSchemaElement (Phần tử chuỗi) {
if (element.length () == 1) {
parseBooleanSchemaElement (phần tử);
}
}
private void parseBooleanSchemaElement (Phần tử chuỗi) {
char c = element.charAt (0);
if (Character.isLetter (c)) {
booleanArgs.put (c, false);
}
}
private boolean parseArguments () {
cho (String arg: args)
parseArgument (arg);
trả về true;
}
private void parseArgument (String arg) {
if (arg.startsWith ("-"))
parseElements (arg);
}
private void parseElements (String arg) {
for (int i = 1; i <arg.length (); i ++)
parseElement (arg.charAt (i));
}
private void parseElement (char argChar) {
if (isBoolean (argChar)) {
numberOfArguments ++;
setBooleanArg (argChar, true);
} khác
bất ngờArguments.add (argChar);
}
private void setBooleanArg (char argChar, boolean value) {
booleanArgs.put (argChar, giá trị);
}
boolean riêng isBoolean (char argChar) {
trả về booleanArgs.containsKey (argChar);
}
public int cardinality () {
trả về numberOfArguments;
}
sử dụng chuỗi công khai () {
nếu (schema.length ()> 0)
return "- [" + schema + "]";
Liệt kê 14-9 (tiếp theo)
Args.java (chỉ Boolean)
www.it-ebooks.info

Trang 239
208
Chương 14: Sàng lọc thành công
Mặc dù bạn có thể tìm thấy nhiều điều để phàn nàn trong đoạn mã này, nhưng nó thực sự không quá tệ.
Nó nhỏ gọn và đơn giản và dễ hiểu. Tuy nhiên, trong mã này, có thể dễ dàng thấy
hạt của đống sau mưng mủ. Nó khá rõ ràng làm thế nào điều này phát triển thành mớ hỗn độn sau này.
Lưu ý rằng mớ hỗn độn sau chỉ có hai kiểu đối số hơn thế này: Chuỗi và
số nguyên . Việc chỉ thêm hai loại đối số nữa đã có tác động tiêu cực lớn đến
mật mã. Nó đã chuyển đổi nó từ một thứ mà lẽ ra có thể bảo trì hợp lý
thành một thứ mà tôi mong đợi sẽ trở nên đầy rệp và mụn cóc.
Tôi đã thêm hai loại đối số tăng dần. Đầu tiên, tôi đã thêm đối số Chuỗi ,
dẫn đến điều này:
khác
trở về "";
}
public String errorMessage () {
nếu (bất ngờArguments.size ()> 0) {
trả về bất ngờArgumentMessage ();
} khác
trở về "";
}
chuỗi riêng tư bất ngờArgumentMessage () {
Thông báo StringBuffer = new StringBuffer ("(Các) đối số -");
cho (char c: bất ngờArguments) {
message.append (c);
}
message.append ("bất ngờ.");
trả về message.toString ();
}
public boolean getBoolean (char arg) {
trả về booleanArgs.get (arg);
}
}
Liệt kê 14-10
Args.java (Boolean và String)
gói com.objectmentor.utilities.getopts;
nhập java.text.ParseException;
nhập java.util. *;
public class Args {
lược đồ chuỗi riêng;
private String [] args;
boolean riêng hợp lệ = true;
private Set <Character> inheritArguments = new TreeSet <Character> ();
bản đồ riêng tư <Character, Boolean> booleanArgs =
new HashMap <Character, Boolean> ();
Liệt kê 14-9 (tiếp theo)
Args.java (chỉ Boolean)
www.it-ebooks.info

Trang 240
209
Args: Bản nháp thô
bản đồ riêng tư <Character, String> stringArgs =
new HashMap <Character, String> ();
private Set <Character> argsFound = new HashSet <Character> ();
private int currentArgument;
char errorArgument = '\ 0';
enum ErrorCode {
OK, MISSING_STRING}
riêng ErrorCode errorCode = ErrorCode.OK;
public Args (String schema, String [] args) ném ParseException {
this.schema = schema;
this.args = args;
hợp lệ = phân tích cú pháp ();
}
private boolean parse () ném ParseException {
if (schema.length () == 0 && args.length == 0)
trả về true;
parseSchema ();
parseArguments ();
trở lại hợp lệ;
}
private boolean parseSchema () ném ParseException {
for (Phần tử chuỗi: schema.split (",")) {
if (element.length ()> 0) {
String trimmedElement = element.trim ();
parseSchemaElement (trimmedElement);
}
}
trả về true;
}
private void parseSchemaElement (String element) ném ParseException {
char elementId = element.charAt (0);
String elementTail = element.substring (1);
validateSchemaElementId (elementId);
if (isBooleanSchemaElement (elementTail))
parseBooleanSchemaElement (elementId);
else if (isStringSchemaElement (elementTail))
parseStringSchemaElement (elementId);
}
private void validateSchemaElementId (char elementId) ném ParseException {
if (! Character.isLetter (elementId)) {
ném ParseException mới (
"Bad character:" + elementId + "ở định dạng Args:" + schema, 0);
}
}
private void parseStringSchemaElement (char elementId) {
stringArgs.put (elementId, "");
}
Liệt kê 14-10 (tiếp theo)
Args.java (Boolean và String)
www.it-ebooks.info

Trang 241
210
Chương 14: Sàng lọc thành công
private boolean isStringSchemaElement (String elementTail) {
return elementTail.equals ("*");
}
private boolean isBooleanSchemaElement (String elementTail) {
return elementTail.length () == 0;
}
private void parseBooleanSchemaElement (char elementId) {
booleanArgs.put (elementId, false);
}
private boolean parseArguments () {
cho (currentArgument = 0; currentArgument <args.length; currentArgument ++)
{
String arg = args [currentArgument];
parseArgument (arg);
}
trả về true;
}
private void parseArgument (String arg) {
if (arg.startsWith ("-"))
parseElements (arg);
}
private void parseElements (String arg) {
for (int i = 1; i <arg.length (); i ++)
parseElement (arg.charAt (i));
}
private void parseElement (char argChar) {
if (setArgument (argChar))
argsFound.add (argChar);
khác {
bất ngờArguments.add (argChar);
hợp lệ = sai;
}
}
private boolean setArgument (char argChar) {
boolean set = true;
if (isBoolean (argChar))
setBooleanArg (argChar, true);
else if (isString (argChar))
setStringArg (argChar, "");
khác
set = false;
trả lại bộ;
}
private void setStringArg (char argChar, String s) {
currentArgument ++;
thử {
Liệt kê 14-10 (tiếp theo)
Args.java (Boolean và String)
www.it-ebooks.info

Trang 242
211
Args: Bản nháp thô
stringArgs.put (argChar, args [currentArgument]);
} catch (ArrayIndexOutOfBoundsException e) {
hợp lệ = sai;
errorArgument = argChar;
errorCode = ErrorCode.MISSING_STRING;
}
}
private boolean isString (char argChar) {
trả về stringArgs.containsKey (argChar);
}
private void setBooleanArg (char argChar, boolean value) {
booleanArgs.put (argChar, giá trị);
}
boolean riêng isBoolean (char argChar) {
trả về booleanArgs.containsKey (argChar);
}
public int cardinality () {
trả về argsFound.size ();
}
sử dụng chuỗi công khai () {
nếu (schema.length ()> 0)
return "- [" + schema + "]";
khác
trở về "";
}
public String errorMessage () ném Exception {
nếu (bất ngờArguments.size ()> 0) {
trả về bất ngờArgumentMessage ();
} khác
switch (errorCode) {
trường hợp MISSING_STRING:
return String.format ("Không thể tìm thấy tham số chuỗi cho -% c.",
errorArgument);
trường hợp OK:
ném ngoại lệ mới ("TILT: Không nên đến đây.");
}
trở về "";
}
chuỗi riêng tư bất ngờArgumentMessage () {
Thông báo StringBuffer = new StringBuffer ("(Các) đối số -");
cho (char c: bất ngờArguments) {
message.append (c);
}
message.append ("bất ngờ.");
trả về message.toString ();
}
Liệt kê 14-10 (tiếp theo)
Args.java (Boolean và String)
www.it-ebooks.info

Trang 243
212
Chương 14: Sàng lọc thành công
Bạn có thể thấy rằng điều này đang bắt đầu vượt khỏi tầm tay. Nó vẫn chưa khủng khiếp, nhưng sự lộn xộn là
chắc chắn bắt đầu phát triển. Đó là một đống, nhưng nó chưa mưng mủ. Nó đã bổ sung
kiểu đối số số nguyên để làm cho đống này thực sự lên men và mưng mủ.
Vì vậy, tôi đã dừng lại
Tôi có thêm ít nhất hai loại đối số nữa và tôi có thể nói rằng chúng sẽ làm cho mọi thứ
tệ hơn nhiều. Nếu tôi tiến lên phía trước, tôi có thể khiến chúng hoạt động, nhưng tôi sẽ
để lại một mớ hỗn độn quá lớn để sửa chữa. Nếu cấu trúc của mã này từng là
có thể bảo trì được, bây giờ là lúc để sửa chữa nó.
Vì vậy, tôi đã ngừng thêm các tính năng và bắt đầu cấu trúc lại. Vừa thêm chuỗi và
đối số số nguyên , tôi biết rằng mỗi loại đối số yêu cầu mã mới trong ba chính
nơi. Đầu tiên, mỗi loại đối số yêu cầu một số cách để phân tích cú pháp phần tử giản đồ của nó theo thứ tự
để chọn HashMap cho loại đó. Tiếp theo, mỗi loại đối số cần được phân tích cú pháp trong
chuỗi dòng lệnh và được chuyển đổi thành kiểu thực của nó. Cuối cùng, mỗi loại đối số cần một
phương thức getXXX để nó có thể được trả về cho người gọi dưới dạng đúng kiểu của nó.
Nhiều kiểu khác nhau, tất cả đều có các phương thức tương tự - đối với tôi nghe giống như một lớp học. Và vì thế
các ArgumentMarshaler khái niệm được sinh ra.
Về chủ nghĩa gia tăng
Một trong những cách tốt nhất để làm hỏng một chương trình là thực hiện những thay đổi lớn đối với cấu trúc của nó
dưới danh nghĩa
sự cải tiến. Một số chương trình không bao giờ phục hồi từ những “cải tiến” như vậy. Vấn đề là ở đó
rất khó để làm cho chương trình hoạt động giống như cách nó hoạt động trước khi "cải tiến".
public boolean getBoolean (char arg) {
trả về falseIfNull (booleanArgs.get (arg));
}
boolean riêng falseIfNull (Boolean b) {
trả về b == null? sai: b;
}
public String getString (char arg) {
trả về blankIfNull (stringArgs.get (arg));
}
private String blankIfNull (Các chuỗi) {
trả về s == null? "": s;
}
public boolean has (char arg) {
trả về argsFound.contains (arg);
}
public boolean isValid () {
trở lại hợp lệ;
}
}
Liệt kê 14-10 (tiếp theo)
Args.java (Boolean và String)
www.it-ebooks.info

Trang 244
213
Args: Bản nháp thô
Để tránh điều này, tôi sử dụng kỷ luật Phát triển theo hướng kiểm tra (TDD). Một trong những cen-
Học thuyết tral của phương pháp này là giữ cho hệ thống hoạt động mọi lúc. Nói cách khác,
sử dụng TDD, tôi không được phép thực hiện thay đổi đối với hệ thống phá vỡ hệ thống đó.
Mọi thay đổi tôi thực hiện phải giữ cho hệ thống hoạt động như trước đây.
Để đạt được điều này, tôi cần một bộ các bài kiểm tra tự động mà tôi có thể chạy theo ý thích và điều đó-
ifies rằng hành vi của hệ thống là không thay đổi. Đối với lớp Args, tôi đã tạo một bộ
của đơn vị và kiểm tra nghiệm thu trong khi tôi đang đóng cọc mưng mủ. Các bài kiểm tra đơn vị là
được viết bằng Java và được quản lý bởi JUnit . Các bài kiểm tra chấp nhận được viết dưới dạng trang wiki
trong FitNesse . Tôi có thể chạy các bài kiểm tra này bất kỳ lúc nào tôi muốn và nếu chúng vượt qua, tôi tự tin
rằng hệ thống đã hoạt động như tôi đã chỉ định.
Vì vậy, tôi đã tiến hành thực hiện một số lượng lớn các thay đổi rất nhỏ. Mỗi thay đổi đã di chuyển
cấu trúc của hệ thống hướng tới khái niệm ArgumentMarshaler . Và mỗi thay đổi vẫn giữ
hệ thống hoạt động. Thay đổi đầu tiên tôi thực hiện là thêm khung của
ArgumentMarshaller đến cuối đống mưng mủ (Liệt kê 14-11).
Rõ ràng, điều này sẽ không phá vỡ bất cứ điều gì. Vì vậy, sau đó tôi đã thực hiện sửa đổi đơn giản nhất
Tôi có thể, một cái sẽ hỏng càng ít càng tốt. Tôi đã thay đổi HashMap cho Boolean
đối số để sử dụng ArgumentMarshaler .
bản đồ riêng tư <Character, ArgumentMarshaler > booleanArgs =
new HashMap <Character, ArgumentMarshaler > ();
Điều này đã làm hỏng một vài tuyên bố, mà tôi đã nhanh chóng sửa chữa.
...
private void parseBooleanSchemaElement (char elementId) {
booleanArgs.put (elementId, new BooleanArgumentMarshaler () );
}
..
Liệt kê 14-11
ArgumentMarshaller được thêm vào Args.java
lớp private ArgumentMarshaler {
boolean riêng booleanValue = false;
public void setBoolean (boolean value) {
booleanValue = giá trị;
}
public boolean getBoolean () {return booleanValue;}
}
lớp riêng BooleanArgumentMarshaler mở rộng ArgumentMarshaler {
}
lớp private StringArgumentMarshaler mở rộng ArgumentMarshaler {
}
lớp riêng IntegerArgumentMarshaler mở rộng ArgumentMarshaler {
}
}
www.it-ebooks.info

Trang 245
214
Chương 14: Sàng lọc thành công
private void setBooleanArg (char argChar, boolean value) {
booleanArgs. get (argChar) .setBoolean (giá trị);
}
...
public boolean getBoolean (char arg) {
return falseIfNull (booleanArgs.get (arg). getBoolean () );
}
Lưu ý rằng những thay đổi này diễn ra như thế nào trong các lĩnh vực mà tôi đã đề cập trước đây: phân tích cú pháp ,
đặt và lấy cho loại đối số. Thật không may, nhỏ như thay đổi này, một số
các bài kiểm tra bắt đầu thất bại. Nếu bạn xem xét kỹ getBoolean , bạn sẽ thấy điều đó nếu bạn gọi nó bằng
'y , ' nhưng không có y cãi nhau, sau đó booleanArgs.get ( 'y') sẽ trở lại rỗng , và func-
tion sẽ ném một NullPointerException . Hàm falseIfNull đã được sử dụng để bảo vệ
chống lại điều này, nhưng thay đổi tôi đã thực hiện khiến chức năng đó trở nên không liên quan.
Chủ nghĩa gia tăng yêu cầu tôi làm việc này nhanh chóng trước khi thực hiện bất kỳ
những thay đổi. Thật vậy, việc sửa chữa không quá khó. Tôi chỉ cần chuyển séc cho null . Nó đã
không còn boolean là null mà tôi cần kiểm tra; đó là ArgumentMarshaller .
Đầu tiên, tôi đã loại bỏ lệnh gọi falseIfNull trong hàm getBoolean . Bây giờ nó vô dụng, vì vậy
Tôi cũng đã loại bỏ chức năng của chính nó. Các bài kiểm tra vẫn không thành công theo cách tương tự, vì vậy tôi
đã quyết định
vết lõm mà tôi đã không đưa ra bất kỳ lỗi mới nào.
public boolean getBoolean (char arg) {
trả về booleanArgs.get (arg) .getBoolean ();
}
Tiếp theo, tôi chia hàm thành hai dòng và đặt ArgumentMarshaller thành biến thể của riêng nó-
có thể đặt tên là đối sốMarshaller . Tôi không quan tâm đến tên biến dài; nó thật tệ
thừa và lộn xộn chức năng. Vì vậy, tôi rút ngắn nó để sáng [N5].
public boolean getBoolean (char arg) {
Args.ArgumentMarshaler am = booleanArgs.get (arg);
return am .getBoolean ();
}
Và sau đó tôi đưa vào logic phát hiện null.
public boolean getBoolean (char arg) {
Args.ArgumentMarshaler am = booleanArgs.get (arg);
return am! = null && am.getBoolean ();
}
Đối số chuỗi
Thêm đối số chuỗi tương tự như thêm đối số boolean . Tôi đã phải thay đổi
các HashMap và nhận được phân tích cú pháp , thiết lập , và có được chức năng làm việc. Không nên có bất kỳ điều gì
nhấn mạnh vào những gì tiếp theo ngoại trừ, có lẽ, rằng tôi dường như đang đặt tất cả các quan điểm của cảnh sát-
trong lớp cơ sở ArgumentMarshaller thay vì phân phối nó cho các dẫn xuất.
bản đồ riêng tư <Character , ArgumentMarshaler > stringArgs =
new HashMap <Character , ArgumentMarshaler > ();
...
www.it-ebooks.info

Trang 246
215
Đối số chuỗi
private void parseStringSchemaElement (char elementId) {
stringArgs.put (elementId , new StringArgumentMarshaler () );
}
...
private void setStringArg (char argChar) ném ArgsException {
currentArgument ++;
thử {
stringArgs .get (argChar) .setString (args [currentArgument]);
} catch (ArrayIndexOutOfBoundsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
errorCode = ErrorCode.MISSING_STRING;
ném ArgsException mới ();
}
}
...
public String getString (char arg) {
Args.ArgumentMarshaler am = stringArgs.get (arg);
trả về am == null? "" : am.getString ();
}
...
lớp private ArgumentMarshaler {
boolean riêng booleanValue = false;
private String stringValue;
public void setBoolean (boolean value) {
booleanValue = giá trị;
}
public boolean getBoolean () {
trả về booleanValue;
}
public void setString (String s) {
stringValue = s;
}
public String getString () {
trả về stringValue == null? "" : Chuỗi giá trị;
}
}
Một lần nữa, những thay đổi này được thực hiện lần lượt và theo cách mà các thử nghiệm giữ
đang chạy, nếu không vượt qua. Khi một bài kiểm tra bị hỏng, tôi đảm bảo rằng nó sẽ vượt qua một lần nữa trước
khi ...
sửa đổi tiếp theo.
Bây giờ bạn sẽ có thể thấy ý định của tôi. Khi tôi nhận được tất cả các điều phối hiện tại
hành vi vào lớp cơ sở ArgumentMarshaler , tôi sẽ bắt đầu đẩy hành vi đó
xuống các dẫn xuất. Điều này sẽ cho phép tôi tiếp tục chạy mọi thứ trong khi tôi dần dần
thay đổi hình dạng của chương trình này.
Bước tiếp theo rõ ràng là di chuyển chức năng đối số int vào
ArgumentMarshaler . Một lần nữa, không có bất kỳ sự ngạc nhiên nào.
bản đồ riêng tư <Character , ArgumentMarshaler > intArgs =
new HashMap <Character , ArgumentMarshaler > ();
...
www.it-ebooks.info

Trang 247
216
Chương 14: Sàng lọc thành công
private void parseIntegerSchemaElement (char elementId) {
intArgs.put (elementId, new IntegerArgumentMarshaler () );
}
...
private void setIntArg (char argChar) ném ArgsException {
currentArgument ++;
Tham số chuỗi = null;
thử {
tham số = args [currentArgument];
intArgs .get (argChar) .setInteger (Integer.parseInt (tham số));
} catch (ArrayIndexOutOfBoundsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
errorCode = ErrorCode.MISSING_INTEGER;
ném ArgsException mới ();
} catch (NumberFormatException e) {
hợp lệ = sai;
errorArgumentId = argChar;
errorParameter = tham số;
errorCode = ErrorCode.INVALID_INTEGER;
ném ArgsException mới ();
}
}
...
public int getInt (char arg) {
Args.ArgumentMarshaler am = intArgs.get (arg);
trả về am == null? 0 : am.getInteger ();
}
...
lớp private ArgumentMarshaler {
boolean riêng booleanValue = false;
private String stringValue;
private int integerValue;
public void setBoolean (boolean value) {
booleanValue = giá trị;
}
public boolean getBoolean () {
trả về booleanValue;
}
public void setString (String s) {
stringValue = s;
}
public String getString () {
trả về stringValue == null? "" : Chuỗi giá trị;
}
public void setInteger (int i) {
integerValue = i;
}
public int getInteger () {
trả về integerValue;
}
}
www.it-ebooks.info

Trang 248
217
Đối số chuỗi
Với tất cả công việc điều phối được chuyển sang ArgumentMarshaler , tôi bắt đầu đẩy chức năng-
ity vào các dẫn xuất. Bước đầu tiên là chuyển hàm setBoolean vào
BooleanArgumentMarshaller và đảm bảo rằng nó được gọi chính xác. Vì vậy, tôi đã tạo một bản tóm tắt
đặt phương pháp.
lớp trừu tượng riêng ArgumentMarshaler {
bảo vệ boolean booleanValue = false;
private String stringValue;
private int integerValue;
public void setBoolean (boolean value) {
booleanValue = giá trị;
}
public boolean getBoolean () {
trả về booleanValue;
}
public void setString (String s) {
stringValue = s;
}
public String getString () {
trả về stringValue == null? "" : Chuỗi giá trị;
}
public void setInteger (int i) {
integerValue = i;
}
public int getInteger () {
trả về integerValue;
}
public abstract void set (String s);
}
Sau đó, tôi triển khai phương thức thiết lập trong BooleanArgumentMarshaller .
lớp riêng BooleanArgumentMarshaler mở rộng ArgumentMarshaler {
public void set (String s) {
booleanValue = true;
}
}
Và cuối cùng tôi đã thay thế cuộc gọi setBoolean bằng một cuộc gọi để đặt .
private void setBooleanArg (char argChar, boolean value) {
booleanArgs.get (argChar) .set ("true");
}
Tất cả các bài kiểm tra vẫn vượt qua. Bởi vì sự thay đổi này đã khiến tập hợp được triển khai cho Boolean-
ArgumentMarshaler , tôi đã xóa phương thức setBoolean khỏi cơ sở ArgumentMarshaler
lớp học.
Lưu ý rằng hàm bộ trừu tượng nhận đối số Chuỗi , nhưng việc triển khai
trong BooleanArgumentMarshaller không sử dụng nó. Tôi đưa lập luận đó vào đó bởi vì tôi
biết rằng StringArgumentMarshaller và IntegerArgumentMarshaller sẽ sử dụng nó.
www.it-ebooks.info

Trang 249
218
Chương 14: Sàng lọc thành công
Tiếp theo, tôi muốn triển khai phương thức get vào BooleanArgumentMarshaler . Triển khai
hàm get luôn xấu vì kiểu trả về phải là Đối tượng , và trong trường hợp này
cần được chuyển thành Boolean .
public boolean getBoolean (char arg) {
Args.ArgumentMarshaler am = booleanArgs.get (arg);
return am! = null && (Boolean) am. lấy ();
}
Chỉ để biên dịch cái này, tôi đã thêm hàm get vào ArgumentMarshaler .
lớp trừu tượng riêng ArgumentMarshaler {
...
public Object get () {
trả về null;
}
}
Điều này được biên soạn và rõ ràng đã thất bại trong các bài kiểm tra Bắt các bài kiểm tra hoạt động trở lại chỉ đơn
giản là
vấn đề làm cho nó trở nên trừu tượng và triển khai nó trong BooleanAgumentMarshaler .
lớp trừu tượng riêng ArgumentMarshaler {
bảo vệ boolean booleanValue = false;
...
public abstract Object get ();
}
lớp riêng BooleanArgumentMarshaler mở rộng ArgumentMarshaler {
public void set (String s) {
booleanValue = true;
}
public Object get () {
trả về booleanValue;
}
}
Một lần nữa các bài kiểm tra lại vượt qua. Vì vậy, cả hai đều nhận và thiết lập triển khai BooleanArgumentMarshaler !
Điều này cho phép tôi xóa hàm getBoolean cũ khỏi ArgumentMarshaler , di chuyển
biến booleanValue được bảo vệ xuống BooleanArgumentMarshaler và đặt nó ở chế độ riêng tư .
Tôi đã thực hiện cùng một kiểu thay đổi cho Chuỗi . Tôi đã triển khai cả set và get , đã xóa
các hàm không sử dụng và đã di chuyển các biến.
private void setStringArg (char argChar) ném ArgsException {
currentArgument ++;
thử {
stringArgs.get (argChar). set (args [currentArgument]);
} catch (ArrayIndexOutOfBoundsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
errorCode = ErrorCode.MISSING_STRING;
ném ArgsException mới ();
}
}
www.it-ebooks.info

Trang 250
219
Đối số chuỗi
...
public String getString (char arg) {
Args.ArgumentMarshaler am = stringArgs.get (arg);
trả về am == null? "": (Chuỗi) sáng. lấy ();
}
...
lớp trừu tượng riêng ArgumentMarshaler {
private int integerValue;
public void setInteger (int i) {
integerValue = i;
}
public int getInteger () {
trả về integerValue;
}
public abstract void set (String s);
public abstract Object get ();
}
lớp riêng BooleanArgumentMarshaler mở rộng ArgumentMarshaler {
boolean riêng booleanValue = false;
public void set (String s) {
booleanValue = true;
}
public Object get () {
trả về booleanValue;
}
}
lớp private StringArgumentMarshaler mở rộng ArgumentMarshaler {
private String stringValue = "";
public void set (String s) {
stringValue = s;
}
public Object get () {
trả về stringValue;
}
}
lớp riêng IntegerArgumentMarshaler mở rộng ArgumentMarshaler {
public void set (String s) {
}
public Object get () {
trả về null;
}
}
}
www.it-ebooks.info

Trang 251
220
Chương 14: Sàng lọc thành công
Cuối cùng, tôi lặp lại quy trình cho số nguyên . Điều này chỉ phức tạp hơn một chút
bởi vì các số nguyên cần được phân tích cú pháp và hoạt động phân tích cú pháp có thể đưa ra một ngoại lệ. Nhưng
kết quả là tốt hơn vì toàn bộ khái niệm về NumberFormatException đã bị chôn vùi trong
IntegerArgumentMarshaler .
private boolean isIntArg (char argChar) {return intArgs.containsKey (argChar);}
private void setIntArg (char argChar) ném ArgsException {
currentArgument ++;
Tham số chuỗi = null;
thử {
tham số = args [currentArgument];
intArgs.get (argChar). set (tham số);
} catch (ArrayIndexOutOfBoundsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
errorCode = ErrorCode.MISSING_INTEGER;
ném ArgsException mới ();
} catch ( ArgsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
errorParameter = tham số;
errorCode = ErrorCode.INVALID_INTEGER;
ném e ;
}
}
...
private void setBooleanArg (char argChar) {
thử {
booleanArgs.get (argChar) .set ("true");
} catch (ArgsException e) {
}
}
...
public int getInt (char arg) {
Args.ArgumentMarshaler am = intArgs.get (arg);
trả về am == null? 0: (Số nguyên) am. lấy ();
}
...
lớp trừu tượng riêng ArgumentMarshaler {
public abstract void set (String s) ném ArgsException;
public abstract Object get ();
}
...
lớp riêng IntegerArgumentMarshaler mở rộng ArgumentMarshaler {
private int intValue = 0;
public void set (String s) ném ArgsException {
thử {
intValue = Integer.parseInt (s);
} catch (NumberFormatException e) {
ném ArgsException mới ();
}
}
public Object get () {
trả về intValue;
}
}
www.it-ebooks.info

Trang 252
221
Đối số chuỗi
Tất nhiên, các bài kiểm tra tiếp tục trôi qua. Tiếp theo, tôi đã loại bỏ ba bản đồ khác nhau tại
đầu của thuật toán. Điều này làm cho toàn bộ hệ thống chung chung hơn nhiều. Tuy nhiên, tôi
không thể loại bỏ chúng chỉ bằng cách xóa chúng vì điều đó sẽ phá vỡ hệ thống.
Thay vào đó, tôi đã thêm một Bản đồ mới cho ArgumentMarshaler và sau đó từng người một thay đổi
phương pháp sử dụng nó thay vì ba bản đồ gốc.
public class Args {
...
bản đồ riêng tư <Character, ArgumentMarshaler> booleanArgs =
new HashMap <Character, ArgumentMarshaler> ();
bản đồ riêng tư <Character, ArgumentMarshaler> stringArgs =
new HashMap <Character, ArgumentMarshaler> ();
bản đồ riêng tư <Character, ArgumentMarshaler> intArgs =
new HashMap <Character, ArgumentMarshaler> ();
Bản đồ riêng tư <Character, ArgumentMarshaler> marshalers =
new HashMap <Character, ArgumentMarshaler> ();
...
private void parseBooleanSchemaElement (char elementId) {
ArgumentMarshaler m = new BooleanArgumentMarshaler ();
booleanArgs.put (elementId, m);
marshalers.put (elementId, m);
}
private void parseIntegerSchemaElement (char elementId) {
ArgumentMarshaler m = new IntegerArgumentMarshaler ();
intArgs.put (elementId, m);
marshalers.put (elementId, m);
}
private void parseStringSchemaElement (char elementId) {
ArgumentMarshaler m = new StringArgumentMarshaler ();
stringArgs.put (elementId, m);
marshalers.put (elementId, m);
}
Tất nhiên tất cả các bài kiểm tra vẫn vượt qua. Tiếp theo, tôi đã thay đổi isBooleanArg từ cái này:
boolean riêng isBooleanArg (char argChar) {
trả về booleanArgs.containsKey (argChar);
}
đến điều này:
boolean riêng isBooleanArg (char argChar) {
ArgumentMarshaler m = marshalers.get (argChar);
trả về m instanceof BooleanArgumentMarshaler;
}
Các bài kiểm tra vẫn vượt qua. Vì vậy, tôi đã thực hiện cùng một thay đổi đối với isIntArg và isStringArg .
boolean riêng isIntArg (char argChar) {
ArgumentMarshaler m = marshalers.get (argChar);
trả về m instanceof IntegerArgumentMarshaler;
}
private boolean isStringArg (char argChar) {
ArgumentMarshaler m = marshalers.get (argChar);
trả về m instanceof StringArgumentMarshaler;
}
www.it-ebooks.info

Trang 253
222
Chương 14: Sàng lọc thành công
Các bài kiểm tra vẫn vượt qua. Vì vậy, tôi đã loại bỏ tất cả các lệnh gọi trùng lặp tới marshalers.get như sau:
private boolean setArgument (char argChar) ném ArgsException {
ArgumentMarshaler m = marshalers.get (argChar);
if (isBooleanArg ( m ))
setBooleanArg (argChar);
else if (isStringArg ( m ))
setStringArg (argChar);
else if (isIntArg ( m ))
setIntArg (argChar);
khác
trả về sai;
trả về true;
}
boolean riêng isIntArg ( ArgumentMarshaler m ) {
trả về m instanceof IntegerArgumentMarshaler;
}
private boolean isStringArg ( ArgumentMarshaler m ) {
trả về m instanceof StringArgumentMarshaler;
}
private boolean isBooleanArg ( ArgumentMarshaler m ) {
trả về m instanceof BooleanArgumentMarshaler;
}
Điều này không có lý do chính đáng cho ba phương thức isxxxArg . Vì vậy, tôi đã sắp xếp chúng:
private boolean setArgument (char argChar) ném ArgsException {
ArgumentMarshaler m = marshalers.get (argChar);
if ( m instanceof BooleanArgumentMarshaler )
setBooleanArg (argChar);
else if ( m instanceof StringArgumentMarshaler )
setStringArg (argChar);
else if ( m instanceof IntegerArgumentMarshaler )
setIntArg (argChar);
khác
trả về sai;
trả về true;
}
Tiếp theo, tôi bắt đầu sử dụng marshalers bản đồ trong bộ chức năng, vi phạm việc sử dụng của người khác
ba bản đồ. Tôi bắt đầu với boolean .
private boolean setArgument (char argChar) ném ArgsException {
ArgumentMarshaler m = marshalers.get (argChar);
if (m instanceof BooleanArgumentMarshaler)
setBooleanArg ( m );
else if (m instanceof StringArgumentMarshaler)
setStringArg (argChar);
else if (m instanceof IntegerArgumentMarshaler)
setIntArg (argChar);
khác
trả về sai;
www.it-ebooks.info

Trang 254
223
Đối số chuỗi
trả về true;
}
...
private void setBooleanArg ( ArgumentMarshaler m ) {
thử {
m .set ("true"); // là: booleanArgs.get (argChar) .set ("true");
} catch (ArgsException e) {
}
}
Các bài kiểm tra vẫn vượt qua, vì vậy tôi cũng làm như vậy với Chuỗi và Số nguyên . Điều này cho phép tôi tương tác-
ghi một số mã quản lý ngoại lệ xấu vào hàm setArgument .
private boolean setArgument (char argChar) ném ArgsException {
ArgumentMarshaler m = marshalers.get (argChar);
thử {
if (m instanceof BooleanArgumentMarshaler)
setBooleanArg (m);
else if (m instanceof StringArgumentMarshaler)
setStringArg ( m );
else if (m instanceof IntegerArgumentMarshaler)
setIntArg ( m );
khác
trả về sai;
} catch (ArgsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
ném e;
}
trả về true;
}
private void setIntArg ( ArgumentMarshaler m ) ném ArgsException {
currentArgument ++;
Tham số chuỗi = null;
thử {
tham số = args [currentArgument];
m .set (tham số);
} catch (ArrayIndexOutOfBoundsException e) {
errorCode = ErrorCode.MISSING_INTEGER;
ném ArgsException mới ();
} catch (ArgsException e) {
errorParameter = tham số;
errorCode = ErrorCode.INVALID_INTEGER;
ném e;
}
}
private void setStringArg ( ArgumentMarshaler m ) ném ArgsException {
currentArgument ++;
thử {
m .set (args [currentArgument]);
} catch (ArrayIndexOutOfBoundsException e) {
errorCode = ErrorCode.MISSING_STRING;
ném ArgsException mới ();
}
}
www.it-ebooks.info

Trang 255
224
Chương 14: Sàng lọc thành công
Tôi gần như có thể xóa ba bản đồ cũ. Đầu tiên, tôi cần thay đổi
Hàm getBoolean từ cái này:
public boolean getBoolean (char arg) {
Args.ArgumentMarshaler am = booleanArgs.get (arg);
return am! = null && (Boolean) am.get ();
}
đến điều này:
public boolean getBoolean (char arg) {
Args.ArgumentMarshaler am = marshalers.get (arg);
boolean b = false;
thử {
b = am! = null && (Boolean) am.get ();
} catch (ClassCastException e) {
b = sai;
}
trả lại b;
}
Sự thay đổi cuối cùng này có thể là một bất ngờ. Tại sao tôi đột nhiên quyết định đối phó với
các ClassCastException ? Lý do là tôi có một bộ bài kiểm tra đơn vị và một bộ riêng biệt
các bài kiểm tra chấp nhận được viết bằng FitNesse. Nó chỉ ra rằng các bài kiểm tra FitNesse đảm bảo rằng nếu
bạn đã gọi getBoolean trên một đối số nonboolean, bạn nhận được sai . Các bài kiểm tra đơn vị đã không.
Cho đến thời điểm này, tôi chỉ chạy thử nghiệm đơn vị. 2
Thay đổi cuối cùng này cho phép tôi rút ra một cách sử dụng khác của bản đồ boolean :
private void parseBooleanSchemaElement (char elementId) {
ArgumentMarshaler m = new BooleanArgumentMarshaler ();
booleanArgs.put (elementId, m);
marshalers.put (elementId, m);
}
Và bây giờ chúng ta có thể xóa bản đồ boolean .
public class Args {
...
bản đồ riêng tư <Character, ArgumentMarshaler> booleanArgs =
new HashMap <Character, ArgumentMarshaler> ();
bản đồ riêng tư <Character, ArgumentMarshaler> stringArgs =
new HashMap <Character, ArgumentMarshaler> ();
bản đồ riêng tư <Character, ArgumentMarshaler> intArgs =
new HashMap <Character, ArgumentMarshaler> ();
Bản đồ riêng tư <Character, ArgumentMarshaler> marshalers =
new HashMap <Character, ArgumentMarshaler> ();
...
Tiếp theo, tôi đã di chuyển các đối số Chuỗi và Số nguyên theo cách tương tự và thực hiện một chút
dọn dẹp bằng boolean .
private void parseBooleanSchemaElement (char elementId) {
marshalers.put (elementId, new BooleanArgumentMarshaler () );
}
2. Để tránh những bất ngờ khác thuộc loại này, tôi đã thêm một bài kiểm tra đơn vị mới gọi tất cả các bài kiểm tra FitNesse.
www.it-ebooks.info

Trang 256
225
Đối số chuỗi
private void parseIntegerSchemaElement (char elementId) {
marshalers.put (elementId, new IntegerArgumentMarshaler () );
}
private void parseStringSchemaElement (char elementId) {
marshalers.put (elementId, new StringArgumentMarshaler () );
}
...
public String getString (char arg) {
Args.ArgumentMarshaler am = marshalers .get (arg);
thử {
trả về am == null? "": (Chuỗi) am.get ();
} catch (ClassCastException e) {
trở về "";
}
}
public int getInt (char arg) {
Args.ArgumentMarshaler am = marshalers .get (arg);
thử {
trả về am == null? 0: (Số nguyên) am.get ();
} catch (Ngoại lệ e) {
trả về 0;
}
}
...
public class Args {
...
bản đồ riêng tư <Character, ArgumentMarshaler> stringArgs =
new HashMap <Character, ArgumentMarshaler> ();
bản đồ riêng tư <Character, ArgumentMarshaler> intArgs =
new HashMap <Character, ArgumentMarshaler> ();
Bản đồ riêng tư <Character, ArgumentMarshaler> marshalers =
new HashMap <Character, ArgumentMarshaler> ();
...
Tiếp theo, tôi đưa ra ba phương pháp phân tích cú pháp vì chúng không còn hoạt động nhiều nữa:
private void parseSchemaElement (String element) ném ParseException {
char elementId = element.charAt (0);
String elementTail = element.substring (1);
validateSchemaElementId (elementId);
if (isBooleanSchemaElement (elementTail))
marshalers.put (elementId, new BooleanArgumentMarshaler ());
else if (isStringSchemaElement (elementTail))
marshalers.put (elementId, new StringArgumentMarshaler ());
else if (isIntegerSchemaElement (elementTail)) {
marshalers.put (elementId, new IntegerArgumentMarshaler ());
} khác {
ném ParseException mới (String.format (
"Đối số:% c có định dạng không hợp lệ:% s.", ElementId, elementTail), 0);
}
}
Được rồi, bây giờ chúng ta hãy nhìn lại toàn bộ bức tranh. Liệt kê 14-12 cho thấy hiện tại
dạng của lớp Args .
www.it-ebooks.info

Trang 257
226
Chương 14: Sàng lọc thành công
Liệt kê 14-12
Args.java (Sau lần tái cấu trúc đầu tiên)
gói com.objectmentor.utilities.getopts;
nhập java.text.ParseException;
nhập java.util. *;
public class Args {
lược đồ chuỗi riêng;
private String [] args;
boolean riêng hợp lệ = true;
private Set <Character> inheritArguments = new TreeSet <Character> ();
Bản đồ riêng tư <Character, ArgumentMarshaler> marshalers =
new HashMap <Character, ArgumentMarshaler> ();
private Set <Character> argsFound = new HashSet <Character> ();
private int currentArgument;
char errorArgumentId = '\ 0';
private String errorParameter = "TILT";
riêng ErrorCode errorCode = ErrorCode.OK;
private enum ErrorCode {
OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT}
public Args (String schema, String [] args) ném ParseException {
this.schema = schema;
this.args = args;
hợp lệ = phân tích cú pháp ();
}
private boolean parse () ném ParseException {
if (schema.length () == 0 && args.length == 0)
trả về true;
parseSchema ();
thử {
parseArguments ();
} catch (ArgsException e) {
}
trở lại hợp lệ;
}
private boolean parseSchema () ném ParseException {
for (Phần tử chuỗi: schema.split (",")) {
if (element.length ()> 0) {
String trimmedElement = element.trim ();
parseSchemaElement (trimmedElement);
}
}
trả về true;
}
private void parseSchemaElement (String element) ném ParseException {
char elementId = element.charAt (0);
String elementTail = element.substring (1);
validateSchemaElementId (elementId);
if (isBooleanSchemaElement (elementTail))
marshalers.put (elementId, new BooleanArgumentMarshaler ());
else if (isStringSchemaElement (elementTail))
marshalers.put (elementId, new StringArgumentMarshaler ());
www.it-ebooks.info
Trang 258
227
Đối số chuỗi
else if (isIntegerSchemaElement (elementTail)) {
marshalers.put (elementId, new IntegerArgumentMarshaler ());
} khác {
ném ParseException mới (String.format (
"Đối số:% c có định dạng không hợp lệ:% s.", ElementId, elementTail), 0);
}
}
private void validateSchemaElementId (char elementId) ném ParseException {
if (! Character.isLetter (elementId)) {
ném ParseException mới (
"Bad character:" + elementId + "ở định dạng Args:" + schema, 0);
}
}
private boolean isStringSchemaElement (String elementTail) {
return elementTail.equals ("*");
}
private boolean isBooleanSchemaElement (String elementTail) {
return elementTail.length () == 0;
}
private boolean isIntegerSchemaElement (String elementTail) {
return elementTail.equals ("#");
}
private boolean parseArguments () ném ArgsException {
for (currentArgument = 0; currentArgument <args.length; currentArgument ++) {
String arg = args [currentArgument];
parseArgument (arg);
}
trả về true;
}
private void parseArgument (String arg) ném ArgsException {
if (arg.startsWith ("-"))
parseElements (arg);
}
private void parseElements (String arg) ném ArgsException {
for (int i = 1; i <arg.length (); i ++)
parseElement (arg.charAt (i));
}
private void parseElement (char argChar) ném ArgsException {
if (setArgument (argChar))
argsFound.add (argChar);
khác {
bất ngờArguments.add (argChar);
errorCode = ErrorCode.UNEXPECTED_ARGUMENT;
hợp lệ = sai;
}
}
Liệt kê 14-12 (tiếp theo)
Args.java (Sau lần tái cấu trúc đầu tiên)
www.it-ebooks.info

Trang 259
228
Chương 14: Sàng lọc thành công
private boolean setArgument (char argChar) ném ArgsException {
ArgumentMarshaler m = marshalers.get (argChar);
thử {
if (m instanceof BooleanArgumentMarshaler)
setBooleanArg (m);
else if (m instanceof StringArgumentMarshaler)
setStringArg (m);
else if (m instanceof IntegerArgumentMarshaler)
setIntArg (m);
khác
trả về sai;
} catch (ArgsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
ném e;
}
trả về true;
}
private void setIntArg (ArgumentMarshaler m) ném ArgsException {
currentArgument ++;
Tham số chuỗi = null;
thử {
tham số = args [currentArgument];
m.set (tham số);
} catch (ArrayIndexOutOfBoundsException e) {
errorCode = ErrorCode.MISSING_INTEGER;
ném ArgsException mới ();
} catch (ArgsException e) {
errorParameter = tham số;
errorCode = ErrorCode.INVALID_INTEGER;
ném e;
}
}
private void setStringArg (ArgumentMarshaler m) ném ArgsException {
currentArgument ++;
thử {
m.set (args [currentArgument]);
} catch (ArrayIndexOutOfBoundsException e) {
errorCode = ErrorCode.MISSING_STRING;
ném ArgsException mới ();
}
}
private void setBooleanArg (ArgumentMarshaler m) {
thử {
m.set ("true");
} catch (ArgsException e) {
}
}
public int cardinality () {
trả về argsFound.size ();
}
Liệt kê 14-12 (tiếp theo)
Args.java (Sau lần tái cấu trúc đầu tiên)
www.it-ebooks.info

Trang 260
229
Đối số chuỗi
sử dụng chuỗi công khai () {
nếu (schema.length ()> 0)
return "- [" + schema + "]";
khác
trở về "";
}
public String errorMessage () ném Exception {
switch (errorCode) {
trường hợp OK:
ném ngoại lệ mới ("TILT: Không nên đến đây.");
trường hợp UNEXPECTED_ARGUMENT:
trả về bất ngờArgumentMessage ();
trường hợp MISSING_STRING:
return String.format ("Không thể tìm thấy tham số chuỗi cho -% c.",
errorArgumentId);
trường hợp INVALID_INTEGER:
return String.format ("Đối số -% c mong đợi một số nguyên nhưng là '% s'.",
errorArgumentId, errorParameter);
trường hợp MISSING_INTEGER:
return String.format ("Không thể tìm thấy tham số số nguyên cho -% c.",
errorArgumentId);
}
trở về "";
}
chuỗi riêng tư bất ngờArgumentMessage () {
Thông báo StringBuffer = new StringBuffer ("(Các) đối số -");
cho (char c: bất ngờArguments) {
message.append (c);
}
message.append ("bất ngờ.");
trả về message.toString ();
}
public boolean getBoolean (char arg) {
Args.ArgumentMarshaler am = marshalers.get (arg);
boolean b = false;
thử {
b = am! = null && (Boolean) am.get ();
} catch (ClassCastException e) {
b = sai;
}
trả lại b;
}
public String getString (char arg) {
Args.ArgumentMarshaler am = marshalers.get (arg);
thử {
trả về am == null? "": (Chuỗi) am.get ();
} catch (ClassCastException e) {
trở về "";
}
}
Liệt kê 14-12 (tiếp theo)
Args.java (Sau lần tái cấu trúc đầu tiên)
www.it-ebooks.info

Trang 261
230
Chương 14: Sàng lọc thành công
public int getInt (char arg) {
Args.ArgumentMarshaler am = marshalers.get (arg);
thử {
trả về am == null? 0: (Số nguyên) am.get ();
} catch (Ngoại lệ e) {
trả về 0;
}
}
public boolean has (char arg) {
trả về argsFound.contains (arg);
}
public boolean isValid () {
trở lại hợp lệ;
}
private class ArgsException mở rộng Exception {
}
lớp trừu tượng riêng ArgumentMarshaler {
public abstract void set (String s) ném ArgsException;
public abstract Object get ();
}
lớp riêng BooleanArgumentMarshaler mở rộng ArgumentMarshaler {
boolean riêng booleanValue = false;
public void set (String s) {
booleanValue = true;
}
public Object get () {
trả về booleanValue;
}
}
lớp private StringArgumentMarshaler mở rộng ArgumentMarshaler {
private String stringValue = "";
public void set (String s) {
stringValue = s;
}
public Object get () {
trả về stringValue;
}
}
lớp riêng IntegerArgumentMarshaler mở rộng ArgumentMarshaler {
private int intValue = 0;
public void set (String s) ném ArgsException {
thử {
intValue = Integer.parseInt (s);
Liệt kê 14-12 (tiếp theo)
Args.java (Sau lần tái cấu trúc đầu tiên)
www.it-ebooks.info

Trang 262
231
Đối số chuỗi
Sau tất cả những công việc đó, điều này là một chút thất vọng. Cấu trúc tốt hơn một chút, nhưng chúng tôi vẫn
có tất cả các biến đó ở trên cùng; vẫn còn một trường hợp kiểu khủng khiếp trong setArgument ; và
tất cả những chức năng được thiết lập thực sự xấu xí. Chưa kể tất cả các xử lý lỗi. Chúng ta vẫn
có rất nhiều việc ở phía trước của chúng tôi.
Tôi thực sự muốn loại bỏ kiểu chữ đó trong setArgument [G23]. Những gì tôi muốn ở
setArgument là một lệnh gọi đến ArgumentMarshaler.set . Điều này có nghĩa là tôi cần phải đẩy
setIntArg , setStringArg và setBooleanArg xuống ArgumentMarshaler thích hợp
các dẫn xuất. Nhưng có một vấn đề.
Nếu bạn xem kỹ setIntArg , bạn sẽ nhận thấy rằng nó sử dụng hai biến phiên bản: args
và currentArg . Để chuyển setIntArg xuống BooleanArgumentMarshaler , tôi sẽ phải chuyển
cả args và currentArgs dưới dạng đối số của hàm. Thật là bẩn [F1]. Tôi muốn vượt qua một
đối số thay vì hai. May mắn thay, có một giải pháp đơn giản. Chúng tôi có thể chuyển đổi các args
mảng vào một danh sách và chuyển một Iterator xuống các hàm đã đặt . Những điều sau đã đưa tôi
mười bước, vượt qua tất cả các bài kiểm tra sau mỗi bước. Nhưng tôi sẽ chỉ cho bạn kết quả. Bạn nên
có thể tìm ra hầu hết các bước nhỏ nhỏ là gì.
public class Args {
lược đồ chuỗi riêng;
private String [] args;
boolean riêng hợp lệ = true;
private Set <Character> inheritArguments = new TreeSet <Character> ();
Bản đồ riêng tư <Character, ArgumentMarshaler> marshalers =
new HashMap <Character, ArgumentMarshaler> ();
private Set <Character> argsFound = new HashSet <Character> ();
private Iterator <String> currentArgument;
char errorArgumentId = '\ 0';
private String errorParameter = "TILT";
riêng ErrorCode errorCode = ErrorCode.OK;
danh sách riêng tư <String> argsList;
private enum ErrorCode {
OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT}
public Args (String schema, String [] args) ném ParseException {
this.schema = schema;
argsList = Arrays.asList (args);
hợp lệ = phân tích cú pháp ();
}
} catch (NumberFormatException e) {
ném ArgsException mới ();
}
}
public Object get () {
trả về intValue;
}
}
}
Liệt kê 14-12 (tiếp theo)
Args.java (Sau lần tái cấu trúc đầu tiên)
www.it-ebooks.info

Trang 263
232
Chương 14: Sàng lọc thành công
private boolean parse () ném ParseException {
if (schema.length () == 0 && argsList.size () == 0)
trả về true;
parseSchema ();
thử {
parseArguments ();
} catch (ArgsException e) {
}
trở lại hợp lệ;
}
---
private boolean parseArguments () ném ArgsException {
for (currentArgument = argsList.iterator () ; currentArgument. hasNext () ;) {
String arg = currentArgument. tiếp theo () ;
parseArgument (arg);
}
trả về true;
}
---
private void setIntArg (ArgumentMarshaler m) ném ArgsException {
Tham số chuỗi = null;
thử {
tham số = currentArgument. tiếp theo () ;
m.set (tham số);
} catch ( NoSuchElementException e) {
errorCode = ErrorCode.MISSING_INTEGER;
ném ArgsException mới ();
} catch (ArgsException e) {
errorParameter = tham số;
errorCode = ErrorCode.INVALID_INTEGER;
ném e;
}
}
private void setStringArg (ArgumentMarshaler m) ném ArgsException {
thử {
m.set (currentArgument .next () );
} catch ( NoSuchElementException e) {
errorCode = ErrorCode.MISSING_STRING;
ném ArgsException mới ();
}
}
Đây là những thay đổi đơn giản giúp tất cả các bài kiểm tra đều vượt qua. Bây giờ chúng ta có thể bắt đầu di
chuyển bộ
hàm xuống các đạo hàm thích hợp. Trước tiên, tôi cần thực hiện thay đổi sau
trong setArgument :
private boolean setArgument (char argChar) ném ArgsException {
ArgumentMarshaler m = marshalers.get (argChar);
if (m == null)
trả về sai;
thử {
if (m instanceof BooleanArgumentMarshaler)
setBooleanArg (m);
else if (m instanceof StringArgumentMarshaler)
setStringArg (m);
else if (m instanceof IntegerArgumentMarshaler)
setIntArg (m);
www.it-ebooks.info

Trang 264
233
Đối số chuỗi
khác
trả về sai;
} catch (ArgsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
ném e;
}
trả về true;
}
Thay đổi này rất quan trọng vì chúng tôi muốn loại bỏ hoàn toàn chuỗi if-else .
Do đó, chúng tôi cần phải loại bỏ tình trạng lỗi.
Bây giờ chúng ta có thể bắt đầu di chuyển các chức năng đã đặt . Hàm setBooleanArg là không đáng kể, vì vậy
chúng tôi sẽ chuẩn bị cái đó trước. Mục tiêu của chúng tôi là thay đổi hàm setBooleanArg thành-
ward vào BooleanArgumentMarshaler .
private boolean setArgument (char argChar) ném ArgsException {
ArgumentMarshaler m = marshalers.get (argChar);
if (m == null)
trả về sai;
thử {
if (m instanceof BooleanArgumentMarshaler)
setBooleanArg (m, currentArgument );
else if (m instanceof StringArgumentMarshaler)
setStringArg (m);
else if (m instanceof IntegerArgumentMarshaler)
setIntArg (m);
} catch (ArgsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
ném e;
}
trả về true;
}
---
private void setBooleanArg (ArgumentMarshaler m,
Trình lặp lại <Chuỗi> currentArgument)
ném ArgsException {
thử {
m.set ("true");
bắt (ArgsException e) {
}
}
Không phải chúng ta vừa đưa vào xử lý ngoại lệ đó sao? Đưa mọi thứ vào để bạn có thể lấy
chúng ra một lần nữa là khá phổ biến trong tái cấu trúc. Mức độ nhỏ của các bước và sự cần thiết phải
giữ cho các bài kiểm tra chạy có nghĩa là bạn di chuyển mọi thứ xung quanh rất nhiều. Refactoring rất giống
giải một khối Rubik. Có rất nhiều bước nhỏ cần thiết để đạt được một mục tiêu lớn. Mỗi
cho phép bước tiếp theo.
Tại sao chúng ta lại vượt qua trình lặp đó khi setBooleanArg chắc chắn không cần nó? Bởi vì
setIntArg và setStringArg sẽ! Và bởi vì tôi muốn triển khai cả ba chức năng này
thông qua một phương thức trừu tượng trong ArgumentMarshaller , tôi cần chuyển nó cho setBooleanArg .
www.it-ebooks.info

Trang 265
234
Chương 14: Sàng lọc thành công
Vì vậy, bây giờ setBooleanArg là vô dụng. Nếu có một hàm đã đặt trong ArgumentMarshaler , chúng tôi
có thể gọi nó trực tiếp. Vì vậy, đã đến lúc thực hiện chức năng đó! Bước đầu tiên là thêm mới
phương thức trừu tượng thành ArgumentMarshaler .
lớp trừu tượng riêng ArgumentMarshaler {
public abstract void set (Iterator <String> currentArgument)
ném ArgsException;
public abstract void set (String s) ném ArgsException;
public abstract Object get ();
}
Tất nhiên điều này phá vỡ tất cả các dẫn xuất. Vì vậy, hãy triển khai phương pháp mới trong mỗi.
lớp riêng BooleanArgumentMarshaler mở rộng ArgumentMarshaler {
boolean riêng booleanValue = false;
public void set (Iterator <String> currentArgument) ném ArgsException {
booleanValue = true;
}
public void set (String s) {
booleanValue = true;
}
public Object get () {
trả về booleanValue;
}
}
lớp private StringArgumentMarshaler mở rộng ArgumentMarshaler {
private String stringValue = "";
public void set (Iterator <String> currentArgument) ném ArgsException {
}
public void set (String s) {
stringValue = s;
}
public Object get () {
trả về stringValue;
}
}
lớp riêng IntegerArgumentMarshaler mở rộng ArgumentMarshaler {
private int intValue = 0;
public void set (Iterator <String> currentArgument) ném ArgsException {
}
public void set (String s) ném ArgsException {
thử {
intValue = Integer.parseInt (s);
} catch (NumberFormatException e) {
ném ArgsException mới ();
}
}
www.it-ebooks.info

Trang 266
235
Đối số chuỗi
public Object get () {
trả về intValue;
}
}
Và bây giờ chúng ta có thể loại bỏ setBooleanArg !
private boolean setArgument (char argChar) ném ArgsException {
ArgumentMarshaler m = marshalers.get (argChar);
if (m == null)
trả về sai;
thử {
if (m instanceof BooleanArgumentMarshaler)
m.set (currentArgument);
else if (m instanceof StringArgumentMarshaler)
setStringArg (m);
else if (m instanceof IntegerArgumentMarshaler)
setIntArg (m);
} catch (ArgsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
ném e;
}
trả về true;
}
Tất cả các bài kiểm tra đều vượt qua và hàm set đang triển khai tới BooleanArgumentMarshaler !
Bây giờ chúng ta có thể làm tương tự đối với Chuỗi và Số nguyên .
private boolean setArgument (char argChar) ném ArgsException {
ArgumentMarshaler m = marshalers.get (argChar);
if (m == null)
trả về sai;
thử {
if (m instanceof BooleanArgumentMarshaler)
m.set (currentArgument);
else if (m instanceof StringArgumentMarshaler)
m.set (currentArgument);
else if (m instanceof IntegerArgumentMarshaler)
m.set (currentArgument);
} catch (ArgsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
ném e;
}
trả về true;
}
---
lớp private StringArgumentMarshaler mở rộng ArgumentMarshaler {
private String stringValue = "";
public void set (Iterator <String> currentArgument) ném ArgsException {
thử {
stringValue = currentArgument.next ();
} catch (NoSuchElementException e) {
errorCode = ErrorCode.MISSING_STRING;
www.it-ebooks.info

Trang 267
236
Chương 14: Sàng lọc thành công
ném ArgsException mới ();
}
}
public void set (String s) {
}
public Object get () {
trả về stringValue;
}
}
lớp riêng IntegerArgumentMarshaler mở rộng ArgumentMarshaler {
private int intValue = 0;
public void set (Iterator <String> currentArgument) ném ArgsException {
Tham số chuỗi = null;
thử {
tham số = currentArgument.next ();
set (tham số);
} catch (NoSuchElementException e) {
errorCode = ErrorCode.MISSING_INTEGER;
ném ArgsException mới ();
} catch (ArgsException e) {
errorParameter = tham số;
errorCode = ErrorCode.INVALID_INTEGER;
ném e;
}
}
public void set (String s) ném ArgsException {
thử {
intValue = Integer.parseInt (s);
} catch (NumberFormatException e) {
ném ArgsException mới ();
}
}
public Object get () {
trả về intValue;
}
}
Và do đó, cuộc đảo chính : Có thể loại bỏ trường hợp loại! Touche!
private boolean setArgument (char argChar) ném ArgsException {
ArgumentMarshaler m = marshalers.get (argChar);
if (m == null)
trả về sai;
thử {
m.set (currentArgument);
trả về true;
} catch (ArgsException e) {
hợp lệ = sai;
errorArgumentId = argChar;
ném e;
}
}
www.it-ebooks.info

Trang 268
237
Đối số chuỗi
Bây giờ chúng ta có thể loại bỏ một số hàm nguy hiểm trong IntegerArgumentMarshaler và dọn dẹp nó
một chút.
lớp riêng IntegerArgumentMarshaler mở rộng ArgumentMarshaler {
private int intValue = 0
public void set (Iterator <String> currentArgument) ném ArgsException {
Tham số chuỗi = null;
thử {
tham số = currentArgument.next ();
intValue = Integer.parseInt (tham số);
} catch (NoSuchElementException e) {
errorCode = ErrorCode.MISSING_INTEGER;
ném ArgsException mới ();
} catch ( NumberFormatException e) {
errorParameter = tham số;
errorCode = ErrorCode.INVALID_INTEGER;
ném ArgsException mới ();
}
}
public Object get () {
trả về intValue;
}
}
Chúng ta cũng có thể biến ArgumentMarshaler thành một giao diện.
giao diện riêng ArgumentMarshaler {
void set (Iterator <String> currentArgument) ném ArgsException;
Đối tượng get ();
}
Vì vậy, bây giờ chúng ta hãy xem việc thêm một kiểu đối số mới vào cấu trúc của chúng ta dễ dàng như thế nào. Nó
nên
yêu cầu rất ít thay đổi và những thay đổi đó nên được tách biệt. Đầu tiên, chúng tôi bắt đầu bằng cách thêm
một trường hợp thử nghiệm mới để kiểm tra xem đối số kép có hoạt động chính xác hay không.
public void testSimpleDoublePresent () ném Exception {
Args args = new Args ("x ##", new String [] {"-x", "42.3"});
khẳng địnhTrue (args.isValid ());
khẳng địnhEquals (1, args.cardinality ());
khẳng địnhTrue (args.has ('x'));
khẳng địnhEquals (42.3, args.getDouble ('x'), .001);
}
Bây giờ chúng ta dọn dẹp mã schema phân tích và thêm ## phát hiện cho đôi
kiểu đối số.
private void parseSchemaElement (String element) ném ParseException {
char elementId = element.charAt (0);
String elementTail = element.substring (1);
validateSchemaElementId (elementId);
if (elementTail. length () == 0 )
marshalers.put (elementId, new BooleanArgumentMarshaler ());
else if (elementTail. equals ("*") )
marshalers.put (elementId, new StringArgumentMarshaler ());
else if (elementTail. bằng ("#") )
marshalers.put (elementId, new IntegerArgumentMarshaler ());
www.it-ebooks.info

Trang 269
238
Chương 14: Sàng lọc thành công
else if (elementTail.equals ("##"))
marshalers.put (elementId, new DoubleArgumentMarshaler ());
khác
ném ParseException mới (String.format (
"Đối số:% c có định dạng không hợp lệ:% s.", ElementId, elementTail), 0);
}
Tiếp theo, chúng ta viết lớp DoubleArgumentMarshaler .
lớp riêng DoubleArgumentMarshaler triển khai ArgumentMarshaler {
private doubleValue = 0;
public void set (Iterator <String> currentArgument) ném ArgsException {
Tham số chuỗi = null;
thử {
tham số = currentArgument.next ();
doubleValue = Double.parseDouble (tham số);
} catch (NoSuchElementException e) {
errorCode = ErrorCode.MISSING_DOUBLE;
ném ArgsException mới ();
} catch (NumberFormatException e) {
errorParameter = tham số;
errorCode = ErrorCode.INVALID_DOUBLE;
ném ArgsException mới ();
}
}
public Object get () {
trả về doubleValue;
}
}
Này buộc chúng tôi để thêm một mới ERRORCODE .
private enum ErrorCode {
OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT,
MISSING_DOUBLE, INVALID_DOUBLE }
Và chúng ta cần một hàm getDouble .
public double getDouble (char arg) {
Args.ArgumentMarshaler am = marshalers.get (arg);
thử {
trả về am == null? 0: (Đôi) am.get ();
} catch (Ngoại lệ e) {
trả về 0,0;
}
}
Và tất cả các bài kiểm tra đều vượt qua! Điều đó khá không đau. Vì vậy, bây giờ hãy đảm bảo tất cả các lỗi
xử lý hoạt động chính xác. Trường hợp kiểm thử tiếp theo kiểm tra xem có lỗi được khai báo không nếu
chuỗi không thể phân tích được đưa vào đối số ## .
public void testInvalidDouble () ném Exception {
Args args = new Args ("x ##", new String [] {"-x", "Bốn mươi hai"});
khẳng địnhFalse (args.isValid ());
khẳng địnhEquals (0, args.cardinality ());
khẳng địnhFalse (args.has ('x'));
khẳng địnhEquals (0, args.getInt ('x'));
www.it-ebooks.info

Trang 270
239
Đối số chuỗi
khẳng địnhEquals ("Đối số -x mong đợi nhân đôi nhưng là 'Bốn mươi hai'.",
args.errorMessage ());
}
---
public String errorMessage () ném Exception {
switch (errorCode) {
trường hợp OK:
ném ngoại lệ mới ("TILT: Không nên đến đây.");
trường hợp UNEXPECTED_ARGUMENT:
trả về bất ngờArgumentMessage ();
trường hợp MISSING_STRING:
return String.format ("Không thể tìm thấy tham số chuỗi cho -% c.",
errorArgumentId);
trường hợp INVALID_INTEGER:
return String.format ("Đối số -% c mong đợi một số nguyên nhưng là '% s'.",
errorArgumentId, errorParameter);
trường hợp MISSING_INTEGER:
return String.format ("Không thể tìm thấy tham số số nguyên cho -% c.",
errorArgumentId);
trường hợp INVALID_DOUBLE:
return String.format ("Đối số -% c mong đợi nhân đôi nhưng là '% s'.",
errorArgumentId, errorParameter);
trường hợp MISSING_DOUBLE:
return String.format ("Không thể tìm thấy tham số kép cho -% c.",
errorArgumentId);
}
trở về "";
}
Và các bài kiểm tra vượt qua. Thử nghiệm tiếp theo đảm bảo rằng chúng tôi phát hiện đúng đối số kép bị thiếu .
public void testMissingDouble () ném Exception {
Args args = new Args ("x ##", new String [] {"- x"});
khẳng địnhFalse (args.isValid ());
khẳng địnhEquals (0, args.cardinality ());
khẳng địnhFalse (args.has ('x'));
khẳng địnhEquals (0,0, args.getDouble ('x'), 0,01);
khẳng địnhEquals ("Không thể tìm thấy tham số kép cho -x.",
args.errorMessage ());
}
Điều này trôi qua như mong đợi. Chúng tôi đã viết nó đơn giản cho sự hoàn chỉnh.
Mã ngoại lệ khá xấu và không thực sự thuộc về lớp Args . Chúng tôi là
cũng loại bỏ ParseException , mà không thực sự thuộc về chúng tôi. Vì vậy, hãy hợp nhất tất cả
ngoại lệ vào một lớp ArgsException duy nhất và chuyển nó vào mô-đun riêng của nó.
public class ArgsException mở rộng Exception {
char errorArgumentId = '\ 0';
private String errorParameter = "TILT";
riêng ErrorCode errorCode = ErrorCode.OK;
public ArgsException () {}
public ArgsException (String message) {super (message);}
public enum ErrorCode {
OK, MISSING_STRING, MISSING_INTEGER, INVALID_INTEGER, UNEXPECTED_ARGUMENT,
MISSING_DOUBLE, INVALID_DOUBLE}
}
---
www.it-ebooks.info

Trang 271
240
Chương 14: Sàng lọc thành công
public class Args {
...
char errorArgumentId = '\ 0';
private String errorParameter = "TILT";
private ArgsException .ErrorCode errorCode = ArgsException .ErrorCode.OK;
danh sách riêng tư <String> argsList;
public Args (String schema, String [] args) ném ArgsException {
this.schema = schema;
argsList = Arrays.asList (args);
hợp lệ = phân tích cú pháp ();
}
private boolean parse () ném ArgsException {
if (schema.length () == 0 && argsList.size () == 0)
trả về true;
parseSchema ();
thử {
parseArguments ();
} catch ( ArgsException e) {
}
trở lại hợp lệ;
}
private boolean parseSchema () ném ArgsException {
...
}
private void parseSchemaElement (String element) ném ArgsException {
...
khác
ném ArgsException mới (
String.format ("Đối số:% c có định dạng không hợp lệ:% s.",
elementId, elementTail));
}
private void validateSchemaElementId (char elementId) ném ArgsException {
if (! Character.isLetter (elementId)) {
ném ArgsException mới (
"Ký tự xấu:" + elementId + "ở định dạng Args:" + schema);
}
}
...
private void parseElement (char argChar) ném ArgsException {
if (setArgument (argChar))
argsFound.add (argChar);
khác {
bất ngờArguments.add (argChar);
errorCode = ArgsException .ErrorCode.UNEXPECTED_ARGUMENT;
hợp lệ = sai;
}
}
...
www.it-ebooks.info

Trang 272
241
Đối số chuỗi
lớp riêng StringArgumentMarshaler triển khai ArgumentMarshaler {
private String stringValue = "";
public void set (Iterator <String> currentArgument) ném ArgsException {
thử {
stringValue = currentArgument.next ();
} catch (NoSuchElementException e) {
errorCode = ArgsException .ErrorCode.MISSING_STRING;
ném ArgsException mới ();
}
}
public Object get () {
trả về stringValue;
}
}
lớp riêng IntegerArgumentMarshaler triển khai ArgumentMarshaler {
private int intValue = 0;
public void set (Iterator <String> currentArgument) ném ArgsException {
Tham số chuỗi = null;
thử {
tham số = currentArgument.next ();
intValue = Integer.parseInt (tham số);
} catch (NoSuchElementException e) {
errorCode = ArgsException.ErrorCode.MISSING_INTEGER;
ném ArgsException mới ();
} catch (NumberFormatException e) {
errorParameter = tham số;
errorCode = ArgsException .ErrorCode.INVALID_INTEGER;
ném ArgsException mới ();
}
}
public Object get () {
trả về intValue;
}
}
lớp riêng DoubleArgumentMarshaler triển khai ArgumentMarshaler {
private doubleValue = 0;
public void set (Iterator <String> currentArgument) ném ArgsException {
Tham số chuỗi = null;
thử {
tham số = currentArgument.next ();
doubleValue = Double.parseDouble (tham số);
} catch (NoSuchElementException e) {
errorCode = ArgsException .ErrorCode.MISSING_DOUBLE;
ném ArgsException mới ();
} catch (NumberFormatException e) {
errorParameter = tham số;
errorCode = ArgsException .ErrorCode.INVALID_DOUBLE;
ném ArgsException mới ();
}
}
www.it-ebooks.info

Trang 273
242
Chương 14: Sàng lọc thành công
public Object get () {
trả về doubleValue;
}
}
}
Cái này đẹp đấy. Bây giờ ngoại lệ duy nhất được Args ném ra là ArgsException . Di chuyển
ArgsException vào mô-đun riêng của nó có nghĩa là chúng ta có thể di chuyển rất nhiều thứ linh tinh
mã hỗ trợ lỗi vào mô-đun đó và ra khỏi mô-đun Args . Nó cung cấp một tự nhiên và
nơi rõ ràng để đặt tất cả mã đó và thực sự sẽ giúp chúng tôi dọn dẹp mô-đun Args
ở đằng trước.
Vì vậy, bây giờ chúng tôi đã hoàn toàn tách biệt ngoại lệ và mã lỗi khỏi Args
mô-đun. (Xem Liệt kê 14-13 đến Liệt kê 14-16.) Điều này đạt được nhờ một loạt
khoảng 30 bước nhỏ, giữ cho các bài kiểm tra trôi qua giữa mỗi bước.
Liệt kê 14-13
ArgsTest.java
gói com.objectmentor.utilities.args;
nhập junit.framework.TestCase;
public class ArgsTest mở rộng TestCase {
public void testCreateWithNoSchemaOrArguments () ném Exception {
Args args = new Args ("", new String [0]);
khẳng địnhEquals (0, args.cardinality ());
}
public void testWithNoSchemaButWithOneArgument () ném Exception {
thử {
new Args ("", new String [] {"- x"});
Thất bại();
} catch (ArgsException e) {
khẳng địnhEquals (ArgsException.ErrorCode.UNEXPECTED_ARGUMENT,
e.getErrorCode ());
khẳng địnhEquals ('x', e.getErrorArgumentId ());
}
}
public void testWithNoSchemaButWithMultipleArguments () ném Exception {
thử {
new Args ("", new String [] {"- x", "-y"});
Thất bại();
} catch (ArgsException e) {
khẳng địnhEquals (ArgsException.ErrorCode.UNEXPECTED_ARGUMENT,
e.getErrorCode ());
khẳng địnhEquals ('x', e.getErrorArgumentId ());
}
}
public void testNonLetterSchema () ném Exception {
thử {
new Args ("*", new String [] {});
fail ("Hàm tạo Args nên ném ngoại lệ");
} catch (ArgsException e) {
www.it-ebooks.info

Trang 274
243
Đối số chuỗi
khẳng địnhEquals (ArgsException.ErrorCode.INVALID_ARGUMENT_NAME,
e.getErrorCode ());
khẳng địnhEquals ('*', e.getErrorArgumentId ());
}
}
public void testInvalidArgumentFormat () ném Exception {
thử {
new Args ("f ~", new String [] {});
fail ("Hàm tạo Args nên có ngoại lệ ném");
} catch (ArgsException e) {
khẳng địnhEquals (ArgsException.ErrorCode.INVALID_FORMAT, e.getErrorCode ());
khẳng địnhEquals ('f', e.getErrorArgumentId ());
}
}
public void testSimpleBooleanPresent () ném Exception {
Args args = new Args ("x", new String [] {"- x"});
khẳng địnhEquals (1, args.cardinality ());
khẳng địnhEquals (true, args.getBoolean ('x'));
}
public void testSimpleStringPresent () ném Ngoại lệ {
Args args = new Args ("x *", new String [] {"- x", "param"});
khẳng địnhEquals (1, args.cardinality ());
khẳng địnhTrue (args.has ('x'));
khẳng địnhEquals ("param", args.getString ('x'));
}
public void testMissingStringArgument () ném Exception {
thử {
new Args ("x *", new String [] {"- x"});
Thất bại();
} catch (ArgsException e) {
khẳng địnhEquals (ArgsException.ErrorCode.MISSING_STRING, e.getErrorCode ());
khẳng địnhEquals ('x', e.getErrorArgumentId ());
}
}
public void testSpacesInFormat () ném Exception {
Args args = new Args ("x, y", new String [] {"- xy"});
khẳng địnhEquals (2, args.cardinality ());
khẳng địnhTrue (args.has ('x'));
khẳng địnhTrue (args.has ('y'));
}
public void testSimpleIntPresent () ném Exception {
Args args = new Args ("x #", new String [] {"- x", "42"});
khẳng địnhEquals (1, args.cardinality ());
khẳng địnhTrue (args.has ('x'));
khẳng địnhEquals (42, args.getInt ('x'));
}
public void testInvalidInteger () ném Exception {
thử {
new Args ("x #", new String [] {"- x", "Bốn mươi hai"});
Liệt kê 14-13 (tiếp theo)
ArgsTest.java
www.it-ebooks.info

Trang 275
244
Chương 14: Sàng lọc thành công
Thất bại();
} catch (ArgsException e) {
khẳng địnhEquals (ArgsException.ErrorCode.INVALID_INTEGER, e.getErrorCode ());
khẳng địnhEquals ('x', e.getErrorArgumentId ());
khẳng địnhEquals ("Bốn mươi hai", e.getErrorParameter ());
}
}
public void testMissingInteger () ném Exception {
thử {
new Args ("x #", new String [] {"- x"});
Thất bại();
} catch (ArgsException e) {
khẳng địnhEquals (ArgsException.ErrorCode.MISSING_INTEGER, e.getErrorCode ());
khẳng địnhEquals ('x', e.getErrorArgumentId ());
}
}
public void testSimpleDoublePresent () ném Exception {
Args args = new Args ("x ##", new String [] {"- x", "42.3"});
khẳng địnhEquals (1, args.cardinality ());
khẳng địnhTrue (args.has ('x'));
khẳng địnhEquals (42.3, args.getDouble ('x'), .001);
}
public void testInvalidDouble () ném Exception {
thử {
new Args ("x ##", new String [] {"- x", "Bốn mươi hai"});
Thất bại();
} catch (ArgsException e) {
khẳng địnhEquals (ArgsException.ErrorCode.INVALID_DOUBLE, e.getErrorCode ());
khẳng địnhEquals ('x', e.getErrorArgumentId ());
khẳng địnhEquals ("Bốn mươi hai", e.getErrorParameter ());
}
}
public void testMissingDouble () ném Exception {
thử {
new Args ("x ##", new String [] {"- x"});
Thất bại();
} catch (ArgsException e) {
khẳng địnhEquals (ArgsException.ErrorCode.MISSING_DOUBLE, e.getErrorCode ());
khẳng địnhEquals ('x', e.getErrorArgumentId ());
}
}
}
Liệt kê 14-14
ArgsExceptionTest.java
public class ArgsExceptionTest mở rộng TestCase {
public void testUnepectMessage () ném Exception {
ArgsException e =
Liệt kê 14-13 (tiếp theo)
ArgsTest.java
www.it-ebooks.info

Trang 276
245
Đối số chuỗi
ArgsException mới (ArgsException.ErrorCode.UNEXPECTED_ARGUMENT,
'x', null);
khẳng địnhEquals ("Đối số -x không mong đợi.", e.errorMessage ());
}
public void testMissingStringMessage () ném Exception {
ArgsException e = new ArgsException (ArgsException.ErrorCode.MISSING_STRING,
'x', null);
khẳng địnhEquals ("Không thể tìm thấy tham số chuỗi cho -x.", e.errorMessage ());
}
public void testInvalidIntegerMessage () ném Exception {
ArgsException e =
ArgsException mới (ArgsException.ErrorCode.INVALID_INTEGER,
'x', "Bốn mươi hai");
khẳng địnhEquals ("Đối số -x mong đợi một số nguyên nhưng là 'Bốn mươi hai'.",
e.errorMessage ());
}
public void testMissingIntegerMessage () ném Exception {
ArgsException e =
new ArgsException (ArgsException.ErrorCode.MISSING_INTEGER, 'x', null);
khẳng địnhEquals ("Không thể tìm thấy tham số nguyên cho -x.", e.errorMessage ());
}
public void testInvalidDoubleMessage () ném Exception {
ArgsException e = new ArgsException (ArgsException.ErrorCode.INVALID_DOUBLE,
'x', "Bốn mươi hai");
khẳng địnhEquals ("Đối số -x mong đợi nhân đôi nhưng là 'Bốn mươi hai'.",
e.errorMessage ());
}
public void testMissingDoubleMessage () ném Exception {
ArgsException e = new ArgsException (ArgsException.ErrorCode.MISSING_DOUBLE,
'x', null);
khẳng địnhEquals ("Không thể tìm thấy tham số kép cho -x.", e.errorMessage ());
}
}
Liệt kê 14-15
ArgsException.java
public class ArgsException mở rộng Exception {
char errorArgumentId = '\ 0';
private String errorParameter = "TILT";
riêng ErrorCode errorCode = ErrorCode.OK;
public ArgsException () {}
public ArgsException (String message) {super (message);}
public ArgsException (ErrorCode errorCode) {
this.errorCode = errorCode;
}
Liệt kê 14-14 (tiếp theo)
ArgsExceptionTest.java
www.it-ebooks.info

Trang 277
246
Chương 14: Sàng lọc thành công
public ArgsException (ErrorCode errorCode, String errorParameter) {
this.errorCode = errorCode;
this.errorParameter = errorParameter;
}
public ArgsException (ErrorCode errorCode, char errorArgumentId,
String errorParameter) {
this.errorCode = errorCode;
this.errorParameter = errorParameter;
this.errorArgumentId = errorArgumentId;
}
public char getErrorArgumentId () {
trả về errorArgumentId;
}
public void setErrorArgumentId (char errorArgumentId) {
this.errorArgumentId = errorArgumentId;
}
public String getErrorParameter () {
trả về errorParameter;
}
public void setErrorParameter (String errorParameter) {
this.errorParameter = errorParameter;
}
public ErrorCode getErrorCode () {
trả về mã lỗi;
}
public void setErrorCode (ErrorCode errorCode) {
this.errorCode = errorCode;
}
public String errorMessage () ném Exception {
switch (errorCode) {
trường hợp OK:
ném ngoại lệ mới ("TILT: Không nên đến đây.");
trường hợp UNEXPECTED_ARGUMENT:
return String.format ("Đối số -% c không mong đợi.", errorArgumentId);
trường hợp MISSING_STRING:
return String.format ("Không thể tìm thấy tham số chuỗi cho -% c.",
errorArgumentId);
trường hợp INVALID_INTEGER:
return String.format ("Đối số -% c mong đợi một số nguyên nhưng là '% s'.",
errorArgumentId, errorParameter);
trường hợp MISSING_INTEGER:
return String.format ("Không thể tìm thấy tham số số nguyên cho -% c.",
errorArgumentId);
trường hợp INVALID_DOUBLE:
return String.format ("Đối số -% c mong đợi nhân đôi nhưng là '% s'.",
errorArgumentId, errorParameter);
Liệt kê 14-15 (tiếp theo)
ArgsException.java
www.it-ebooks.info

Trang 278
247
Đối số chuỗi
trường hợp MISSING_DOUBLE:
return String.format ("Không thể tìm thấy tham số kép cho -% c.",
errorArgumentId);
}
trở về "";
}
public enum ErrorCode {
OK, INVALID_FORMAT, UNEXPECTED_ARGUMENT, INVALID_ARGUMENT_NAME,
MISSING_STRING,
MISSING_INTEGER, INVALID_INTEGER,
MISSING_DOUBLE, INVALID_DOUBLE}
}
Liệt kê 14-16
Args.java
public class Args {
lược đồ chuỗi riêng;
Bản đồ riêng tư <Character, ArgumentMarshaler> marshalers =
new HashMap <Character, ArgumentMarshaler> ();
private Set <Character> argsFound = new HashSet <Character> ();
private Iterator <String> currentArgument;
danh sách riêng tư <String> argsList;
public Args (String schema, String [] args) ném ArgsException {
this.schema = schema;
argsList = Arrays.asList (args);
phân tích cú pháp ();
}
private void parse () ném ArgsException {
parseSchema ();
parseArguments ();
}
private boolean parseSchema () ném ArgsException {
for (Phần tử chuỗi: schema.split (",")) {
if (element.length ()> 0) {
parseSchemaElement (element.trim ());
}
}
trả về true;
}
private void parseSchemaElement (String element) ném ArgsException {
char elementId = element.charAt (0);
String elementTail = element.substring (1);
validateSchemaElementId (elementId);
if (elementTail.length () == 0)
marshalers.put (elementId, new BooleanArgumentMarshaler ());
else if (elementTail.equals ("*"))
marshalers.put (elementId, new StringArgumentMarshaler ());
Liệt kê 14-15 (tiếp theo)
ArgsException.java
www.it-ebooks.info
Trang 279
248
Chương 14: Sàng lọc thành công
else if (elementTail.equals ("#"))
marshalers.put (elementId, new IntegerArgumentMarshaler ());
else if (elementTail.equals ("##"))
marshalers.put (elementId, new DoubleArgumentMarshaler ());
khác
ném ArgsException mới (ArgsException.ErrorCode.INVALID_FORMAT,
elementId, elementTail);
}
private void validateSchemaElementId (char elementId) ném ArgsException {
if (! Character.isLetter (elementId)) {
ném ArgsException mới (ArgsException.ErrorCode.INVALID_ARGUMENT_NAME,
elementId, null);
}
}
private void parseArguments () ném ArgsException {
for (currentArgument = argsList.iterator (); currentArgument.hasNext ();) {
String arg = currentArgument.next ();
parseArgument (arg);
}
}
private void parseArgument (String arg) ném ArgsException {
if (arg.startsWith ("-"))
parseElements (arg);
}
private void parseElements (String arg) ném ArgsException {
for (int i = 1; i <arg.length (); i ++)
parseElement (arg.charAt (i));
}
private void parseElement (char argChar) ném ArgsException {
if (setArgument (argChar))
argsFound.add (argChar);
khác {
ném ArgsException mới (ArgsException.ErrorCode.UNEXPECTED_ARGUMENT,
argChar, null);
}
}
private boolean setArgument (char argChar) ném ArgsException {
ArgumentMarshaler m = marshalers.get (argChar);
if (m == null)
trả về sai;
thử {
m.set (currentArgument);
trả về true;
} catch (ArgsException e) {
e.setErrorArgumentId (argChar);
ném e;
}
}
Liệt kê 14-16 (tiếp theo)
Args.java
www.it-ebooks.info

Trang 280
249
Đối số chuỗi
public int cardinality () {
trả về argsFound.size ();
}
sử dụng chuỗi công khai () {
nếu (schema.length ()> 0)
return "- [" + schema + "]";
khác
trở về "";
}
public boolean getBoolean (char arg) {
ArgumentMarshaler am = marshalers.get (arg);
boolean b = false;
thử {
b = am! = null && (Boolean) am.get ();
} catch (ClassCastException e) {
b = sai;
}
trả lại b;
}
public String getString (char arg) {
ArgumentMarshaler am = marshalers.get (arg);
thử {
trả về am == null? "": (Chuỗi) am.get ();
} catch (ClassCastException e) {
trở về "";
}
}
public int getInt (char arg) {
ArgumentMarshaler am = marshalers.get (arg);
thử {
trả về am == null? 0: (Số nguyên) am.get ();
} catch (Ngoại lệ e) {
trả về 0;
}
}
public double getDouble (char arg) {
ArgumentMarshaler am = marshalers.get (arg);
thử {
trả về am == null? 0: (Đôi) am.get ();
} catch (Ngoại lệ e) {
trả về 0,0;
}
}
public boolean has (char arg) {
trả về argsFound.contains (arg);
}
}
Liệt kê 14-16 (tiếp theo)
Args.java
www.it-ebooks.info

Trang 281
250
Chương 14: Sàng lọc thành công
Phần lớn các thay đổi đối với lớp Args là xóa. Rất nhiều mã vừa nhận được
chuyển ra khỏi Args và đưa vào ArgsException . Đẹp. Chúng tôi cũng đã di chuyển tất cả
ArgumentMarshaller s vào các tệp của riêng họ. Tốt hơn!
Phần lớn thiết kế phần mềm tốt chỉ đơn giản là phân vùng — tạo ra
nơi để đặt các loại mã khác nhau. Sự tách biệt các mối quan tâm này làm cho mã nhiều
đơn giản hơn để hiểu và duy trì.
Quan tâm đặc biệt là phương thức errorMessage của ArgsException . Rõ ràng đó là một vio-
lation của SRP để đưa định dạng thông báo lỗi vào Args . Args nên về
xử lý các đối số, không phải về định dạng của thông báo lỗi. Tuy nhiên, nó có
thực sự có ý nghĩa khi đặt mã định dạng thông báo lỗi vào ArgsException ?
Thành thật mà nói, đó là một sự thỏa hiệp. Người dùng không thích các thông báo lỗi do
ArgsException sẽ phải viết riêng của chúng. Nhưng sự tiện lợi của việc có lỗi đóng hộp
tin nhắn đã được chuẩn bị cho bạn không phải là không đáng kể.
Bây giờ, rõ ràng là chúng ta đang ở trong khoảng cách đáng kể đến giải pháp cuối cùng
xuất hiện ở đầu chương này. Tôi sẽ để lại những biến đổi cuối cùng cho bạn như một bài tập.
Phần kết luận
Nó không đủ để mã hoạt động. Mã hoạt động thường bị hỏng nặng. Lập trình viên
những người tự thỏa mãn với mã làm việc đơn thuần đang hành xử thiếu chuyên nghiệp. Họ
có thể sợ rằng họ không có thời gian để cải thiện cấu trúc và thiết kế mã của họ, nhưng tôi
không đồng ý. Không có gì có tác động làm suy giảm sâu sắc và lâu dài hơn đối với sự phát triển-
dự án cố vấn hơn là mã xấu. Lịch trình tồi có thể được làm lại, yêu cầu không tốt có thể được làm lại-
bị phạt. Động lực của đội xấu có thể được sửa chữa. Nhưng mã xấu bị thối rữa và lên men, trở thành
trọng lượng không thể thay đổi kéo theo đội xuống. Hết lần này đến lần khác tôi thấy các đội nghiền
thu thập thông tin bởi vì, trong sự vội vàng của họ, họ đã tạo ra một đoạn mã ác tính mãi mãi
sau đó đã thống trị số phận của họ.
Tất nhiên mã xấu có thể được làm sạch. Nhưng nó rất đắt. Khi mã bị thối rữa, mod-
các ules bóng gió với nhau, tạo ra rất nhiều phụ thuộc ẩn và rối-
cies. Tìm kiếm và phá vỡ những phụ thuộc cũ là một nhiệm vụ lâu dài và gian khổ. Mặt khác,
giữ mã sạch là tương đối dễ dàng. Nếu bạn làm một mô-đun lộn xộn vào buổi sáng, thì đó là
dễ dàng để làm sạch nó vào buổi chiều. Tốt hơn nữa, nếu bạn đã gây ra một mớ hỗn độn cách đây 5 phút, nó
rất dễ dàng để làm sạch nó ngay bây giờ.
Vì vậy, giải pháp là liên tục giữ cho mã của bạn sạch sẽ và đơn giản nhất có thể.
Không bao giờ để thối bắt đầu.
www.it-ebooks.info

Trang 282
251

15
JUnit Internals
JUnit là một trong những framework nổi tiếng nhất của Java. Khi các khuôn khổ hoạt động, nó đơn giản trong
khái niệm, chính xác trong định nghĩa và trang nhã trong thực hiện. Nhưng mã là gì
trông giống như? Trong chương này, chúng tôi sẽ phê bình một ví dụ được rút ra từ khung công tác JUnit.
www.it-ebooks.info

Trang 283
252
Chương 15: Nội bộ JUnit
Khung JUnit
JUnit đã có nhiều tác giả, nhưng nó bắt đầu với Kent Beck và Eric Gamma cùng nhau trên một
máy bay đến Atlanta. Kent muốn học Java và Eric muốn tìm hiểu về Kent's Small-
khuôn khổ thử nghiệm nói chuyện. “Điều gì có thể tự nhiên hơn đối với một vài chuyên gia trong khu vực chật chội
quý hơn là rút máy tính xách tay của chúng tôi ra và bắt đầu viết mã? " 1 Sau ba giờ ở độ cao
làm việc, họ đã viết những điều cơ bản về JUnit.
Mô-đun chúng ta sẽ xem xét là đoạn mã thông minh giúp xác định so sánh chuỗi-
con trai lỗi. Mô-đun này được gọi là ComparisonCompactor . Cho hai chuỗi khác nhau,
chẳng hạn như ABCDE và ABXDE, nó sẽ cho thấy sự khác biệt bằng cách tạo một chuỗi chẳng hạn như
<... B [X] D ...> .
Tôi có thể giải thích thêm, nhưng các trường hợp thử nghiệm hoạt động tốt hơn. Vì vậy, hãy xem Liệt kê 15-1
và bạn sẽ hiểu sâu các yêu cầu của mô-đun này. Trong khi bạn ở đó,
phê bình cấu trúc của các bài kiểm tra. Chúng có thể đơn giản hơn hoặc rõ ràng hơn?
1. Hướng dẫn bỏ túi JUnit , Kent Beck, O'Reilly, 2004, tr. 43.
Liệt kê 15-1
ComparisonCompactorTest.java
gói junit.tests.framework;
nhập junit.framework.ComparisonCompactor;
nhập junit.framework.TestCase;
public class ComparisonCompactorTest mở rộng TestCase {
public void testMessage () {
String fail = new ComparisonCompactor (0, "b", "c"). Compact ("a");
khẳng địnhTrue ("dự kiến: <[b]> nhưng là: <[c]>". bằng (thất bại));
}
public void testStartSame () {
String fail = new ComparisonCompactor (1, "ba", "bc"). Compact (null);
khẳng địnhEquals ("mong đợi: <b [a]> nhưng là: <b [c]>", thất bại);
}
public void testEndSame () {
String fail = new ComparisonCompactor (1, "ab", "cb"). Compact (null);
khẳng địnhEquals ("mong đợi: <[a] b> nhưng là: <[c] b>", thất bại);
}
public void testSame () {
String fail = new ComparisonCompactor (1, "ab", "ab"). Compact (null);
khẳng địnhEquals ("mong đợi: <ab> nhưng là: <ab>", thất bại);
}
public void testNoContextStartAndEndSame () {
String fail = new ComparisonCompactor (0, "abc", "adc"). Compact (null);
khẳng địnhEquals ("mong đợi: <... [b] ...> nhưng là: <... [d] ...>", thất bại);
}
www.it-ebooks.info

Trang 284
253
Khung JUnit
public void testStartAndEndContext () {
String fail = new ComparisonCompactor (1, "abc", "adc"). Compact (null);
khẳng địnhEquals ("mong đợi: <a [b] c> nhưng là: <a [d] c>", thất bại);
}
public void testStartAndEndContextWithEllipses () {
Chuỗi thất bại =
new ComparisonCompactor (1, "abcde", "abfde"). compact (null);
khẳng địnhEquals ("dự kiến: <... b [c] d ...> nhưng là: <... b [f] d ...>", fail);
}
public void testComparisonErrorStartSameComplete () {
String fail = new ComparisonCompactor (2, "ab", "abc"). Compact (null);
khẳng địnhEquals ("mong đợi: <ab []> nhưng là: <ab [c]>", thất bại);
}
public void testComparisonErrorEndSameComplete () {
String fail = new ComparisonCompactor (0, "bc", "abc"). Compact (null);
khẳng địnhEquals ("mong đợi: <[] ...> nhưng là: <[a] ...>", thất bại);
}
public void testComparisonErrorEndSameCompleteContext () {
String fail = new ComparisonCompactor (2, "bc", "abc"). Compact (null);
khẳng địnhEquals ("mong đợi: <[] bc> nhưng là: <[a] bc>", thất bại);
}
public void testComparisonErrorOverlapingMatches () {
String fail = new ComparisonCompactor (0, "abc", "abbc"). Compact (null);
khẳng địnhEquals ("mong đợi: <... [] ...> nhưng là: <... [b] ...>", thất bại);
}
public void testComparisonErrorOverlapingMatchesContext () {
String fail = new ComparisonCompactor (2, "abc", "abbc"). Compact (null);
khẳng địnhEquals ("mong đợi: <ab [] c> nhưng là: <ab [b] c>", thất bại);
}
public void testComparisonErrorOverlapingMatches2 () {
String fail = new ComparisonCompactor (0, "abcdde",
"abcde"). compact (null);
khẳng địnhEquals ("mong đợi: <... [d] ...> nhưng là: <... [] ...>", thất bại);
}
public void testComparisonErrorOverlapingMatches2Context () {
Chuỗi thất bại =
new ComparisonCompactor (2, "abcdde", "abcde"). compact (null);
khẳng địnhEquals ("dự kiến: <... cd [d] e> nhưng là: <... cd [] e>", thất bại);
}
public void testComparisonErrorWithActualNull () {
String fail = new ComparisonCompactor (0, "a", null) .compact (null);
khẳng địnhEquals ("mong đợi: <a> nhưng là: <null>", thất bại);
}
public void testComparisonErrorWithActualNullContext () {
String fail = new ComparisonCompactor (2, "a", null) .compact (null);
Liệt kê 15-1 (tiếp theo)
ComparisonCompactorTest.java
www.it-ebooks.info

Trang 285
254
Chương 15: Nội bộ JUnit
Tôi đã chạy phân tích mức độ phù hợp của mã trên ComparisonCompactor bằng cách sử dụng các bài kiểm tra này. Mật

được bảo hiểm 100 phần trăm. Mỗi dòng mã, mỗi khi tuyên bố và cho vòng lặp, được thực hiện bởi
Các bài kiểm tra. Điều này mang lại cho tôi sự tin tưởng cao rằng mã hoạt động và mức độ cao
tôn trọng sự khéo léo của các tác giả.
Mã cho ComparisonCompactor nằm trong Liệt kê 15-2. Hãy dành một chút thời gian để xem qua điều này
mã. Tôi nghĩ bạn sẽ thấy nó được phân vùng độc đáo, diễn đạt hợp lý và đơn giản trong
kết cấu. Khi bạn đã hoàn tất, chúng ta sẽ cùng nhau chọn các nits.
khẳng địnhEquals ("mong đợi: <a> nhưng là: <null>", thất bại);
}
public void testComparisonErrorWithEosystemNull () {
String fail = new ComparisonCompactor (0, null, "a"). Compact (null);
khẳng địnhEquals ("mong đợi: <null> nhưng là: <a>", thất bại);
}
public void testComparisonErrorWithE DựNullContext () {
String fail = new ComparisonCompactor (2, null, "a"). Compact (null);
khẳng địnhEquals ("mong đợi: <null> nhưng là: <a>", thất bại);
}
public void testBug609972 () {
String fail = new ComparisonCompactor (10, "S & P500", "0"). Compact (null);
khẳng địnhEquals ("mong đợi: <[S & P50] 0> nhưng là: <[] 0>", thất bại);
}
}
Liệt kê 15-2
ComparisonCompactor.java (Bản gốc)
gói junit.framework;
Public class ComparisonCompactor {
private static final String ELLIPSIS = "...";
Chuỗi cuối cùng tĩnh riêng tư DELTA_END = "]";
chuỗi cuối cùng tĩnh riêng DELTA_START = "[";
private int fContextLength;
chuỗi riêng fE dự kiến;
private String fActual;
int fPrefix riêng;
int fSuffix riêng tư;
public ComparisonCompactor (int contextLength,
Chuỗi dự kiến,
Chuỗi thực tế) {
fContextLength = contextLength;
fEpris = dự kiến;
fActual = thực tế;
}
Liệt kê 15-1 (tiếp theo)
ComparisonCompactorTest.java
www.it-ebooks.info

Trang 286
255
Khung JUnit
public String compact (String message) {
if (fEosystem == null || fActual == null || areStringsEqual ())
return Assert.format (message, fE dự kiến, fActual);
findCommonPrefix ();
findCommonSuffix ();
Chuỗi dự kiến = compactString (fE dự kiến);
Chuỗi thực tế = compactString (fActual);
return Assert.format (thông báo, dự kiến, thực tế);
}
private String compactString (Nguồn chuỗi) {
Kết quả chuỗi = DELTA_START +
source.substring (fPrefix, source.length () -
fSuffix + 1) + DELTA_END;
if (fPrefix> 0)
result = computeCommonPrefix () + kết quả;
nếu (fSuffix> 0)
result = result + computeCommonSuffix ();
trả về kết quả;
}
private void findCommonPrefix () {
fPrefix = 0;
int end = Math.min (fEosystem.length (), fActual.length ());
for (; fPrefix <end; fPrefix ++) {
if (fEosystem.charAt (fPrefix)! = fActual.charAt (fPrefix))
phá vỡ;
}
}
private void findCommonSuffix () {
int dự kiếnSuffix = fEosystem.length () - 1;
int realSuffix = fActual.length () - 1;
cho (;
thực tếSuffix> = fPrefix && mong đợiSuffix> = fPrefix;
Thực tếSuffix--, Dự kiếnSuffix--) {
if (fEooter.charAt (mong đợiSuffix)! = fActual.charAt (thực tếSuffix))
phá vỡ;
}
fSuffix = fEooter.length () - dự kiếnSuffix;
}
private String computeCommonPrefix () {
return (fPrefix> fContextLength? ELLIPSIS: "") +
fEpris.substring (Math.max (0, fPrefix - fContextLength),
fPrefix);
}
private String computeCommonSuffix () {
int end = Math.min (fEosystem.length () - fSuffix + 1 + fContextLength,
fE dự kiến.length ());
trả về fEosystem.substring (fEosystem.length () - fSuffix + 1, end) +
(fE dự kiến.length () - fSuffix + 1 <fE dự kiến.length () -
fContextLength? ELLIPSIS: "");
}
Liệt kê 15-2 (tiếp theo)
ComparisonCompactor.java (Bản gốc)
www.it-ebooks.info

Trang 287
256
Chương 15: Nội bộ JUnit
Bạn có thể có một vài phàn nàn về mô-đun này. Có một số diễn đạt dài
và một số +1 kỳ lạ , v.v. Nhưng nhìn chung mô-đun này là khá tốt. Rốt cuộc, nó
có thể trông giống như Liệt kê 15-3.
boolean riêng areStringsEqual () {
return fEosystem.equals (fActual);
}
}
Liệt kê 15-3
ComparisonCompator.java (được giải cấu trúc)
gói junit.framework;
Public class ComparisonCompactor {
int ctxt riêng tư;
private String s1;
private String s2;
int pfx riêng tư;
int sfx riêng tư;
public ComparisonCompactor (int ctxt, String s1, String s2) {
this.ctxt = ctxt;
this.s1 = s1;
this.s2 = s2;
}
public String compact (String msg) {
if (s1 == null || s2 == null || s1.equals (s2))
return Assert.format (msg, s1, s2);
pfx = 0;
for (; pfx <Math.min (s1.length (), s2.length ()); pfx ++) {
if (s1.charAt (pfx)! = s2.charAt (pfx))
phá vỡ;
}
int sfx1 = s1.length () - 1;
int sfx2 = s2.length () - 1;
for (; sfx2> = pfx && sfx1> = pfx; sfx2--, sfx1--) {
if (s1.charAt (sfx1)! = s2.charAt (sfx2))
phá vỡ;
}
sfx = s1.length () - sfx1;
Chuỗi cmp1 = compactString (s1);
Chuỗi cmp2 = compactString (s2);
return Assert.format (msg, cmp1, cmp2);
}
private String compactString (Chuỗi s) {
Kết quả chuỗi =
"[" + s.substring (pfx, s.length () - sfx + 1) + "]";
nếu (pfx> 0)
result = (pfx> ctxt? "...": "") +
s1.substring (Math.max (0, pfx - ctxt), pfx) + kết quả;
Liệt kê 15-2 (tiếp theo)
ComparisonCompactor.java (Bản gốc)
www.it-ebooks.info

Trang 288
257
Khung JUnit
Mặc dù các tác giả đã để lại mô-đun này trong tình trạng rất tốt, Quy tắc Hướng đạo sinh  2 cho biết
chúng ta, chúng ta nên để nó sạch hơn chúng ta đã tìm thấy. Vì vậy, làm cách nào chúng ta có thể cải thiện bản gốc
mã trong Liệt kê 15-2?
Điều đầu tiên tôi không quan tâm là tiền tố f cho các biến thành viên [N6]. Của ngày hôm nay
môi trường làm cho loại mã hóa phạm vi này trở nên thừa. Vì vậy, hãy loại bỏ tất cả các f 's.
private int contextLength;
chuỗi riêng dự kiến;
chuỗi riêng tư thực tế;
tiền tố int riêng tư;
hậu tố int riêng tư;
Tiếp theo, chúng ta có một điều kiện không được đóng gói ở đầu hàm compact
[G28].
public String compact (String message) {
if (mong đợi == null || thực tế == null || areStringsEqual ())
return Assert.format (thông báo, dự kiến, thực tế);
findCommonPrefix ();
findCommonSuffix ();
Chuỗi dự kiến = compactString (this.e dự kiến);
Chuỗi thực tế = compactString (this.actual);
return Assert.format (thông báo, dự kiến, thực tế);
}
Điều kiện này nên được đóng gói để làm cho ý định của chúng tôi rõ ràng. Vì vậy, hãy trích xuất một phương pháp
giải thích nó.
public String compact (String message) {
if ( shouldNotCompact () )
return Assert.format (thông báo, dự kiến, thực tế);
findCommonPrefix ();
findCommonSuffix ();
Chuỗi dự kiến = compactString (this.e dự kiến);
Chuỗi thực tế = compactString (this.actual);
return Assert.format (thông báo, dự kiến, thực tế);
}
if (sfx> 0) {
int end = Math.min (s1.length () - sfx + 1 + ctxt, s1.length ());
result = result + (s1.substring (s1.length () - sfx + 1, end) +
(s1.length () - sfx + 1 <s1.length () - ctxt? "...": ""));
}
trả về kết quả;
}
}
2. Xem “Quy tắc Hướng đạo sinh” trên trang 14.
Liệt kê 15-3 (tiếp theo)
ComparisonCompator.java (được giải cấu trúc)
www.it-ebooks.info

Trang 289
258
Chương 15: Nội bộ JUnit
boolean riêng shouldNotCompact () {
trả về mong đợi == null || thực tế == null || areStringsEqual ();
}
Tôi không quan tâm lắm đến cái này. Dự kiến và cái này. Ký hiệu thực tế trong hàm nhỏ gọn-
sự. Điều này xảy ra khi chúng ta thay đổi tên của fExpected để mong đợi . Tại sao có
các biến trong hàm này có cùng tên với các biến thành viên? Không họ
đại diện cho cái gì khác [N4]? Chúng ta nên đặt tên rõ ràng.
String compactEosystem = compactString ( dự kiến );
String compactActual = compactString ( thực tế );
Phủ định hơi khó hiểu hơn so với khẳng định [G29]. Vì vậy, hãy biến điều đó nếu
trên đầu của nó và đảo ngược ý nghĩa của điều kiện.
public String compact (String message) {
if ( canBeCompacted ()) {
findCommonPrefix ();
findCommonSuffix ();
String compactEosystem = compactString (dự kiến);
String compactActual = compactString (thực tế);
return Assert.format (message, compactE dự kiến, compactActual);
} khác {
return Assert.format (thông báo, dự kiến, thực tế);
}
}
boolean riêng canBeCompacted () {
trả về mong đợi ! = null && thực tế ! = null &&! areStringsEqual ();
}
Tên của hàm thật lạ [N7]. Mặc dù nó làm gọn các dây nhưng nó
thực sự có thể không thu gọn các chuỗi nếu canBeCompacted trả về false . Vì vậy, đặt tên này
hàm compact ẩn tác dụng phụ của việc kiểm tra lỗi. Cũng lưu ý rằng chức năng
trả về một thông báo được định dạng, không chỉ là các chuỗi đã nén. Vì vậy, tên của hàm
thực sự phải là formatCompactedComparison . Điều đó làm cho nó đọc tốt hơn rất nhiều khi chụp
với đối số hàm:
public String formatCompactedComparison (String message) {
Cơ thể của nếu tuyên bố là nơi nén chặt thực sự của dự kiến và thực tế
chuỗi được thực hiện. Chúng ta nên giải nén nó dưới dạng một phương thức có tên
là compactEosystemAndActual . Làm sao-
hơn bao giờ hết, chúng tôi muốn hàm formatCompactedComparison thực hiện tất cả các định dạng. Các
hàm compact ... không nên làm gì khác ngoài việc thu gọn [G30]. Vì vậy, hãy chia nó ra như sau:
...
private String compactE dự kiến;
private String compactActual;
...
public String formatCompactedComparison (String message) {
if (canBeCompacted ()) {
compactEprisAndActual ();
return Assert.format (message, compactE dự kiến, compactActual);
} khác {
www.it-ebooks.info

Trang 290
259
Khung JUnit
return Assert.format (thông báo, dự kiến, thực tế);
}
}
private void compactEosystemAndActual () {
findCommonPrefix ();
findCommonSuffix ();
compactEpris = compactString (dự kiến);
compactActual = compactString (thực tế);
}
Lưu ý rằng điều này yêu cầu chúng tôi quảng bá compactE dự kiến và compactActual cho thành viên
biến. Tôi không thích cách mà hai dòng cuối cùng của hàm mới trả về các biến,
nhưng hai cái đầu tiên thì không. Họ không sử dụng các quy ước nhất quán [G11]. Vì vậy, chúng ta nên
thay đổi findCommonPrefix và findCommonSuffix để trả về giá trị tiền tố và hậu tố.
private void compactEosystemAndActual () {
prefixIndex = findCommonPrefix ();
hậu tốIndex = findCommonSuffix ();
compactEpris = compactString (dự kiến);
compactActual = compactString (thực tế);
}
private int findCommonPrefix () {
tiền tố int Chỉ số = 0;
int end = Math.min (dự kiến.length (), thực tế.length ());
for (; prefix Index <end; prefix Index ++) {
if (expected.charAt (prefix Index )! = actual.charAt (prefix Index ))
phá vỡ;
}
trả về prefixIndex;
}
private int findCommonSuffix () {
int dự kiếnSuffix = dự kiến.length () - 1;
int realSuffix = real.length () - 1;
for (; realSuffix> = prefix Index &&ualitySuffix> = prefixIndex;
Thực tếSuffix--, Dự kiếnSuffix--) {
nếu (dự kiến.charAt (dự kiếnSuffix)! = thực tế.charAt (thực tếSuffix))
phá vỡ;
}
trả về dự kiến.length () - dự kiếnSuffix;
}
Chúng ta cũng nên thay đổi tên của các biến thành viên để chính xác hơn một chút
[N1]; xét cho cùng, cả hai đều là chỉ số.
Kiểm tra cẩn thận findCommonSuffix cho thấy một khớp nối thời gian ẩn [G31]; nó
phụ thuộc vào thực tế là prefixIndex được tính bằng findCommonPrefix . Nếu hai func-
hàng tấn được gọi không theo thứ tự, sẽ có một phiên gỡ lỗi khó khăn phía trước. Vì vậy, để
để lộ khớp nối tạm thời này, hãy để findCommonSuffix lấy prefixIndex làm
tranh luận.
private void compactEosystemAndActual () {
prefixIndex = findCommonPrefix ();
hậu tốIndex = findCommonSuffix ( prefixIndex );
www.it-ebooks.info

Trang 291
260
Chương 15: Nội bộ JUnit
compactEpris = compactString (dự kiến);
compactActual = compactString (thực tế);
}
private int findCommonSuffix ( int prefixIndex ) {
int dự kiếnSuffix = dự kiến.length () - 1;
int realSuffix = real.length () - 1;
for (; realSuffix> = prefixIndex &&ualitySuffix> = prefixIndex;
Thực tếSuffix--, Dự kiếnSuffix--) {
nếu (dự kiến.charAt (dự kiếnSuffix)! = thực tế.charAt (thực tếSuffix))
phá vỡ;
}
trả về dự kiến.length () - dự kiếnSuffix;
}
Tôi không thực sự hài lòng với điều này. Việc truyền đối số prefixIndex hơi tùy ý
[G32]. Nó hoạt động để thiết lập thứ tự nhưng không làm gì để giải thích sự cần thiết của điều đó
đặt hàng. Một lập trình viên khác có thể hoàn tác những gì chúng tôi đã làm vì không có dấu hiệu-
cho rằng thông số này thực sự cần thiết. Vì vậy, hãy thực hiện một cách khác.
private void compactEosystemAndActual () {
findCommonPrefixAndSuffix ();
compactEpris = compactString (dự kiến);
compactActual = compactString (thực tế);
}
private void findCommonPrefixAndSuffix () {
findCommonPrefix ();
int dự kiếnSuffix = dự kiến.length () - 1;
int realSuffix = real.length () - 1;
cho (;
realSuffix> = prefixIndex &&ualitySuffix> = prefixIndex;
realSuffix--, mong đợiSuffix--
){
nếu (dự kiến.charAt (dự kiếnSuffix)! = thực tế.charAt (thực tếSuffix))
phá vỡ;
}
hậu tốIndex = dự kiến.length () - dự kiếnSuffix;
}
private void findCommonPrefix () {
prefixIndex = 0;
int end = Math.min (dự kiến.length (), thực tế.length ());
for (; prefixIndex <end; prefixIndex ++)
nếu (dự kiến.charAt (prefixIndex)! = thực tế.charAt (prefixIndex))
phá vỡ;
}
Chúng tôi đưa findCommonPrefix và findCommonSuffix trở lại như cũ, thay đổi
tên của findCommonSuffix to findCommon PrefixAnd Suffix và gọi nó là findCommon-
Tiền tố trước khi làm bất cứ điều gì khác. Điều đó thiết lập bản chất thời gian của hai chức năng-
theo một cách ấn tượng hơn nhiều so với giải pháp trước đó. Nó cũng chỉ ra cách xấu xí
findCommonPrefixAndSuffix là. Hãy làm sạch nó ngay bây giờ.
private void findCommonPrefixAndSuffix () {
findCommonPrefix ();
hậu tố intLength = 1;
www.it-ebooks.info

Trang 292
261
Khung JUnit
for (;! hậu tốOverlapsPrefix (hậu tốLength); hậu tốLength ++) {
if (charFromEnd (dự kiến, hậu tốLength)! =
charFromEnd (thực tế, hậu tốLength))
phá vỡ;
}
hậu tốIndex = hậu tốLength;
}
char riêng tư charFromEnd (Chuỗi s, int i) {
trả về s.charAt (s.length () - i);}
hậu tố boolean privateOverlapsPrefix (int hậu tốLength) {
trả về thực tế.length () - hậu tốLength <prefixLength ||
dự kiến.length () - hậu tốLength <prefixLength;
}
Điều này tốt hơn nhiều. Nó cho thấy rằng hậu tốIndex thực sự là độ dài của hậu tố
và không được đặt tên tốt. Điều này cũng đúng với prefixIndex , mặc dù trong trường hợp đó "chỉ mục" và
"Chiều dài" là đồng nghĩa. Mặc dù vậy, việc sử dụng "độ dài" vẫn nhất quán hơn. Vấn đề là
rằng biến hậu tốIndex không dựa trên 0; nó là 1 dựa trên và do đó không phải là độ dài thực. Điều này
cũng là lý do mà có tất cả các +1 đó trong computeCommonSuffix [G33]. Vì vậy, hãy khắc phục điều đó.
Kết quả là trong Liệt kê 15-4.
Liệt kê 15-4
ComparisonCompactor.java (tạm thời)
Public class ComparisonCompactor {
...
private int hậu tốLength ;
...
private void findCommonPrefixAndSuffix () {
findCommonPrefix ();
hậu tốLength = 0;
for (;! hậu tốOverlapsPrefix (hậu tốLength); hậu tốLength ++) {
if (charFromEnd (dự kiến, hậu tốLength)! =
charFromEnd (thực tế, hậu tốLength))
phá vỡ;
}
}
char riêng tư charFromEnd (Chuỗi s, int i) {
return s.charAt (s.length () - i - 1 );
}
hậu tố boolean privateOverlapsPrefix (int hậu tốLength) {
trả về thực tế.length () - hậu tốLength <= prefixLength ||
dự kiến.length () - hậu tốLength <= prefixLength;
}
...
private String compactString (Nguồn chuỗi) {
Kết quả chuỗi =
DELTA_START +
source.substring (prefixLength, source.length () - hậu tốLength ) +
DELTA_END;
if (prefixLength> 0)
result = computeCommonPrefix () + kết quả;
www.it-ebooks.info
Trang 293
262
Chương 15: Nội bộ JUnit
Chúng tôi đã thay thế +1 trong computeCommonSuffix bằng -1 trong charFromEnd , nơi nó tạo ra
nghĩa hoàn hảo và hai toán tử <= trong hậu tốOverlapsPrefix , nơi chúng cũng làm cho
giác quan. Điều này cho phép chúng tôi thay đổi tên của hậu tốIndex thành hậu tốLength , tăng cường đáng kể-
khả năng đọc của mã.
Có một vấn đề, tuy nhiên. Khi tôi loại bỏ +1, tôi nhận thấy dòng sau
trong compactString :
if (hậu tốLength> 0)
Hãy xem nó trong Liệt kê 15-4. Theo quyền, vì hậu tốLength bây giờ nhỏ hơn nó một
đã từng, tôi nên thay đổi toán tử > thành toán tử > = . Nhưng điều đó không có ý nghĩa. Nó
có ý nghĩa bây giờ! Điều này có nghĩa là nó không có ý nghĩa và có thể là một lỗi.
Chà, không hoàn toàn là một lỗi. Sau khi phân tích sâu hơn chúng ta thấy rằng nếu tuyên bố tại một ngăn
hậu tố có độ dài bằng không khi được thêm vào. Trước khi chúng tôi thực hiện thay đổi, những nếu tuyên bố là
không hoạt động vì hậu tốIndex không bao giờ có thể nhỏ hơn một!
Cuộc gọi này đặt ra câu hỏi cả hai  nếu báo cáo trong compactString ! Có vẻ như họ
cả hai đều có thể bị loại bỏ. Vì vậy, hãy bình luận chúng ra và chạy các bài kiểm tra. Họ đã đỗ! Vì thế
hãy cấu trúc lại compactString để loại bỏ các câu lệnh if không liên quan và tạo
chức năng đơn giản hơn nhiều [G9].
private String compactString (Nguồn chuỗi) {
trở về
computeCommonPrefix () +
DELTA_START +
source.substring (prefixLength, source.length () - hậu tốLength) +
DELTA_END +
computeCommonSuffix ();
}
Điều này tốt hơn nhiều! Bây giờ chúng ta thấy rằng hàm compactString chỉ đơn giản là soạn thảo
các mảnh với nhau. Chúng ta có thể làm cho điều này rõ ràng hơn. Thật vậy, có rất nhiều
if ( hậu tốLength > 0)
result = result + computeCommonSuffix ();
trả về kết quả;
}
...
private String computeCommonSuffix () {
int end = Math.min (dự kiến.length () - hậu tốLength +
contextLength, dự kiến.length ()
);
trở về
dự kiến.substring (dự kiến.length () - hậu tốLength , end) +
(dự kiến.length () - hậu tốLength <
dự kiến.length () - contextLength?
ELLIPSIS: "");
}
Liệt kê 15-4 (tiếp theo)
ComparisonCompactor.java (tạm thời)
www.it-ebooks.info

Trang 294
263
Khung JUnit
dọn dẹp chúng tôi có thể làm. Nhưng thay vì kéo bạn qua phần còn lại của các thay đổi, tôi sẽ chỉ
hiển thị cho bạn kết quả trong Liệt kê 15-5.
Liệt kê 15-5
ComparisonCompactor.java (cuối cùng)
gói junit.framework;
Public class ComparisonCompactor {
private static final String ELLIPSIS = "...";
Chuỗi cuối cùng tĩnh riêng tư DELTA_END = "]";
chuỗi cuối cùng tĩnh riêng DELTA_START = "[";
private int contextLength;
chuỗi riêng dự kiến;
chuỗi riêng tư thực tế;
private int prefixLength;
private int hậu tốLength;
Public ComparisonCompactor (
int contextLength, String mong đợi, String thực tế
){
this.contextLength = contextLength;
this.eosystem = dự kiến;
this.actual = thực tế;
}
public String formatCompactedComparison (String message) {
String compactEosystem = dự kiến;
String compactActual = thực tế;
if (shouldBeCompacted ()) {
findCommonPrefixAndSuffix ();
compactEpris = compact (dự kiến);
compactActual = nhỏ gọn (thực tế);
}
return Assert.format (message, compactE dự kiến, compactActual);
}
boolean riêng shouldBeCompacted () {
return! shouldNotBeCompacted ();
}
boolean riêng shouldNotBeCompacted () {
trả về mong đợi == null ||
thực tế == null ||
dự kiến.equals (thực tế);
}
private void findCommonPrefixAndSuffix () {
findCommonPrefix ();
hậu tốLength = 0;
for (;! hậu tốOverlapsPrefix (); hậu tốLength ++) {
if (charFromEnd (dự kiến, hậu tốLength)! =
charFromEnd (thực tế, hậu tốLength)
)
www.it-ebooks.info

Trang 295
264
Chương 15: Nội bộ JUnit
phá vỡ;
}
}
char riêng tư charFromEnd (Chuỗi s, int i) {
return s.charAt (s.length () - i - 1);
}
hậu tố boolean privateOverlapsPrefix () {
trả về thực tế.length () - hậu tốLength <= prefixLength ||
dự kiến.length () - hậu tốLength <= prefixLength;
}
private void findCommonPrefix () {
prefixLength = 0;
int end = Math.min (dự kiến.length (), thực tế.length ());
for (; prefixLength <end; prefixLength ++)
nếu (dự kiến.charAt (prefixLength)! = thực tế.charAt (prefixLength))
phá vỡ;
}
private String compact (String s) {
trả về StringBuilder mới ()
.append (startedEllipsis ())
.append (startContext ())
.append (DELTA_START)
.append ((các) delta)
.append (DELTA_END)
.append (endContext ())
.append (endEllipsis ())
.toString ();
}
private String startedEllipsis () {
trả về prefixLength> contextLength? ELLIPSIS: "";
}
chuỗi riêng tư startContext () {
int contextStart = Math.max (0, prefixLength - contextLength);
int contextEnd = prefixLength;
trả về dự kiến.substring (contextStart, contextEnd);
}
chuỗi riêng delta (Chuỗi) {
int deltaStart = prefixLength;
int deltaEnd = s.length () - hậu tốLength;
return s.substring (deltaStart, deltaEnd);
}
private String endContext () {
int contextStart = mong đợi.length () - hậu tốLength;
int contextEnd =
Math.min (contextStart + contextLength, dự kiến.length ());
trả về dự kiến.substring (contextStart, contextEnd);
}
Liệt kê 15-5 (tiếp theo)
ComparisonCompactor.java (cuối cùng)
www.it-ebooks.info

Trang 296
265
Phần kết luận
Điều này thực sự khá đẹp. Mô-đun được tách thành một nhóm chức năng phân tích
tions và một nhóm chức năng tổng hợp khác. Chúng được sắp xếp theo cấu trúc liên kết để
định nghĩa của mỗi chức năng xuất hiện ngay sau khi nó được sử dụng. Tất cả các chức năng phân tích xuất hiện
đầu tiên, và tất cả các hàm tổng hợp xuất hiện sau cùng.
Nếu bạn xem xét cẩn thận, bạn sẽ nhận thấy rằng tôi đã đảo ngược một số quyết định mà tôi đã đưa ra
trước đó trong chương này. Ví dụ: tôi đã đưa một số phương pháp được trích xuất trở lại vào
formatCompactedComparison , và tôi đã thay đổi ý nghĩa của shouldNotBeCompacted expres-
sion. Đây là điển hình. Thường thì một cấu trúc lại dẫn đến một cấu trúc khác dẫn đến việc hoàn tác
Đầu tiên. Tái cấu trúc là một quá trình lặp đi lặp lại đầy thử và sai, chắc chắn hội tụ về
một cái gì đó mà chúng tôi cảm thấy xứng đáng với một chuyên gia.
Phần kết luận
Và như vậy chúng tôi đã thỏa mãn Quy tắc Hướng đạo sinh. Chúng tôi đã để mô-đun này gọn gàng hơn một chút
chúng tôi đã tìm thấy nó. Không phải là nó đã không sạch. Các tác giả đã làm một công việc xuất sắc với nó.
Nhưng không có mô-đun nào là miễn dịch với sự cải tiến và mỗi chúng ta có trách nhiệm
để lại mã tốt hơn một chút so với chúng tôi đã tìm thấy.
private String endEllipsis () {
return (hậu tốLength> contextLength? ELLIPSIS: "");
}
}
Liệt kê 15-5 (tiếp theo)
ComparisonCompactor.java (cuối cùng)
www.it-ebooks.info

Trang 297
Trang này cố ý để trống
www.it-ebooks.info

Trang 298
267

16
Refactoring SerialDate
Nếu bạn truy cập http://www.jfree.org/jcommon/index.php , bạn sẽ tìm thấy thư viện JCommon.
Sâu bên trong thư viện đó có một gói có tên org.jfree.date . Trong gói đó
có một lớp tên là SerialDate . Chúng ta sẽ khám phá lớp học đó.
Tác giả của SerialDate là David Gilbert. David rõ ràng là một người có kinh nghiệm và
lập trình viên cưng. Như chúng ta sẽ thấy, anh ấy thể hiện một mức độ chuyên nghiệp đáng kể và
kỷ luật trong mã của mình. Đối với tất cả các ý định và mục đích, đây là “mã tốt”. Và tôi là
sẽ xé nó thành từng mảnh.
www.it-ebooks.info

Trang 299
268
Chương 16: Refactoring SerialDate
Đây không phải là một hoạt động ác ý. Tôi cũng không nghĩ rằng tôi giỏi hơn David nhiều
rằng tôi bằng cách nào đó có quyền đánh giá mã của anh ta. Thật vậy, nếu bạn tìm thấy một số
mã của tôi, tôi chắc rằng bạn có thể tìm thấy nhiều điều để phàn nàn.
Không, đây không phải là một hoạt động xấu xa hay kiêu ngạo. Những gì tôi sắp làm là không có gì
hơn và không kém gì một đánh giá chuyên nghiệp. Đó là điều mà tất cả chúng ta nên
thoải mái làm. Và đó là điều chúng ta nên hoan nghênh khi nó được thực hiện cho chúng ta. Nó là
chỉ qua những bài phê bình như thế này mà chúng ta mới học được. Các bác sĩ làm điều đó. Phi công làm điều
đó. Luật sư làm
nó. Và chúng tôi các lập trình viên cũng cần học cách làm điều đó.
Một điều nữa về David Gilbert: David không chỉ là một lập trình viên giỏi.
David có can đảm và thiện chí để cung cấp miễn phí mã của mình cho cộng đồng nói chung.
Anh ấy đặt nó ra ngoài trời cho mọi người xem và mời công chúng sử dụng cũng như giám sát công khai. Điều này
đã được thực hiện tốt!
SerialDate (Liệt kê B-1, trang 349) là một lớp đại diện cho một ngày tháng trong Java. Tại sao có
một lớp đại diện cho một ngày, khi Java đã có java.util.Date và
java.util.Calendar và những người khác? Tác giả viết lớp này để đáp lại một nỗi đau mà tôi
đã thường cảm thấy bản thân mình. Nhận xét trong Javadoc mở đầu của anh ấy (dòng 67) giải thích nó tốt. Chúng
tôi
có thể ngụy biện về ý định của anh ấy, nhưng tôi chắc chắn phải giải quyết vấn đề này, và tôi
chào mừng một lớp học về ngày tháng thay vì thời gian.
Đầu tiên, làm cho nó hoạt động
Có một số bài kiểm tra đơn vị trong một lớp có tên SerialDateTests (Liệt kê B-2, trang 366). Các
các bài kiểm tra đều đạt. Thật không may, khi kiểm tra nhanh các bài kiểm tra cho thấy rằng chúng không kiểm tra
mọi-
điều [T1]. Ví dụ: thực hiện tìm kiếm "Tìm Tập quán" trên phương pháp MonthCodeToQuarter
(dòng 334) cho biết rằng nó không được sử dụng [F4]. Do đó, các bài kiểm tra đơn vị không kiểm tra nó.
Vì vậy, tôi đã kích hoạt Clover để xem những gì các bài kiểm tra đơn vị bao gồm và những gì chúng không. cỏ ba lá
báo cáo rằng các bài kiểm tra đơn vị chỉ thực hiện 91 trong số 185 câu lệnh thực thi trong SerialDate
(~ 50 phần trăm) [T2]. Bản đồ vùng phủ sóng trông giống như một chiếc chăn chắp vá, với những khối lượng lớn
không
mã ecuted rải rác khắp lớp.
Mục tiêu của tôi là hoàn toàn hiểu và cũng cấu trúc lại lớp này. Tôi không thể làm điều đó
mà không có phạm vi kiểm tra lớn hơn nhiều. Vì vậy, tôi đã viết bộ ứng dụng hoàn toàn độc lập của riêng mình
kiểm tra đơn vị (Liệt kê B-4, trang 374).
Khi bạn xem qua các bài kiểm tra này, bạn sẽ lưu ý rằng nhiều bài kiểm tra trong số đó được nhận xét.
Các bài kiểm tra này đã không vượt qua. Chúng đại diện cho hành vi mà tôi nghĩ SerialDate nên có. Vì vậy, như
Tôi cấu trúc lại SerialDate , tôi cũng sẽ làm việc để làm cho các bài kiểm tra này vượt qua.
Ngay cả với một số bài kiểm tra được nhận xét, Clover báo cáo rằng các bài kiểm tra đơn vị mới là
thực thi 170 (92 phần trăm) trong số 185 câu lệnh thực thi. Điều này khá tốt, và tôi
nghĩ rằng chúng tôi sẽ có thể đạt được con số này cao hơn.
Một số bài kiểm tra có nhận xét đầu tiên (dòng 23-63) tôi hơi tự phụ. Các
chương trình không được thiết kế để vượt qua các bài kiểm tra này, nhưng hành vi dường như rõ ràng [G2] đối với
tôi.
www.it-ebooks.info

Trang 300
269
Đầu tiên, làm cho nó hoạt động
Tôi không chắc tại sao phương thức testWeekdayCodeToString lại được viết ngay từ đầu, nhưng
bởi vì nó ở đó, rõ ràng là nó không nên phân biệt chữ hoa chữ thường. Viết các bài kiểm tra này
đã tầm thường [T3]. Làm cho họ vượt qua thậm chí còn dễ dàng hơn; Tôi vừa đổi dòng 259 và 263 thành
sử dụng equalsIgnoreCase .
Tôi để lại các bài kiểm tra ở dòng 32 và dòng 45 đã nhận xét vì tôi không rõ điều đó
chữ viết tắt "tues" và "thurs" phải được hỗ trợ.
Các bài kiểm tra trên dòng 153 và dòng 154 không vượt qua. Rõ ràng, họ nên [G2]. Chúng ta có thể dễ dàng
sửa lỗi này và các bài kiểm tra trên dòng 163 đến dòng 213, bằng cách thực hiện các thay đổi sau đối với
hàm stringToMonthCode .
Kiểm tra nhận xét trên dòng 318 cho thấy một lỗi trong phương thức getFollowingDayOfWeek
(dòng 672). Ngày 25 tháng 12 năm 2004, là một ngày thứ bảy. Thứ Bảy tuần sau là ngày 1 tháng Một,
2005. Tuy nhiên, khi chúng tôi chạy thử nghiệm, chúng tôi thấy rằng getFollowingDayOfWeek trả về Decem-
ngày 25 là thứ Bảy tiếp theo ngày 25 tháng 12. Rõ ràng, điều này là sai [G3], [T1]. Chúng tôi
xem vấn đề ở dòng 685. Đây là lỗi điều kiện biên điển hình [T5]. Nó sẽ đọc là
sau:
Điều thú vị là chức năng này là mục tiêu của một lần sửa chữa trước đó. Sự thay đổi
history (dòng 43) cho thấy rằng "lỗi" đã được sửa trong getPreviousDayOfWeek , getFollowing-
DayOfWeek , và getNearestDayOfWeek [T6].
Bài kiểm tra đơn vị testGetNearestDayOfWeek (dòng 329), kiểm tra getNearestDayOfWeek
phương pháp (dòng 705), không bắt đầu dài và đầy đủ như hiện tại. Tôi đã thêm rất nhiều
của các trường hợp thử nghiệm đối với nó bởi vì các trường hợp thử nghiệm ban đầu của tôi không phải tất cả đều
vượt qua [T6]. Bạn có thể thấy mẫu
thất bại bằng cách xem xét các trường hợp thử nghiệm nào được nhận xét. Khuôn mẫu đó đang bộc lộ [T7].
Nó cho thấy rằng thuật toán không thành công nếu ngày gần nhất là trong tương lai. Rõ ràng là có một số
loại lỗi điều kiện biên [T5].
Mô hình bao phủ thử nghiệm được báo cáo bởi Clover cũng rất thú vị [T8]. Dòng 719
không bao giờ được thực hiện! Điều này có nghĩa rằng nếu tuyên bố trong dòng 718 là luôn luôn sai. Chắc chắn rồi
đủ, một cái nhìn vào mã cho thấy rằng điều này phải đúng. Biến điều chỉnh luôn âm-
ative và do đó không thể lớn hơn hoặc bằng 4. Vì vậy thuật toán này chỉ là sai.
457
if ((kết quả <1) || (kết quả> 12)) {
kết quả = -1;
458
for (int i = 0; i <monthNames.length; i ++) {
459
if (s.equalsIgnoreCase (shortMonthNames [i])) {
460
kết quả = i + 1;
461
phá vỡ;
462
}
463
if (s.equalsIgnoreCase (monthNames [i])) {
464
kết quả = i + 1;
465
phá vỡ;
466
}
467
}
468
}
685
if (baseDOW> = targetWeekday) {
www.it-ebooks.info

Trang 301
270
Chương 16: Refactoring SerialDate
Thuật toán đúng được hiển thị bên dưới:
Cuối cùng, các bài kiểm tra tại dòng 417 và 429 có thể được thực hiện đơn giản bằng cách ném một
IllegalArgumentException thay vì trả về một chuỗi lỗi từ weekInMonthToString
và relativeToString .
Với những thay đổi này, tất cả các bài kiểm tra đơn vị đều vượt qua và tôi tin rằng SerialDate hiện hoạt động. Vậy bây
giờ
đã đến lúc làm cho nó "đúng".
Sau đó làm cho nó đúng
Chúng tôi sẽ đi từ đầu đến cuối của SerialDate , cải thiện nó khi chúng tôi tiếp tục
dọc theo. Mặc dù bạn sẽ không thấy điều này trong cuộc thảo luận, nhưng tôi sẽ chạy tất cả JCommon
kiểm tra đơn vị, bao gồm kiểm tra đơn vị cải tiến của tôi cho SerialDate , sau mỗi thay đổi tôi thực hiện. Vì thế
hãy yên tâm rằng mọi thay đổi bạn thấy ở đây đều phù hợp với tất cả JCommon .
Bắt đầu từ dòng 1, chúng tôi thấy một loạt các nhận xét với thông tin giấy phép, bản quyền,
tác giả và thay đổi lịch sử. Tôi thừa nhận rằng có một số pháp lý nhất định cần phải
được giải quyết, và vì vậy bản quyền và giấy phép phải được duy trì. Mặt khác, sự thay đổi
bánh nướng là một phần còn sót lại từ những năm 1960. Chúng tôi có các công cụ kiểm soát mã nguồn thực hiện
việc này cho chúng tôi ngay bây giờ.
Lịch sử này nên được xóa [C1].
Danh sách nhập bắt đầu từ dòng 61 có thể được rút ngắn bằng cách sử dụng java.text. * Và
java.util. * . [J1]
Tôi lúng túng trước định dạng HTML trong Javadoc (dòng 67). Có một tệp nguồn với
nhiều hơn một ngôn ngữ trong đó làm phiền tôi. Nhận xét này có bốn ngôn ngữ trong đó: Java,
Tiếng Anh, Javadoc và html [G1]. Với nhiều ngôn ngữ được sử dụng, thật khó để giữ mọi thứ
thẳng. Ví dụ: vị trí đẹp của dòng 71 và dòng 72 bị mất khi Javadoc
được tạo ra, nhưng ai muốn xem <ul> và <li> trong mã nguồn? Một chiến lược tốt hơn
có thể chỉ bao quanh toàn bộ nhận xét bằng <pre> để định dạng
rõ ràng trong mã nguồn được giữ nguyên trong Javadoc. 1
Dòng 86 là khai báo lớp. Tại sao lớp này có tên là SerialDate ? Dấu hiệu là gì
hư vô của thế giới "nối tiếp"? Có phải vì lớp này có nguồn gốc từ Serializable không? Cái đó
dường như không có khả năng.
int delta = targetDOW - base.getDayOfWeek ();
int positiveDelta = delta + 7;
int điều chỉnh = positiveDelta% 7;
if (điều chỉnh> 3)
điều chỉnh - = 7;
return SerialDate.addDays (điều chỉnh, căn cứ);
1. Một giải pháp tốt hơn nữa sẽ là Javadoc trình bày tất cả các nhận xét dưới dạng định dạng trước, để các nhận xét xuất hiện
giống nhau trong cả mã và tài liệu.
www.it-ebooks.info

Trang 302
271
Sau đó làm cho nó đúng
Tôi sẽ không bắt bạn phải đoán. Tôi biết tại sao (hoặc ít nhất tôi nghĩ rằng tôi biết tại sao) từ
"Nối tiếp" đã được sử dụng. Manh mối nằm trong các hằng số SERIAL_LOWER_BOUND và
SERIAL_UPPER_BOUND trên dòng 98 và dòng 101. Một manh mối tốt hơn nằm trong nhận xét
bắt đầu trên dòng 830. Lớp này được đặt tên là SerialDate vì nó được triển khai bằng cách sử dụng
"Số sê-ri", là số ngày kể từ ngày 30 tháng 12 năm 1899.
Tôi có hai vấn đề với điều này. Đầu tiên, thuật ngữ "số sê-ri" không thực sự chính xác.
Đây có thể là một phân biệt, nhưng biểu diễn này mang tính chất bù tương đối hơn là một số nối tiếp
bến. Thuật ngữ “số sê-ri” liên quan nhiều đến các dấu hiệu nhận biết sản phẩm hơn là
ngày. Vì vậy, tôi không thấy tên này có tính mô tả đặc biệt [N1]. Một thuật ngữ mô tả nhiều hơn
có thể là "thứ tự".
Vấn đề thứ hai là đáng kể hơn. Tên SerialDate ngụ ý một triển khai-
sự. Lớp này là một lớp trừu tượng. Không cần phải ám chỉ bất cứ điều gì về
thực hiện. Thật vậy, có lý do chính đáng để ẩn việc thực hiện! Vì vậy, tôi tìm thấy điều này
tên ở mức trừu tượng sai [N2]. Theo tôi, tên của lớp này
đơn giản nên là Ngày .
Thật không may, đã có quá nhiều lớp trong thư viện Java có tên là Date , vì vậy
đây có lẽ không phải là cái tên tốt nhất để chọn. Bởi vì lớp học này là tất cả về ngày, thay vì
thời gian, tôi đã cân nhắc đặt tên nó là Ngày , nhưng cái tên này cũng được sử dụng nhiều ở những nơi khác. bên
trong
kết thúc, tôi đã chọn DayDate là thỏa hiệp tốt nhất.
Từ bây giờ trong cuộc thảo luận này, tôi sẽ sử dụng thuật ngữ DayDate . Tôi để nó cho bạn nhớ-
ber rằng danh sách bạn đang xem vẫn sử dụng SerialDate .
Tôi hiểu tại sao DayDate thừa hưởng từ tương đương và Serializable . Nhưng tại sao nó
kế thừa từ MonthConstants ? Lớp MonthConstants (Liệt kê B-3, trang 372) chỉ là một
nhóm các hằng số tĩnh cuối cùng xác định các tháng. Kế thừa từ các lớp học với con-
stants là một thủ thuật cũ mà các lập trình viên Java đã sử dụng để họ có thể tránh sử dụng expres-
sions như MonthConstants.January , nhưng đó là một ý tưởng tồi [J2]. MonthConstants thực sự nên
một enum.
lớp trừu tượng công khai DayDate thực hiện Có thể so sánh,
Có thể nối tiếp {
public static enum Tháng {
THÁNG 1 (1),
2 THÁNG 2),
THÁNG 3 (3),
THÁNG 4 (4),
CÓ THỂ (5),
THÁNG 6 (6),
THÁNG 7 (7),
THÁNG 8 (8),
THÁNG 9 (9),
THÁNG 10 (10),
NGÀY 11 THÁNG 11),
THÁNG 12 (12);
Tháng (chỉ mục int) {
this.index = chỉ mục;
}
www.it-ebooks.info

Trang 303
272
Chương 16: Refactoring SerialDate
Việc thay đổi MonthConstants thành enum này buộc phải thay đổi khá nhiều đối với lớp DayDate
và tất cả là người dùng. Tôi đã mất một giờ để thực hiện tất cả các thay đổi. Tuy nhiên, bất kỳ chức năng nào
được sử dụng để lấy một int cho một tháng, bây giờ lấy một liệt kê Tháng . Điều này có nghĩa là chúng ta có thể thoát
khỏi
của phương pháp isValidMonthCode (dòng 326) và kiểm tra lỗi mã tháng chẳng hạn như
đó trong monthCodeToQuarter (dòng 356) [G5].
Tiếp theo, chúng ta có dòng 91, serialVersionUID . Biến này được sử dụng để điều khiển bộ nối tiếp.
Nếu chúng tôi thay đổi nó, thì bất kỳ Ngày nào được viết bằng phiên bản cũ hơn của phần mềm sẽ không
có thể đọc được nữa và sẽ cho kết quả trong một InvalidClassException . Nếu bạn không khai báo
biến serialVersionUID , sau đó trình biên dịch tự động tạo một biến cho bạn và nó
sẽ khác mỗi khi bạn thực hiện thay đổi đối với mô-đun. Tôi biết rằng tất cả các tài liệu-
ments khuyên bạn nên kiểm soát thủ công đối với biến này, nhưng đối với tôi dường như điều khiển tự động
xe đẩy tuần tự hóa an toàn hơn rất nhiều [G4]. Rốt cuộc, tôi muốn gỡ lỗi một
InvalidClassException so với hành vi kỳ quặc sẽ xảy ra sau đó nếu tôi quên thay đổi
serialVersionUID . Vì vậy, tôi sẽ xóa biến — ít nhất là vào lúc này. 2
Tôi thấy nhận xét ở dòng 93 là thừa. Các bình luận thừa chỉ là nơi để tập hợp-
diễn thuyết dối trá và thông tin sai lệch [C2]. Vì vậy, tôi sẽ loại bỏ nó và ilk của nó.
Các nhận xét ở dòng 97 và dòng 100 nói về số sê-ri, mà tôi đã thảo luận
trước đó [C1]. Các biến mà họ mô tả là ngày sớm nhất và muộn nhất có thể
DayDate có thể mô tả. Điều này có thể được làm rõ ràng hơn một chút [N1].
Tôi không rõ tại sao EARLIEST_DATE_ORDINAL lại là 2 thay vì 0. Có một gợi ý trong
nhận xét về dòng 829 gợi ý rằng điều này có liên quan đến cách ngày tháng
được biểu diễn trong Microsoft Excel. Có một cái nhìn sâu sắc hơn nhiều được cung cấp trong một dẫn xuất của
DayDate được gọi là SpreadsheetDate (Liệt kê B-5, trang 382). Chú thích ở dòng 71 mô tả
vấn đề độc đáo.
Vấn đề tôi gặp phải là vấn đề này dường như liên quan đến nông cụ-
tion của SpreadsheetDate và không liên quan gì đến DayDate . Tôi kết luận từ điều này rằng
công khai tháng tĩnh thực hiện (int monthIndex) {
cho (Tháng m: Tháng.values ()) {
if (m.index == monthIndex)
trả lại m;
}
ném mới IllegalArgumentException ("Chỉ mục tháng không hợp lệ" + monthIndex);
}
công khai chỉ mục int cuối cùng;
}
2. Một số người đánh giá văn bản này đã ngoại lệ đối với quyết định này. Họ cho rằng trong một khuôn khổ mã nguồn mở, nó là
tốt hơn để xác nhận kiểm soát thủ công đối với ID nối tiếp để những thay đổi nhỏ đối với phần mềm không làm
không hợp lệ. Đây là một điểm công bằng. Tuy nhiên, ít nhất sự thất bại, mặc dù bất tiện, có thể có một nguyên nhân rõ ràng. Mặt khác
tay, nếu tác giả của lớp quên cập nhật ID, thì chế độ lỗi không được xác định và rất có thể là im lặng. Tôi
nghĩ rằng đạo lý thực sự của câu chuyện này là bạn không nên mong đợi việc bỏ qua các phiên bản.
public static final int EARLIEST_DATE_ORDINAL = 2; // 1/1/1900
public static final int LATEST_DATE_ORDINAL = 2958465; // 12/31/9999
www.it-ebooks.info

Trang 304
273
Sau đó làm cho nó đúng
EARLIEST_DATE_ORDINAL và LATEST_DATE_ORDINAL không thực sự thuộc về DayDate và
sẽ được chuyển đến SpreadsheetDate [G6].
Thật vậy, việc tìm kiếm mã cho thấy rằng các biến này chỉ được sử dụng trong
Ngày của bảng tính . Không có gì trong DayDate , cũng như trong bất kỳ lớp nào khác trong khuôn khổ JCommon , sử
dụng
chúng. Do đó, tôi sẽ chuyển chúng xuống SpreadsheetDate .
Các biến tiếp theo, MINIMUM_YEAR_SUPPORTED và MAXIMUM_YEAR_SUPPORTED (dòng 104
và dòng 107), cung cấp điều gì đó khó xử. Rõ ràng rằng nếu DayDate là một bản tóm tắt
lớp không cung cấp báo trước về việc triển khai, thì nó sẽ không thông báo cho chúng tôi
khoảng một năm tối thiểu hoặc tối đa. Một lần nữa, tôi muốn chuyển các biến này xuống
vào SpreadsheetDate [G6]. Tuy nhiên, một tìm kiếm nhanh về người dùng của các biến này cho thấy
mà một lớp khác sử dụng chúng: RelativeDayOfWeekRule (Liệt kê B-6, trang 390). Chúng ta thấy rằng
sử dụng ở dòng 177 và dòng 178 trong hàm getDate , nơi chúng được sử dụng để kiểm tra
đối số để getDate là một năm hợp lệ. Vấn đề nan giải là người dùng của một lớp trừu tượng
cần thông tin về việc thực hiện nó.
Những gì chúng ta cần làm là cung cấp thông tin này mà không gây ô nhiễm cho chính DayDate .
Thông thường, chúng ta sẽ lấy thông tin triển khai từ một phiên bản của phái sinh.
Tuy nhiên, hàm getDate không được chuyển qua một phiên bản của DayDate . Tuy nhiên, nó không
trả về một thể hiện như vậy, có nghĩa là ở đâu đó nó phải đang tạo ra nó. Dòng 187
thông qua dòng 205 cung cấp gợi ý. Phiên bản DayDate đang được tạo bởi một trong những
ba chức năng, getPreviousDayOfWeek , getNearestDayOfWeek hoặc getFollowingDayOfWeek .
Nhìn lại danh sách DayDate , chúng ta thấy rằng các hàm này (dòng 638–724) đều trả về
một ngày được tạo bởi addDays (dòng 571), gọi createInstance (dòng 808), tạo
một SpreadsheetDate ! [G7].
Nói chung, đó là một ý tưởng tồi cho các lớp cơ sở biết về các dẫn xuất của chúng. Để khắc phục điều này, chúng tôi
nên sử dụng A BSTRACT F actory 3 mô hình và tạo ra một DayDateFactory . Nhà máy này sẽ
tạo các phiên bản DayDate mà chúng tôi cần và cũng có thể trả lời các câu hỏi về
thực hiện, chẳng hạn như ngày tối đa và tối thiểu.
3. [GOF].
public abstract class DayDateFactory {
private static DayDateFactory factory = new SpreadsheetDateFactory ();
public static void setInstance (nhà máy DayDateFactory) {
DayDateFactory.factory = nhà máy;
}
được bảo vệ trừu tượng DayDate _makeDate (int ordinal);
được bảo vệ trừu tượng DayDate _makeDate (ngày tháng, ngày tháng. tháng, năm);
trừu tượng được bảo vệ DayDate _makeDate (ngày, tháng, năm);
được bảo vệ trừu tượng DayDate _makeDate (ngày java.util.Date);
trừu tượng bảo vệ int _getMinimumYear ();
bảo vệ trừu tượng int _getMaximumYear ();
public static DayDate makeDate (int ordinal) {
return factory._makeDate (thứ tự);
}
www.it-ebooks.info

Trang 305
274
Chương 16: Refactoring SerialDate
Lớp nhà máy này thay thế các phương thức createInstance bằng các phương thức makeDate ,
cải thiện tên khá nhiều [N1]. Nó mặc định là SpreadsheetDateFactory nhưng có thể
thay đổi bất cứ lúc nào để sử dụng một nhà máy khác. Các phương thức tĩnh ủy quyền cho trừu tượng
phương pháp sử dụng một sự kết hợp của chỉ số S INGLETON , 4 D ECORATOR , 5 và A BSTRACT F actory
mà tôi đã thấy là hữu ích.
Các SpreadsheetDateFactory vẻ như thế này.
public static DayDate makeDate (int day, DayDate.Month month, int year) {
return factory._makeDate (ngày, tháng, năm);
}
public static DayDate makeDate (int day, int month, int year) {
return factory._makeDate (ngày, tháng, năm);
}
public static DayDate makeDate (java.util.Date date) {
return factory._makeDate (ngày);
}
public static int getMinimumYear () {
return factory._getMinimumYear ();
}
public static int getMaximumYear () {
return factory._getMaximumYear ();
}
}
4. Đã dẫn.
5. Đã dẫn.
public class SpreadsheetDateFactory mở rộng DayDateFactory {
public DayDate _makeDate (int ordinal) {
trả về SpreadsheetDate mới (thứ tự);
}
public DayDate _makeDate (int day, DayDate.Month month, int year) {
trả về SpreadsheetDate mới (ngày, tháng, năm);
}
public DayDate _makeDate (int day, int month, int year) {
trả về SpreadsheetDate mới (ngày, tháng, năm);
}
public DayDate _makeDate (Ngày tháng) {
lịch GregorianCalendar cuối cùng = new GregorianCalendar ();
Calendar.setTime (ngày tháng);
trả về SpreadsheetDate mới (
Calendar.get (Lịch.DATE),
DayDate.Month.make (calendar.get (Calendar.MONTH) + 1),
Calendar.get (Lịch.YEAR));
}
www.it-ebooks.info

Trang 306
275
Sau đó làm cho nó đúng
Như bạn thấy, tôi đã chuyển MINIMUM_YEAR_SUPPORTED và
MAXIMUM_YEAR_SUPPORTED biến vào SpreadsheetDate , nơi chúng thuộc về [G6].
Vấn đề tiếp theo trong DayDate là các hằng số ngày bắt đầu từ dòng 109. Chúng sẽ
thực sự là một enum [J3] khác. Chúng tôi đã thấy mẫu này trước đây, vì vậy tôi sẽ không lặp lại nó ở đây. Bạn sẽ
xem nó trong danh sách cuối cùng.
Tiếp theo, chúng tôi thấy một loạt bảng bắt đầu bằng LAST_DAY_OF_MONTH ở dòng 140. Đầu tiên của tôi
vấn đề với các bảng này là các nhận xét mô tả chúng là thừa [C3]. Của chúng
tên là đủ. Vì vậy, tôi sẽ xóa các bình luận.
Có vẻ như không có lý do chính đáng nào mà bảng này không phải là riêng tư [G8], bởi vì có một
hàm tĩnh lastDayOfMonth cung cấp cùng một dữ liệu.
Bảng tiếp theo, AGGREGATE_DAYS_TO_END_OF_MONTH , bí ẩn hơn một chút vì nó là
không được sử dụng ở bất kỳ đâu trong khuôn khổ JCommon [G9]. Vì vậy, tôi đã xóa nó.
Điều này cũng xảy ra với LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_MONTH.
Bảng tiếp theo, AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH , chỉ được sử dụng trong Spread-
sheetDate (dòng 434 và dòng 473). Điều này đặt ra câu hỏi liệu nó có nên được di chuyển hay không
sang Ngày bảng tính . Lập luận để không di chuyển nó là bảng không cụ thể cho bất kỳ
triển khai cụ thể [G6]. Mặt khác, không có triển khai nào khác ngoài
SpreadsheetDate thực sự tồn tại và vì vậy bảng phải được chuyển đến gần vị trí của nó
đã sử dụng [G10].
Điều giải quyết tranh luận đối với tôi là để nhất quán [G11], chúng ta nên
bảng riêng tư và hiển thị nó thông qua một hàm như julianDateOfLastDayOfMonth . Không ai
dường như cần một chức năng như vậy. Hơn nữa, bảng có thể được chuyển trở lại DayDate một cách dễ dàng
nếu bất kỳ triển khai mới của DayDate cần nó. Vì vậy, tôi đã di chuyển nó.
Tương tự với bảng, LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_MONTH .
Tiếp theo, chúng ta thấy ba bộ hằng số có thể được biến thành enum (dòng 162–205).
Lựa chọn đầu tiên trong số ba lựa chọn một tuần trong vòng một tháng. Tôi đã đổi nó thành một enum có tên
Tuần trong tháng .
được bảo vệ int _getMinimumYear () {
trả về SpreadsheetDate.MINIMUM_YEAR_SUPPORTED;
}
được bảo vệ int _getMaximumYear () {
trả về SpreadsheetDate.MAXIMUM_YEAR_SUPPORTED;
}
}
public enum WeekInMonth {
FIRST (1), SECOND (2), THIRD (3), FOURTH (4), LAST (0);
công khai chỉ mục int cuối cùng;
WeekInMonth (int index) {
this.index = chỉ mục;
}
}
www.it-ebooks.info

Trang 307
276
Chương 16: Refactoring SerialDate
Bộ hằng số thứ hai (dòng 177–187) khó hiểu hơn một chút. Các INCLUDE_NONE ,
Các hằng số INCLUDE_FIRST , INCLUDE_SECOND và INCLUDE_BOTH được sử dụng để mô tả liệu
ngày điểm cuối xác định của một phạm vi phải được bao gồm trong phạm vi đó. Về mặt toán học,
điều này được mô tả bằng cách sử dụng các thuật ngữ “khoảng thời gian mở”, “khoảng thời gian nửa mở” và
“khoảng thời gian đóng
val. ” Tôi nghĩ rằng nó rõ ràng hơn bằng cách sử dụng danh pháp toán học [N3], vì vậy tôi đã thay đổi nó thành
enum có tên là DateInterval với các điều tra viên CLOSED , CLOSED_LEFT , CLOSED_RIGHT và OPEN .
Tập hợp hằng số thứ ba (dòng 18–205) mô tả liệu một tìm kiếm cụ thể
ngày trong tuần sẽ dẫn đến trường hợp cuối cùng, tiếp theo hoặc gần nhất. Quyết định gọi cái gì
điều này rất khó. Cuối cùng, tôi đã giải quyết cho WeekdayRange với CUỐI CÙNG , TIẾP THEO và GẦN NHẤT
điều tra viên.
Bạn có thể không đồng ý với những cái tên tôi đã chọn. Chúng có ý nghĩa với tôi, nhưng chúng
có thể không có ý nghĩa đối với bạn. Vấn đề là bây giờ chúng đang ở dạng giúp chúng dễ dàng
để thay đổi [J3]. Chúng không được chuyển dưới dạng số nguyên nữa; chúng được chuyển dưới dạng ký hiệu. tôi có
thể
sử dụng chức năng "thay đổi tên" của IDE của tôi để thay đổi tên hoặc loại mà không
lo lắng rằng tôi đã bỏ lỡ một số -1 hoặc 2 ở đâu đó trong mã hoặc một số đối số int dec-
sự than thở được mô tả kém.
Trường mô tả ở dòng 208 dường như không được sử dụng bởi bất kỳ ai. Tôi đã xóa nó cùng
với bộ truy cập và bộ đột biến [G9].
Tôi cũng đã xóa phương thức khởi tạo mặc định suy biến tại dòng 213 [G12]. Trình biên dịch sẽ
tạo ra nó cho chúng tôi.
Chúng tôi có thể bỏ qua phương thức isValidWeekdayCode (dòng 216–238) vì chúng tôi đã xóa
nó khi chúng tôi tạo bảng liệt kê Ngày .
Điều này đưa chúng ta đến phương thức stringToWeekdayCode (dòng 242–270). Javadocs đó
không thêm nhiều vào chữ ký phương thức chỉ là lộn xộn [C3], [G12]. Giá trị duy nhất này
Javadoc thêm vào là mô tả của giá trị trả về -1 . Tuy nhiên, vì chúng tôi đã thay đổi thành
Liệt kê ngày , nhận xét thực tế là sai [C2]. Phương thức bây giờ ném một
IllegalArgumentException . Vì vậy, tôi đã xóa Javadoc.
Tôi cũng đã xóa tất cả các từ khóa cuối cùng trong các đối số và khai báo biến. Xa như
Tôi có thể nói, chúng không thêm giá trị thực nhưng đã làm tăng thêm sự lộn xộn [G12]. Chung kết loại
bay khi đối mặt với một số sự khôn ngoan thông thường. Ví dụ, Robert Simmons 6 mạnh mẽ
khuyến nghị chúng tôi “. . . rải cuối cùng trên toàn bộ mã của bạn. " Rõ ràng là tôi không đồng ý. tôi nghĩ vậy
có một vài cách sử dụng tốt cho cuối cùng , chẳng hạn như hằng số cuối cùng thường xuyên, nhưng nếu không
từ khóa thêm ít giá trị và tạo ra nhiều thứ lộn xộn. Có lẽ tôi cảm thấy như vậy bởi vì
các loại lỗi cuối cùng có thể mắc phải đã bị bắt bởi các bài kiểm tra đơn vị tôi viết.
Tôi không quan tâm đến các câu lệnh if trùng lặp [G5] bên trong vòng lặp for (dòng 259 và
dòng 263), vì vậy tôi đã kết nối chúng thành một câu lệnh if duy nhất bằng cách sử dụng dấu || nhà điều hành. Tôi
cũng đã sử dụng
các ngày liệt kê chỉ đạo cho vòng lặp và thực hiện một vài thay đổi mỹ phẩm khác.
Tôi nhận ra rằng phương pháp này không thực sự thuộc về DayDate . Nó thực sự là
chức năng phân tích cú pháp của Ngày . Vì vậy, tôi đã chuyển nó vào bảng liệt kê Ngày . Tuy nhiên, điều đó đã
khiến Ngày
6. [Simmons04], tr. 73.
www.it-ebooks.info
Trang 308
277
Sau đó làm cho nó đúng
sự liệt kê khá lớn. Vì khái niệm về Ngày không phụ thuộc vào Ngày nên tôi đã chuyển
các ngày liệt kê bên ngoài của DayDate lớp thành tập tin nguồn riêng của mình [G13].
Tôi cũng đã chuyển hàm tiếp theo, weekdayCodeToString (dòng 272–286) sang Day
liệt kê và gọi nó là Chuỗi .
Có hai hàm getMonths (dòng 288–316). Cuộc gọi đầu tiên thứ hai. Các
thứ hai không bao giờ được gọi bởi bất cứ ai ngoài người đầu tiên. Do đó, tôi đã thu gọn cả hai thành một và
đã đơn giản hóa chúng [G9], [G12], [F4]. Cuối cùng, tôi đã đổi tên để giống mình hơn một chút-
mô tả [N1].
Ngày enum công khai {
THỨ HAI (Calendar.MONDAY),
TUESDAY (Lịch.TUESDAY),
WEDNESDAY (Calendar.WEDNESDAY), s
THURSDAY (Calendar.THURSDAY),
THỨ SÁU (Calendar.FRIDAY),
THỨ BẢY (Lịch.SATURDAY),
CHỦ NHẬT (Lịch.SUNDAY);
công khai chỉ mục int cuối cùng;
private static DateFormatSymbols dateSymbols = new DateFormatSymbols ();
Ngày (trong ngày) {
index = ngày;
}
public static Day make (int index) ném IllegalArgumentException {
cho (Ngày d: Ngày.values ())
if (d.index == index)
trả lại d;
ném IllegalArgumentException mới (
String.format ("Chỉ mục ngày không hợp lệ:% d.", Chỉ mục));
}
public static Day parse (String s) ném IllegalArgumentException {
Chuỗi [] shortWeekdayNames =
dateSymbols.getShortWeekdays ();
String [] weekDayNames =
dateSymbols.getWeekdays ();
s = s.trim ();
for (Day day: Day.values ()) {
if (s.equalsIgnoreCase (shortWeekdayNames [day.index]) ||
s.equalsIgnoreCase (weekDayNames [day.index])) {
ngày trở về;
}
}
ném IllegalArgumentException mới (
String.format ("% s không phải là chuỗi ngày trong tuần hợp lệ", s));
}
public String toString () {
return dateSymbols.getWeekdays () [index];
}
}
www.it-ebooks.info

Trang 309
278
Chương 16: Refactoring SerialDate
Hàm isValidMonthCode (dòng 326–346) không còn liên quan theo Tháng
enum, vì vậy tôi đã xóa nó [G9].
Hàm monthCodeToQuarter (dòng 356–375) có mùi của F EATURE E NVY 7 [G14]
và có thể thuộc về tháng enum như một phương thức có tên là quý . Vì vậy, tôi đã thay thế nó.
Điều này làm cho tháng enum đủ lớn để được xếp vào lớp riêng của nó. Vì vậy, tôi đã chuyển nó ra khỏi
DayDate phù hợp với Day enum [G11], [G13].
Hai phương thức tiếp theo được đặt tên là monthCodeToString (dòng 377–426). Một lần nữa, chúng tôi thấy
mô hình của một phương thức gọi phương thức sinh đôi của nó với một cờ. Thường là một ý tưởng tồi nếu vượt qua
một lá cờ
làm đối số cho một hàm, đặc biệt là khi cờ đó chỉ cần chọn định dạng của out-
đặt [G15]. Tôi đã đổi tên, đơn giản hóa và cấu trúc lại các chức năng này và chuyển chúng vào
Tháng enum [N1], [N3], [C3], [G14].
Phương thức tiếp theo là stringToMonthCode (dòng 428–472). Tôi đã đổi tên nó, chuyển nó vào
Tháng enum, và đơn giản hóa nó [N1], [N3], [C3], [G14], [G12].
public static String [] getMonthNames () {
ngày trả vềFormatSymbols.getMonths ();
}
7. [Tái cấu trúc].
public int 1/4 () {
trả về 1 + (chỉ mục-1) / 3;
}
public String toString () {
return dateFormatSymbols.getMonths () [index - 1];
}
public String toShortString () {
return dateFormatSymbols.getShortMonths () [index - 1];
}
phân tích cú pháp tháng tĩnh công khai (Chuỗi) {
s = s.trim ();
cho (Tháng m: Tháng.values ())
if (m.match (s))
trả lại m;
thử {
return make (Integer.parseInt (s));
}
bắt (NumberFormatException e) {}
ném IllegalArgumentException mới ("Tháng không hợp lệ" + s);
}
www.it-ebooks.info

Trang 310
279
Sau đó làm cho nó đúng
Các isLeapYear phương pháp (dòng 495-517) có thể được thực hiện một chút biểu cảm hơn [G16].
Hàm tiếp theo, leapYearCount (dòng 519–536) không thực sự thuộc về DayDate .
Không ai gọi nó ngoại trừ hai phương thức trong SpreadsheetDate . Vì vậy, tôi đã đẩy nó xuống [G6].
Hàm lastDayOfMonth (dòng 538–560) sử dụng LAST_DAY_OF_MONTH
mảng. Mảng này thực sự thuộc về Tháng enum [G17], vì vậy tôi đã chuyển nó đến đó. Tôi cũng đơn giản hóa-
đã hoàn thiện chức năng và làm cho nó biểu cảm hơn một chút [G16].
Bây giờ mọi thứ bắt đầu trở nên thú vị hơn một chút. Hàm tiếp theo là addDays (dòng 562–
576). Trước hết, bởi vì hàm này hoạt động trên các biến của DayDate , nó không nên
tĩnh [G18]. Vì vậy, tôi đã thay đổi nó thành một phương thức thể hiện. Thứ hai, nó gọi hàm
toSerial . Hàm này nên được đổi tên thànhOrdinal [N1]. Cuối cùng, phương pháp có thể
giản thể.
Tương tự với addMonths (dòng 578–602). Nó phải là một phương thức thể hiện [G18].
Thuật toán hơi phức tạp, vì vậy tôi đã sử dụng E XPLAINING T EMPORARY V ARIABLES 8 [G19]
để làm cho nó minh bạch hơn. Tôi cũng đã đổi tên phương thức getYYY thành getYear [N1].
so khớp boolean riêng (Chuỗi) {
trả về s.equalsIgnoreCase (toString ()) ||
s.equalsIgnoreCase (toShortString ());
}
public static boolean isLeapYear (int year) {
boolean thứ tư = năm% 4 == 0;
boolean trămth = năm% 100 == 0;
boolean bốnHàng trăm = năm% 400 == 0;
trả về thứ tư && (! trămth || bốnH trăm);
}
public static int lastDayOfMonth (Tháng tháng, năm) {
if (month == Month.FEBRUARY && isLeapYear (năm))
return month.lastDay () + 1;
khác
return month.lastDay ();
}
public DayDate addDays (int days) {
return DayDateFactory.makeDate (toOrdinal () + ngày);
}
8. [Beck97].
public DayDate addMonths (int months) {
int thisMonthAsOrdinal = 12 * getYear () + getMonth (). index - 1;
int resultMonthAsOrdinal = thisMonthAsOrdinal + tháng;
int resultYear = resultMonthAsOrdinal / 12;
Kết quả thángMonth = Month.make (resultMonthAsOrdinal% 12 + 1);
www.it-ebooks.info

Trang 311
280
Chương 16: Refactoring SerialDate
Hàm addYears (dòng 604–626) không gây ngạc nhiên so với các hàm khác.
Có một chút ngứa ở phía sau tâm trí của tôi đang làm phiền tôi về việc thay đổi
các phương thức này từ static sang instance. Biểu thức date.addDays (5) có làm cho nó không
rõ ràng rằng đối tượng ngày tháng không thay đổi và một phiên bản DayDate mới được trả về?
Hay nó ngụ ý sai rằng chúng ta đang thêm năm ngày vào đối tượng ngày tháng ? Bạn có thể
không nghĩ rằng đó là một vấn đề lớn, nhưng một chút mã trông giống như sau có thể rất
lừa dối [G20].
Ngày DayDate = DateFactory.makeDate (5, Month.DECEMBER, 1952);
date.addDays (7); // tăng ngày một tuần.
Ai đó đọc mã này rất có thể sẽ chấp nhận rằng addDays đang thay đổi
đối tượng ngày tháng . Vì vậy, chúng ta cần một cái tên phá vỡ sự mơ hồ này [N4]. Vì vậy, tôi đã đổi tên thành
plusDays và plusMonths . Đối với tôi, có vẻ như mục đích của phương pháp được nắm bắt độc đáo bởi
Ngày DayDate = oldDate.plusDays (5);
trong khi phần sau không đọc đủ trôi chảy để người đọc có thể đơn giản chấp nhận rằng
đối tượng ngày được thay đổi:
date.plusDays (5);
Các thuật toán tiếp tục trở nên thú vị hơn. getPreviousDayOfWeek (dòng 628–
660) hoạt động nhưng hơi phức tạp. Sau một số suy nghĩ về những gì thực sự đang xảy ra
[G21], tôi đã có thể đơn giản hóa nó và sử dụng E XPLAINING T EMPORARY V ARIABLES [G19] để
làm cho nó rõ ràng hơn. Tôi cũng đã thay đổi nó từ một phương thức tĩnh thành một phương thức cá thể [G18] và
đã loại bỏ phương thức đối tượng trùng lặp [G5] (dòng 997–1008).
Phân tích và kết quả giống hệt nhau đã xảy ra cho getFollowingDayOfWeek (dòng 662–693).
int lastDayOfResultMonth = lastDayOfMonth (resultMonth, resultYear);
int resultDay = Math.min (getDayOfMonth (), lastDayOfResultMonth);
return DayDateFactory.makeDate (resultDay, resultMonth, resultYear);
}
public DayDate plusYears (các năm) {
int resultYear = getYear () + năm;
int lastDayOfMonthInResultYear = lastDayOfMonth (getMonth (), resultYear);
int resultDay = Math.min (getDayOfMonth (), lastDayOfMonthInResultYear);
return DayDateFactory.makeDate (resultDay, getMonth (), resultYear);
}
public DayDate getPreviousDayOfWeek (Mục tiêu ngàyDayOfWeek) {
int offsetToTarget = targetDayOfWeek.index - getDayOfWeek (). index;
if (offsetToTarget> = 0)
offsetToTarget - = 7;
return plusDays (offsetToTarget);
}
public DayDate getFollowingDayOfWeek (Mục tiêu ngàyDayOfWeek) {
int offsetToTarget = targetDayOfWeek.index - getDayOfWeek (). index;
if (offsetToTarget <= 0)
www.it-ebooks.info

Trang 312
281
Sau đó làm cho nó đúng
Hàm tiếp theo là getNearestDayOfWeek (dòng 695–726), chúng tôi đã sửa lại
trên trang 270. Nhưng những thay đổi tôi đã thực hiện trước đó không phù hợp với mẫu hiện tại trong
hai chức năng cuối cùng [G11]. Vì vậy, tôi đã làm cho nó nhất quán và sử dụng một số E XPLAINING T EMPO-
RARY V ARIABLES [G19] để làm rõ thuật toán.
Các getEndOfCurrentMonth phương pháp (dòng 728-740) là một chút kỳ lạ bởi vì nó là một
phương thức thể hiện ghen tị với [G14] lớp riêng của nó bằng cách lấy đối số DayDate . Tôi đã làm cho nó một
true instance method và làm rõ một vài tên.
Refactoring weekInMonthToString (dòng 742–761) hóa ra rất thú vị
thật. Sử dụng các công cụ tái cấu trúc của IDE của tôi, lần đầu tiên tôi chuyển phương thức này sang WeekInMonth
enum mà tôi đã tạo lại ở trang 275. Sau đó, tôi đổi tên phương thức thành toString . Tiếp theo, tôi
đã thay đổi nó từ một phương thức tĩnh thành một phương thức thể hiện. Tất cả các bài kiểm tra vẫn vượt qua. (Bạn
có thể
đoán xem tôi sẽ đi đâu?)
Tiếp theo, tôi đã xóa hoàn toàn phương thức này! Năm xác nhận không thành công (dòng 411–415, Liệt kê B-4,
trang 374). Tôi đã thay đổi những dòng này để sử dụng tên của các điều tra viên ( FIRST,
THỨ HAI ,. . .). Tất cả các bài kiểm tra đều vượt qua. Bạn có thể thấy tại sao không? Bạn cũng có thể thấy lý do tại
sao mỗi
các bước cần thiết? Công cụ tái cấu trúc đảm bảo rằng tất cả những người gọi trước
weekInMonthToString hiện được gọi là toString trên bảng liệt kê weekInMonth vì tất cả enu-
merator triển khai toString để chỉ cần trả lại tên của chúng. . . .
Thật không may, tôi đã hơi quá thông minh. Thanh lịch như chuỗi tái cấu trúc tuyệt vời đó-
là, cuối cùng tôi nhận ra rằng những người dùng duy nhất của chức năng này là những bài kiểm tra tôi vừa sửa đổi-
ified, vì vậy tôi đã xóa các bài kiểm tra.
Đánh lừa tôi một lần, xấu hổ về bạn. Lừa tôi hai lần, xấu hổ về tôi! Vì vậy, sau khi xác định rằng
không ai khác ngoài các thử nghiệm được gọi là relativeToString (dòng 765–781), tôi chỉ cần xóa
chức năng và các thử nghiệm của nó.
offsetToTarget + = 7;
return plusDays (offsetToTarget);
}
public DayDate getNearestDayOfWeek (mục tiêu ngày cuối cùng của ngày) {
int offsetToThisWeeksTarget = targetDay.index - getDayOfWeek (). index;
int offsetToFutureTarget = (offsetToThisWeeksTarget + 7)% 7;
int offsetToPreviousTarget = offsetToFutureTarget - 7;
if (offsetToFutureTarget> 3)
return plusDays (offsetToPreviousTarget);
khác
return plusDays (offsetToFutureTarget);
}
public DayDate getEndOfMonth () {
Tháng tháng = getMonth ();
int year = getYear ();
int lastDay = lastDayOfMonth (tháng, năm);
return DayDateFactory.makeDate (Ngày cuối cùng, tháng, năm);
}
www.it-ebooks.info

Trang 313
282
Chương 16: Refactoring SerialDate
Cuối cùng chúng ta cũng đã chuyển sang các phương thức trừu tượng của lớp trừu tượng này. Và người đầu tiên
thích hợp khi chúng đến: toSerial (dòng 838–844). Trở lại trang 279 Tôi đã thay đổi
tên thành toOrdinal . Sau khi xem xét nó trong bối cảnh này, tôi quyết định tên nên là
đã thay đổi thành getOrdinalDay .
Phương thức trừu tượng tiếp theo là toDate (dòng 838–844). Nó chuyển đổi một Ngày thành một
java.util.Date . Tại sao phương pháp này trừu tượng? Nếu chúng ta xem xét việc triển khai nó trong
SpreadsheetDate (dòng 198–207, Liệt kê B-5, trang 382), chúng tôi thấy rằng nó không phụ thuộc vào
bất kỳ thứ gì trong việc triển khai lớp đó [G6]. Vì vậy, tôi đã đẩy nó lên.
Các phương thức getYYYY , getMonth và getDayOfMonth rất trừu tượng. Tuy nhiên,
Phương thức getDayOfWeek là một phương thức khác sẽ được kéo lên từ SpreadSheetDate
vì nó không phụ thuộc vào bất cứ thứ gì không thể tìm thấy trong DayDate [G6]. Hay không?
Nếu bạn xem xét kỹ (dòng 247, Liệt kê B-5, trang 382), bạn sẽ thấy rằng thuật toán
mặc nhiên phụ thuộc vào nguồn gốc của ngày thứ tự (nói cách khác, ngày trong tuần của
ngày 0). Vì vậy, mặc dù hàm này không có phụ thuộc vật lý nhưng không thể di chuyển
đối với DayDate , nó có một sự phụ thuộc logic.
Sự phụ thuộc logic như thế này làm phiền tôi [G22]. Nếu một cái gì đó hợp lý phụ thuộc vào
triển khai, sau đó một cái gì đó vật lý cũng nên. Ngoài ra, đối với tôi dường như
bản thân thuật toán có thể chung chung với một phần nhỏ hơn nhiều phụ thuộc vào
thực hiện [G6].
Vì vậy, tôi đã tạo ra một phương pháp trừu tượng trong DayDate tên getDayOfWeekForOrdinalZero và
đã triển khai nó trong SpreadsheetDate để trả về Day.SATURDAY . Sau đó, tôi đã di chuyển getDayOfWeek
lên đến DayDate và thay đổi nó để gọi getOrdinalDay và getDayOfWeekForOrdinal-
Không .
Ngoài ra, hãy xem kỹ nhận xét từ dòng 895 đến dòng 899. Đây có phải là
sự lặp lại thực sự cần thiết? Như thường lệ, tôi đã xóa bình luận này cùng với tất cả những người khác.
Phương pháp tiếp theo là so sánh (dòng 902–913). Một lần nữa, phương pháp này không phù hợp
trừu tượng [G6], vì vậy tôi đã kéo việc triển khai vào DayDate . Ngoài ra, tên không
đủ giao tiếp [N1]. Phương thức này thực sự trả về sự khác biệt trong số ngày kể từ khi
tranh luận. Vì vậy, tôi đã đổi tên thành daysSince . Ngoài ra, tôi lưu ý rằng không có bất kỳ thử nghiệm nào
cho phương pháp này, vì vậy tôi đã viết chúng.
Sáu hàm tiếp theo (dòng 915–980) là tất cả các phương thức trừu tượng nên được áp dụng-
được đề cập trong DayDate . Vì vậy, tôi đã kéo tất cả chúng lên từ SpreadsheetDate .
Hàm cuối cùng, isInRange (dòng 982–995) cũng cần phải được kéo lên và tham chiếu lại
săn chắc. Câu lệnh switch hơi xấu một chút [G23] và có thể được thay thế bằng cách di chuyển các trường hợp
vào enum DateInterval .
public Day getDayOfWeek () {
Ngày bắt đầuDay = getDayOfWeekForOrdinalZero ();
int startOffset = startedDay.index - Day.SUNDAY.index;
return Day.make ((getOrdinalDay () + startedOffset)% 7 + 1);
}
www.it-ebooks.info

Trang 314
283
Sau đó làm cho nó đúng
Điều đó đưa chúng ta đến cuối DayDate . Vì vậy, bây giờ chúng tôi sẽ thực hiện một lần nữa trên toàn bộ
lớp để xem nó chảy tốt như thế nào.
Đầu tiên, chú thích mở đầu đã lỗi thời, vì vậy tôi đã rút ngắn và cải thiện nó [C2].
Tiếp theo, tôi chuyển tất cả các enums còn lại vào các tệp riêng của chúng [G12].
Tiếp theo, tôi đã di chuyển biến tĩnh ( dateFormatSymbols ) và ba phương thức tĩnh
( getMonthNames ,  isLeapYear ,  lastDayOfMonth ) vào một lớp mới có tên là DateUtil [G6].
Tôi đã chuyển các phương thức trừu tượng lên trên cùng nơi chúng thuộc về [G24].
Tôi đã thay đổi Month.make thành Month.fromInt [N1] và làm tương tự cho tất cả các enum khác.
Tôi cũng đã tạo một trình truy cập toInt () cho tất cả các enum và đặt trường chỉ mục ở chế độ riêng tư.
Có một số trùng lặp thú vị [G5] trong plusYears và plusMonths mà tôi đã
có thể loại bỏ bằng cách trích xuất một phương thức mới có tên là trueLastDayOfMonth , làm cho
cả ba phương pháp rõ ràng hơn nhiều.
Tôi đã loại bỏ số 1 kỳ diệu [G25], thay thế nó bằng Month.JANUARY.toInt () hoặc
Day.SUNDAY.toInt () , nếu thích hợp. Tôi đã dành một ít thời gian với SpreadsheetDate , dọn dẹp
các thuật toán một chút. Kết quả cuối cùng có trong Liệt kê B-7, trang 394, đến hết
Liệt kê B-16, trang 405.
public enum DateInterval {
MỞ {
public boolean isIn (int d, int left, int right) {
return d> left && d <right;
}
},
CLOSED_LEFT {
public boolean isIn (int d, int left, int right) {
return d> = left && d <right;
}
},
CLOSED_RIGHT {
public boolean isIn (int d, int left, int right) {
return d> left && d <= right;
}
},
ĐÃ ĐÓNG CỬA {
public boolean isIn (int d, int left, int right) {
return d> = left && d <= right;
}
};
public trừu tượng boolean isIn (int d, int left, int right);
}
public boolean isInRange (DayDate d1, DayDate d2, DateInterval khoảng thời gian) {
int left = Math.min (d1.getOrdinalDay (), d2.getOrdinalDay ());
int right = Math.max (d1.getOrdinalDay (), d2.getOrdinalDay ());
return khoảng.isIn (getOrdinalDay (), left, right);
}
www.it-ebooks.info

Trang 315
284
Chương 16: Refactoring SerialDate
Điều thú vị là độ phủ của mã trong DayDate đã giảm xuống còn 84,9 phần trăm! Đây không phải là
vì ít chức năng đang được thử nghiệm; đúng hơn là vì lớp học đã bị thu hẹp quá nhiều
rằng một vài dòng không được che đậy có trọng lượng lớn hơn. DayDate hiện có 45 trong số 53 lần thực thi-
các câu lệnh có thể được kiểm tra bao hàm. Những dòng không được che đậy quá tầm thường nên chúng không
đáng
thử nghiệm.
Phần kết luận
Vì vậy, một lần nữa chúng tôi đã tuân theo Quy tắc Hướng đạo sinh. Chúng tôi đã kiểm tra mã rõ ràng hơn một chút
so với khi chúng tôi kiểm tra nó. Nó mất một chút thời gian, nhưng nó là giá trị nó. Phạm vi kiểm tra là
tăng lên, một số lỗi đã được sửa, mã được làm rõ và thu nhỏ. Người tiếp theo
nhìn vào đoạn mã này, hy vọng sẽ thấy nó dễ xử lý hơn chúng ta đã làm. Người đó cũng sẽ
có lẽ có thể làm sạch nó nhiều hơn một chút so với chúng tôi đã làm.
Thư mục
[GOF]: Mẫu thiết kế: Các yếu tố của phần mềm hướng đối tượng có thể tái sử dụng , Gamma et al.,
Addison-Wesley, 1996.
[Simmons04]: Hardcore Java , Robert Simmons, Jr., O'Reilly, 2004.
[Refactoring]: Refactoring: Cải thiện thiết kế của mã hiện có , Martin Fowler và cộng sự,
Addison-Wesley, 1999.
[Beck97]: Các mẫu thực hành tốt nhất cho Smalltalk , Kent Beck, Prentice Hall, 1997.
www.it-ebooks.info

Trang 316
285
17
Mùi và phương pháp chẩn đoán
Trong cuốn sách Refactoring tuyệt vời của mình , 1 Martin Fowler đã xác định nhiều “Code
Có mùi. ” Danh sách tiếp theo bao gồm nhiều mùi của Martin và thêm nhiều mùi khác của tôi
sở hữu. Nó cũng bao gồm các loại ngọc trai và phương pháp phỏng đoán khác mà tôi sử dụng để thực hành giao
dịch của mình.
1. [Tái cấu trúc].
www.it-ebooks.info

Trang 317
286
Chương 17: Mùi và phương pháp chẩn đoán
Tôi đã biên soạn danh sách này bằng cách xem qua một số chương trình khác nhau và cấu trúc lại
chúng. Khi tôi thực hiện mỗi thay đổi, tôi tự hỏi bản thân tại sao tôi lại thực hiện thay đổi đó và sau đó viết
lý do ở đây. Kết quả là một danh sách khá dài những thứ có mùi khó chịu đối với tôi khi tôi đọc
mã.
Danh sách này có nghĩa là được đọc từ trên xuống dưới và cũng được sử dụng như một tài liệu tham khảo.
Có một tham chiếu chéo cho mỗi heuristic cho bạn biết nơi nó được tham chiếu trong
phần còn lại của văn bản trong “Phụ lục C” trên trang 409.
Bình luận
C1: Thông tin không phù hợp
Không thích hợp cho một nhận xét để nắm giữ thông tin tốt hơn trong một loại hệ thống khác-
chẳng hạn như hệ thống kiểm soát mã nguồn của bạn, hệ thống theo dõi vấn đề của bạn hoặc bất kỳ
hệ thống lưu trữ hồ sơ. Thay đổi lịch sử, chẳng hạn, chỉ làm lộn xộn các tệp nguồn với
khối lượng văn bản lịch sử và không thú vị. Nói chung, siêu dữ liệu chẳng hạn như tác giả, cuối cùng-
ngày sửa đổi, số SPR, v.v. sẽ không xuất hiện trong nhận xét. Nhận xét nên
được dành riêng cho các ghi chú kỹ thuật về mã và thiết kế.
C2: Nhận xét lỗi thời
Một nhận xét đã trở nên cũ, không liên quan và không chính xác là lỗi thời. Nhận xét cũ đi
Mau. Tốt nhất là không nên viết bình luận sẽ trở nên lỗi thời. Nếu bạn thấy lỗi thời
bình luận, tốt nhất là cập nhật nó hoặc loại bỏ nó càng nhanh càng tốt. Nhận xét lỗi thời
có xu hướng di chuyển khỏi mã mà họ đã từng mô tả. Chúng trở thành những hòn đảo nổi của
sự không liên quan và sai hướng trong mã.
C3: Nhận xét thừa
Một nhận xét là thừa nếu nó mô tả điều gì đó mô tả đầy đủ về chính nó. Đối với
thí dụ:
i ++; // gia số i
Một ví dụ khác là Javadoc không nói gì nhiều hơn (hoặc thậm chí ít hơn) hàm
Chữ ký:
/ **
* @param sellRequest
* @trở về
* @throws ManagedComponentException
*/
công khai SellResponse beginSellItem (SellRequest sellRequest)
ném ManagedComponentException
Nhận xét phải nói những điều mà mã không thể nói cho chính nó.
www.it-ebooks.info
Trang 318
287
Môi trường
C4: Nhận xét bằng văn bản kém
Một nhận xét đáng để viết là đáng để viết tốt. Nếu bạn định viết bình luận,
dành thời gian để đảm bảo rằng đó là nhận xét tốt nhất mà bạn có thể viết. Chọn từ của bạn
cẩn thận. Sử dụng đúng ngữ pháp và dấu câu. Đừng lan man. Đừng nói rõ ràng.
Hãy ngắn gọn.
C5: Mã nhận xét ra
Nó khiến tôi phát điên khi thấy những đoạn mã được bình luận ra ngoài. Ai biết nó bao nhiêu tuổi
Là? Ai biết nó có ý nghĩa hay không? Tuy nhiên, không ai sẽ xóa nó vì tất cả mọi người
giả sử người khác cần nó hoặc có kế hoạch cho nó.
Mã đó nằm đó và thối rữa, ngày càng ít phù hợp hơn với mỗi ngày trôi qua. Nó
gọi các hàm không còn tồn tại. Nó sử dụng các biến có tên đã thay đổi. Nó theo sau
những quy ước đã lỗi thời từ lâu. Nó làm ô nhiễm các mô-đun chứa nó và làm phân tán
những người cố gắng đọc nó. Mã nhận xét là một điều ghê tởm .
Khi bạn thấy mã được bình luận, hãy xóa nó! Đừng lo lắng, kiểm soát mã nguồn
hệ thống vẫn nhớ nó. Nếu ai thực sự cần thì có thể quay lại xem
phiên bản trước. Đừng chịu đựng mã bình luận để tồn tại.
Môi trường
E1: Xây dựng yêu cầu nhiều hơn một bước
Xây dựng một dự án nên là một hoạt động nhỏ. Bạn không cần phải kiểm tra nhiều
phần nhỏ ra khỏi kiểm soát mã nguồn. Bạn không nên cần một chuỗi phức tạp
mands hoặc tập lệnh phụ thuộc ngữ cảnh để xây dựng các phần tử riêng lẻ. Bạn nên
không phải tìm kiếm gần và xa cho tất cả các tệp JAR, tệp XML bổ sung nhỏ khác nhau, và các
hiện vật mà hệ thống yêu cầu. Bạn sẽ có thể kiểm tra hệ thống bằng một sim-
lệnh ple và sau đó đưa ra một lệnh đơn giản khác để xây dựng nó.
svn lấy mySystem
cd mySystem
kiến tất cả
E2: Thử nghiệm yêu cầu nhiều hơn một bước
Bạn sẽ có thể chạy tất cả các bài kiểm tra đơn vị chỉ với một lệnh. Trong trường hợp tốt nhất bạn
có thể chạy tất cả các bài kiểm tra bằng cách nhấp vào một nút trong IDE của bạn. Trong trường hợp xấu nhất bạn
nên
có thể đưa ra một lệnh đơn giản trong một trình bao. Có thể chạy tất cả các bài kiểm tra là như vậy
cơ bản và quan trọng đến mức phải thực hiện nhanh chóng, dễ dàng và rõ ràng.
www.it-ebooks.info

Trang 319
288
Chương 17: Mùi và phương pháp chẩn đoán
Chức năng
F1: Quá nhiều đối số
Các hàm nên có một số lượng nhỏ đối số. Không có lập luận nào là tốt nhất, tiếp theo là
một, hai và ba. Nhiều hơn ba là rất đáng nghi vấn và nên tránh với prej-
udice. (Xem “Đối số Hàm” trên trang 40.)
F2: Đối số đầu ra
Các đối số đầu ra là phản trực giác. Người đọc mong đợi các đối số là đầu vào, không phải là
đặt. Nếu hàm của bạn phải thay đổi trạng thái của một cái gì đó, hãy yêu cầu nó thay đổi trạng thái của
đối tượng mà nó được gọi. (Xem “Đối số đầu ra” trên trang 45.)
F3: Cờ đối số
Các đối số Boolean tuyên bố rằng hàm thực hiện nhiều hơn một việc. họ đang
khó hiểu và cần được loại bỏ. (Xem “Đối số về Cờ” trên trang 41.)
F4: Chức năng chết
Các phương thức không bao giờ được gọi nên bị loại bỏ. Giữ mã chết xung quanh là lãng phí.
Đừng ngại xóa chức năng. Hãy nhớ rằng, hệ thống kiểm soát mã nguồn của bạn vẫn
nhớ nó.
Chung
G1: Nhiều ngôn ngữ trong một tệp nguồn
Môi trường lập trình hiện đại ngày nay cho phép đặt nhiều ngôn ngữ khác nhau
vào một tệp nguồn duy nhất. Ví dụ: một tệp nguồn Java có thể chứa các đoạn mã của XML,
HTML, YAML, JavaDoc, Tiếng Anh, JavaScript, v.v. Ví dụ khác, ngoài
HTML một tệp JSP có thể chứa Java, cú pháp thư viện thẻ, nhận xét tiếng Anh, Javadocs,
XML, JavaScript, v.v. Điều này tốt nhất là khó hiểu và tệ nhất là cẩu thả bất cẩn.
Lý tưởng là tệp nguồn chứa một và chỉ một ngôn ngữ. Thực tế, chúng tôi
có thể sẽ phải sử dụng nhiều hơn một. Nhưng chúng ta nên cố gắng giảm thiểu cả hai
số lượng và mức độ của các ngôn ngữ bổ sung trong các tệp nguồn của chúng tôi.
G2: Hành vi rõ ràng không được thực hiện
Theo “Nguyên tắc ít bất ngờ nhất”, 2 bất kỳ hàm hoặc lớp nào nên triển khai
những hành vi mà một lập trình viên khác có thể mong đợi một cách hợp lý. Ví dụ, hãy xem xét một
hàm dịch tên của một ngày thành một enum đại diện cho ngày.
2. Hoặc “Nguyên tắc ít kinh ngạc nhất ”: http://en.wikipedia.org/wiki/
Principle_of_least_astonishment
www.it-ebooks.info

Trang 320
289
Chung
Day day = DayDate.StringToDay (String dayName);
Chúng tôi hy vọng chuỗi "Monday" sẽ được dịch sang Day.MONDAY . Chúng tôi cũng mong đợi
các từ viết tắt phổ biến sẽ được dịch và chúng tôi hy vọng hàm sẽ bỏ qua
trường hợp.
Khi một hành vi rõ ràng không được thực hiện, người đọc và người dùng mã không thể
còn phụ thuộc vào trực giác của họ về tên hàm. Họ mất niềm tin vào bản gốc
tác giả và phải đọc lại các chi tiết của mã.
G3: Hành vi không đúng tại ranh giới
Có vẻ như hiển nhiên khi nói rằng mã phải hoạt động chính xác. Vấn đề là chúng ta hiếm khi
nhận ra hành vi đúng phức tạp như thế nào. Các nhà phát triển thường viết các hàm
họ nghĩ rằng sẽ hiệu quả, và sau đó tin tưởng vào trực giác của họ hơn là cố gắng chứng minh
rằng mã của họ hoạt động trong tất cả các trường hợp góc và ranh giới.
Không có sự thay thế cho sự thẩm định. Mọi điều kiện biên, mọi góc
trường hợp, mọi câu hỏi và ngoại lệ đại diện cho một cái gì đó có thể gây nhầm lẫn cho
thuật toán trực quan. Đừng dựa vào trực giác của bạn . Tìm kiếm mọi điều kiện biên và
viết một bài kiểm tra cho nó.
G4: Các quyền an toàn bị ghi đè
Chernobyl tan chảy vì người quản lý nhà máy làm việc quá nhiều trong các cơ cấu an toàn-
từng cái một. Sự an toàn đã làm cho việc chạy thử nghiệm trở nên bất tiện. Các
kết quả là cuộc thử nghiệm đã không được thực hiện và thế giới đã chứng kiến nó đầu tiên
thảm họa hạt nhân.
Sẽ rất rủi ro khi ghi đè lên các an toàn. Thực hiện điều khiển thủ công đối với serialVersionUID có thể
cần thiết, nhưng nó luôn có rủi ro. Tắt một số cảnh báo trình biên dịch nhất định (hoặc tất cả các cảnh báo!)
có thể giúp bạn xây dựng thành công, nhưng có nguy cơ dẫn đến các phiên gỡ lỗi vô tận. Xoay-
việc bỏ qua các bài kiểm tra thất bại và nói với bản thân rằng bạn sẽ vượt qua chúng sau này cũng tệ như giả vờ vậy
thẻ tín dụng của bạn là tiền miễn phí.
G5: Sao chép
Đây là một trong những quy tắc quan trọng nhất trong cuốn sách này, và bạn nên thực hiện nó rất nghiêm túc.
Hầu như mọi tác giả viết về thiết kế phần mềm đều đề cập đến quy tắc này. Dave Thomas
và Andy Hunt gọi đó là nguyên tắc 3 KHÔ (Đừng lặp lại chính mình). Kent Beck đã làm được
một trong những nguyên tắc cốt lõi của Lập trình cực đoan và được gọi là: “Một lần và chỉ một lần”.
Ron Jeffries xếp quy tắc này ở vị trí thứ hai, ngay dưới điểm vượt qua tất cả các bài kiểm tra.
Mỗi khi bạn thấy mã trùng lặp, nó thể hiện một cơ hội bị bỏ lỡ cho
sự trừu tượng. Sự trùng lặp đó có thể trở thành một chương trình con hoặc có thể là một
lớp học hoàn toàn. Bằng cách gấp bản sao chép thành một sự trừu tượng như vậy, bạn sẽ tăng từ vựng-
ngôn ngữ thiết kế của bạn Các lập trình viên khác có thể sử dụng các phương tiện trừu tượng
3. [PRAG].
www.it-ebooks.info

Trang 321
290
Chương 17: Mùi và phương pháp chẩn đoán
bạn tạo. Việc mã hóa trở nên nhanh hơn và ít bị lỗi hơn vì bạn đã nâng
mức độ trừu tượng.
Hình thức trùng lặp rõ ràng nhất là khi bạn có các cụm mã giống hệt nhau
trông giống như một số lập trình viên đã sử dụng chuột, dán cùng một đoạn mã lên trên và
hơn một lần nữa. Chúng nên được thay thế bằng các phương pháp đơn giản.
Một dạng tinh tế hơn là chuỗi chuyển đổi / trường hợp hoặc chuỗi if / else xuất hiện lặp đi lặp lại trong
các mô-đun khác nhau, luôn thử nghiệm cho cùng một tập hợp các điều kiện. Những thứ này nên được thay thế
với tính đa hình.
Vẫn còn tinh vi hơn là các mô-đun có các thuật toán tương tự, nhưng không chia sẻ
dòng mã tương tự. Đây vẫn là sự trùng lặp và cần được giải quyết bằng cách sử dụng T EM-
PLATE M ETHOD , mẫu 4 hoặc S TRATEGY 5 .
Thật vậy, hầu hết các mẫu thiết kế đã xuất hiện trong mười lăm năm qua là sim-
ply những cách nổi tiếng để loại bỏ sự trùng lặp. Vì vậy, các Biểu mẫu Codd Thường cũng là một giai đoạn-
egy để loại bỏ trùng lặp trong lược đồ cơ sở dữ liệu. Bản thân OO là một chiến lược để tổ chức
mô-đun và loại bỏ sự trùng lặp. Không có gì đáng ngạc nhiên khi lập trình có cấu trúc cũng vậy.
Tôi nghĩ rằng điểm đã được thực hiện. Tìm và loại bỏ sự trùng lặp ở bất cứ đâu bạn có thể.
G6: Mã ở mức trừu tượng sai
Điều quan trọng là tạo ra các khái niệm trừu tượng để tách các khái niệm chung cấp cao hơn khỏi
cấp khái niệm chi tiết. Đôi khi chúng tôi làm điều này bằng cách tạo các lớp trừu tượng để giữ
các khái niệm cấp cao hơn và các dẫn xuất để chứa các khái niệm cấp thấp hơn. Khi chúng tôi làm điều này,
chúng ta cần đảm bảo rằng quá trình phân tách đã hoàn tất. Chúng tôi muốn tất cả các khái niệm cấp thấp hơn
ở trong các dẫn xuất và tất cả các khái niệm cấp cao hơn ở trong lớp cơ sở.
Ví dụ: hằng số, biến hoặc hàm tiện ích chỉ liên quan đến chi tiết
thực thi không nên có trong lớp cơ sở. Lớp cơ sở nên biết không-
về họ.
Quy tắc này cũng liên quan đến tệp nguồn, thành phần và mô-đun. Phần mềm tốt
thiết kế yêu cầu chúng tôi tách các khái niệm ở các cấp độ khác nhau và đặt chúng ở các
hộp đựng. Đôi khi các vùng chứa này là các lớp cơ sở hoặc các dẫn xuất và đôi khi chúng
là các tệp nguồn, mô-đun hoặc thành phần. Dù trường hợp có thể là gì, sự tách biệt cần
để được hoàn chỉnh. Chúng tôi không muốn các khái niệm cấp thấp hơn và cao hơn trộn lẫn với nhau.
Hãy xem xét đoạn mã sau:
giao diện công cộng Stack {
Đối tượng pop () ném EmptyException;
void push (Object o) ném FullException;
double phần trămFull ();
4. [GOF].
5. [GOF].
www.it-ebooks.info

Trang 322
291
Chung
class EmptyException mở rộng Exception {}
class FullException mở rộng Exception {}
}
Hàm phần trămFull ở mức trừu tượng sai. Mặc dù có
nhiều triển khai của Stack trong đó khái niệm đầy đủ là hợp lý, có những
triển khai mà đơn giản là không thể biết chúng đầy đủ như thế nào. Vì vậy, hàm sẽ là
tốt hơn được đặt trong một giao diện phái sinh như BoundStack .
Có lẽ bạn đang nghĩ rằng việc triển khai chỉ có thể trả về 0 nếu ngăn xếp
là vô hạn. Vấn đề với điều đó là không có ngăn xếp nào thực sự là vô hạn. Bạn không thể
thực sự ngăn chặn OutOfMemoryException bằng cách kiểm tra
stack.percentFull () <50.0.
Việc triển khai hàm để trả về 0 sẽ là nói dối.
Vấn đề là bạn không thể nói dối hoặc giả mạo theo cách của bạn từ một sự trừu tượng không đúng chỗ. Iso-
kiêng những gì trừu tượng là một trong những điều khó nhất mà các nhà phát triển phần mềm làm, và không có
sửa chữa nhanh chóng khi bạn làm sai.
G7: Các lớp cơ sở tùy thuộc vào các phái sinh của chúng
Lý do phổ biến nhất để phân vùng các khái niệm thành các lớp cơ sở và phái sinh là như vậy
rằng các khái niệm lớp cơ sở cấp cao hơn có thể độc lập với đạo hàm cấp thấp hơn
khái niệm giai cấp. Do đó, khi chúng ta thấy các lớp cơ sở đề cập đến tên của các dẫn xuất của chúng-
tives, chúng tôi nghi ngờ một vấn đề. Nói chung, các lớp cơ sở không nên biết gì về
các dẫn xuất.
Tất nhiên, có những ngoại lệ đối với quy tắc này. Đôi khi số lượng các dẫn xuất là
cố định nghiêm ngặt và lớp cơ sở có mã lựa chọn giữa các dẫn xuất. Chúng tôi thấy điều này
rất nhiều trong các triển khai máy trạng thái hữu hạn. Tuy nhiên, trong trường hợp đó, các dẫn xuất và cơ số
lớp được liên kết chặt chẽ và luôn triển khai cùng nhau trong cùng một tệp jar. Nói chung
trường hợp chúng tôi muốn có thể triển khai các dẫn xuất và cơ sở trong các tệp jar khác nhau.
Triển khai các dẫn xuất và cơ sở trong các tệp jar khác nhau và đảm bảo các tệp jar cơ sở
không biết gì về nội dung của các tệp jar phái sinh cho phép chúng tôi triển khai hệ thống của mình
trong các thành phần rời rạc và độc lập. Khi các thành phần đó được sửa đổi, chúng có thể
được triển khai lại mà không cần phải triển khai lại các thành phần cơ sở. Điều này có nghĩa là
tác động của một sự thay đổi được giảm thiểu đáng kể và việc duy trì các hệ thống tại hiện trường được thực hiện
nhiều
đơn giản hơn.
G8: Quá nhiều thông tin
Các mô-đun được xác định rõ có giao diện rất nhỏ cho phép bạn làm được nhiều việc với một ít.
Các mô-đun được xác định kém có giao diện rộng và sâu buộc bạn phải sử dụng nhiều
cử chỉ để hoàn thành những việc đơn giản. Một giao diện được xác định rõ ràng không cung cấp nhiều chức năng-
điểm để phụ thuộc vào, vì vậy khớp nối là thấp. Một giao diện được xác định kém cung cấp nhiều func-
tions mà bạn phải gọi, vì vậy khớp nối cao.
www.it-ebooks.info

Trang 323
292
Chương 17: Mùi và phương pháp chẩn đoán
Các nhà phát triển phần mềm giỏi học cách hạn chế những gì họ phơi bày trên các giao diện của
các lớp và mô-đun. Lớp càng có ít phương thức thì càng tốt. Càng ít biến thì func-
tion biết về, càng tốt. Lớp càng có ít biến thể hiện càng tốt.
Ẩn dữ liệu của bạn. Ẩn các chức năng tiện ích của bạn. Ẩn hằng số và thời gian tạm thời của bạn.
Không tạo các lớp với nhiều phương thức hoặc nhiều biến phiên bản. Đừng tạo ra nhiều
các biến và hàm được bảo vệ cho các lớp con của bạn. Tập trung vào việc giữ các giao diện
rất chặt chẽ và rất nhỏ. Giúp duy trì khớp nối thấp bằng cách hạn chế thông tin.
G9: Mã chết
Mã chết là mã không được thực thi. Bạn tìm thấy nó trong cơ thể của một nếu tuyên bố rằng séc
cho một điều kiện không thể xảy ra. Bạn tìm thấy nó trong khối bắt của một lần thử không bao giờ ném .
Bạn tìm thấy nó trong các phương thức tiện ích nhỏ không bao giờ được gọi hoặc điều kiện chuyển đổi / trường hợp
không bao giờ xảy ra.
Vấn đề với mã chết là sau một thời gian, nó bắt đầu có mùi. Nó càng cũ,
mùi trở nên mạnh hơn và chua hơn. Điều này là do mã chết không hoàn toàn
cập nhật khi thiết kế thay đổi. Nó vẫn biên dịch , nhưng nó không tuân theo các quy ước mới hơn hoặc
quy tắc. Nó được viết vào thời điểm mà hệ thống đã khác . Khi bạn tìm thấy mã chết, hãy làm
Điều đúng đắn. Cho nó chôn cất tử tế. Xóa nó khỏi hệ thống.
G10: Tách dọc
Các biến và hàm nên được định nghĩa gần với nơi chúng được sử dụng. Biến cục bộ
nên được khai báo ngay trên lần sử dụng đầu tiên của chúng và phải có phạm vi dọc nhỏ. Chúng tôi
không muốn các biến cục bộ được khai báo hàng trăm dòng khác xa với cách sử dụng của chúng.
Các chức năng riêng tư nên được xác định ngay bên dưới lần sử dụng đầu tiên của chúng. Các chức năng riêng
thuộc phạm vi của cả lớp, nhưng chúng tôi vẫn muốn giới hạn khoảng cách theo chiều dọc
giữa lời gọi và định nghĩa. Tìm một chức năng riêng chỉ nên là một vấn đề
quét xuống từ lần sử dụng đầu tiên.
G11: Không nhất quán
Nếu bạn làm điều gì đó theo một cách nhất định, hãy làm tất cả những việc tương tự theo cùng một cách. Điều này
quay trở lại
theo nguyên tắc ít bất ngờ nhất. Hãy cẩn thận với các quy ước bạn chọn và một lần
đã chọn, hãy cẩn thận để tiếp tục theo dõi chúng.
Nếu trong một hàm cụ thể, bạn sử dụng một phản hồi có tên biến để giữ một
HttpServletResponse , sau đó sử dụng nhất quán cùng một tên biến trong các hàm khác
sử dụng các đối tượng HttpServletResponse . Nếu bạn đặt tên cho một phương thức processVerificationRequest ,
sau đó sử dụng một tên tương tự, chẳng hạn như processDeletionRequest , cho các phương thức xử lý
các loại yêu cầu khác.
Tính nhất quán đơn giản như thế này, khi được áp dụng một cách đáng tin cậy, có thể làm cho mã dễ dàng hơn nhiều
đọc và sửa đổi.
www.it-ebooks.info

Trang 324
293
Chung
G12: Lộn xộn
Việc sử dụng là một phương thức khởi tạo mặc định không có triển khai nào? Tất cả những gì nó làm là lộn xộn
lên mã với các hiện vật vô nghĩa. Các biến không được sử dụng, các hàm không bao giờ
được gọi, nhận xét không thêm thông tin, v.v. Tất cả những thứ này lộn xộn và
cần được loại bỏ. Giữ cho các tệp nguồn của bạn sạch sẽ, được tổ chức tốt và không bị lộn xộn.
G13: Khớp nối nhân tạo
Những thứ không phụ thuộc vào nhau không nên được ghép nối một cách giả tạo. Ví dụ,
các enums chung không được chứa trong các lớp cụ thể hơn vì điều này buộc
toàn bộ ứng dụng để biết về các lớp cụ thể hơn này. Nói chung cũng vậy
mục đích các hàm tĩnh được khai báo trong các lớp cụ thể.
Nói chung, khớp nối nhân tạo là một khớp nối giữa hai mô-đun phục vụ không
mục đích trực tiếp. Đó là kết quả của việc đưa một biến, hằng số hoặc hàm vào một
vị trí thuận tiện, mặc dù không thích hợp. Đây là sự lười biếng và bất cẩn.
Hãy dành thời gian để tìm ra vị trí của các hàm, hằng và biến
khai báo. Đừng chỉ ném chúng vào nơi thuận tiện nhất trong tầm tay và sau đó bỏ chúng đi
ở đó.
G14: Tính năng đố kỵ
Đây là một trong những mùi mã của Martin Fowler. 6 Các phương thức của một lớp cần được quan tâm
các biến và hàm của lớp mà chúng thuộc về chứ không phải các biến và hàm
của các lớp khác. Khi một phương thức sử dụng trình truy cập và trình đột biến của một số đối tượng khác để
thao tác dữ liệu trong đối tượng đó, sau đó nó ganh tị với phạm vi của lớp đó khác
vật. Nó ước rằng nó ở bên trong lớp khác đó để nó có thể truy cập trực tiếp vào
các biến mà nó đang thao tác. Ví dụ:
lớp công khai HourlyPayCalculator {
công khai Tiền tínhWeeklyPay (Hàng giờ Nhân viên e) {
int tenthRate = e.getTenthRate (). getPennies ();
int tenthsWorked = e.getTenthsWorked ();
int thẳngTime = Math.min (400, tenthsWorked);
int overTime = Math.max (0, tenthsWorked - thẳng thời gian);
int thẳngPay = StraightTime * tenthRate;
int timePay = (int) Math.round (overTime * tenthRate * 1.5);
trả lại Tiền mới (Trả thẳng + Trả thêm giờ);
}
}
Các calculateWeeklyPay đạt phương pháp vào HourlyEmployee đối tượng để có được các dữ liệu trên
mà nó hoạt động. Các calculateWeeklyPay phương pháp ganh tị với phạm vi HourlyEmployee . Nó
"Mong muốn" rằng nó có thể nằm trong HourlyE Employee .
6. [Tái cấu trúc].
www.it-ebooks.info

Trang 325
294
Chương 17: Mùi và phương pháp chẩn đoán
Tất cả những điều khác đều bình đẳng, chúng tôi muốn loại bỏ tính năng Envy vì nó làm lộ nội dung
của lớp này sang lớp khác. Tuy nhiên, đôi khi, Feature Envy là một tội ác cần thiết. Xem xét
tiếp theo:
lớp công khai HourlyErantyeeReport {
nhân viên theo giờ riêng;
public HourlyEmpleteeeReport (HourlyE Employee e) {
this.employee = e;
}
Báo cáo chuỗiHours () {
return String.format (
"Tên:% s \ t Giờ:% d.% 1d \ n",
nhân viên.getName (),
worker.getTenthsWorked () / 10,
nhân viên.getTenthsWorked ()% 10);
}
}
Rõ ràng, phương thức reportHours ghen tị với lớp HourlyE Employee . Mặt khác, chúng tôi
không muốn Nhân viên theo giờ phải biết về định dạng của báo cáo. Di chuyển điều đó cho-
chuỗi mat vào lớp HourlyE Employee sẽ vi phạm một số nguyên tắc của hướng đối tượng
thiết kế. 7 Nó sẽ kết hợp Hàng giờ Nhân viên với định dạng của báo cáo, khiến báo cáo có thể thay đổi
ở định dạng đó.
G15: Đối số của bộ chọn
Khó có điều gì đáng ghê tởm hơn một lập luận sai lầm lủng lẳng ở cuối một
chức năng gọi. Nó có nghĩa là gì? Nó sẽ thay đổi điều gì nếu nó là sự thật ? Không chỉ là
mục đích của đối số bộ chọn khó nhớ, mỗi đối số bộ chọn kết hợp
nhiều chức năng thành một. Đối số bộ chọn chỉ là một cách lười biếng để tránh chia nhỏ
chức năng thành một số chức năng nhỏ hơn. Xem xét:
public int allowWeeklyPay (boolean ngoài giờ) {
int tenthRate = getTenthRate ();
int tenthsWorked = getTenthsWorked ();
int thẳngTime = Math.min (400, tenthsWorked);
int overTime = Math.max (0, tenthsWorked - thẳng thời gian);
int thẳngPay = StraightTime * tenthRate;
tăng gấp đôiRate = làm thêm giờ? 1,5: 1,0 * tenthRate;
int timePay = (int) Math.round (overTime * timeRate);
trả thẳngPay + làm thêmPay;
}
Bạn gọi hàm này với true nếu thời gian làm thêm giờ được trả là thời gian rưỡi và với
sai nếu làm thêm giờ được trả theo thời gian thẳng. Nó đủ tệ để bạn phải nhớ những gì
tính toánWeeklyPay (sai) có nghĩa là bất cứ khi nào bạn tình cờ gặp nó. Nhưng
7. Cụ thể là Nguyên tắc Trách nhiệm Đơn lẻ, Nguyên tắc Đóng mở và Nguyên tắc Đóng chung. Xem [PPP].
www.it-ebooks.info
Trang 326
295
Chung
Sự xấu hổ thực sự của một chức năng như thế này là tác giả đã bỏ lỡ cơ hội để viết
tiếp theo:
public int thẳngPay () {
trả về getTenthsWorked () * getTenthRate ();
}
public int overTimePay () {
int overTimeTenths = Math.max (0, getTenthsWorked () - 400);
int overTimePay = overTimeBonus (overTimeTenths);
trả về StraightPay () + overTimePay;
}
private int overTimeBonus (int overTimeTenths) {
tiền thưởng gấp đôi = 0,5 * getTenthRate () * overTimeTenths;
return (int) Math.round (tiền thưởng);
}
Tất nhiên, bộ chọn không cần phải là boolean . Chúng có thể là enum, số nguyên hoặc bất kỳ
loại đối số được sử dụng để chọn hành vi của hàm. Nói chung tốt hơn là nên
có nhiều chức năng hơn là chuyển một số mã vào một chức năng để chọn hành vi.
G16: Ý định bị che khuất
Chúng tôi muốn mã càng biểu cảm càng tốt. Biểu thức chạy, ký hiệu Hungary,
và những con số ảo diệu đều che khuất ý đồ của tác giả. Ví dụ: đây là overTimePay
hoạt động như nó có thể đã xuất hiện:
public int m_otCalc () {
trả về iThsWkd * iThsRte +
(int) Math.round (0.5 * iThsRte *
Math.max (0, iThsWkd - 400)
);
}
Nhỏ và dày đặc như những gì có thể xuất hiện, nó cũng hầu như không thể xuyên thủng. Nó đáng để tak-
dành thời gian để làm cho ý định của mã của chúng tôi hiển thị cho người đọc của chúng tôi.
G17: Trách nhiệm đặt nhầm chỗ
Một trong những quyết định quan trọng nhất mà một nhà phát triển phần mềm có thể thực hiện là đặt mã ở đâu.
Ví dụ, hằng số PI nên đi đâu? Nó có nên được trong lớp Toán không? Có lẽ nó
thuộc lớp Lượng giác ? Hoặc có thể trong lớp Circle ?
Nguyên tắc ít bất ngờ nhất được áp dụng ở đây. Mã phải được đặt ở nơi
độc giả sẽ tự nhiên mong đợi nó được. Các PI liên tục nên đi đâu các chức năng trang điểm
được khai báo. Hằng số OVERTIME_RATE phải được khai báo trong HourlyPay-
Lớp máy tính .
Đôi khi chúng tôi “thông minh” về vị trí đặt một số chức năng nhất định. Chúng tôi sẽ đặt nó trong một
chức năng thuận tiện cho chúng tôi, nhưng không nhất thiết phải trực quan đối với người đọc. Ví dụ,
có lẽ chúng ta cần in một báo cáo với tổng số giờ mà một nhân viên đã làm việc. Chúng tôi
www.it-ebooks.info

Trang 327
296
Chương 17: Mùi và phương pháp chẩn đoán
có thể tổng hợp số giờ đó trong mã in báo cáo hoặc chúng tôi có thể cố gắng duy trì hoạt động-
Tổng ning trong mã chấp nhận thẻ thời gian.
Một cách để đưa ra quyết định này là xem tên của các hàm. Hãy nói rằng
mô-đun báo cáo của chúng tôi có một chức năng được đặt tên là getTotalHours . Cũng giả sử rằng mô-đun đó
chấp nhận thẻ thời gian có chức năng saveTimeCard . Chức năng nào trong số hai chức năng này, theo tên của nó,
ngụ ý rằng nó tính tổng? Câu trả lời nên được rõ ràng.
Rõ ràng, đôi khi có những lý do hiệu suất tại sao nên tính tổng
khi thẻ thời gian được chấp nhận hơn là khi báo cáo được in. Tốt thôi, nhưng những cái tên
của các chức năng phải phản ánh điều này. Ví dụ, phải có một computeRunning-
Chức năng TotalOfHours trong mô-đun thẻ thời gian.
G18: Tĩnh không phù hợp
Math.max (double a, double b) là một phương thức tĩnh tốt. Nó không hoạt động trên một
ví dụ; thực sự, sẽ là ngớ ngẩn nếu phải nói Math mới (). max (a, b) hoặc thậm chí a.max (b) .
Tất cả dữ liệu mà max sử dụng đến từ hai đối số của nó chứ không phải từ bất kỳ "sở hữu" nào
vật. Thêm vào điểm, hầu như không có cơ hội mà chúng tôi muốn Math.max được
đa hình.
Tuy nhiên, đôi khi, chúng ta viết các hàm tĩnh không được phép tĩnh. Ví dụ,
xem xét:
HourlyPayCalculator.calculatePay (nhân viên, làm thêm giờRate).
Một lần nữa, đây có vẻ như là một hàm tĩnh hợp lý . Nó không hoạt động trên bất kỳ
đối tượng và nhận tất cả dữ liệu từ các đối số của nó. Tuy nhiên, có một cơ hội hợp lý rằng
chúng ta sẽ muốn hàm này là đa hình. Chúng tôi có thể muốn triển khai một số
các thuật toán để tính toán lương hàng giờ, ví dụ: OvertimeHourlyPayCalculator và
StraightTimeHourlyPayCalculator . Vì vậy, trong trường hợp này, hàm không nên tĩnh. Nó
nên là một chức năng thành viên không cố định của Nhân viên .
Nói chung, bạn nên thích các phương thức nonstatic hơn các phương thức tĩnh. Khi nghi ngờ,
làm cho chức năng không ổn định. Nếu bạn thực sự muốn một hàm tĩnh, hãy đảm bảo rằng
không có khả năng bạn muốn nó hoạt động đa hình.
G19: Sử dụng các biến giải thích
Kent Beck đã viết về điều này trong cuốn sách tuyệt vời của anh ấy Smalltalk Best Practice Patterns  8 và một lần
nữa
gần đây hơn trong cuốn sách Các mô hình triển khai tuyệt vời không kém của anh ấy . 9 Một trong những sức mạnh
nữa-
Cách tối đa để làm cho một chương trình có thể đọc được là chia nhỏ các phép tính thành giá trị trung gian
ues được giữ trong các biến có tên có nghĩa.
8. [Beck97], tr. 108.
9. [Beck07].
www.it-ebooks.info

Trang 328
297
Chung
Hãy xem xét ví dụ này từ FitNesse:
Đối sánh trận đấu = headerPattern.matcher (dòng);
if (match.find ())
{
Khóa chuỗi = match.group (1);
Giá trị chuỗi = match.group (2);
headers.put (key.toLowerCase (), giá trị);
}
Việc sử dụng các biến giải thích một cách đơn giản làm rõ ràng rằng nhóm đối sánh đầu tiên là
các trọng điểm, và nhóm phù hợp thứ hai là giá trị .
Thật khó để làm quá điều này. Nhiều biến giải thích hơn thường tốt hơn ít biến hơn. Nó
Điều đáng chú ý là làm thế nào một mô-đun mờ đục có thể đột nhiên trở nên trong suốt chỉ đơn giản bằng cách phá
vỡ-
nhập các phép tính vào các giá trị trung gian được đặt tên tốt.
G20: Tên chức năng nói lên điều chúng làm
Nhìn vào mã này:
Ngày newDate = date.add (5);
Bạn có mong đợi điều này sẽ thêm năm ngày vào ngày? Hay là vài tuần, hoặc vài giờ? Là ngày
phiên bản đã thay đổi hay hàm chỉ trả về Ngày mới mà không thay đổi Ngày cũ?
Bạn không thể biết từ cuộc gọi chức năng làm gì .
Nếu hàm thêm năm ngày vào ngày và thay đổi ngày, thì nó sẽ được gọi là
addDaysTo hoặc increaseByDays . Mặt khác, nếu hàm trả về một ngày mới là
năm ngày sau nhưng không thay đổi phiên bản ngày, nó sẽ được gọi là daysLater hoặc
ngày kể từ khi .
Nếu bạn phải xem cách triển khai (hoặc tài liệu) của hàm để biết
những gì nó làm, sau đó bạn nên làm việc để tìm một cái tên tốt hơn hoặc sắp xếp lại chức năng để
rằng nó có thể được đặt trong các hàm có tên tốt hơn.
G21: Hiểu thuật toán
Rất nhiều đoạn mã rất hài hước được viết bởi vì mọi người không dành thời gian để hiểu
thuật toán. Họ có một cái gì đó để hoạt động bằng cách cắm đủ nếu các câu lệnh và cờ,
mà không thực sự dừng lại để xem xét điều gì đang thực sự diễn ra.
Lập trình thường là một cuộc khám phá. Bạn nghĩ rằng bạn biết thuật toán phù hợp cho
một cái gì đó, nhưng sau đó bạn loay hoay với nó, thúc đẩy và chọc vào nó, cho đến khi bạn nhận được nó
làm việc." Làm thế nào để bạn biết nó "hoạt động"? Bởi vì nó vượt qua các trường hợp thử nghiệm mà bạn có thể
nghĩ ra.
Không có gì sai với cách tiếp cận này. Thật vậy, thường thì đó là cách duy nhất để có được
chức năng để làm những gì bạn nghĩ nó phải. Tuy nhiên, nó là không đủ để để lại báo giá
đánh dấu xung quanh từ “làm việc”.
www.it-ebooks.info

Trang 329
298
Chương 17: Mùi và phương pháp chẩn đoán
Trước khi bạn coi mình đã hoàn thành một chức năng, hãy đảm bảo rằng bạn hiểu
làm thế nào nó hoạt động. Nó không đủ tốt để nó vượt qua tất cả các bài kiểm tra. Bạn phải biết  10 rằng
giải pháp là chính xác.
Thông thường, cách tốt nhất để có được kiến thức và hiểu biết này là cấu trúc lại hàm-
tập trung vào một cái gì đó sạch sẽ và biểu cảm đến mức rõ ràng nó hoạt động như thế nào.
G22: Làm cho phụ thuộc logic trở nên vật lý
Nếu một mô-đun phụ thuộc vào mô-đun khác, thì sự phụ thuộc đó phải là vật lý, không chỉ là logic.
Mô-đun phụ thuộc không nên đưa ra các giả định (nói cách khác, phụ thuộc lôgic-
cies) về mô-đun mà nó phụ thuộc vào. Thay vào đó, nó nên yêu cầu mô-đun đó một cách rõ ràng cho tất cả
thông tin nó phụ thuộc vào.
Ví dụ: hãy tưởng tượng rằng bạn đang viết một hàm in một báo cáo văn bản thuần túy về
giờ làm việc của nhân viên. Một lớp có tên HourlyReporter tập hợp tất cả dữ liệu vào một
biểu mẫu thuận tiện và sau đó chuyển nó đến HourlyReportFormatter để in nó. (Xem Liệt kê 17-1.)
10. Có sự khác biệt giữa việc biết mã hoạt động như thế nào và biết liệu thuật toán có thực hiện công việc cần thiết của nó hay không.
Không chắc chắn rằng một thuật toán là phù hợp thường là một thực tế của cuộc sống. Không chắc chắn những gì mã của bạn làm chỉ là sự lười biếng.
Liệt kê 17-1
HourlyReporter.java
lớp công khai HourlyReporter {
private HourlyReportFormatter định dạng;
trang <LineItem> danh sách riêng tư;
cuối cùng riêng tư int PAGE_SIZE = 55;
public HourlyReporter (HourlyReportFormatter định dạng) {
this.formatter = định dạng;
page = new ArrayList <LineItem> ();
}
public void createReport (Danh sách nhân viên <HourlyE Jobee>) {
cho (HourlyE Employee e: nhân viên) {
addLineItemToPage (e);
if (page.size () == PAGE_SIZE)
printAndClearItemList ();
}
if (page.size ()> 0)
printAndClearItemList ();
}
private void printAndClearItemList () {
formatter.format (trang);
page.clear ();
}
private void addLineItemToPage (HourlyE Employee e) {
Mục LineItem = new LineItem ();
item.name = e.getName ();
item.hours = e.getTenthsWorked () / 10;
www.it-ebooks.info

Trang 330
299
Chung
Mã này có một phụ thuộc logic chưa được vật lý hóa. Bạn có thể phát hiện ra nó không? Nó
là hằng số PAGE_SIZE . Tại sao HourlyReporter nên biết kích thước của trang? Trang
kích thước phải do HourlyReportFormatter chịu trách nhiệm .
Thực tế là PAGE_SIZE được khai báo trong HourlyReporter thể hiện không đúng chỗ
trách nhiệm [G17] khiến HourlyReporter cho rằng nó biết kích thước trang
nên được. Một giả định như vậy là một sự phụ thuộc logic. HourlyReporter phụ thuộc vào
thực tế là HourlyReportFormatter có thể xử lý các kích thước trang là 55. Nếu một số triển khai
HourlyReportFormatter không thể xử lý các kích thước như vậy, khi đó sẽ xảy ra lỗi.
Chúng ta có thể vật lý hóa sự phụ thuộc này bằng cách tạo một phương thức mới trong HourlyReport-
Định dạng có tên getMaxPageSize () . Sau đó HourlyReporter sẽ gọi hàm đó thay vì
sử dụng hằng số PAGE_SIZE .
G23: Thích Đa hình thành Nếu / Khác hoặc Chuyển đổi / Trường hợp
Đây có vẻ là một gợi ý kỳ lạ với chủ đề của Chương 6. Rốt cuộc, trong chương I đó
đưa ra quan điểm rằng câu lệnh chuyển đổi có thể phù hợp trong các phần của hệ thống
trong đó việc thêm các chức năng mới có nhiều khả năng hơn là thêm các loại mới.
Đầu tiên, hầu hết mọi người sử dụng câu lệnh switch vì đó là giải pháp vũ phu rõ ràng,
không phải vì đó là giải pháp phù hợp với hoàn cảnh. Vì vậy, kinh nghiệm này ở đây để nhắc nhở chúng ta
xem xét tính đa hình trước khi sử dụng công tắc.
Thứ hai, các trường hợp hàm biến động nhiều hơn loại là tương đối hiếm. Vì thế
mọi tuyên bố chuyển đổi nên được nghi ngờ.
Tôi sử dụng quy tắc “O NE S WITCH ” sau: Không thể có nhiều hơn một trạng thái chuyển đổi-
cố vấn cho một loại lựa chọn nhất định. Các trường hợp trong câu lệnh switch đó phải tạo ra đa
các đối tượng phic thay thế các câu lệnh switch khác trong phần còn lại của hệ thống.
G24: Tuân theo các quy ước chuẩn
Mỗi nhóm phải tuân theo một tiêu chuẩn mã hóa dựa trên các tiêu chuẩn chung của ngành. Cá tuyết này-
ing tiêu chuẩn nên chỉ định những thứ như nơi khai báo các biến cá thể; cách đặt tên
các lớp, phương thức và biến; niềng răng ở đâu; và như thế. Nhóm không cần
một tài liệu để mô tả các quy ước này vì mã của chúng cung cấp các ví dụ.
item.tenths = e.getTenthsWorked ()% 10;
page.add (item);
}
public class LineItem {
công khai tên chuỗi;
giờ int công khai;
public int phần mười;
}
}
Liệt kê 17-1 (tiếp theo)
HourlyReporter.java
www.it-ebooks.info

Trang 331
300
Chương 17: Mùi và phương pháp chẩn đoán
Mọi người trong nhóm nên tuân theo các quy ước này. Điều này có nghĩa là mỗi đội
thành viên phải đủ trưởng thành để nhận ra rằng việc bạn đặt ở đâu không quan trọng
niềng răng miễn là tất cả các bạn đồng ý về nơi đặt chúng.
Nếu bạn muốn biết tôi tuân theo những quy ước nào, bạn sẽ thấy chúng trong phần đã cấu trúc lại
mã trong Liệt kê B-7 trên trang 394, đến Liệt kê B-14.
G25: Thay thế các số ma thuật bằng các hằng số được đặt tên
Đây có lẽ là một trong những quy tắc lâu đời nhất trong phát triển phần mềm. Tôi nhớ đã đọc nó trong
cuối những năm 60 trong sổ tay giới thiệu COBOL, FORTRAN và PL / 1. Nói chung là tệ
ý tưởng để có số nguyên trong mã của bạn. Bạn nên ẩn chúng sau các hằng số được đặt tên tốt.
Ví dụ: số 86.400 nên được ẩn sau hằng số
SECONDS_PER_DAY . Nếu bạn đang in 55 dòng trên mỗi trang, thì hằng số 55 nên được ẩn đi-
ẩn sau hằng số LINES_PER_PAGE .
Một số hằng số rất dễ nhận ra chúng không phải lúc nào cũng cần một hằng số được đặt tên
để ẩn đằng sau miễn là chúng được sử dụng cùng với mã rất tự giải thích. Đối với
thí dụ:
đôi milesWalked = feetWalked / 5280,0;
int dailyPay = hourlyRate * 8;
gấp đôi chu vi = bán kính * Math.PI * 2;
Chúng ta có thực sự cần các hằng số FEET_PER_MILE , WORK_HOURS_PER_DAY và TWO trong
các ví dụ trên? Rõ ràng, trường hợp cuối cùng là vô lý. Có một số công thức trong đó
stants chỉ đơn giản là viết tốt hơn dưới dạng số thô. Bạn có thể ngụy biện về
Trường hợp WORK_HOURS_PER_DAY vì luật hoặc quy ước có thể thay đổi. Mặt khác
tay, công thức đó đọc rất hay với 8 trong đó nên tôi sẽ miễn cưỡng thêm 17
nhân vật đối với gánh nặng của độc giả. Và trong trường hợp FEET_PER_MILE , số 5280 là như vậy
một hằng số rất nổi tiếng và độc đáo đến nỗi người đọc có thể nhận ra nó ngay cả khi nó đứng
một mình trên một trang không có bối cảnh xung quanh nó.
Các hằng số như 3.141592653589793 cũng rất nổi tiếng và dễ dàng nhận ra.
Tuy nhiên, khả năng xảy ra sai sót là quá lớn để chúng còn nguyên. Mỗi khi ai đó nhìn thấy
3,1415927535890793, họ biết rằng đó là p, và vì vậy họ không xem xét kỹ lưỡng nó. (Bạn đã
bắt lỗi một chữ số?) Chúng tôi cũng không muốn mọi người sử dụng 3,14, 3,14159, 3,142, v.v.
ra ngoài. Vì vậy, đó là một điều tốt mà Math.PI đã được định nghĩa cho chúng ta.
Thuật ngữ "Magic Number" không chỉ áp dụng cho các con số. Nó áp dụng cho bất kỳ mã thông báo nào
có giá trị không tự mô tả. Ví dụ:
khẳng địnhEquals (7777, Employee.find (“John Doe”). workerNumber ());
Có hai con số kỳ diệu trong khẳng định này. Đầu tiên rõ ràng là 7777, mặc dù
những gì nó có thể có nghĩa là không rõ ràng. Con số kỳ diệu thứ hai là "John Doe," và một lần nữa
ý định không rõ ràng.
Hóa ra "John Doe" là tên của nhân viên số 7777 trong một dữ liệu thử nghiệm nổi tiếng-
cơ sở do nhóm của chúng tôi tạo ra. Mọi người trong nhóm biết điều đó khi bạn kết nối với
www.it-ebooks.info

Trang 332
301
Chung
cơ sở dữ liệu, nó sẽ có một số nhân viên đã nấu chín nó với các giá trị nổi tiếng
và các thuộc tính. Nó cũng chỉ ra rằng "John Doe" đại diện cho nhân viên theo giờ duy nhất trong
cơ sở dữ liệu thử nghiệm đó. Vì vậy, bài kiểm tra này thực sự nên đọc:
khẳng địnhEquals (
HOURLY_EMPLOYEE_ID,
Employee.find (HOURLY_EMPLOYEE_NAME) .employeeNumber ());
G26: Hãy chính xác
Kỳ vọng kết quả phù hợp đầu tiên là kết quả phù hợp duy nhất với một truy vấn có lẽ là điều ngây thơ. Sử dụng nổi
số điểm để đại diện cho tiền tệ gần như là tội phạm. Tránh khóa và / hoặc giao dịch
quản lý bởi vì bạn không nghĩ rằng cập nhật đồng thời có khả năng là lười nhất. Khai báo
một biến trở thành ArrayList khi Danh sách đến hạn bị ràng buộc quá mức. Tạo tất cả các biến thể-
ables được bảo vệ theo mặc định là không đủ ràng buộc.
Khi bạn đưa ra quyết định trong mã của mình, hãy đảm bảo bạn đưa ra quyết định chính xác . Biết tại sao
bạn đã đạt được nó và bạn sẽ đối phó với bất kỳ trường hợp ngoại lệ nào. Đừng lười biếng về phần trước
quyết định của bạn. Nếu bạn quyết định gọi một hàm có thể trả về null , hãy đảm bảo
bạn kiểm tra cho null . Nếu bạn truy vấn những gì bạn nghĩ là bản ghi duy nhất trong cơ sở dữ liệu,
đảm bảo mã của bạn kiểm tra để chắc chắn rằng không có mã khác. Nếu bạn cần đối phó với
rency, sử dụng số nguyên 11 và xử lý làm tròn một cách thích hợp. Nếu có khả năng
cập nhật đồng thời, hãy đảm bảo rằng bạn triển khai một số loại cơ chế khóa.
Sự mơ hồ và không chính xác trong mã là kết quả của sự bất đồng hoặc lười biếng.
Trong cả hai trường hợp, chúng nên được loại bỏ.
G27: Cấu trúc so với Công ước
Thực thi các quyết định thiết kế với cấu trúc hơn quy ước. Quy ước đặt tên là tốt,
nhưng chúng kém hơn các cấu trúc buộc tuân thủ. Ví dụ: chuyển đổi / trường hợp với
Các kiểu liệt kê được đặt tên độc đáo sẽ kém hơn các lớp cơ sở với các phương thức trừu tượng. Không ai là
buộc phải thực hiện lệnh switch / case theo cùng một cách mỗi lần; nhưng cơ sở
các lớp thực thi rằng các lớp cụ thể đã thực hiện tất cả các phương thức trừu tượng.
G28: Đóng gói các điều kiện
Logic Boolean đủ khó để hiểu mà không cần phải xem nó trong ngữ cảnh của một if
hoặc câu lệnh while . Trích xuất các hàm giải thích mục đích của điều kiện.
Ví dụ:
if (shouldBeDeleted (timer))
thích hơn
if (timer.hasExpired () &&! timer.isRecurrent ())
11. Hoặc tốt hơn, một lớp Money sử dụng số nguyên.
www.it-ebooks.info

Trang 333
302
Chương 17: Mùi và phương pháp chẩn đoán
G29: Tránh các điều kiện phủ định
Tiêu cực khó hiểu hơn một chút so với tích cực. Vì vậy, khi có thể, điều kiện-
als nên được thể hiện dưới dạng tích cực. Ví dụ:
if (buffer.shouldCompact ())
thích hơn
if (! buffer.shouldNotCompact ())
G30: Các chức năng nên làm một việc
Việc tạo các hàm có nhiều phần thực hiện một loạt các
các hoạt động. Chức năng của loại hình này làm nhiều hơn một điều , và cần được chuyển đổi thành
nhiều chức năng nhỏ hơn, mỗi chức năng làm một việc .
Ví dụ:
public void pay () {
cho (Nhân viên e: nhân viên) {
if (e.isPayday ()) {
Tiền trả = e.calculatePay ();
e.deliverPay (trả tiền);
}
}
}
Đoạn mã này thực hiện ba điều. Nó lặp lại trên tất cả các nhân viên, kiểm tra xem liệu
mỗi nhân viên phải được trả lương, và sau đó trả tiền cho nhân viên. Mã này sẽ tốt hơn
Viết như:
public void pay () {
cho (Nhân viên e: nhân viên)
payIfN Cần thiết (e);
}
private void payIfN Needary (Nhân viên e) {
if (e.isPayday ())
tính toánAndDeliverPay (e);
}
private void CalculAndDeliverPay (Nhân viên e) {
Tiền trả = e.calculatePay ();
e.deliverPay (trả tiền);
}
Mỗi chức năng này làm một việc. (Xem “Do One Thing” trên trang 35)
G31: Mối ghép tạm thời ẩn
Khớp nối tạm thời thường là cần thiết, nhưng bạn không nên ẩn khớp nối. Kết cấu
các đối số của các hàm của bạn sao cho thứ tự mà chúng nên được gọi là obvi-
ous. Hãy xem xét những điều sau:
www.it-ebooks.info

Trang 334
303
Chung
lớp công khai MoogDiver {
Gradient gradient;
Liệt kê các splines <Spline>;
công khai khoảng trống lặn (lý do chuỗi) {
saturateGradient ();
reticulateSplines ();
lặnForMoog (lý do);
}
...
}
Thứ tự của ba chức năng là quan trọng. Bạn phải bão hòa gradient trước khi bạn
có thể sắp xếp lại các splines, và chỉ sau đó bạn mới có thể lặn tìm moog. Thật không may, mã
không thực thi khớp nối thời gian này. Một lập trình viên khác có thể gọi reticulate-
Splines trước saturateGradient được gọi, dẫn đến lỗi không bão hòaGradientException .
Một giải pháp tốt hơn là:
lớp công khai MoogDiver {
Gradient gradient;
Liệt kê các splines <Spline>;
công khai khoảng trống lặn (lý do chuỗi) {
Gradient gradient = saturateGradient ();
Danh sách <Spline> splines = reticulateSplines (gradient);
lặnForMoog (splines, lý do);
}
...
}
Điều này cho thấy khớp nối thời gian bằng cách tạo ra một lữ đoàn xô. Mỗi chức năng tạo ra một
kết quả là chức năng tiếp theo cần, vì vậy không có cách nào hợp lý để gọi chúng không theo thứ tự.
Bạn có thể phàn nàn rằng điều này làm tăng độ phức tạp của các chức năng và bạn sẽ
đúng. Nhưng sự phức tạp về cú pháp bổ sung đó cho thấy sự phức tạp về thời gian thực sự của tình huống.
Lưu ý rằng tôi đã để nguyên các biến phiên bản. Tôi cho rằng chúng cần thiết bởi
các phương thức vate trong lớp. Mặc dù vậy, tôi muốn các lập luận tại chỗ để làm cho
khớp nối rõ ràng.
G32: Đừng tùy tiện
Có lý do cho cách bạn cấu trúc mã của mình và đảm bảo lý do đó là thông báo-
được tạo bởi cấu trúc của mã. Nếu một cấu trúc có vẻ độc đoán, những người khác sẽ cảm thấy được trao quyền
để thay đổi nó. Nếu một cấu trúc xuất hiện nhất quán trong toàn hệ thống, những người khác sẽ sử dụng nó
và bảo tồn quy ước. Ví dụ: gần đây tôi đã hợp nhất các thay đổi đối với FitNesse và
phát hiện ra rằng một trong những người cam kết của chúng tôi đã làm điều này:
public class AliasLinkWidget mở rộng ParentWidget
{
public static class VariableExpandingWidgetRoot {
...
...
}
www.it-ebooks.info

Trang 335
304
Chương 17: Mùi và phương pháp chẩn đoán
Vấn đề với điều này là VariableExpandingWidgetRoot không cần phải
bên trong phạm vi của AliasLinkWidget . Hơn nữa, các lớp không liên quan khác đã sử dụng
AliasLinkWidget.VariableExpandingWidgetRoot . Những lớp học này không cần biết
về AliasLinkWidget .
Có lẽ lập trình viên đã đưa VariableExpandingWidgetRoot vào
AliasWidget như một vấn đề thuận tiện, hoặc có lẽ anh ấy nghĩ rằng nó thực sự cần thiết
phạm vi bên trong AliasWidget . Dù lý do là gì, kết quả vẫn là tùy tiện. Quán rượu-
các lớp lic không phải là tiện ích của một số lớp khác không nên được đặt trong phạm vi
lớp học. Quy ước là đặt chúng ở chế độ công khai ở cấp cao nhất trong gói của chúng.
G33: Đóng gói các điều kiện ranh giới
Các điều kiện ranh giới khó theo dõi. Đặt quá trình xử lý chúng ở một nơi.
Đừng để chúng bị rò rỉ trên toàn bộ mã. Chúng tôi không muốn một bầy +1 giây và -1 giây phân tán cho đến nay
và yon. Hãy xem xét ví dụ đơn giản này từ FIT:
if (level + 1 <tags.length)
{
part = new Parse (body, tags, level + 1, offset + endTag);
body = null;
}
Lưu ý rằng cấp độ + 1 xuất hiện hai lần. Đây là một điều kiện biên nên được đóng gói-
được đặt trong một biến có tên như nextLevel .
int nextLevel = level + 1;
if (nextLevel <tags.length)
{
part = new Phân tích cú pháp (body, tags, nextLevel, offset + endTag);
body = null;
}
G34: Các hàm chỉ nên giảm một cấp độ trừu tượng
Tất cả các câu lệnh trong một hàm phải được viết ở cùng một mức độ trừu tượng,
phải thấp hơn một cấp so với thao tác được mô tả bằng tên của hàm. Điều này
có thể là kinh nghiệm khó nhất trong số này để giải thích và làm theo. Mặc dù ý tưởng là đơn giản
đủ rồi, con người quá giỏi trong việc trộn lẫn các cấp độ trừu tượng một cách liền mạch. Xem xét,
ví dụ: mã sau được lấy từ FitNesse:
public String render () ném Exception
{
StringBuffer html = new StringBuffer ("<hr");
nếu (kích thước> 0)
html.append ("size = \" "). append (size + 1) .append (" \ "");
html.append (">");
trả về html.toString ();
}
www.it-ebooks.info

Trang 336
305
Chung
Nghiên cứu trong giây lát và bạn có thể thấy những gì đang xảy ra. Hàm này xây dựng HTML
thẻ vẽ một quy tắc ngang trên trang. Chiều cao của quy tắc đó được chỉ định trong
biến kích thước .
Bây giờ hãy nhìn lại. Phương pháp này trộn ít nhất hai cấp độ trừu tượng. Đầu tiên là
khái niệm rằng một quy tắc ngang có một kích thước. Thứ hai là cú pháp của chính thẻ HR .
Mã này đến từ mô-đun HruleWidget trong FitNesse. Mô-đun này phát hiện một hàng
bốn hoặc nhiều dấu gạch ngang và chuyển nó thành thẻ HR thích hợp. Càng nhiều dấu gạch ngang,
kích thước lớn hơn.
Tôi đã cấu trúc lại đoạn mã này như sau. Lưu ý rằng tôi đã thay đổi tên của kích thước lĩnh vực
để phản ánh mục đích thực sự của nó. Nó chứa nhiều dấu gạch ngang.
public String render () ném Exception
{
HtmlTag hr = new HtmlTag ("hr");
nếu (dấu gạch ngang> 0)
hr.addAttribute ("kích thước", hrSize (extraDashes));
return hr.html ();
}
private String hrSize (int height)
{
int hrSize = height + 1;
return String.format ("% d", hrSize);
}
Thay đổi này tách biệt hai cấp độ trừu tượng một cách độc đáo. Các làm cho chức năng đơn giản con-
cấu trúc một thẻ HR mà không cần biết bất kỳ điều gì về cú pháp HTML của thẻ đó.
Các HtmlTag mô-đun sẽ chăm sóc của tất cả các vấn đề cú pháp khó chịu.
Thật vậy, bằng cách thực hiện thay đổi này, tôi đã mắc phải một lỗi nhỏ. Mã ban đầu không đặt
dấu gạch chéo đóng trên thẻ HR, như tiêu chuẩn XHTML sẽ có. (Nói cách khác, nó
phát ra <hr> thay vì <hr /> .) Mô-đun HtmlTag đã được thay đổi để phù hợp với
XHTML từ lâu.
Tách các mức trừu tượng là một trong những chức năng quan trọng nhất của refactor-
ing, và đó là một trong những điều khó nhất để làm tốt. Để làm ví dụ, hãy xem đoạn mã dưới đây. Điều này
là nỗ lực đầu tiên của tôi trong việc tách các cấp độ trừu tượng trong HruleWidget.render
phương pháp .
public String render () ném Exception
{
HtmlTag hr = new HtmlTag ("hr");
nếu (kích thước> 0) {
hr.addAttribute ("kích thước", "" + (kích thước + 1));
}
return hr.html ();
}
Mục tiêu của tôi, tại thời điểm này, là tạo ra sự tách biệt cần thiết và làm cho các bài kiểm tra vượt qua.
Tôi đã hoàn thành mục tiêu đó một cách dễ dàng, nhưng kết quả là một hàm vẫn có nhiều cấp độ khác nhau
của sự trừu tượng. Trong trường hợp này, các cấp độ hỗn hợp là xây dựng thẻ nhân sự và
www.it-ebooks.info

Trang 337
306
Chương 17: Mùi và phương pháp chẩn đoán
giải thích và định dạng của biến kích thước . Điều này chỉ ra rằng khi bạn phá vỡ một
hoạt động dọc theo các dòng trừu tượng, bạn thường khám phá các dòng trừu tượng mới
bị che khuất bởi cấu trúc trước đó.
G35: Giữ dữ liệu có thể định cấu hình ở mức cao
Nếu bạn có một hằng số chẳng hạn như giá trị mặc định hoặc giá trị cấu hình đã biết và được mong đợi
ở mức độ trừu tượng cao, đừng chôn vùi nó trong một chức năng ở mức độ thấp. Phơi bày nó như một lập luận-
đề cập đến hàm cấp thấp đó được gọi từ hàm cấp cao. Hãy xem xét những điều sau
mã từ FitNesse:
public static void main (String [] args) ném Exception
{
Đối số đối số = parseCommandLine (args);
...
}
Đối số lớp công khai
{
public static final String DEFAULT_PATH = ".";
public static final String DEFAULT_ROOT = "FitNesseRoot";
public static final int DEFAULT_PORT = 80;
public static final int DEFAULT_VERSION_DAYS = 14;
...
}
Các đối số dòng lệnh được phân tích cú pháp trong dòng thực thi đầu tiên của FitNesse. Các
giá trị mặc định của các đối số đó được chỉ định ở trên cùng của lớp Đối số . Bạn không
phải đi tìm các cấp thấp của hệ thống cho các câu lệnh như sau:
if (objects.port == 0) // sử dụng 80 theo mặc định
Hằng số cấu hình nằm ở mức rất cao và dễ thay đổi. Họ nhận được
được truyền cho phần còn lại của ứng dụng. Các cấp thấp hơn của ứng dụng không sở hữu
giá trị của các hằng số này.
G36: Tránh điều hướng chuyển tiếp
Nói chung, chúng tôi không muốn một mô-đun nào biết nhiều về các cộng tác viên của nó. Thêm bài phát biểu-
cụ thể là, nếu A cộng tác với B và B cộng tác với C , chúng tôi không muốn các mô-đun sử dụng
Một biết về C . (Ví dụ: chúng tôi không muốn a.getB (). GetC (). DoSomething ();. )
Điều này đôi khi được gọi là Định luật Demeter. Các lập trình viên thực dụng gọi nó là
"Viết mã xấu hổ." 12 Trong cả hai trường hợp, điều quan trọng là đảm bảo rằng các mô-đun biết
chỉ về các cộng tác viên trực tiếp của họ và không biết bản đồ điều hướng của toàn bộ
hệ thống.
Nếu nhiều mô-đun sử dụng một số dạng của câu lệnh a.getB (). GetC () , thì nó sẽ là
khó có thể thay đổi thiết kế và kiến trúc để xen một Q giữa B và C . Bạn sẽ
12. [PRAG], tr. 138.
www.it-ebooks.info

Trang 338
307
Java
phải tìm mọi thể hiện của a.getB (). getC () và chuyển nó thành a.getB (). getQ (). getC () .
Đây là cách mà các kiến trúc trở nên cứng nhắc. Quá nhiều mô-đun biết quá nhiều về
ngành kiến trúc.
Thay vì chúng tôi muốn các cộng tác viên ngay lập tức của mình cung cấp tất cả các dịch vụ mà chúng tôi
cần. Chúng tôi
không cần phải đi lang thang trong biểu đồ đối tượng của hệ thống, tìm kiếm phương pháp mà chúng tôi
muốn gọi. Thay vào đó, chúng ta chỉ có thể nói:
myCollaborator.doSomething ().
Java
J1: Tránh danh sách nhập dài bằng cách sử dụng ký tự đại diện
Nếu bạn sử dụng hai hoặc nhiều lớp từ một gói, thì hãy nhập toàn bộ gói với
gói nhập khẩu. *;
Danh sách hàng nhập khẩu dài gây khó khăn cho người đọc. Chúng tôi không muốn làm lộn xộn
mô-đun với 80 dòng nhập khẩu. Thay vì chúng tôi muốn nhập khẩu là một tuyên bố ngắn gọn
về những gói mà chúng tôi cộng tác.
Nhập khẩu cụ thể là phụ thuộc cứng, trong khi nhập khẩu ký tự đại diện thì không. Nếu bạn nói-
cụ thể là nhập một lớp, thì lớp đó phải tồn tại. Nhưng nếu bạn nhập một gói có ký tự hoang dã-
thẻ, không có lớp cụ thể nào cần tồn tại. Câu lệnh nhập chỉ cần thêm gói vào
con đường tìm kiếm khi tìm kiếm tên. Vì vậy, không có sự phụ thuộc thực sự nào được tạo ra bởi
nhập khẩu và do đó chúng phục vụ để giữ cho các mô-đun của chúng tôi ít bị ghép nối hơn.
Đôi khi danh sách dài các nhập khẩu cụ thể có thể hữu ích. Ví dụ, nếu
bạn đang xử lý mã kế thừa và bạn muốn tìm hiểu những lớp bạn cần xây dựng
mô hình và sơ khai cho, bạn có thể xem qua danh sách các mặt hàng nhập khẩu cụ thể để tìm ra sự thật
tên đủ điều kiện của tất cả các lớp đó và sau đó đặt các gốc thích hợp vào vị trí. Tuy nhiên,
việc sử dụng này cho các mặt hàng nhập khẩu cụ thể là rất hiếm. Hơn nữa, hầu hết các IDE hiện đại sẽ cho phép bạn
để chuyển đổi các mục nhập có ký tự đại diện thành danh sách các mục nhập cụ thể bằng một lệnh duy nhất. Vì thế
ngay cả trong trường hợp cũ, tốt hơn là nên nhập các ký tự đại diện.
Nhập ký tự đại diện đôi khi có thể gây ra xung đột và mơ hồ về tên. Hai lớp
có cùng tên, nhưng trong các gói khác nhau, sẽ cần được nhập cụ thể hoặc tại
ít đặc biệt đủ tiêu chuẩn khi sử dụng. Điều này có thể gây phiền toái nhưng hiếm khi sử dụng
nhập khẩu ký tự đại diện nhìn chung vẫn tốt hơn so với nhập khẩu cụ thể.
J2: Không kế thừa các hằng số
Tôi đã nhìn thấy điều này vài lần và nó luôn khiến tôi nhăn mặt. Một lập trình viên đặt một số
hằng số trong một giao diện và sau đó có được quyền truy cập vào các hằng số đó bằng cách kế thừa liên
khuôn mặt. Hãy xem đoạn mã sau:
lớp công khai Hàng giờ Nhân viên mở rộng Nhân viên {
private int tenthsWorked;
riêng tư nhân đôi hàng giờ;
www.it-ebooks.info

Trang 339
308
Chương 17: Mùi và phương pháp chẩn đoán
công khai tiền tínhPay () {
int thẳngTime = Math.min (tenthsWorked, TENTHS_PER_WEEK);
int overTime = tenthsWorked - thẳng thời gian;
trả lại tiền mới (
hourlyRate * (tenthsWorked + OVERTIME_RATE * overTime)
);
}
...
}
Ở đâu các hằng TENTHS_PER_WEEK và OVERTIME_RATE đến từ đâu? Họ có thể có
đến từ lớp Nhân viên ; vì vậy chúng ta hãy xem xét điều đó:
public abstract class Nhân viên triển khai PayrollConstants {
công khai trừu tượng boolean isPayday ();
công khai tóm tắt Tiền tính toánPay ();
public abstract void deliveryPay (Trả tiền);
}
Không, không phải ở đó. Nhưng sau đó ở đâu? Nhìn kỹ nhân viên lớp . Nó thực hiện
Bảng lương .
giao diện công khai PayrollConstants {
public static final int TENTHS_PER_WEEK = 400;
công khai tĩnh cuối cùng kép OVERTIME_RATE = 1,5;
}
Đây là một thực hành ghê tởm! Các hằng số được ẩn ở trên cùng của hệ thống phân cấp kế thừa.
Chà! Đừng sử dụng kế thừa như một cách để gian lận các quy tắc phạm vi của ngôn ngữ. Sử dụng tĩnh
nhập khẩu thay thế.
nhập PayrollConstants tĩnh. *;
lớp công khai Hàng giờ Nhân viên mở rộng Nhân viên {
private int tenthsWorked;
riêng tư nhân đôi hàng giờ;
công khai tiền tínhPay () {
int thẳngTime = Math.min (tenthsWorked, TENTHS_PER_WEEK);
int overTime = tenthsWorked - thẳng thời gian;
trả lại tiền mới (
hourlyRate * (tenthsWorked + OVERTIME_RATE * overTime)
);
}
...
}
J3: Hằng số so với Enums
Bây giờ các enum đã được thêm vào ngôn ngữ (Java 5), hãy sử dụng chúng! Đừng tiếp tục sử dụng
thủ thuật cũ của public static final int s. Ý nghĩa của int s có thể bị mất. Nghĩa của
enum s không thể, bởi vì chúng thuộc về một kiểu liệt kê được đặt tên.
Hơn nữa, hãy nghiên cứu kỹ cú pháp của enum s. Chúng có thể có các phương thức và trường.
Điều này làm cho chúng trở thành những công cụ rất mạnh mẽ cho phép biểu đạt và linh hoạt hơn nhiều so với
int s. Hãy xem xét biến thể này trên mã bảng lương:
www.it-ebooks.info

Trang 340
309
Tên
lớp công khai Hàng giờ Nhân viên mở rộng Nhân viên {
private int tenthsWorked;
Cấp độ HourlyPayGrade;
công khai tiền tínhPay () {
int thẳngTime = Math.min (tenthsWorked, TENTHS_PER_WEEK);
int overTime = tenthsWorked - thẳng thời gian;
trả lại tiền mới (
grade.rate () * (tenthsWorked + OVERTIME_RATE * overTime)
);
}
...
}
public enum HourlyPayGrade {
HỌC NGHỀ {
tỷ lệ kép công khai () {
trả về 1,0;
}
},
LEUTENANT_JOURNEYMAN {
tỷ lệ kép công khai () {
trả về 1,2;
}
},
HÀNH TRÌNH {
tỷ lệ kép công khai () {
trả về 1,5;
}
},
BẬC THẦY {
tỷ lệ kép công khai () {
trả về 2.0;
}
};
public abstract double rate ();
}
Tên
N1: Chọn tên mô tả
Đừng quá vội vàng để chọn một cái tên. Đảm bảo rằng tên có tính mô tả. Nhớ lấy
ý nghĩa có xu hướng thay đổi khi phần mềm phát triển, vì vậy hãy thường xuyên đánh giá lại tính phù hợp của
những cái tên bạn chọn.
Đây không chỉ là một khuyến nghị "dễ chịu". Tên trong phần mềm là 90%
điều gì làm cho phần mềm có thể đọc được. Bạn cần dành thời gian để chọn chúng một cách khôn ngoan và giữ
chúng có liên quan. Tên quá quan trọng để đối xử bất cẩn.
Hãy xem xét đoạn mã dưới đây. Nó làm gì? Nếu tôi cho bạn xem mã được chọn tốt
tên, nó sẽ có ý nghĩa hoàn hảo đối với bạn, nhưng như thế này, nó chỉ là một đống biểu tượng
và những con số kỳ diệu.
www.it-ebooks.info

Trang 341
310
Chương 17: Mùi và phương pháp chẩn đoán
public int x () {
int q = 0;
int z = 0;
for (int kk = 0; kk <10; kk ++) {
nếu (l [z] == 10)
{
q + = 10 + (l [z + 1] + l [z + 2]);
z + = 1;
}
khác nếu (l [z] + l [z + 1] == 10)
{
q + = 10 + l [z + 2];
z + = 2;
} khác {
q + = l [z] + l [z + 1];
z + = 2;
}
}
trả về q;
}
Đây là cách viết mã. Đoạn mã này thực sự ít hoàn chỉnh hơn
hơn cái ở trên. Tuy nhiên, bạn có thể suy ra ngay lập tức nó đang cố gắng làm gì và bạn có thể
rất có thể viết các hàm còn thiếu dựa trên ý nghĩa suy ra đó. Con số kỳ diệu-
bers không còn là phép thuật nữa và cấu trúc của thuật toán có tính mô tả hấp dẫn.
public int score () {
int điểm = 0;
int khung = 0;
for (int frameNumber = 0; frameNumber <10; frameNumber ++) {
if (isStrike (frame)) {
điểm + = 10 + nextTwoBallsForStrike (khung);
khung + = 1;
} else if (isSpare (frame)) {
điểm + = 10 + nextBallForSpare (khung);
khung + = 2;
} khác {
điểm + = haiBallsInFrame (khung);
khung + = 2;
}
}
điểm trả về;
}
Sức mạnh của những cái tên được chọn cẩn thận là chúng làm quá tải cấu trúc của mã
với mô tả. Sự quá tải đó đặt ra kỳ vọng của độc giả về những gì khác
chức năng trong mô-đun làm gì. Bạn có thể suy ra việc triển khai isStrike () bằng cách xem
đoạn mã trên. Khi bạn đọc phương thức isStrike , bạn sẽ thấy "khá nhiều điều bạn
hy vọng." 13
private boolean isStrike (int frame) {
trả về các cuộn [frame] == 10;
}
13. Xem trích dẫn của Ward Cunningham ở trang 11.
www.it-ebooks.info

Trang 342
311
Tên
N2: Chọn tên ở mức độ trừu tượng phù hợp
Đừng chọn những cái tên liên quan đến việc triển khai; chọn tên phản ánh mức độ của
sự trừu tượng của lớp hoặc hàm bạn đang làm việc. Điều này rất khó thực hiện. Một lần nữa, mọi người
quá giỏi trong việc trộn các mức độ trừu tượng. Mỗi lần bạn vượt qua
mã, bạn có thể sẽ tìm thấy một số biến được đặt tên ở cấp quá thấp. Bạn nên lấy
cơ hội để thay đổi những tên đó khi bạn tìm thấy chúng. Làm cho mã có thể đọc được
đòi hỏi sự cống hiến để cải tiến liên tục. Xem xét giao diện Modem bên dưới:
Modem giao diện công cộng {
quay số boolean (String phoneNumber);
boolean ngắt kết nối ();
boolean gửi (char c);
char recv ();
Chuỗi getConnectedPhoneNumber ();
}
Lúc đầu, điều này có vẻ ổn. Các chức năng đều có vẻ phù hợp. Thật vậy, đối với nhiều ứng dụng
họ đang. Nhưng bây giờ hãy xem xét một ứng dụng trong đó một số modem không được kết nối bằng
quay số. Thay vì chúng được kết nối vĩnh viễn bằng cách nối dây cứng với nhau (hãy nghĩ về
modem cáp cung cấp truy cập Internet cho hầu hết các gia đình ngày nay). Có lẽ một số
được kết nối bằng cách gửi một số cổng tới bộ chuyển đổi qua kết nối USB. Rõ ràng là khái niệm
số điện thoại ở mức trừu tượng sai. Một chiến lược đặt tên tốt hơn cho điều này
kịch bản có thể là:
Modem giao diện công cộng {
boolean kết nối (String connectionLocator);
boolean ngắt kết nối ();
boolean gửi (char c);
char recv ();
Chuỗi getConnectedLocator ();
}
Bây giờ những cái tên không đưa ra bất kỳ cam kết nào về số điện thoại. Chúng vẫn có thể được sử dụng
cho số điện thoại hoặc chúng có thể được sử dụng cho bất kỳ loại chiến lược kết nối nào khác.
N3: Sử dụng danh pháp chuẩn nếu có thể
Tên sẽ dễ hiểu hơn nếu chúng dựa trên quy ước hoặc cách sử dụng hiện có. Cho kỳ thi-
xin vui lòng, nếu bạn đang sử dụng mẫu D ECORATOR , bạn nên sử dụng từ Decorator trong tên
của các lớp trang trí. Ví dụ: AutoHangupModemDecorator có thể là tên của
lớp trang trí một Modem với khả năng tự động treo vào cuối phiên.
Hoa văn chỉ là một loại tiêu chuẩn. Trong Java, ví dụ, các hàm chuyển đổi
các đối tượng để biểu diễn chuỗi thường được đặt tên là toString . Tốt hơn là nên làm theo conven-
nhiều thứ như thế này hơn là để phát minh ra của riêng bạn.
Các nhóm thường sẽ phát minh ra hệ thống tên tiêu chuẩn của riêng họ cho một dự án cụ thể.
Eric Evans đề cập đến điều này như một ngôn ngữ phổ biến cho dự án. 14 Mã của bạn nên sử dụng
14. [DDD].
www.it-ebooks.info

Trang 343
312
Chương 17: Mùi và phương pháp chẩn đoán
các thuật ngữ từ ngôn ngữ này một cách rộng rãi. Tóm lại, bạn càng có thể sử dụng nhiều tên
quá tải với các ý nghĩa đặc biệt có liên quan đến dự án của bạn, thì càng dễ dàng
người đọc để biết mã của bạn đang nói về điều gì.
N4: Tên rõ ràng
Chọn tên làm cho hoạt động của một hàm hoặc biến trở nên rõ ràng. Xem xét
ví dụ này từ FitNesse:
private String doRename () ném Exception
{
if (refactorRefferences)
renameRefferences ();
renamePage ();
pathToRename.removeNameFromEnd ();
pathToRename.addNameToEnd (newName);
trả về PathParser.render (pathToRename);
}
Tên của chức năng này không nói lên chức năng đó làm gì ngoại trừ những nội dung rộng và mơ hồ
điều kiện. Điều này được nhấn mạnh bởi thực tế là có một hàm có tên là renamePage bên trong
hàm có tên doRename ! Những cái tên cho bạn biết điều gì về sự khác biệt giữa
hai chức năng? Không có gì.
Tên tốt hơn cho hàm đó là renamePageAndOptionallyAllRefutions . Điều này có thể
có vẻ dài và đúng là như vậy, nhưng nó chỉ được gọi từ một nơi trong mô-đun, vì vậy nó có thể giải thích
giá trị lớn hơn chiều dài.
N5: Sử dụng tên dài cho phạm vi dài
Độ dài của tên phải liên quan đến độ dài của phạm vi. Bạn có thể sử dụng rất ngắn
tên biến cho phạm vi nhỏ, nhưng đối với phạm vi lớn, bạn nên sử dụng tên dài hơn.
Các tên biến như i và j chỉ ổn nếu phạm vi của chúng dài năm dòng. Xem xét điều này
đoạn trích từ "Trò chơi bowling" tiêu chuẩn cũ:
private void rollMany (int n, int chân)
{
for (int i = 0; i <n; i ++)
g.roll (ghim);
}
Điều này hoàn toàn rõ ràng và sẽ bị xáo trộn nếu biến tôi được thay thế bằng một số-
điều khó chịu như rollCount . Mặt khác, các biến và hàm có tên viết tắt
mất ý nghĩa của chúng trên một khoảng cách dài. Vì vậy, phạm vi của tên càng dài, càng dài và
chính xác hơn tên nên được.
N6: Tránh mã hóa
Tên không được mã hóa với thông tin về loại hoặc phạm vi. Các tiền tố như m_ hoặc f
vô dụng trong môi trường ngày nay. Ngoài ra dự án và / hoặc mã hóa hệ thống con như
www.it-ebooks.info

Trang 344
313
Kiểm tra
vis_ (cho hệ thống hình ảnh trực quan) gây mất tập trung và dư thừa. Một lần nữa, môi trường ngày nay-
ments cung cấp tất cả thông tin đó mà không cần phải đọc tên. Giữ của bạn
tên không ô nhiễm Hungary.
N7: Tên nên mô tả tác dụng phụ
Tên phải mô tả tất cả mọi thứ mà một hàm, biến hoặc lớp có hoặc thực hiện. Đừng trốn
tác dụng phụ với một cái tên. Đừng sử dụng một động từ đơn giản để mô tả một chức năng làm được nhiều việc hơn
hơn chỉ là hành động đơn giản. Ví dụ: hãy xem xét mã này từ TestNG:
public ObjectOutputStream getOos () ném IOException {
if (m_oos == null) {
m_oos = new ObjectOutputStream (m_socket.getOutputStream ());
}
trả về m_oos;
}
Hàm này thực hiện nhiều hơn một chút so với việc nhận được một “oos”; nó tạo ra "oos" nếu nó không được tạo ra
đã chuẩn bị sẵn sàng. Do đó, một cái tên tốt hơn có thể là createOrReturnOos .
Kiểm tra
T1: Kiểm tra không đủ
Có bao nhiêu bài kiểm tra trong một bộ thử nghiệm? Thật không may, số liệu mà nhiều lập trình viên sử dụng
là "Như vậy là đủ." Một bộ thử nghiệm nên kiểm tra mọi thứ có thể bị phá vỡ.
Các bài kiểm tra là không đủ miễn là có các điều kiện chưa được khám phá bởi
kiểm tra hoặc tính toán chưa được xác thực.
T2: Sử dụng Công cụ Bảo hiểm!
Các công cụ bao phủ báo cáo những lỗ hổng trong chiến lược thử nghiệm của bạn. Chúng giúp bạn dễ dàng tìm thấy
các mô-đun,
các lớp và các chức năng không được kiểm tra đầy đủ. Hầu hết các IDE cung cấp cho bạn một dấu hiệu trực quan,
vạch đánh dấu được bao phủ bởi màu xanh lá cây và những vạch không được bao phủ bởi màu đỏ. Điều này làm cho

nhanh chóng và dễ dàng để tìm nếu hoặc bắt các câu lệnh chưa được kiểm tra.
T3: Đừng bỏ qua những bài kiểm tra tầm thường
Chúng dễ viết và giá trị tài liệu của chúng cao hơn chi phí sản xuất
chúng.
T4: Một bài kiểm tra bị bỏ qua là một câu hỏi về sự mơ hồ
Đôi khi chúng tôi không chắc chắn về một chi tiết hành vi vì các yêu cầu
không rõ. Chúng tôi có thể bày tỏ câu hỏi của mình về các yêu cầu dưới dạng một bài kiểm tra được nhận xét
ra ngoài, hoặc dưới dạng một bài kiểm tra có chú thích bằng @Ignore . Bạn chọn cái nào phụ thuộc vào việc
mơ hồ là về một cái gì đó có thể biên dịch hoặc không.
www.it-ebooks.info

Trang 345
314
Chương 17: Mùi và phương pháp chẩn đoán
T5: Điều kiện ranh giới thử nghiệm
Đặc biệt cẩn thận để kiểm tra các điều kiện biên. Chúng tôi thường nhận được giữa một thuật toán
đúng nhưng đánh giá sai ranh giới.
T6: Kiểm tra hoàn toàn gần lỗi
Các con bọ có xu hướng tụ họp lại. Khi bạn tìm thấy một lỗi trong một chức năng, điều khôn ngoan là nên thực hiện
thử nghiệm của chức năng đó. Có thể bạn sẽ thấy rằng lỗi không đơn độc.
T7: Hình thái thất bại đang bộc lộ
Đôi khi bạn có thể chẩn đoán sự cố bằng cách tìm ra các mẫu trong cách các trường hợp thử nghiệm thất bại.
Đây là một lập luận khác để làm cho các trường hợp thử nghiệm hoàn chỉnh nhất có thể. Hoàn thành bài kiểm tra
các trường hợp, được đặt hàng một cách hợp lý, phơi bày các mẫu.
Ví dụ đơn giản, giả sử bạn nhận thấy rằng tất cả các bài kiểm tra có đầu vào lớn hơn năm
ký tự không thành công? Hoặc điều gì sẽ xảy ra nếu bất kỳ bài kiểm tra nào vượt qua một số âm trong cuộc tranh
luận thứ hai
đề cập đến một chức năng không thành công? Đôi khi chỉ nhìn thấy mô hình màu đỏ và xanh lá cây trong bài kiểm
tra
báo cáo đủ để châm ngòi cho "Aha!" dẫn đến giải pháp. Nhìn lại trang 267 để
xem một ví dụ thú vị về điều này trong ví dụ SerialDate .
T8: Các mẫu bao phủ thử nghiệm có thể được tiết lộ
Nhìn vào mã được thực thi hoặc không được thực thi bởi các bài kiểm tra vượt qua cung cấp manh mối tại sao
thi rớt thì rớt.
T9: Kiểm tra sẽ nhanh chóng
Kiểm tra chậm là kiểm tra sẽ không chạy. Khi mọi thứ trở nên chặt chẽ, các bài kiểm tra chậm sẽ
rớt khỏi bộ. Vì vậy, làm những gì bạn phải làm để giữ cho các bài kiểm tra của bạn nhanh chóng.
Phần kết luận
Danh sách kinh nghiệm và mùi này khó có thể nói là đầy đủ. Thật vậy, tôi không chắc
rằng một danh sách như vậy có thể bao giờ được hoàn thành. Nhưng có lẽ sự hoàn chỉnh không phải là mục tiêu,
bởi vì những gì danh sách này làm là ngụ ý một hệ thống giá trị.
Thật vậy, hệ giá trị đó là mục tiêu và chủ đề của cuốn sách này. Mã sạch là
không được viết bằng cách tuân theo một bộ quy tắc. Bạn không trở thành một nghệ nhân phần mềm bằng cách học-
ing một danh sách các heuristics. Tính chuyên nghiệp và sự khéo léo đến từ các giá trị thúc đẩy
kỷ luật.
www.it-ebooks.info

Trang 346
315
Thư mục
Thư mục
[Refactoring]: Refactoring: Cải thiện thiết kế của mã hiện có , Martin Fowler và cộng sự,
Addison-Wesley, 1999.
[PRAG]: Lập trình viên thực dụng , Andrew Hunt, Dave Thomas, Addison-Wesley,
2000.
[GOF]: Mẫu thiết kế: Các yếu tố của phần mềm hướng đối tượng có thể tái sử dụng , Gamma et al.,
Addison-Wesley, 1996.
[Beck97]: Các mẫu thực hành tốt nhất cho Smalltalk , Kent Beck, Prentice Hall, 1997.
[Beck07]: Các mẫu triển khai , Kent Beck, Addison-Wesley, 2008.
[PPP]: Phát triển phần mềm Agile: Nguyên tắc, Mẫu và Thực tiễn , Robert C. Martin,
Prentice Hall, 2002.
[DDD]: Thiết kế theo hướng miền , Eric Evans, Addison-Wesley, 2003.
www.it-ebooks.info

Trang 347
Trang này cố ý để trống
www.it-ebooks.info
Trang 348
317

Phụ lục A
Đồng thời II
bởi Brett L. Schuchert
Phụ lục này hỗ trợ và khuếch đại chương Đồng tiền trên trang 177. Nó được viết
như một loạt các chủ đề độc lập và bạn thường có thể đọc chúng theo bất kỳ thứ tự nào. Có
một số trùng lặp giữa các phần để cho phép đọc như vậy.
Ví dụ về Máy khách / Máy chủ
Hãy tưởng tượng một ứng dụng máy khách / máy chủ đơn giản. Một máy chủ ngồi và chờ nghe trên một ổ cắm cho
một khách hàng để kết nối. Một khách hàng kết nối và gửi một yêu cầu.
Máy chủ
Đây là phiên bản đơn giản hóa của một ứng dụng máy chủ. Nguồn đầy đủ cho ví dụ này là có sẵn-
có thể bắt đầu từ trang 343, Máy khách / Máy chủ chưa đọc .
ServerSocket serverSocket = new ServerSocket (8009);
trong khi (keepProcessing) {
thử {
Socket socket = serverSocket.accept ();
quy trình (ổ cắm);
} catch (Ngoại lệ e) {
xử lý (e);
}
}
www.it-ebooks.info

Trang 349
318
Phụ lục A: Đồng thời II
Ứng dụng đơn giản này chờ kết nối, xử lý một tin nhắn đến và sau đó
lại đợi yêu cầu ứng dụng tiếp theo đến. Đây là mã khách hàng kết nối với
người phục vụ:
private void connectSendReceive (int i) {
thử {
Socket socket = new Socket ("localhost", PORT);
MessageUtils.sendMessage (socket, Integer.toString (i));
MessageUtils.getMessage (ổ cắm);
socket.close ();
} catch (Ngoại lệ e) {
e.printStackTrace ();
}
}
Cặp máy khách / máy chủ này hoạt động tốt như thế nào? Làm thế nào chúng ta có thể chính thức mô tả điều đó
mance? Đây là một bài kiểm tra xác nhận rằng hiệu suất là "chấp nhận được":
@Test (thời gian chờ = 10000)
public void shouldRunInUnder10Seconds () ném Exception {
Chủ đề [] chủ đề = createThreads ();
startAllThreadsw (chủ đề);
waitForAllThreadsToFinish (chủ đề);
}
Việc thiết lập được bỏ qua để giữ cho ví dụ đơn giản (xem “ ClientTest.java ” trên trang 344). Điều này
kiểm tra khẳng định rằng nó sẽ hoàn thành trong vòng 10.000 mili giây.
Đây là một ví dụ cổ điển về việc xác thực thông lượng của một hệ thống. Hệ thống này nên
hoàn thành một loạt các yêu cầu của khách hàng trong mười giây. Miễn là máy chủ có thể xử lý từng
yêu cầu khách hàng cá nhân trong thời gian, thử nghiệm sẽ vượt qua.
Điều gì xảy ra nếu thử nghiệm không thành công? Thiếu sót khi phát triển một số loại vòng bỏ phiếu sự kiện,
không có nhiều việc phải làm trong một luồng duy nhất sẽ làm cho mã này nhanh hơn. Sẽ
sử dụng nhiều chủ đề giải quyết vấn đề? Có thể, nhưng chúng ta cần biết thời gian ở đâu
được chi tiêu. Có hai khả năng:
• I / O — sử dụng ổ cắm, kết nối với cơ sở dữ liệu, chờ hoán đổi bộ nhớ ảo,
và như thế.
• Bộ xử lý — tính toán số, xử lý biểu thức chính quy, thu gom rác,
và như thế.
Hệ thống thường có một số trong mỗi hệ thống, nhưng đối với một hoạt động nhất định, chúng có xu hướng thống
trị. Nếu
mã bị ràng buộc bởi bộ xử lý, nhiều phần cứng xử lý hơn có thể cải thiện thông lượng, khiến
vượt qua bài kiểm tra của chúng tôi. Nhưng chỉ có rất nhiều chu kỳ CPU có sẵn, vì vậy việc thêm các luồng vào
vấn đề liên quan đến bộ xử lý sẽ không làm cho nó hoạt động nhanh hơn.
Mặt khác, nếu quy trình bị ràng buộc I / O, thì sự đồng thời có thể làm tăng hiệu quả
hiệu lực. Khi một phần của hệ thống đang đợi I / O, phần khác có thể sử dụng thời gian chờ đó
để xử lý thứ gì đó khác, sử dụng hiệu quả hơn CPU có sẵn.
www.it-ebooks.info

Trang 350
319
Ví dụ về Máy khách / Máy chủ
Thêm luồng
Giả sử tại thời điểm này kiểm tra hiệu suất không thành công. Làm thế nào chúng ta có thể cải thiện thông qua-
đặt để kiểm tra hiệu suất vượt qua? Nếu phương thức xử lý của máy chủ bị ràng buộc I / O,
thì đây là một cách để làm cho máy chủ sử dụng các luồng (chỉ cần thay đổi processMessage ):
quá trình void (ổ cắm Socket cuối cùng) {
if (socket == null)
trở về;
Runnable clientHandler = new Runnable () {
public void run () {
thử {
String message = MessageUtils.getMessage (socket);
MessageUtils.sendMessage (socket, "Đã xử lý:" + tin nhắn);
closeIgnoringException (ổ cắm);
} catch (Ngoại lệ e) {
e.printStackTrace ();
}
}
};
Thread clientConnection = new Thread (clientHandler);
clientConnection.start ();
}
Giả sử rằng thay đổi này làm cho bài kiểm tra vượt qua; 1 mã đã hoàn thành, chính xác?
Quan sát máy chủ
Máy chủ được cập nhật hoàn thành kiểm tra thành công chỉ trong hơn một giây. Không may,
giải pháp này là một chút ngây thơ và giới thiệu một số vấn đề mới.
Máy chủ của chúng tôi có thể tạo bao nhiêu luồng? Mã không đặt giới hạn, vì vậy chúng tôi có thể
có thể đạt được giới hạn do Máy ảo Java (JVM) áp đặt. Đối với nhiều hệ thống đơn giản-
điều này có thể đủ. Nhưng điều gì sẽ xảy ra nếu hệ thống hỗ trợ nhiều người dùng công khai
mạng lưới? Nếu quá nhiều người dùng kết nối cùng lúc, hệ thống có thể tạm dừng.
Nhưng hãy đặt vấn đề hành vi sang một bên trong lúc này. Giải pháp được hiển thị có xác suất-
sự sạch sẽ và cấu trúc. Mã máy chủ có bao nhiêu trách nhiệm?
• Quản lý kết nối ổ cắm
• Xử lý khách hàng
• Chính sách phân luồng
• Chính sách tắt máy chủ
Thật không may, tất cả những trách nhiệm này nằm trong chức năng quy trình . Ngoài ra,
mã vượt qua nhiều cấp độ trừu tượng khác nhau. Vì vậy, nhỏ như hàm quá trình, nó
cần được phân vùng lại.
1. Bạn có thể xác minh điều đó cho chính mình bằng cách thử mã trước và sau. Xem lại mã chưa đọc bắt đầu từ trang 343.
Xem lại mã luồng bắt đầu từ trang 346.
www.it-ebooks.info

Trang 351
320
Phụ lục A: Đồng thời II
Máy chủ có một số lý do để thay đổi; do đó nó vi phạm Trách nhiệm duy nhất
Nguyên tắc. Để giữ sạch các hệ thống đồng thời, quản lý luồng nên được giữ ở một số ít,
những nơi được kiểm soát tốt. Hơn nữa, bất kỳ mã nào quản lý các chuỗi sẽ không làm gì cả
khác với quản lý luồng. Tại sao? Nếu không có lý do nào khác ngoài việc theo dõi nó-
Các vấn đề tiền tệ đủ khó mà không phải giải quyết các vấn đề phi tiền tệ khác tại
cùng lúc.
Nếu chúng tôi tạo một lớp riêng cho từng trách nhiệm được liệt kê ở trên, bao gồm
trách nhiệm quản lý luồng, sau đó khi chúng tôi thay đổi chiến lược quản lý luồng,
thay đổi sẽ ít ảnh hưởng đến mã tổng thể hơn và sẽ không ảnh hưởng đến các trách nhiệm khác. Điều này
cũng giúp bạn dễ dàng kiểm tra tất cả các trách nhiệm khác mà không phải lo lắng
về phân luồng. Đây là một phiên bản cập nhật thực hiện điều đó:
public void run () {
trong khi (keepProcessing) {
thử {
ClientConnection clientConnection = connectManager.awaitClient ();
ClientRequestProcessor requestProcessor
= new ClientRequestProcessor (clientConnection);
clientScheduler.schedule (requestProcessor);
} catch (Ngoại lệ e) {
e.printStackTrace ();
}
}
connectManager.shutdown ();
}
Điều này hiện tập trung tất cả những thứ liên quan đến chuỗi vào một nơi, clientScheduler . Nếu có
vấn đề đồng thời, chỉ có một nơi để xem xét:
giao diện công cộng ClientScheduler {
lịch trình vô hiệu (ClientRequestProcessor requestProcessor);
}
Chính sách hiện tại rất dễ thực hiện:
public class ThreadPerRequestScheduler triển khai ClientScheduler {
lịch trống công khai (yêu cầu ClientRequestProcessor cuối cùng) {
Runnable runnable = new Runnable () {
public void run () {
requestProcessor.process ();
}
};
Chủ đề luồng = new Thread (runnable);
thread.start ();
}
}
Sau khi cô lập tất cả quản lý luồng vào một nơi duy nhất, việc thay đổi sẽ dễ dàng hơn nhiều
cách chúng tôi kiểm soát các luồng. Ví dụ: chuyển sang khung Java 5 Executor
liên quan đến việc viết một lớp mới và cắm nó vào (Liệt kê A-1).
www.it-ebooks.info

Trang 352
321
Các con đường thực hiện có thể có
Phần kết luận
Việc giới thiệu đồng thời trong ví dụ cụ thể này cho thấy một cách để cải thiện
thông lượng của hệ thống và một cách xác thực thông lượng đó thông qua khung thử nghiệm-
công việc. Tập trung tất cả mã đồng thời vào một số lượng nhỏ các lớp là một ví dụ về
áp dụng Nguyên tắc Trách nhiệm Đơn lẻ. Trong trường hợp lập trình đồng thời, điều này
trở nên đặc biệt quan trọng vì tính phức tạp của nó.
Các con đường thực hiện có thể có
Xem lại phương thức incrementValue , một phương thức Java một dòng không có vòng lặp hoặc phân nhánh:
public class IdGenerator {
int lastIdUsed;
public int incrementValue () {
return ++ lastIdUsed;
}
}
Bỏ qua tràn số nguyên và giả sử rằng chỉ một luồng có quyền truy cập vào một phiên bản duy nhất
của IdGenerator . Trong trường hợp này, có một đường dẫn thực thi duy nhất và một đường dẫn duy nhất được đảm
bảo
kết quả:
• Giá trị trả về bằng giá trị của lastIdUsed , cả hai đều lớn hơn một
so với trước khi gọi phương thức.
Liệt kê A-1
ExecutorClientScheduler.java
nhập java.util.concurrent.Executor;
nhập java.util.concurrent.Executor;
public class ExecutorClientScheduler triển khai ClientScheduler {
Chấp hành viên thừa hành;
public ExecutorClientScheduler (int availableThreads) {
thi hành viên = Executor.newFixedThreadPool (có sẵnThreads);
}
lịch trống công khai (yêu cầu ClientRequestProcessor cuối cùng) {
Runnable runnable = new Runnable () {
public void run () {
requestProcessor.process ();
}
};
executive.execute (runnable);
}
}
www.it-ebooks.info

Trang 353
322
Phụ lục A: Đồng thời II
Điều gì xảy ra nếu chúng ta sử dụng hai luồng và không thay đổi phương thức? Là những gì
kết quả có thể xảy ra nếu mỗi luồng gọi một lần incrementValue ? Có bao nhiêu con đường khả thi của
thực hiện ở đó? Đầu tiên, các kết quả (giả sử lastIdUsed bắt đầu bằng giá trị 93):
• Luồng 1 nhận giá trị 94, luồng 2 nhận giá trị 95 và lastIdUsed bây giờ là 95.
• Luồng 1 nhận giá trị 95, luồng 2 nhận giá trị 94 và lastIdUsed bây giờ là 95.
• Luồng 1 nhận giá trị 94, luồng 2 nhận giá trị 94 và lastIdUsed bây giờ là 94.
Kết quả cuối cùng, trong khi đáng ngạc nhiên, là có thể. Để xem các kết quả khác nhau này có khả năng như thế
nào-
ble, chúng ta cần hiểu số lượng các đường dẫn thực thi có thể có và cách Java
Máy ảo thực thi chúng.
Số đường dẫn
Để tính toán số lượng đường dẫn thực thi có thể có, chúng ta sẽ bắt đầu với byte được tạo-
mã. Một dòng của java ( return ++ lastIdUsed; ) trở thành tám hướng dẫn mã byte. Nó
có thể để hai luồng xen kẽ việc thực hiện tám hướng dẫn này
cách một người chia bài xen kẽ các quân bài khi anh ta xáo trộn bộ bài. 2 Ngay cả khi chỉ có tám thẻ trong
mỗi ván bài, có một số kết quả xáo trộn đáng chú ý.
Đối với trường hợp đơn giản này của N lệnh trong một chuỗi, không có vòng lặp hoặc điều kiện, và T
chủ đề, tổng số đường dẫn thực thi có thể có bằng
2. Đây là một chút đơn giản hóa. Tuy nhiên, với mục đích của cuộc thảo luận này, chúng ta có thể sử dụng mô hình đơn giản hóa này.
Tính toán các thử thách có thể xảy ra
Điều này đến từ một email từ Uncle Bob gửi Brett:
Với N bước và T chủ đề có T * N tổng số bước. Trước mỗi bước
có một chuyển đổi ngữ cảnh chọn giữa các luồng T. Mỗi con đường có thể
do đó được biểu thị dưới dạng một chuỗi các chữ số biểu thị các công tắc ngữ cảnh.
Với các bước A và B và chủ đề 1 và 2, sáu đường dẫn có thể là 1122,
1212, 1221, 2112, 2121 và 2211. Hoặc, xét theo các bước, nó là A1B1A2B2,
A1A2B1B2, A1A2B2B1, A2A1B1B2, A2A1B2B1 và A2B2A1B1. Đối với
ba chủ đề thứ tự là 112233, 112323, 113223, 113232, 112233,
121233, 121323, 121332, 123132, 123123 ,. . . .
Một đặc điểm của các chuỗi này là luôn phải có N
trường hợp của mỗi T . Vì vậy, chuỗi 111111 không hợp lệ vì nó có sáu
các trường hợp 1 và 0 các trường hợp 2 và 3.
NT
()!
N !
T
--------------
www.it-ebooks.info

Trang 354
323
Các con đường thực hiện có thể có
Đối với trường hợp đơn giản của chúng tôi về một dòng mã Java, tương đương với tám dòng mã byte
và hai luồng, tổng số đường dẫn có thể thực thi là 12.870. Nếu loại
lastIdUsed là một dài , sau đó mỗi đọc / ghi trở thành hai hoạt động thay vì một, và
số lần thử thách có thể trở thành 2,704,156.
Điều gì xảy ra nếu chúng ta thực hiện một thay đổi đối với phương pháp này?
công khai đồng bộ hóa void incrementValue () {
++ lastIdUsed;
}
Số đường dẫn thực thi có thể trở thành hai cho hai luồng và N! bên trong
trường hợp chung.
Đào sâu hơn
Còn về kết quả đáng ngạc nhiên là hai luồng đều có thể gọi phương thức một lần (trước
chúng tôi đã thêm đồng bộ hóa ) và nhận được cùng một kết quả số? Làm thế nào là có thể? Đầu tiên
những điều đầu tiên.
Hoạt động nguyên tử là gì? Chúng ta có thể định nghĩa một hoạt động nguyên tử là bất kỳ hoạt động nào
không bị gián đoạn. Ví dụ, trong đoạn mã sau, dòng 5, trong đó 0 được gán cho
lastid , là nguyên tử vì theo mô hình Bộ nhớ Java, gán cho 32-bit
giá trị không bị gián đoạn.
Vì vậy, chúng tôi muốn các hoán vị của N 1, N 2,. . . và N T ' s. Đây là
thực sự chỉ là hoán vị của N * T những thứ được lấy N * T tại một thời điểm, đó là
( N * T ) !, nhưng đã loại bỏ tất cả các bản sao. Vì vậy, mẹo là đếm
nhân đôi và trừ đi từ ( N * T ) !.
Cho trước hai bước và hai chủ đề, có bao nhiêu bản sao? Mỗi
chuỗi bốn chữ số có hai chữ số 1 và hai chữ số 2. Mỗi cặp đó có thể là
được hoán đổi mà không làm thay đổi cảm giác của chuỗi. Bạn có thể hoán đổi số 1 hoặc
cả hai, hoặc không. Vì vậy, có bốn isomorphs cho mỗi chuỗi,
có nghĩa là có ba bản sao. Vì vậy, ba trong số bốn tùy chọn là
trùng lặp; cách khác, một trong bốn hoán vị KHÔNG trùng lặp-
cates. 4! * .25 = 6. Vì vậy, suy luận này có vẻ hiệu quả.
Có bao nhiêu bản sao? Trong trường hợp N = 2 và T = 2, I
có thể hoán đổi số 1, 2 hoặc cả hai. Trong trường hợp N = 2 và T = 3, I
có thể hoán đổi các số 1, 2, 3, 1 và 2, 1 và 3 hoặc 2 và 3. Hoán đổi-
ping chỉ là các hoán vị của N . Hãy nói rằng có P hoán vị của N .
Các số cách khác nhau để sắp xếp những hoán vị là P ** T .
Vì vậy, số lượng isomorphs có thể là N ! ** T . Và số lượng
đường dẫn là ( T * N )! / ( N ! ** T ). Một lần nữa, trong trường hợp T = 2, N = 2 của chúng tôi, chúng tôi nhận được
6 (24/4).
Với N = 2 và T = 3 ta được 720/8 = 90.
Với N = 3 và T = 3 ta được 9! / 6 ^ 3 = 1680.
Tính toán các thử thách có thể xảy ra (tiếp theo)
www.it-ebooks.info

Trang 355
324
Phụ lục A: Đồng thời II
01: public class Ví dụ {
02: int lastId;
03:
04: public void resetId () {
05:
giá trị = 0;
06:}
07:
08: public int getNextId () {
09:
++ giá trị;
10:}
11:}
Điều gì xảy ra nếu chúng ta thay đổi kiểu lastId từ int thành long ? Dòng 5 có còn nguyên tử không?
Không theo đặc điểm kỹ thuật của JVM. Nó có thể là nguyên tử trên một bộ xử lý cụ thể,
nhưng theo đặc tả JVM, việc gán cho bất kỳ giá trị 64 bit nào yêu cầu hai
Phép gán 32-bit. Điều này có nghĩa là giữa lần gán 32 bit đầu tiên và lần thứ hai
Phép gán 32 bit, một số luồng khác có thể lẻn vào và thay đổi một trong các giá trị.
Còn toán tử tăng trước, ++, trên dòng 9 thì sao? Toán tử tăng trước có thể
bị gián đoạn, vì vậy nó không phải là nguyên tử. Để hiểu, hãy xem lại mã byte của cả hai
phương pháp chi tiết.
Trước khi chúng ta đi xa hơn, đây là ba định nghĩa quan trọng:
• Khung — Mọi lệnh gọi phương thức đều yêu cầu một khung. Khung bao gồm sự trở lại
địa chỉ, bất kỳ tham số nào được truyền vào phương thức và các biến cục bộ được xác định trong
phương pháp. Đây là một kỹ thuật tiêu chuẩn được sử dụng để xác định một ngăn xếp cuộc gọi, được sử dụng bởi
ngôn ngữ hiện đại cho phép gọi hàm / phương thức cơ bản và cho phép
lời gọi đệ quy.
• Biến cục bộ — Bất kỳ biến nào được xác định trong phạm vi của phương pháp. Tất cả meth không ổn định-
ods có ít nhất một biến, biến này , đại diện cho đối tượng hiện tại, đối tượng
đã nhận được tin nhắn gần đây nhất (trong chuỗi hiện tại), điều này gây ra
lệnh gọi phương thức.
• Toán hạng ngăn xếp — Nhiều lệnh trong Máy ảo Java có tham số-
ters. Ngăn xếp toán hạng là nơi đặt các tham số đó. Ngăn xếp là một tiêu chuẩn
cấu trúc dữ liệu cuối cùng vào, ra trước (LIFO).
Đây là mã byte được tạo cho resetId () :
Dễ nhớ
Sự miêu tả
Toán hạng
Xếp sau
ALOAD 0
Tải biến thứ 0 vào ngăn xếp toán hạng.
Biến thứ 0 là gì? Nó là cái này ., Hiện tại
vật. Khi phương thức được gọi,
người nhận tin nhắn, một ví dụ của Ví dụ ,
đã được đẩy vào mảng biến cục bộ của
khung được tạo để gọi phương thức. Đây là
luôn là biến đầu tiên được đặt trong mọi trường hợp
phương pháp.
điều này
www.it-ebooks.info

Trang 356
325
Các con đường thực hiện có thể có
Ba hướng dẫn này được đảm bảo là nguyên tử bởi vì, mặc dù chuỗi
việc thực thi chúng có thể bị gián đoạn sau bất kỳ một trong số chúng, thông tin cho
Lệnh PUTFIELD (giá trị không đổi 0 trên đầu ngăn xếp và tham chiếu đến
cái này bên dưới phần trên cùng, cùng với giá trị trường) không thể bị một luồng khác chạm vào.
Vì vậy, khi việc gán xảy ra, chúng tôi đảm bảo rằng giá trị 0 sẽ được lưu trữ trong
giá trị trường. Hoạt động là nguyên tử. Các toán hạng đều xử lý thông tin cục bộ cho
vì vậy không có sự can thiệp giữa nhiều luồng.
Vì vậy, nếu ba hướng dẫn này được thực thi bởi mười chủ đề, có 4,38679733629e + 24
các thử thách có thể xảy ra. Tuy nhiên, chỉ có một kết quả có thể xảy ra, vì vậy các thử thách khác nhau
không liên quan. Nó chỉ xảy ra như vậy rằng kết quả tương tự được đảm bảo trong thời gian dài trong trường hợp
này
cũng. Tại sao? Tất cả mười chủ đề đang gán một giá trị không đổi. Ngay cả khi chúng xen kẽ với
nhau, kết quả cuối cùng là như nhau.
Với hoạt động ++ trong phương thức getNextId , sẽ có một số vấn đề.
Giả sử rằng lastId giữ 42 ở đầu phương pháp này. Đây là mã byte cho việc này
phương pháp mới:
ICONST_0
Đặt giá trị không đổi 0 vào ngăn xếp toán hạng. cái này, 0
PUTFIELD cuối cùng
Lưu trữ giá trị cao nhất trên ngăn xếp (là 0) vào
giá trị trường của đối tượng được tham chiếu bởi
tham chiếu đối tượng một cách xa đầu
chồng, cái này .
<empty>
Dễ nhớ
Sự miêu tả
Toán hạng
Xếp sau
ALOAD 0
Tải nó vào ngăn xếp toán hạng
điều này
DUP
Sao chép phần trên cùng của ngăn xếp. Bây giờ chúng tôi có hai
bản sao của điều này trên ngăn xếp toán hạng.
cái này , cái này
GETFIELD lastId
Lấy giá trị của trường lastId từ
đối tượng được trỏ đến trên đầu ngăn xếp ( cái này ) và
lưu trữ lại giá trị đó vào ngăn xếp.
cái này , 42
ICONST_1
Đẩy hằng số nguyên 1 lên ngăn xếp.
cái này , 42, 1
IADD
Số nguyên thêm hai giá trị hàng đầu vào toán hạng
xếp chồng và lưu trữ kết quả trở lại toán hạng
cây rơm.
cái này , 43
DUP_X1
Nhân đôi giá trị 43 và đặt nó trước giá trị này .
43, cái này , 43
Giá trị PUTFIELD
Lưu trữ giá trị cao nhất trên ngăn xếp toán hạng, 43, vào
giá trị trường của đối tượng hiện tại, được đại diện bởi
giá trị tiếp theo đến đầu trên ngăn xếp toán hạng, cái này .
43
TÔI QUAY TRỞ LẠI
trả về giá trị hàng đầu (và duy nhất) trên ngăn xếp.
<empty>
Dễ nhớ
Sự miêu tả
Toán hạng
Xếp sau
www.it-ebooks.info

Trang 357
326
Phụ lục A: Đồng thời II
Hãy tưởng tượng trường hợp chuỗi đầu tiên hoàn thành ba hướng dẫn đầu tiên, lên đến và
bao gồm GETFIELD, và sau đó nó bị gián đoạn. Một chuỗi thứ hai tiếp quản và thực hiện
toàn bộ phương thức, tăng dần lastId lên một; nó được 43 trở lại. Sau đó, chủ đề đầu tiên chọn
nơi nó rời đi; 42 vẫn còn trên ngăn xếp toán hạng vì đó là giá trị của lastId khi nó
đã thực thi GETFIELD. Nó thêm một để nhận lại 43 và lưu trữ kết quả. Giá trị 43 là
cũng quay lại chủ đề đầu tiên. Kết quả là một trong các giá trị gia tăng bị mất vì
sợi đầu tiên giẫm lên sợi thứ hai sau khi sợi thứ hai ngắt sợi thứ nhất.
Làm cho phương thức getNexId () được đồng bộ hóa sẽ khắc phục được sự cố này.
Phần kết luận
Không cần thiết phải hiểu sâu sắc về mã byte để hiểu cách các luồng có thể
giẫm lên nhau. Nếu bạn có thể hiểu một ví dụ này, nó sẽ chứng minh vị trí
khả năng của nhiều chủ đề dẫm lên nhau, đó là đủ kiến thức.
Điều đó đang được nói, những gì ví dụ tầm thường này chứng minh là cần phải hiểu
mô hình bộ nhớ đủ để biết những gì được và không an toàn. Đó là một quan niệm sai lầm phổ biến rằng
toán tử ++ (tăng trước hoặc tăng sau) là nguyên tử, và rõ ràng là không. Nó nghĩa là bạn
cần phải biết:
• Nơi có các đối tượng / giá trị được chia sẻ
• Mã có thể gây ra sự cố đọc / cập nhật đồng thời
• Làm thế nào để bảo vệ các vấn đề đồng thời như vậy xảy ra
Biết thư viện của bạn
Khung chấp hành viên
Như đã trình bày trong ExecutorClientScheduler.java trên trang 321, khung Executor-
công việc được giới thiệu trong Java 5 cho phép thực thi phức tạp bằng cách sử dụng nhóm luồng. Đây là một
trong gói java.util.concurrent .
Nếu bạn đang tạo luồng và không sử dụng nhóm luồng hoặc đang sử dụng một bản viết tay
một, bạn nên cân nhắc sử dụng Executor . Nó sẽ làm cho mã của bạn sạch hơn, dễ dàng hơn
thấp và nhỏ hơn.
Khung Executor sẽ gộp các luồng, tự động thay đổi kích thước và tạo lại các luồng
Nếu cần. Nó cũng hỗ trợ tương lai, một cấu trúc lập trình đồng thời phổ biến. Các
Khung Executor hoạt động với các lớp triển khai Runnable và cũng hoạt động với
lớp mà thực hiện các Callable giao diện. Một Callable trông giống như một Runnable , nhưng nó có thể
trả về một kết quả, đây là một nhu cầu phổ biến trong các giải pháp đa luồng.
Một tương lai rất hữu ích khi mã cần thực thi nhiều hoạt động độc lập và
đợi cả hai kết thúc:
public String processRequest (String message) ném Exception {
Callable <String> makeExternalCall = new Callable <String> () {
www.it-ebooks.info

Trang 358
327
Biết thư viện của bạn
public String call () ném Exception {
Kết quả chuỗi = "";
// thực hiện yêu cầu bên ngoài
trả về kết quả;
}
};
Future <String> result = executeService.submit (makeExternalCall);
String partResult = doSomeLocalProcessing ();
trả về result.get () + partResult;
}
Trong ví dụ này, phương thức bắt đầu thực thi đối tượng makeExternalCall . Phương pháp con-
thiếc chế biến khác. Dòng cuối cùng gọi result.get () , sẽ chặn cho đến tương lai
hoàn thành.
Giải pháp không chặn
Máy ảo Java 5 tận dụng thiết kế bộ xử lý hiện đại, hỗ trợ đáng tin cậy,
cập nhật không chặn. Ví dụ, hãy xem xét một lớp sử dụng đồng bộ hóa (và ở đó-
chặn trước) để cung cấp bản cập nhật an toàn cho một chuỗi giá trị:
public class ObjectWithValue {
giá trị int riêng tư;
giá trị public voidised incrementValue () {++; }
public int getValue () {giá trị trả về; }
}
Java 5 có một loạt các lớp mới cho các tình huống như thế này: AtomicBoolean ,
AtomicInteger và AtomicReference là ba ví dụ; còn một số nữa. Chúng ta có thể
viết lại đoạn mã trên để sử dụng cách tiếp cận không chặn như sau:
public class ObjectWithValue {
private AtomicInteger value = new AtomicInteger (0);
public void incrementValue () {
value.incrementAndGet ();
}
public int getValue () {
trả về giá trị.get ();
}
}
Mặc dù điều này sử dụng một đối tượng thay vì một nguyên thủy và gửi các thông báo như
incrementAndGet () thay vì ++, hiệu suất của lớp này gần như luôn đánh bại
phiên bản trước. Trong một số trường hợp, nó sẽ chỉ nhanh hơn một chút, nhưng những trường hợp nó sẽ
chậm hơn hầu như không tồn tại.
Sao có thể như thế được? Các bộ xử lý hiện đại có một hoạt động thường được gọi là So sánh
và Hoán đổi (CAS) . Hoạt động này tương tự như khóa lạc quan trong cơ sở dữ liệu, trong khi
phiên bản đồng bộ tương tự như khóa bi quan.
www.it-ebooks.info

Trang 359
328
Phụ lục A: Đồng thời II
Các đồng bộ từ khóa luôn có được một khóa, ngay cả khi một sợi thứ hai không phải là
cố gắng cập nhật cùng một giá trị. Mặc dù hiệu suất của khóa nội tại có
cải tiến từ phiên bản này sang phiên bản khác, chúng vẫn còn tốn kém.
Phiên bản không chặn bắt đầu với giả định rằng nhiều luồng thường làm
không sửa đổi cùng một giá trị đủ thường xuyên để phát sinh vấn đề. Thay vào đó, nó hiệu quả
phát hiện xem tình huống như vậy đã xảy ra hay chưa và thử lại cho đến khi cập nhật thành công-
đầy đủ. Việc phát hiện này hầu như luôn luôn ít tốn kém hơn so với việc mua một khóa, ngay cả khi
tình huống tranh chấp cao.
Làm cách nào để Máy ảo thực hiện được điều này? Hoạt động CAS là nguyên tử. Logi-
cally, hoạt động CAS trông giống như sau:
int biếnBeingSet;
void simulateNonBlockingSet (int newValue) {
int currentValue;
làm {
currentValue = variableBeingSet
} while (currentValue! = so sánhAndSwap (currentValue, newValue));
}
int sync CompareAndSwap (int currentValue, int newValue) {
if (variableBeingSet == currentValue) {
variableBeingSet = newValue;
trả về giá trị currentValue;
}
trả về biếnBeingSet;
}
Khi một phương thức cố gắng cập nhật một biến được chia sẻ, phép toán CAS xác minh rằng
biến nhận được thiết lập vẫn có giá trị cuối cùng đã biết. Nếu vậy, thì biến được thay đổi. Nếu
không, sau đó biến không được đặt vì một luồng khác đã quản lý để cản trở. Các
phương pháp thực hiện thử (sử dụng phép toán CAS) thấy rằng thay đổi không được thực hiện
và thử lại.
Các lớp an toàn không đọc
Có một số lớp vốn không an toàn cho luồng. Đây là vài ví dụ:
• SimpleDateFormat
• Kết nối cơ sở dữ liệu
• Vùng chứa trong java.util
• Servlet
Lưu ý rằng một số lớp tập hợp có các phương thức riêng lẻ an toàn cho chuỗi. Tuy nhiên,
bất kỳ hoạt động nào liên quan đến việc gọi nhiều hơn một phương thức thì không. Ví dụ, nếu bạn làm
không muốn thay thế thứ gì đó trong HashTable vì nó đã ở đó, bạn có thể viết
mã sau:
if (! hashTable.containsKey (someKey)) {
hashTable.put (someKey, SomeValue mới ());
}
www.it-ebooks.info

Trang 360
329
Sự phụ thuộc giữa các phương thức có thể phá vỡ mã đồng thời
Mỗi phương pháp riêng lẻ đều an toàn. Tuy nhiên, một chuỗi khác có thể thêm giá trị vào
giữa các cuộc gọi containsKey và put . Có một số tùy chọn để khắc phục sự cố này.
• Khóa HashTable trước và đảm bảo rằng tất cả những người dùng HashTable khác cũng làm như vậy—
khóa dựa trên máy khách:
đồng bộ hóa (bản đồ) {
if (! map.conainsKey (key))
map.put (khóa, giá trị);
}
• Bao bọc HashTable trong đối tượng của chính nó và sử dụng một API khác — khóa dựa trên máy chủ
sử dụng A DAPTOR :
public class WrappedHashtable <K, V> {
private Map <K, V> map = new Hashtable <K, V> ();
public đồng bộ hóa void putIfAbsent (khóa K, giá trị V) {
if (map.containsKey (key))
map.put (khóa, giá trị);
}
}
• Sử dụng các bộ sưu tập an toàn cho chuỗi:
ConcurrentHashMap <Integer, String> map = new ConcurrentHashMap <Integer,
Chuỗi> ();
map.putIfAbsent (khóa, giá trị);
Các bộ sưu tập trong java.util.concurrent có các hoạt động như putIfAbsent () để accommo-
ngày các hoạt động đó.
Phụ thuộc giữa Phương pháp s
Có thể phá vỡ mã đồng thời
Dưới đây là một ví dụ đơn giản về cách giới thiệu sự phụ thuộc giữa các phương thức:
public class IntegerIterator triển khai Iterator <Integer>
private Integer nextValue = 0;
boolean được đồng bộ hóa công khai hasNext () {
trả về nextValue <100000;
}
Integer được đồng bộ hóa công khai next () {
if (nextValue == 100000)
ném mới IteratorPastEndException ();
trả về nextValue ++;
}
Integer được đồng bộ hóa công khai getNextValue () {
trả về nextValue;
}
}
Đây là một số mã để sử dụng IntegerIterator này :
Trình lặp IntegerIterator = new IntegerIterator ();
while (iterator.hasNext ()) {
www.it-ebooks.info

Trang 361
330
Phụ lục A: Đồng thời II
int nextValue = iterator.next ();
// làm gì đó với nextValue
}
Nếu một luồng thực thi mã này, sẽ không có vấn đề gì. Nhưng điều gì sẽ xảy ra nếu hai luồng
cố gắng chia sẻ một phiên bản IngeterIterator với mục đích mỗi chuỗi sẽ
xử lý các giá trị mà nó nhận được, nhưng mỗi phần tử của danh sách chỉ được xử lý một lần? Hầu hết
thời gian, không có gì xấu xảy ra; các chủ đề vui vẻ chia sẻ danh sách, xử lý các phần tử
chúng được đưa ra bởi trình lặp và dừng khi trình lặp hoàn tất. Tuy nhiên, có
một cơ hội nhỏ là vào cuối lần lặp lại, hai luồng sẽ can thiệp vào
khác và khiến một luồng vượt ra ngoài phần cuối của trình lặp và đưa ra một ngoại lệ.
Đây là vấn đề: Chủ đề 1 hỏi câu hỏi hasNext () , trả về true . Chủ đề
1 được đánh trước và sau đó Chủ đề 2 hỏi câu hỏi tương tự, điều này vẫn đúng . Chủ đề 2
sau đó gọi next () , trả về một giá trị như mong đợi nhưng có tác dụng phụ là tạo
hasNext () trả về false . Chủ đề 1 bắt đầu lại, suy nghĩ hasNext () vẫn đúng , và sau đó
cuộc gọi tiếp theo () . Mặc dù các phương thức riêng lẻ được đồng bộ hóa, máy khách sử dụng hai
các phương pháp.
Đây là một vấn đề thực tế và là một ví dụ về các loại vấn đề nảy sinh trong
mã hiện tại. Trong tình huống cụ thể này, vấn đề này đặc biệt phức tạp vì
thời gian mà điều này gây ra lỗi là khi nó xảy ra trong lần lặp cuối cùng của trình lặp.
Nếu các chủ đề xảy ra bị đứt vừa phải, thì một trong các chủ đề có thể vượt ra ngoài kết thúc
của trình lặp. Đây là loại lỗi xảy ra rất lâu sau khi một hệ thống được hỗ trợ
và rất khó để theo dõi.
Bạn có ba lựa chọn:
• Chịu đựng thất bại.
• Giải quyết vấn đề bằng cách thay đổi máy khách: khóa dựa trên máy khách
• Giải quyết vấn đề bằng cách thay đổi máy chủ, điều này cũng làm thay đổi máy khách:
khóa dựa trên máy chủ
Chịu đựng thất bại
Đôi khi bạn có thể thiết lập mọi thứ sao cho sự thất bại không gây hại. Ví dụ,
khách hàng trên có thể bắt ngoại lệ và dọn dẹp. Thành thật mà nói, điều này là một chút cẩu thả. Nó đúng hơn
như dọn dẹp bộ nhớ bị rò rỉ bằng cách khởi động lại lúc nửa đêm.
Khóa dựa trên khách hàng
Để làm cho IntegerIterator hoạt động chính xác với nhiều luồng, hãy thay đổi ứng dụng khách này (và
mọi khách hàng khác) như sau:
Trình lặp IntegerIterator = new IntegerIterator ();
trong khi (đúng) {
int nextValue;
www.it-ebooks.info

Trang 362
331
Sự phụ thuộc giữa các phương thức có thể phá vỡ mã đồng thời
được đồng bộ hóa (trình lặp) {
if (! iterator.hasNext ())
phá vỡ;
nextValue = iterator.next ();
}
doSometingWith (nextValue);
}
Mỗi khách hàng giới thiệu một khóa thông qua từ khóa được đồng bộ hóa . Sự trùng lặp này vi phạm
Nguyên tắc DRY, nhưng có thể cần thiết nếu mã sử dụng các công cụ của bên thứ ba không an toàn cho luồng.
Chiến lược này là rủi ro vì tất cả các lập trình viên sử dụng máy chủ phải nhớ
khóa trước khi sử dụng và mở khóa khi hoàn tất. Nhiều (nhiều!) Năm trước tôi đã làm việc trên một
hệ thống sử dụng khóa dựa trên máy khách trên một tài nguyên được chia sẻ. Tài nguyên đã được sử dụng trong
hàng trăm vị trí khác nhau trong suốt mã. Một lập trình viên kém đã quên khóa
tài nguyên ở một trong những nơi đó.
Hệ thống này là một hệ thống chia sẻ thời gian đa đầu cuối chạy phần mềm kế toán
cho Địa phương 705 của công đoàn xe tải. Máy tính ở trên sàn nâng, môi trường-
kiểm soát phòng 50 dặm về phía bắc của 705 trụ sở địa phương. Tại trụ sở chính họ
đã có hàng chục nhân viên nhập dữ liệu nhập các bài đăng về phí công đoàn vào thiết bị đầu cuối. Thuật ngữ-
nals được kết nối với máy tính bằng đường dây điện thoại chuyên dụng và chế độ bán song công 600bps
modem. (Đây là một rất, rất lâu trước đây.)
Khoảng một lần mỗi ngày, một trong các thiết bị đầu cuối sẽ "khóa". Không có vần hoặc rea-
con trai với nó. Việc khóa không có ưu tiên cho các thiết bị đầu cuối cụ thể hoặc thời gian cụ thể. Nó
như thể có ai đó đang tung xúc xắc đang chọn thời gian và thiết bị đầu cuối để khóa.
Đôi khi nhiều hơn một thiết bị đầu cuối sẽ bị khóa. Đôi khi ngày trôi qua mà không
bất kỳ khóa nào.
Lúc đầu, giải pháp duy nhất là khởi động lại. Nhưng việc khởi động lại rất khó để phối hợp. Chúng ta đã có
gọi đến trụ sở chính và yêu cầu mọi người hoàn thành những gì họ đang làm trên tất cả các điều kiện-
nals. Sau đó, chúng tôi có thể tắt và khởi động lại. Nếu ai đó đang làm điều gì đó quan trọng
mất một hoặc hai giờ, thiết bị đầu cuối bị khóa chỉ đơn giản là phải tiếp tục khóa.
Sau một vài tuần gỡ lỗi, chúng tôi nhận thấy rằng nguyên nhân là do bộ đếm bộ đệm vòng
đã không đồng bộ với con trỏ của nó. Bộ đệm này kiểm soát đầu ra đến thiết bị đầu cuối. Các
giá trị con trỏ chỉ ra rằng bộ đệm trống, nhưng bộ đếm cho biết bộ đệm đã đầy. Bởi vì
nó trống rỗng, không có gì để hiển thị; nhưng vì nó cũng đã đầy nên không có gì có thể
được thêm vào bộ đệm để hiển thị trên màn hình.
Vì vậy, chúng tôi biết tại sao các thiết bị đầu cuối bị khóa, nhưng chúng tôi không biết tại sao bộ đệm chuông
không được đồng bộ hóa. Vì vậy, chúng tôi đã thêm một bản hack để giải quyết vấn đề. Nó có thể
đọc công tắc bảng điều khiển phía trước trên máy tính. (Đây là cách đây rất, rất, rất lâu rồi.)
Chúng tôi đã viết một hàm bẫy nhỏ để phát hiện khi một trong các công tắc này được ném và
sau đó tìm kiếm một bộ đệm vòng trống và đầy. Nếu một cái được tìm thấy, nó sẽ đặt lại
đệm để trống. Thì đấy! (Các) thiết bị đầu cuối bị khóa bắt đầu hiển thị trở lại.
Vì vậy, bây giờ chúng tôi không phải khởi động lại hệ thống khi thiết bị đầu cuối bị khóa. Địa phương
chỉ cần gọi cho chúng tôi và nói với chúng tôi rằng chúng tôi đã bị khóa, và sau đó chúng tôi chỉ đi vào
phòng puter và bật công tắc.
www.it-ebooks.info

Trang 363
332
Phụ lục A: Đồng thời II
Tất nhiên, đôi khi họ làm việc vào cuối tuần, còn chúng tôi thì không. Vì vậy, chúng tôi đã thêm một
hoạt động với bộ lập lịch đã kiểm tra tất cả các bộ đệm chuông một lần mỗi phút và đặt lại bất kỳ
vừa trống vừa đầy. Điều này làm cho các màn hình được mở ra trước khi Địa phương có thể
thậm chí nhận được trên điện thoại.
Đó là vài tuần nữa miệt mài hết trang này đến trang khác của bản lắp ráp nguyên khối lan-
mã guage trước khi chúng tôi tìm ra thủ phạm. Chúng tôi đã thực hiện một phép toán và tính toán rằng
thời gian của các khóa phù hợp với một lần sử dụng không được bảo vệ của bộ đệm vòng. Vì thế
tất cả những gì chúng tôi phải làm là phát hiện ra rằng một lỗi sử dụng. Thật không may, điều này đã quá lâu trước
đây
rằng chúng tôi không có công cụ tìm kiếm hoặc tham chiếu chéo hoặc bất kỳ loại trợ giúp tự động nào khác.
Chúng tôi chỉ đơn giản là phải nghiền ngẫm các danh sách.
Tôi đã học được một bài học quan trọng vào mùa đông lạnh giá ở Chicago năm 1971. Khóa dựa trên khách hàng
thực sự thổi.
Khóa dựa trên máy chủ
Có thể loại bỏ sự trùng lặp bằng cách thực hiện các thay đổi sau đối với IntegerIterator :
public class IntegerIteratorServerLocked {
private Integer nextValue = 0;
Integer được đồng bộ hóa công khai getNextOrNull () {
nếu (nextValue <100000)
trả về nextValue ++;
khác
trả về null;
}
}
Và mã khách hàng cũng thay đổi:
trong khi (đúng) {
Integer nextValue = iterator.getNextOrNull ();
if (tiếp theo == null)
phá vỡ;
// làm gì đó với nextValue
}
Trong trường hợp này, chúng tôi thực sự thay đổi API của lớp chúng tôi thành nhận biết đa luồng. 3 Khách hàng
cần thực hiện kiểm tra null thay vì kiểm tra hasNext () .
Nói chung, bạn nên thích khóa dựa trên máy chủ vì những lý do sau:
• Nó làm giảm mã lặp lại — Khóa dựa trên máy khách buộc mỗi máy khách phải khóa máy chủ
đúng cách. Bằng cách đặt mã khóa vào máy chủ, khách hàng có thể tự do sử dụng đối tượng
và không phải lo lắng về việc viết thêm mã khóa.
3. Trên thực tế, giao diện Iterator vốn dĩ không an toàn theo luồng. Nó không bao giờ được thiết kế để sử dụng bởi nhiều chủ đề, vì vậy điều này
sẽ không có gì ngạc nhiên.
www.it-ebooks.info

Trang 364
333
Tăng thông lượng
• Nó cho phép hiệu suất tốt hơn — Bạn có thể hoán đổi một máy chủ an toàn luồng cho một máy chủ không
luồng an toàn trong trường hợp triển khai đơn luồng, do đó tránh tất cả
trên không.
• Nó làm giảm khả năng xảy ra lỗi — Tất cả những gì cần làm là một lập trình viên quên khóa
đúng cách.
• Nó thực thi một chính sách duy nhất — Chính sách này ở một nơi, máy chủ, thay vì nhiều
từng địa điểm, từng khách hàng.
• Nó làm giảm phạm vi của các biến được chia sẻ — Khách hàng không biết về chúng hoặc làm thế nào
chúng bị khóa. Tất cả những thứ đó đều được ẩn trong máy chủ. Khi mọi thứ vỡ ra, số lượng
những nơi cần tìm nhỏ hơn.
Nếu bạn không sở hữu mã máy chủ thì sao?
• Sử dụng A DAPTOR để thay đổi API và thêm khóa
lớp công khai ThreadSafeIntegerIterator {
private IntegerIterator iterator = new IntegerIterator ();
Integer được đồng bộ hóa công khai getNextOrNull () {
if (iterator.hasNext ())
trả về iterator.next ();
trả về null;
}
}
• HOẶC tốt hơn, sử dụng các bộ sưu tập an toàn chuỗi với các giao diện mở rộng
Tăng thông lượng
Giả sử rằng chúng ta muốn ra ngoài mạng và đọc nội dung của một tập hợp các trang từ
danh sách các URL. Khi mỗi trang được đọc, chúng tôi sẽ phân tích cú pháp nó để tích lũy một số thống kê. Một lần
tất cả các trang được đọc, chúng tôi sẽ in một báo cáo tóm tắt.
Lớp sau trả về nội dung của một trang, được cung cấp một URL.
Trình đọc trang lớp công khai {
// ...
public String getPageFor (String url) {
Phương thức HttpMethod = new GetMethod (url);
thử {
httpClient.executeMethod (phương thức);
Chuỗi phản hồi = method.getResponseBodyAsString ();
trả lời phản hồi;
} catch (Ngoại lệ e) {
xử lý (e);
} cuối cùng {
method.releaseConnection ();
}
}
}
www.it-ebooks.info

Trang 365
334
Phụ lục A: Đồng thời II
Lớp tiếp theo là trình lặp cung cấp nội dung của các trang dựa trên trình lặp của
URL:
Trình tạo trang lớp công khai {
trình đọc PageReader riêng tư;
các url URLIterator riêng tư;
public PageIterator (trình đọc PageReader, các url URLIterator) {
this.urls = urls;
this.reader = reader;
}
đồng bộ hóa công khai String getNextPageOrNull () {
if (urls.hasNext ())
getPageFor (urls.next ());
khác
trả về null;
}
public String getPageFor (String url) {
return reader.getPageFor (url);
}
}
Một phiên bản của PageIterator có thể được chia sẻ giữa nhiều chuỗi khác nhau, mỗi chuỗi
một người sử dụng phiên bản riêng của PageReader để đọc và phân tích cú pháp các trang mà nó nhận được từ
trình lặp.
Lưu ý rằng chúng tôi đã giữ cho khối được đồng bộ hóa rất nhỏ. Nó chỉ chứa các quan trọng
phần sâu bên trong PageIterator . Luôn luôn tốt hơn nếu đồng bộ hóa càng ít càng tốt
trái ngược với việc đồng bộ hóa càng nhiều càng tốt.
Tính toán thông lượng đơn luồng
Bây giờ chúng ta hãy thực hiện một số phép tính đơn giản. Đối với mục đích của đối số, giả sử như sau:
• Thời gian I / O để truy xuất một trang (trung bình): 1 giây
• Thời gian xử lý để phân tích cú pháp trang (trung bình): 0,5 giây
• I / O yêu cầu 0 phần trăm CPU trong khi xử lý yêu cầu 100 phần trăm.
Đối với N trang được xử lý bởi một luồng duy nhất, tổng thời gian thực hiện là 1,5 giây-
onds * N . Hình A-1 cho thấy một ảnh chụp nhanh 13 trang hoặc khoảng 19,5 giây.
Hình A-1
Chủ đề đơn
www.it-ebooks.info

Trang 366
335
Bế tắc
Tính toán đa luồng thông lượng
Nếu có thể truy xuất các trang theo bất kỳ thứ tự nào và xử lý các trang đó một cách độc lập, thì
có thể sử dụng nhiều luồng để tăng thông lượng. Điều gì xảy ra nếu chúng ta sử dụng ba
chủ đề? Chúng ta có thể thu được bao nhiêu trang trong cùng một thời điểm?
Như bạn có thể thấy trong Hình A-2, giải pháp đa luồng cho phép giới hạn quy trình
phân tích cú pháp của các trang để chồng chéo với việc đọc các trang liên kết với I / O. Trong một lý tưởng hóa
thế giới, điều này có nghĩa là bộ xử lý được sử dụng đầy đủ. Mỗi trang một giây đã đọc hết-
vỗ nhẹ bằng hai cú đánh. Do đó, chúng tôi có thể xử lý hai trang mỗi giây, tức là ba lần
thông lượng của giải pháp đơn luồng.
Bế tắc
Hãy tưởng tượng một ứng dụng Web có hai nhóm tài nguyên được chia sẻ có kích thước hữu hạn:
• Một nhóm các kết nối cơ sở dữ liệu cho công việc cục bộ trong quá trình lưu trữ
• Một nhóm các kết nối MQ đến một kho lưu trữ chính
Giả sử có hai thao tác trong ứng dụng này, hãy tạo và cập nhật:
• Tạo — Có được kết nối đến kho lưu trữ chính và cơ sở dữ liệu. Nói chuyện với chủ dịch vụ
kho lưu trữ và sau đó lưu trữ công việc trong công việc cục bộ trong cơ sở dữ liệu quy trình.
Hình A-2
Ba chủ đề đồng thời
www.it-ebooks.info

Trang 367
336
Phụ lục A: Đồng thời II
• Cập nhật — Có được kết nối với cơ sở dữ liệu và sau đó là kho lưu trữ chính. Đọc từ công việc
trong cơ sở dữ liệu quy trình và sau đó gửi đến kho lưu trữ chính
Điều gì xảy ra khi có nhiều người dùng hơn kích thước hồ bơi? Xem xét mỗi hồ bơi có
một kích thước của mười.
• Mười người dùng cố gắng sử dụng tạo, vì vậy tất cả mười kết nối cơ sở dữ liệu được thu thập, và mỗi
luồng bị gián đoạn sau khi có được kết nối cơ sở dữ liệu nhưng trước khi có được kết nối
nection kho lưu trữ chính.
• Mười người dùng cố gắng sử dụng bản cập nhật, vì vậy tất cả mười kết nối kho lưu trữ chính đều được thu thập,
và mỗi luồng bị gián đoạn sau khi có được kho lưu trữ chính nhưng trước khi trắng-
nhập kết nối cơ sở dữ liệu.
• Bây giờ mười chủ đề "tạo" phải đợi để có được kết nối kho lưu trữ chính, nhưng
mười chủ đề "cập nhật" phải đợi để có được kết nối cơ sở dữ liệu.
• Bế tắc. Hệ thống không bao giờ phục hồi.
Điều này nghe có vẻ giống như một tình huống khó xảy ra, nhưng ai muốn một hệ thống đóng băng rắn
mỗi tuần khác? Ai muốn gỡ lỗi một hệ thống có các triệu chứng quá khó
tái sản xuất? Đây là loại vấn đề xảy ra trên thực địa, sau đó mất hàng tuần để giải quyết.
Một "giải pháp" điển hình là giới thiệu các câu lệnh gỡ lỗi để tìm hiểu điều gì đang xảy ra-
ing. Tất nhiên, các câu lệnh gỡ lỗi thay đổi mã đủ để xảy ra bế tắc
trong một tình huống khác và mất nhiều tháng để xảy ra một lần nữa. 4
Để thực sự giải quyết được vấn đề bế tắc, chúng ta cần hiểu nguyên nhân của nó là gì. Đó
là bốn điều kiện cần thiết để xảy ra bế tắc:
• Loại trừ lẫn nhau
• Khóa và chờ
• Không có quyền ưu tiên
• Vòng chờ
Loại trừ lẫn nhau
Loại trừ lẫn nhau xảy ra khi nhiều luồng cần sử dụng cùng một tài nguyên và những
tài nguyên
• Không thể được sử dụng bởi nhiều chủ đề cùng một lúc.
• Số lượng có hạn.
Một ví dụ phổ biến của tài nguyên như vậy là kết nối cơ sở dữ liệu, tệp mở để ghi,
khóa bản ghi, hoặc một semaphore.
4. Ví dụ, ai đó thêm một số đầu ra gỡ lỗi và vấn đề "biến mất." Mã gỡ lỗi "khắc phục" sự cố
vì vậy nó vẫn còn trong hệ thống.
www.it-ebooks.info

Trang 368
337
Bế tắc
Khóa và chờ
Khi một chuỗi có được tài nguyên, nó sẽ không giải phóng tài nguyên đó cho đến khi nó có được tất cả
các tài nguyên khác mà nó yêu cầu và đã hoàn thành công việc của nó.
Không có quyền ưu tiên
Một luồng không thể lấy tài nguyên từ một luồng khác. Khi một chủ đề giữ một
tài nguyên, cách duy nhất để một luồng khác có được nó là luồng đang giữ giải phóng nó.
Chờ vòng tròn
Đây cũng được gọi là cái ôm chết chóc. Hãy tưởng tượng hai luồng, T1 và T2, và hai
tài nguyên, R1 và R2. T1 có R1, T2 có R2. T1 cũng yêu cầu R2 và T2 cũng yêu cầu R1.
Điều này tạo ra một cái gì đó giống như Hình A-3:
Tất cả bốn điều kiện này phải giữ cho bế tắc có thể xảy ra. Phá vỡ bất kỳ một trong những
điều kiện và bế tắc không thực hiện được.
Phá vỡ loại trừ lẫn nhau
Một chiến lược để tránh bế tắc là bỏ qua điều kiện loại trừ lẫn nhau. Bạn
có thể làm được điều này bằng cách
• Sử dụng các tài nguyên cho phép sử dụng đồng thời, ví dụ: AtomicInteger .
• Tăng số lượng tài nguyên sao cho bằng hoặc vượt quá số lượng
chủ đề peting.
• Kiểm tra xem tất cả tài nguyên của bạn đều miễn phí trước khi chiếm đoạt bất kỳ tài nguyên nào.
Thật không may, hầu hết các tài nguyên có số lượng hạn chế và không cho phép đồng thời
sử dụng. Và không có gì lạ khi danh tính của tài nguyên thứ hai được xác định trên
kết quả của hoạt động đầu tiên. Nhưng đừng nản lòng; còn lại ba điều kiện.
Hình A-3
www.it-ebooks.info
Trang 369
338
Phụ lục A: Đồng thời II
Phá vỡ khóa và chờ đợi
Bạn cũng có thể loại bỏ bế tắc nếu bạn từ chối chờ đợi. Kiểm tra từng tài nguyên trước khi bạn
nắm bắt nó, và giải phóng tất cả các tài nguyên và bắt đầu lại nếu bạn gặp phải một tài nguyên bận.
Cách tiếp cận này đưa ra một số vấn đề tiềm ẩn:
• Đói — Một chuỗi tiếp tục không thể có được tài nguyên mà nó cần (có thể
có sự kết hợp độc đáo của các nguồn tài nguyên mà hiếm khi tất cả đều có sẵn).
• Livelock — Một số chuỗi có thể đi vào bước khóa và tất cả đều có được một tài nguyên và
sau đó phát hành một tài nguyên, lặp đi lặp lại. Điều này đặc biệt có thể xảy ra với
Các thuật toán lập lịch CPU (nghĩ rằng thiết bị nhúng hoặc viết tay đơn giản
các thuật toán cân bằng luồng).
Cả hai điều này có thể gây ra thông lượng kém. Kết quả đầu tiên là sử dụng CPU thấp,
trong khi kết quả thứ hai dẫn đến việc sử dụng CPU cao và vô dụng.
Chiến lược này nghe có vẻ không hiệu quả nhưng còn hơn không. Nó có lợi ích mà nó
hầu như luôn luôn có thể được thực hiện nếu vẫn thất bại.
Đột phá
Một chiến lược khác để tránh bế tắc là cho phép các chuỗi lấy tài nguyên khỏi
các chủ đề khác. Điều này thường được thực hiện thông qua một cơ chế yêu cầu đơn giản. Khi một chủ đề
phát hiện ra rằng một tài nguyên đang bận, nó yêu cầu chủ sở hữu giải phóng nó. Nếu chủ nhân cũng đang đợi
đối với một số tài nguyên khác, nó giải phóng tất cả và bắt đầu lại.
Điều này tương tự như cách tiếp cận trước đó nhưng có lợi ích là một chuỗi được phép
chờ một nguồn lực. Điều này làm giảm số lần khởi động. Được cảnh báo, tuy nhiên,
quản lý tất cả những yêu cầu đó có thể khó khăn.
Đập vòng chờ đợi
Đây là cách tiếp cận phổ biến nhất để ngăn chặn bế tắc. Đối với hầu hết các hệ thống, nó yêu cầu
không nhiều hơn một quy ước đơn giản được tất cả các bên đồng ý.
Trong ví dụ trên với Chủ đề 1 muốn cả Tài nguyên 1 và Tài nguyên 2 và
Luồng 2 muốn cả Tài nguyên 2 và sau đó là Tài nguyên 1, chỉ cần buộc cả Luồng 1 và
Luồng 2 để phân bổ tài nguyên theo cùng một thứ tự khiến cho việc chờ vòng tròn là không thể.
Nói chung hơn, nếu tất cả các chuỗi có thể đồng ý về thứ tự toàn cầu của các tài nguyên và nếu
tất cả phân bổ tài nguyên theo thứ tự đó, khi đó bế tắc là không thể. Giống như tất cả các chiến lược khác-
gies, điều này có thể gây ra vấn đề:
• Thứ tự mua có thể không tương ứng với thứ tự sử dụng; do đó một nguồn lực
có được khi bắt đầu có thể không được sử dụng cho đến khi kết thúc. Điều này có thể khiến tài nguyên
bị khóa lâu hơn mức cần thiết nghiêm ngặt.
www.it-ebooks.info

Trang 370
339
Kiểm tra mã đa luồng
• Đôi khi bạn không thể áp đặt mệnh lệnh cho việc mua lại các nguồn lực. Nếu ID của
tài nguyên thứ hai đến từ một hoạt động được thực hiện trên đầu tiên, sau đó thứ tự là
không khả thi.
Vì vậy, có nhiều cách để tránh bế tắc. Một số dẫn đến chết đói, trong khi những người khác
sử dụng nhiều CPU và giảm khả năng phản hồi. TANSTAAFL! 5
Cô lập phần liên quan đến luồng trong giải pháp của bạn để cho phép điều chỉnh và thử nghiệma-
tion là một cách hiệu quả để có được những hiểu biết cần thiết để xác định các chiến lược tốt nhất.
Kiểm tra mã đa luồng
Làm thế nào chúng ta có thể viết một bài kiểm tra để chứng minh mã sau đây bị hỏng?
01: lớp công khai ClassWithThreadingProblem {
02: int nextId;
03:
04: public int takeNextId () {
05:
trả về nextId ++;
06:}
07:}
Đây là mô tả về một bài kiểm tra sẽ chứng minh mã bị hỏng:
• Nhớ giá trị hiện tại của nextId .
• Tạo hai luồng, cả hai đều gọi takeNextId () một lần.
• Xác minh rằng nextId nhiều hơn hai so với những gì chúng ta đã bắt đầu.
• Chạy điều này cho đến khi chúng tôi chứng minh rằng nextId chỉ được tăng lên một
của hai.
Liệt kê A-2 cho thấy một bài kiểm tra như vậy:
5. Không có cái gọi là bữa trưa miễn phí.
Liệt kê A-2
ClassWithThreadingProblemTest.java
01: gói ví dụ;
02:
03: nhập static org.junit.Assert.fail;
04:
05: nhập khẩu org.junit.Test;
06:
07: lớp công khai ClassWithThreadingProblemTest {
08: @Test
09: public void twoThreadsShouldFailEventently () ném Exception {
10:
cuối cùng ClassWithThreadingProblem classWithThreadingProblem
= new ClassWithThreadingProblem ();
11:
www.it-ebooks.info

Trang 371
340
Phụ lục A: Đồng thời II
12:
Runnable runnable = new Runnable () {
13:
public void run () {
14:
classWithThreadingProblem.takeNextId ();
15:
}
16:
};
17:
18:
for (int i = 0; i <50000; ++ i) {
19:
int startId = classWithThreadingProblem.lastId;
20:
int dự kiếnResult = 2 + startId;
21:
22:
Thread t1 = new Thread (runnable);
23:
Thread t2 = new Thread (runnable);
24:
t1.start ();
25:
t2.start ();
26:
t1.join ();
27:
t2.join ();
28:
29:
int endId = classWithThreadingProblem.lastId;
30:
31:
if (endId! = mong đợiResult)
32:
trở về;
33:
}
34:
35:
fail ("Đáng lẽ ra một vấn đề về luồng nhưng nó đã không xảy ra.");
36:}
37:}
Hàng
Sự miêu tả
10
Tạo một phiên bản ClassWithThreadingProblem duy nhất . Lưu ý, chúng ta phải sử dụng
từ khóa cuối cùng bởi vì chúng tôi sử dụng nó bên dưới trong một lớp bên trong ẩn danh.
12–16
Tạo một lớp bên trong ẩn danh sử dụng phiên bản duy nhất của
ClassWithThreadingProblem .
18
Chạy mã này "đủ" lần để chứng minh rằng mã không thành công, nhưng không
nhiều đến mức bài kiểm tra “mất quá nhiều thời gian”. Đây là một hành động cân bằng; chúng tôi không
muốn chờ đợi quá lâu để chứng tỏ sự thất bại. Chọn số này thật khó—
mặc dù sau này chúng ta sẽ thấy rằng chúng ta có thể giảm đáng kể con số này.
19
Ghi nhớ giá trị bắt đầu. Thử nghiệm này đang cố gắng chứng minh rằng mã trong
ClassWithThreadingProblem bị hỏng. Nếu thử nghiệm này vượt qua, nó đã chứng minh rằng
mã đã bị hỏng. Nếu kiểm tra này không thành công, kiểm tra không thể chứng minh rằng mã
bị phá vỡ.
20
Chúng tôi hy vọng giá trị cuối cùng sẽ nhiều hơn giá trị hiện tại hai lần.
22–23
Tạo hai luồng, cả hai đều sử dụng đối tượng mà chúng ta đã tạo ở dòng 12–16.
Điều này cung cấp cho chúng tôi tiềm năng của hai luồng đang cố gắng sử dụng phiên bản duy nhất của chúng tôi
của ClassWithThreadingProblem và gây nhiễu lẫn nhau.
Liệt kê A-2 (tiếp theo)
ClassWithThreadingProblemTest.java
www.it-ebooks.info

Trang 372
341
Kiểm tra mã đa luồng
Kiểm tra này chắc chắn thiết lập các điều kiện cho sự cố cập nhật đồng thời. Tuy nhiên,
vấn đề xảy ra không thường xuyên đến nỗi phần lớn các lần kiểm tra này sẽ không phát hiện ra nó.
Thật vậy, để thực sự phát hiện ra vấn đề, chúng ta cần đặt số lần lặp lên hơn một
triệu. Ngay cả sau đó, trong mười lần thực thi với số vòng lặp là 1.000.000, sự cố đã xảy ra
chỉ một lần. Điều đó có nghĩa là chúng ta có thể nên đặt số lần lặp lại nhiều hơn một hun-
nạo vét triệu để có được những hỏng hóc đáng tin cậy. Chúng ta chuẩn bị đợi bao lâu?
Ngay cả khi chúng tôi đã điều chỉnh thử nghiệm để nhận được các lỗi đáng tin cậy trên một máy, chúng tôi có thể sẽ
để sửa lại bài kiểm tra với các giá trị khác nhau để chứng minh lỗi trên một máy khác,
hệ điều hành hoặc phiên bản của JVM.
Và đây là một vấn đề đơn giản . Nếu chúng tôi không thể chứng minh mã bị hỏng một cách dễ dàng với điều này
vấn đề, làm thế nào chúng ta sẽ phát hiện ra các vấn đề thực sự phức tạp?
Vậy chúng ta có thể thực hiện những cách tiếp cận nào để chứng minh sự thất bại đơn giản này? Và, quan trọng hơn-
nhanh chóng, làm thế nào chúng ta có thể viết các bài kiểm tra sẽ chứng minh các lỗi trong mã phức tạp hơn? Làm
sao
liệu chúng ta có thể phát hiện ra mã của chúng ta có bị lỗi hay không khi chúng ta không biết phải tìm ở đâu?
Dưới đây là một vài ý tưởng:
• Thử nghiệm Monte Carlo. Làm cho các bài kiểm tra linh hoạt để chúng có thể được điều chỉnh. Sau đó chạy thử
nghiệm
trở lên — nói trên máy chủ thử nghiệm — thay đổi ngẫu nhiên các giá trị điều chỉnh. Nếu các bài kiểm tra bao giờ
không thành công, mã bị hỏng. Đảm bảo bắt đầu viết các bài kiểm tra đó sớm để liên tục
máy chủ tích hợp sẽ sớm chạy chúng. Nhân tiện, hãy đảm bảo rằng bạn ghi nhật ký cẩn thận
các điều kiện mà thử nghiệm không thành công.
• Chạy thử nghiệm trên mọi nền tảng triển khai mục tiêu. Nhiều lần. Tiếp tục-
ously. Các thử nghiệm chạy càng lâu mà không bị lỗi, thì càng có nhiều khả năng
- Mã sản xuất đúng hoặc
- Các bài kiểm tra không đủ để chỉ ra các vấn đề.
• Chạy thử nghiệm trên một máy có tải trọng khác nhau. Nếu bạn có thể mô phỏng tải gần với
môi trường sản xuất, làm như vậy.
24–25
Làm cho hai chủ đề của chúng tôi đủ điều kiện để chạy.
26–27
Chờ cho cả hai chủ đề kết thúc trước khi chúng tôi kiểm tra kết quả.
29
Ghi lại giá trị cuối cùng thực tế.
31–32
EndId của chúng ta có khác với những gì chúng ta mong đợi không? Nếu vậy, hãy quay lại kết thúc bài kiểm tra—
chúng tôi đã chứng minh rằng mã bị hỏng. Nếu không, hãy thử lại.
35
Nếu chúng tôi đến đây, thử nghiệm của chúng tôi không thể chứng minh mã sản xuất là bro-
ken trong một khoảng thời gian "hợp lý"; mã của chúng tôi đã bị lỗi. Hoặc mã
không bị hỏng hoặc chúng tôi đã không chạy đủ số lần lặp lại để đạt được điều kiện lỗi
xảy ra.
Hàng
Sự miêu tả
www.it-ebooks.info

Trang 373
342
Phụ lục A: Đồng thời II
Tuy nhiên, ngay cả khi bạn làm tất cả những điều này, bạn vẫn không có cơ hội tốt để tìm thấy-
vấn đề về luồng với mã của bạn. Những vấn đề ngấm ngầm nhất là những vấn đề
có mặt cắt ngang nhỏ đến mức chúng chỉ xảy ra một lần trong một tỷ cơ hội. Như là
các vấn đề là nỗi kinh hoàng của các hệ thống phức tạp.
Hỗ trợ công cụ để kiểm tra mã dựa trên chuỗi
IBM đã tạo ra một công cụ có tên là ConTest. 6 Nó thiết lập các lớp học để làm cho nó có nhiều khả năng
mã không an toàn luồng không thành công.
Chúng tôi không có bất kỳ mối quan hệ trực tiếp nào với IBM hoặc nhóm phát triển ConTest.
Một đồng nghiệp của chúng tôi đã chỉ chúng tôi đến nó. Chúng tôi nhận thấy sự cải thiện đáng kể trong khả năng
tìm kiếm
vấn đề phân luồng sau một vài phút sử dụng nó.
Dưới đây là phác thảo về cách sử dụng ConTest:
• Viết các bài kiểm tra và mã sản xuất, đảm bảo có các bài kiểm tra được thiết kế đặc biệt để
mô phỏng nhiều người dùng dưới các tải khác nhau, như đã đề cập ở trên.
• Kiểm tra thiết bị và mã sản xuất với ConTest.
• Chạy các bài kiểm tra.
Khi chúng tôi sửa đổi mã bằng ConTest, tỷ lệ thành công của chúng tôi đã đi từ khoảng một lần thất bại-
urê trong mười triệu lần lặp đến khoảng một lần thất bại trong ba mươi lần lặp lại. Đây là các giá trị vòng lặp
trong một số lần chạy thử nghiệm sau khi đo đạc: 13, 23, 0, 54, 16, 14, 6, 69, 107, 49, 2. Vì vậy
rõ ràng các lớp công cụ đã thất bại trước đó nhiều và với độ tin cậy cao hơn nhiều.
Phần kết luận
Chương này là một cuộc hành trình ngắn ngủi qua lãnh thổ rộng lớn và nguy hiểm của
lập trình đồng thời. Chúng tôi hầu như không bị trầy xước bề mặt. Sự nhấn mạnh của chúng tôi ở đây là phân biệt
ciplines để giúp giữ sạch mã đồng thời, nhưng bạn nên tìm hiểu thêm nếu
bạn sẽ viết các hệ thống đồng thời. Chúng tôi khuyên bạn nên bắt đầu với Doug Lea's
cuốn sách tuyệt vời Lập trình đồng thời trong Java: Nguyên tắc và Mẫu thiết kế.  7
Trong chương này, chúng ta đã nói về cập nhật đồng thời và các nguyên tắc của hệ thống sạch
thời gian và khóa có thể ngăn chặn nó. Chúng tôi đã nói về cách các chuỗi có thể nâng cao
thông lượng của hệ thống liên kết I / O và cho thấy các kỹ thuật sạch để đạt được
cải tiến. Chúng tôi đã nói về bế tắc và các kỷ luật để ngăn chặn nó một cách sạch sẽ
6. http://www.haifa.ibm.com/projects/verification/contest/index.html
7. Xem [Lea99] tr. 191.
www.it-ebooks.info

Trang 374
343
Hướng dẫn: Ví dụ về mã đầy đủ
đường. Cuối cùng, chúng tôi đã nói về các chiến lược để giải quyết các vấn đề đồng thời bằng cách
ma cua ban.
Hướng dẫn: Ví dụ về mã đầy đủ
Máy khách / Máy chủ chưa đọc
Liệt kê A-3
Server.java
gói com.objectmentor.clientserver.nonthreaded;
nhập java.io.IOException;
nhập java.net.ServerSocket;
nhập java.net.Socket;
nhập java.net.SocketException;
nhập thông thường.MessageUtils;
public class Server triển khai Runnable {
ServerSocket serverSocket;
boolean dễ bay hơi keepProcessing = true;
Máy chủ công cộng (int port, int millisecondsTimeout) ném IOException {
serverSocket = new ServerSocket (cổng);
serverSocket.setSoTimeout (mili giâyTimeout);
}
public void run () {
System.out.printf ("Máy chủ Khởi động \ n");
trong khi (keepProcessing) {
thử {
System.out.printf ("chấp nhận máy khách \ n");
Socket socket = serverSocket.accept ();
System.out.printf ("có máy khách \ n");
quy trình (ổ cắm);
} catch (Ngoại lệ e) {
xử lý (e);
}
}
}
xử lý void riêng (Ngoại lệ e) {
if (! (e instanceof SocketException)) {
e.printStackTrace ();
}
}
public void stopProcessing () {
keepProcessing = false;
closeIgnoringException (serverSocket);
}
www.it-ebooks.info

Trang 375
344
Phụ lục A: Đồng thời II
quá trình void (Socket socket) {
if (socket == null)
trở về;
thử {
System.out.printf ("Máy chủ: nhận tin nhắn \ n");
String message = MessageUtils.getMessage (socket);
System.out.printf ("Máy chủ: nhận được thông báo:% s \ n", thông báo);
Thread.sleep (1000);
System.out.printf ("Máy chủ: gửi trả lời:% s \ n", tin nhắn);
MessageUtils.sendMessage (socket, "Đã xử lý:" + tin nhắn);
System.out.printf ("Máy chủ: đã gửi \ n");
closeIgnoringException (ổ cắm);
} catch (Ngoại lệ e) {
e.printStackTrace ();
}
}
private void closeIgnoringException (Socket ổ cắm) {
if (socket! = null)
thử {
socket.close ();
} bắt (bỏ qua IOException) {
}
}
private void closeIgnoringException (ServerSocket serverSocket) {
if (serverSocket! = null)
thử {
serverSocket.close ();
} bắt (bỏ qua IOException) {
}
}
}
Liệt kê A-4
ClientTest.java
gói com.objectmentor.clientserver.nonthreaded;
nhập java.io.IOException;
nhập java.net.ServerSocket;
nhập java.net.Socket;
nhập java.net.SocketException;
nhập thông thường.MessageUtils;
public class Server triển khai Runnable {
ServerSocket serverSocket;
boolean dễ bay hơi keepProcessing = true;
Liệt kê A-3 (tiếp theo)
Server.java
www.it-ebooks.info

Trang 376
345
Hướng dẫn: Ví dụ về mã đầy đủ
Máy chủ công cộng (int port, int millisecondsTimeout) ném IOException {
serverSocket = new ServerSocket (cổng);
serverSocket.setSoTimeout (mili giâyTimeout);
}
public void run () {
System.out.printf ("Máy chủ Khởi động \ n");
trong khi (keepProcessing) {
thử {
System.out.printf ("chấp nhận máy khách \ n");
Socket socket = serverSocket.accept ();
System.out.printf ("có máy khách \ n");
quy trình (ổ cắm);
} catch (Ngoại lệ e) {
xử lý (e);
}
}
}
xử lý void riêng (Ngoại lệ e) {
if (! (e instanceof SocketException)) {
e.printStackTrace ();
}
}
public void stopProcessing () {
keepProcessing = false;
closeIgnoringException (serverSocket);
}
quá trình void (Socket socket) {
if (socket == null)
trở về;
thử {
System.out.printf ("Máy chủ: nhận tin nhắn \ n");
String message = MessageUtils.getMessage (socket);
System.out.printf ("Máy chủ: nhận được thông báo:% s \ n", thông báo);
Thread.sleep (1000);
System.out.printf ("Máy chủ: gửi trả lời:% s \ n", tin nhắn);
MessageUtils.sendMessage (socket, "Đã xử lý:" + tin nhắn);
System.out.printf ("Máy chủ: đã gửi \ n");
closeIgnoringException (ổ cắm);
} catch (Ngoại lệ e) {
e.printStackTrace ();
}
}
private void closeIgnoringException (Socket ổ cắm) {
if (socket! = null)
thử {
socket.close ();
Liệt kê A-4 (tiếp theo)
ClientTest.java
www.it-ebooks.info

Trang 377
346
Phụ lục A: Đồng thời II
Máy khách / Máy chủ sử dụng chuỗi
Việc thay đổi máy chủ để sử dụng các chuỗi chỉ yêu cầu thay đổi thông báo quy trình (mới
dòng được nhấn mạnh để nổi bật):
quá trình void (ổ cắm Socket cuối cùng) {
if (socket == null)
trở về;
Runnable clientHandler = new Runnable () {
public void run () {
} bắt (bỏ qua IOException) {
}
}
private void closeIgnoringException (ServerSocket serverSocket) {
if (serverSocket! = null)
thử {
serverSocket.close ();
} bắt (bỏ qua IOException) {
}
}
}
Liệt kê A-5
MessageUtils.java
gói chung;
nhập java.io.IOException;
nhập java.io.InputStream;
nhập java.io.ObjectInputStream;
nhập java.io.ObjectOutputStream;
nhập java.io.OutputStream;
nhập java.net.Socket;
lớp công khai MessageUtils {
public static void sendMessage (Socket socket, String message)
ném IOException {
Luồng OutputStream = socket.getOutputStream ();
ObjectOutputStream oos = new ObjectOutputStream (luồng);
oos.writeUTF (tin nhắn);
oos.flush ();
}
public static String getMessage (Socket socket) ném IOException {
Dòng InputStream = socket.getInputStream ();
ObjectInputStream ois = new ObjectInputStream (luồng);
trả về ois.readUTF ();
}
}
Liệt kê A-4 (tiếp theo)
ClientTest.java
www.it-ebooks.info

Trang 378
347
Hướng dẫn: Ví dụ về mã đầy đủ
thử {
System.out.printf ("Máy chủ: nhận tin nhắn \ n");
String message = MessageUtils.getMessage (socket);
System.out.printf ("Máy chủ: nhận được thông báo:% s \ n", thông báo);
Thread.sleep (1000);
System.out.printf ("Máy chủ: gửi trả lời:% s \ n", tin nhắn);
MessageUtils.sendMessage (socket, "Đã xử lý:" + tin nhắn);
System.out.printf ("Máy chủ: đã gửi \ n");
closeIgnoringException (ổ cắm);
} catch (Ngoại lệ e) {
e.printStackTrace ();
}
}
};
Thread clientConnection = new Thread (clientHandler);
clientConnection.start ();
}
www.it-ebooks.info

Trang 379
Trang này cố ý để trống
www.it-ebooks.info

Trang 380
349
Phụ lục B
org.jfree.date.SerialDate
Danh sách B-1
SerialDate.Java
1 / * ================================================= =========================
2 * JCommon: thư viện lớp đa năng miễn phí cho nền tảng Java (tm)
3 * ================================================ ========================
4*
Bản quyền 5 * (C) 2000-2005, bởi Object Refinery Limited và những người đóng góp.
6*
7 * Thông tin dự án: http://www.jfree.org/jcommon/index.html
số 8 *
9 * Thư viện này là phần mềm miễn phí; bạn có thể phân phối lại nó và / hoặc sửa đổi nó
10 * theo các điều khoản của Giấy phép Công cộng Ít hơn GNU do
11 * Tổ chức Phần mềm Tự do; phiên bản 2.1 của Giấy phép hoặc
12 * (theo tùy chọn của bạn) bất kỳ phiên bản nào mới hơn.
13 *
14 * Thư viện này được phân phối với hy vọng rằng nó sẽ hữu ích, nhưng
15 * KHÔNG CÓ BẤT KỲ BẢO HÀNH NÀO; thậm chí không có bảo đảm ngụ ý về KHẢ NĂNG LẠNH
16 * hoặc PHÙ HỢP VỚI MỤC ĐÍCH CỤ THỂ. Xem GNU Lesser General Public
17 * Giấy phép để biết thêm chi tiết.
18 *
19 * Bạn nên nhận được một bản sao của GNU Lesser General Public
20 * Giấy phép cùng với thư viện này; nếu không, hãy viết thư cho Phần mềm Miễn phí
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22 * Hoa Kỳ.
23 *
24 * [Java là nhãn hiệu hoặc nhãn hiệu đã đăng ký của Sun Microsystems, Inc.
25 * tại Hoa Kỳ và các quốc gia khác.]
26 *
27 * ---------------
28 * SerialDate.java
29 * ---------------
30 * (C) Bản quyền 2001-2005, bởi Object Refinery Limited.
31 *
32 * Tác giả gốc: David Gilbert (cho Object Refinery Limited);
33 * (Các) người đóng góp: -;
34 *
35 * $ Id: SerialDate.java, v 1.7 2005/11/03 09:25:17 mungady Exp $
36 *
37 * Thay đổi (từ 11-10-2001)
www.it-ebooks.info

Trang 381
350
Phụ lục B: org.jfree.date.SerialDate
38 * --------------------------
39 * 11-10-2001: Tổ chức lại lớp học và chuyển nó sang gói mới
40 *
com.jrefinery.date (DG);
41 * 05-Nov-2001: Đã thêm phương thức getDescription () và loại bỏ NotableDate
42 *
lớp (DG);
43 * 12-11-2001: IBD yêu cầu phương thức setDescription (), bây giờ là NotableDate
44 *
lớp học đã biến mất (DG); Đã thay đổi getPreviousDayOfWeek (),
45 *
getFollowingDayOfWeek () và getNearestDayOfWeek () để sửa
46 *
bọ (DG);
47 * 05-12-2001: Sửa lỗi trong lớp SpreadsheetDate (DG);
48 * 29 tháng 5 năm 2002: Chuyển các hằng số tháng vào một giao diện riêng
49 *
(Kết quả tháng) (DG);
50 * 27-8-2002: Sửa lỗi trong phương thức addMonths (), nhờ N ??? levka Petr (DG);
51 * 03-10-2002: Sửa lỗi được Checkstyle (DG) báo cáo;
52 * 13-Tháng Ba-2003: Thực hiện Serializable (DG);
53 * 29-05-2003: Sửa lỗi trong phương pháp addMonths (DG);
54 * 04-09-2003: Thực hiện So sánh. Đã cập nhật javadocs isInRange (DG);
55 * 05-01-2005: Sửa lỗi trong phương thức addYears () (1096282) (DG);
56 *
57 * /
58
59 gói org.jfree.date;
60
61 nhập java.io.Serializable;
62 nhập java.text.DateFormatSymbols;
63 nhập java.text.SimpleDateFormat;
64 nhập java.util.Calendar;
65 nhập java.util.GregorianCalendar;
66
67 / **
68 * Một lớp trừu tượng xác định các yêu cầu của chúng ta đối với thao tác ngày tháng,
69 * mà không buộc phải thực hiện cụ thể.
70 * <P>
71 * Yêu cầu 1: khớp ít nhất những gì Excel làm cho ngày tháng;
72 * Yêu cầu 2: class là bất biến;
73 * <P>
74 * Tại sao không chỉ sử dụng java.util.Date? Chúng tôi sẽ làm, khi nó có ý nghĩa. Đôi khi,
75 * java.util.Date có thể * quá * chính xác - nó đại diện cho một thời điểm tức thì,
76 * chính xác đến 1/1000 giây (với chính ngày tháng tùy thuộc vào
77 * múi giờ). Đôi khi chúng ta chỉ muốn đại diện cho một ngày cụ thể (ví dụ: 21
78 * tháng 1 năm 2015) mà không liên quan đến bản thân về thời gian trong ngày, hoặc
Múi giờ 79 * hoặc bất kỳ thứ gì khác. Đó là những gì chúng tôi đã xác định SerialDate cho.
80 * <P>
81 * Bạn có thể gọi getInstance () để lấy một lớp con cụ thể của SerialDate,
82 * mà không cần lo lắng về việc thực hiện chính xác.
83 *
84 * @author David Gilbert
85 * /
86 công khai lớp trừu tượng SerialDate thực hiện Có thể so sánh,
87
Có thể nối tiếp,
88
MonthConstants {
89
90 / ** Để tuần tự hóa. * /
91 private static cuối cùng dài serialVersionUID = -293716040467423637L;
92
93 / ** Các ký hiệu định dạng ngày. * /
94 công khai tĩnh cuối cùng DateFormatSymbols
95
DATE_FORMAT_SYMBOLS = new SimpleDateFormat (). GetDateFormatSymbols ();
96
97 / ** Số sê-ri của ngày 1 tháng 1 năm 1900. * /
98 public static final int SERIAL_LOWER_BOUND = 2;
99
100 / ** Số sê-ri cho ngày 31 tháng 12 năm 9999. * /
101 public static final int SERIAL_UPPER_BOUND = 2958465;
102
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 382
351
Phụ lục B: org.jfree.date.SerialDate
103 / ** Giá trị năm thấp nhất được định dạng ngày này hỗ trợ. * /
104 public static final int MINIMUM_YEAR_SUPPORTED = 1900;
105
106 / ** Giá trị năm cao nhất được định dạng ngày này hỗ trợ. * /
107 công khai tĩnh cuối cùng int MAXIMUM_YEAR_SUPPORTED = 9999;
108
109 / ** Hằng số hữu ích cho Thứ Hai. Tương đương với java.util.Calendar.MONDAY. * /
110 public static final int MONDAY = Calendar.MONDAY;
111
112 / **
113 * Hằng số hữu ích cho Thứ Ba. Tương đương với java.util.Calendar.TUESDAY.
114 * /
115 public static final int TUESDAY = Calendar.TUESDAY;
116
117 / **
118 * Hằng số hữu ích cho Thứ Tư. Tương đương với
119 * java.util.Calendar. NGÀY THỨ TƯ.
120 * /
121 public static final int WEDNESDAY = Calendar.WEDNESDAY;
122
123 / **
124 * Hằng số hữu ích cho Thrusday. Tương đương với java.util.Calendar.THURSDAY.
125 * /
126 public static final int THURSDAY = Calendar.THURSDAY;
127
128 / ** Hằng số hữu ích cho Thứ Sáu. Tương đương với java.util.Calendar.FRIDAY. * /
129 public static final int FRIDAY = Calendar.FRIDAY;
130
131 / **
132 * Hằng số hữu ích cho Thứ Bảy. Tương đương với java.util.Calendar.SATURDAY.
133 * /
134 public static final int SATURDAY = Calendar.SATURDAY;
135
136 / ** Hằng số hữu ích cho Chủ nhật. Tương đương với java.util.Calendar.SUNDAY. * /
137 public static final int SUNDAY = Calendar.SUNDAY;
138
139 / ** Số ngày trong mỗi tháng tính theo năm không nhuận. * /
140 static final int [] LAST_DAY_OF_MONTH =
141
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
142
143 / ** Số ngày trong một năm (không nhuận) tính đến cuối mỗi tháng. * /
144 static cuối cùng int [] AGGREGATE_DAYS_TO_END_OF_MONTH =
145
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
146
147 / ** Số ngày trong năm tính đến cuối tháng trước đó. * /
148 static cuối cùng int [] AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH =
149
{0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
150
151 / ** Số ngày trong năm nhuận tính đến cuối mỗi tháng. * /
152 giá trị cuối cùng tĩnh [] LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_MONTH =
153
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
154
155 / **
156 * Số ngày trong một năm nhuận tính đến cuối tháng trước đó.
157 * /
158 static final int []
159
LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH =
160
{0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
161
162 / ** Một hằng số hữu ích để chỉ tuần đầu tiên trong tháng. * /
163 public static final int FIRST_WEEK_IN_MONTH = 1;
164
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 383
352
Phụ lục B: org.jfree.date.SerialDate
165 / ** Một hằng số hữu ích để chỉ tuần thứ hai trong tháng. * /
166 public static final int SECOND_WEEK_IN_MONTH = 2;
167
168 / ** Một hằng số hữu ích để chỉ tuần thứ ba trong tháng. * /
169 public static final int THIRD_WEEK_IN_MONTH = 3;
170
171 / ** Một hằng số hữu ích để chỉ tuần thứ tư trong tháng. * /
172 public static final int FOURTH_WEEK_IN_MONTH = 4;
173
174 / ** Một hằng số hữu ích để chỉ tuần cuối cùng trong tháng. * /
175 public static final int LAST_WEEK_IN_MONTH = 0;
176
177 / ** Hằng số phạm vi hữu ích. * /
178 public static final int INCLUDE_NONE = 0;
179
180 / ** Hằng số phạm vi hữu ích. * /
181 public static final int INCLUDE_FIRST = 1;
182
183 / ** Hằng số phạm vi hữu ích. * /
184 public static final int INCLUDE_SECOND = 2;
185
186 / ** Hằng số phạm vi hữu ích. * /
187 public static final int INCLUDE_BOTH = 3;
188
189 / **
190 * Hằng số hữu ích để chỉ định một ngày trong tuần so với một ngày cố định
191 * ngày.
192 * /
193 public static final int PRECEDING = -1;
194
195 / **
196 * Hằng số hữu ích để chỉ định một ngày trong tuần so với một ngày cố định
197 * ngày.
198 * /
199 public static final int NEAREST = 0;
200
201 / **
202 * Hằng số hữu ích để chỉ định một ngày trong tuần so với một ngày cố định
203 * ngày.
204 * /
205 public static final int FOLLOWING = 1;
206
207 / ** Mô tả cho ngày tháng. * /
208 private String mô tả;
209
210 / **
211 * Hàm tạo mặc định.
212 * /
213 SerialDate được bảo vệ () {
214}
215
216 / **
217 * Trả về <code> true </code> nếu mã số nguyên được cung cấp đại diện cho
218 * hợp lệ trong ngày trong tuần và <code> false </code> nếu không.
219 *
Mã 220 * @param mã đang được kiểm tra tính hợp lệ.
221 *
222 * @return <code> true </code> nếu mã số nguyên được cung cấp đại diện cho
223 *
ngày-trong-tuần hợp lệ và <code> false </code> nếu không.
224 * /
225 public static boolean isValidWeekdayCode (mã int cuối cùng) {
226
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 384
353
Phụ lục B: org.jfree.date.SerialDate
227
chuyển đổi (mã) {
228
trường hợp CHỦ NHẬT:
229
trường hợp THỨ HAI:
230
trường hợp THỨ TƯ:
231
trường hợp THỨ TƯ:
232
trường hợp THỨ NĂM:
233
trường hợp THỨ SÁU:
234
trường hợp THỨ BẢY:
235
trả về true;
236
mặc định:
237
trả về sai;
238
}
239
240}
241
242 / **
243 * Chuyển đổi chuỗi được cung cấp thành một ngày trong tuần.
244 *
245 * @param sa chuỗi đại diện cho ngày trong tuần.
246 *
247 * @return <code> -1 </code> nếu chuỗi không thể chuyển đổi, ngày của
248 *
tuần khác.
249 * /
250 public static int stringToWeekdayCode (Chuỗi s) {
251
252
cuối cùng chuỗi [] shortWeekdayNames
253
= DATE_FORMAT_SYMBOLS.getShortWeekdays ();
254
cuối cùng String [] weekDayNames = DATE_FORMAT_SYMBOLS.getWeekdays ();
255
256
int kết quả = -1;
257
s = s.trim ();
258
for (int i = 0; i <weekDayNames.length; i ++) {
259
if (s.equals (shortWeekdayNames [i])) {
260
kết quả = i;
261
phá vỡ;
262
}
263
if (s.equals (weekDayNames [i])) {
264
kết quả = i;
265
phá vỡ;
266
}
267
}
268
trả về kết quả;
269
270}
271
272 / **
273 * Trả về một chuỗi đại diện cho ngày trong tuần đã cung cấp.
274 * <P>
275 * Cần tìm cách tiếp cận tốt hơn.
276 *
277 * @param ngày trong tuần ngày trong tuần.
278 *
279 * @ quay lại một chuỗi đại diện cho ngày trong tuần được cung cấp.
280 * /
281 public static String trong tuầnCodeToString (cuối cùng trong ngày trong tuần) {
282
283
cuối cùng chuỗi [] các ngày trong tuần = DATE_FORMAT_SYMBOLS.getWeekdays ();
284
trả về các ngày trong tuần [ngày trong tuần];
285
286}
287
288 / **
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 385
354
Phụ lục B: org.jfree.date.SerialDate
289 * Trả về một mảng tên tháng.
290 *
291 * @ quay trở lại một mảng tên tháng.
292 * /
293 public static String [] getMonths () {
294
295
trả về getMonths (sai);
296
297}
298
299 / **
300 * Trả về một mảng tên tháng.
301 *
302 * @param đã rút ngắn một cờ chỉ ra rằng tên tháng được rút ngắn phải
303 *
được trả lại.
304 *
305 * @ quay trở lại một mảng tên tháng.
306 * /
307 public static String [] getMonths (rút gọn boolean cuối cùng) {
308
309
if (rút gọn) {
310
trả về DATE_FORMAT_SYMBOLS.getShortMonths ();
311
}
312
khác {
313
trả về DATE_FORMAT_SYMBOLS.getMonths ();
314
}
315
316}
317
318 / **
319 * Trả về true nếu mã số nguyên được cung cấp đại diện cho một tháng hợp lệ.
320 *
Mã 321 * @param mã đang được kiểm tra tính hợp lệ.
322 *
323 * @return <code> true </code> nếu mã số nguyên được cung cấp đại diện cho
324 *
tháng hợp lệ.
325 * /
326 public static boolean isValidMonthCode (mã int cuối cùng) {
327
328
chuyển đổi (mã) {
329
trường hợp THÁNG 1:
330
trường hợp THÁNG 2:
331
trường hợp THÁNG 3:
332
trường hợp THÁNG 4:
333
trường hợp CÓ THỂ:
334
trường hợp THÁNG 6:
335
trường hợp tháng 7:
336
trường hợp THÁNG 8:
337
trường hợp THÁNG 9:
338
trường hợp THÁNG 10:
339
trường hợp THÁNG 11:
340
trường hợp THÁNG 12:
341
trả về true;
342
mặc định:
343
trả về sai;
344
}
345
346}
347
348 / **
349 * Trả về quý cho tháng được chỉ định.
350 *
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 386
355
Phụ lục B: org.jfree.date.SerialDate
351 * @param mã mã tháng (1-12).
352 *
353 * @ quay trở lại quý của tháng đó.
354 * @throws java.lang.IllegalArgumentException
355 * /
356 public static int monthCodeToQuarter (mã int cuối cùng) {
357
358
chuyển đổi (mã) {
359
trường hợp THÁNG 1:
360
trường hợp THÁNG 2:
361
trường hợp THÁNG 3: trả về 1;
362
trường hợp THÁNG 4:
363
trường hợp CÓ THỂ:
364
case JUNE: return 2;
365
trường hợp tháng 7:
366
trường hợp THÁNG 8:
367
trường hợp THÁNG 9: trả về 3;
368
trường hợp THÁNG 10:
369
trường hợp THÁNG 11:
370
case THÁNG 12: trả về 4;
371
default: ném IllegalArgumentException mới (
372
"SerialDate.monthCodeToQuarter: mã tháng không hợp lệ.");
373
}
374
375}
376
377 / **
378 * Trả về một chuỗi đại diện cho tháng đã cung cấp.
379 * <P>
380 * Chuỗi được trả về là dạng dài của tên tháng được lấy từ
381 * ngôn ngữ mặc định.
382 *
383 * @param tháng trong tháng.
384 *
385 * @ quay lại một chuỗi đại diện cho tháng đã cung cấp.
386 * /
387 public static String monthCodeToString (cuối cùng của tháng) {
388
389
return monthCodeToString (tháng, false);
390
391}
392
393 / **
394 * Trả về một chuỗi đại diện cho tháng đã cung cấp.
395 * <P>
396 * Chuỗi trả về là dạng dài hoặc ngắn của tên tháng được lấy
397 * từ ngôn ngữ mặc định.
398 *
399 * @param tháng trong tháng.
400 * @param được rút ngắn nếu <code> true </code> trả về chữ viết tắt của
401 *
tháng.
402 *
403 * @ quay lại một chuỗi đại diện cho tháng đã cung cấp.
404 * @throws java.lang.IllegalArgumentException
405 * /
406 public static String monthCodeToString (tháng cuối cùng,
407
rút gọn boolean cuối cùng) {
408
409
// kiểm tra các đối số ...
410
if (! isValidMonthCode (tháng)) {
411
ném IllegalArgumentException mới (
412
"SerialDate.monthCodeToString: tháng nằm ngoài phạm vi hợp lệ.");
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 387
356
Phụ lục B: org.jfree.date.SerialDate
413
}
414
415
cuối cùng String [] tháng;
416
417
if (rút gọn) {
418
tháng = DATE_FORMAT_SYMBOLS.getShortMonths ();
419
}
420
khác {
421
tháng = DATE_FORMAT_SYMBOLS.getMonths ();
422
}
423
424
trả lại tháng [tháng - 1];
425
426}
427
428 / **
429 * Chuyển đổi một chuỗi thành mã tháng.
430 * <P>
431 * Phương thức này sẽ trả về một trong các hằng JANUARY, FEBRUARY, ...,
432 * THÁNG 12 tương ứng với chuỗi. Nếu chuỗi không
433 * được công nhận, phương thức này trả về -1.
434 *
435 * @param là chuỗi cần phân tích cú pháp.
436 *
437 * @return <code> -1 </code> nếu chuỗi không thể phân tích cú pháp, thì tháng của
438 *
năm khác.
439 * /
440 public static int stringToMonthCode (Chuỗi s) {
441
442
cuối cùng String [] shortMonthNames = DATE_FORMAT_SYMBOLS.getShortMonths ();
443
cuối cùng String [] monthNames = DATE_FORMAT_SYMBOLS.getMonths ();
444
445
int kết quả = -1;
446
s = s.trim ();
447
448
// trước tiên hãy thử phân tích cú pháp chuỗi dưới dạng số nguyên (1-12) ...
449
thử {
450
result = Integer.parseInt (s);
451
}
452
bắt (NumberFormatException e) {
453
// ngăn chặn
454
}
455
456
// bây giờ tìm kiếm tên tháng ...
457
if ((kết quả <1) || (kết quả> 12)) {
458
for (int i = 0; i <monthNames.length; i ++) {
459
if (s.equals (shortMonthNames [i])) {
460
kết quả = i + 1;
461
phá vỡ;
462
}
463
if (s.equals (monthNames [i])) {
464
kết quả = i + 1;
465
phá vỡ;
466
}
467
}
468
}
469
470
trả về kết quả;
471
472}
473
474 / **
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 388
357
Phụ lục B: org.jfree.date.SerialDate
475 * Trả về true nếu mã số nguyên được cung cấp đại diện cho một giá trị hợp lệ
476 * tuần trong tháng và ngược lại là false.
477 *
Mã 478 * @param mã đang được kiểm tra tính hợp lệ.
479 * @return <code> true </code> nếu mã số nguyên được cung cấp đại diện cho
480 *
hợp lệ tuần trong tháng.
481 * /
482 public static boolean isValidWeekInMonthCode (mã int cuối cùng) {
483
484
chuyển đổi (mã) {
485
trường hợp FIRST_WEEK_IN_MONTH:
486
trường hợp SECOND_WEEK_IN_MONTH:
487
trường hợp THIRD_WEEK_IN_MONTH:
488
trường hợp FOURTH_WEEK_IN_MONTH:
489
case LAST_WEEK_IN_MONTH: trả về true;
490
default: trả về false;
491
}
492
493}
494
495 / **
496 * Xác định xem năm được chỉ định có phải là năm nhuận hay không.
497 *
498 * @param yyyy năm (trong khoảng từ 1900 đến 9999).
499 *
500 * @return <code> true </code> nếu năm được chỉ định là năm nhuận.
501 * /
502 public static boolean isLeapYear (final int yyyy) {
503
504
if ((yyyy% 4)! = 0) {
505
trả về sai;
506
}
507
else if ((yyyy% 400) == 0) {
508
trả về true;
509
}
510
else if ((yyyy% 100) == 0) {
511
trả về sai;
512
}
513
khác {
514
trả về true;
515
}
516
517}
518
519 / **
520 * Trả về số năm nhuận từ năm 1900 đến năm cụ thể
521 * ĐỘC QUYỀN.
522 * <P>
523 * Lưu ý rằng năm 1900 không phải là năm nhuận.
524 *
525 * @param yyyy năm (trong khoảng 1900 đến 9999).
526 *
527 * @ quay lại số năm nhuận từ năm 1900 về năm cụ thể.
528 * /
529 public static int leapYearCount (final int yyyy) {
530
531
cuối cùng int leap4 = (yyyy - 1896) / 4;
532
end int leap100 = (yyyy - 1800) / 100;
533
cuối cùng int leap400 = (yyyy - 1600) / 400;
534
trở lại leapp4 - leap100 + leap400;
535
536}
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 389
358
Phụ lục B: org.jfree.date.SerialDate
537
538 / **
539 * Trả về số của ngày cuối cùng của tháng, có tính đến
540 * năm nhuận.
541 *
542 * @param tháng trong tháng.
543 * @param yyyy năm (trong khoảng 1900 đến 9999).
544 *
545 * @ quay lại số của ngày cuối cùng của tháng.
546 * /
547 public static int lastDayOfMonth (cuối cùng trong tháng, cuối cùng trong yyyy) {
548
549
kết quả int cuối cùng = LAST_DAY_OF_MONTH [tháng];
550
if (tháng! = FEBRUARY) {
551
trả về kết quả;
552
}
553
else if (isLeapYear (yyyy)) {
554
trả về kết quả + 1;
555
}
556
khác {
557
trả về kết quả;
558
}
559
560}
561
562 / **
563 * Tạo một ngày mới bằng cách thêm số ngày được chỉ định vào cơ sở
564 * ngày.
565 *
566 * @param days số ngày cần thêm (có thể là số âm).
567 * @param lấy ngày gốc.
568 *
569 * @ quay trở lại một ngày mới.
570 * /
571 public static SerialDate addDays (các ngày cuối cùng, cơ sở SerialDate cuối cùng) {
572
573
cuối cùng int serialDayNumber = base.toSerial () + ngày;
574
trả về SerialDate.createInstance (serialDayNumber);
575
576}
577
578 / **
579 * Tạo một ngày mới bằng cách thêm số tháng được chỉ định vào cơ sở
580 * ngày.
581 * <P>
582 * Nếu ngày gốc gần cuối tháng, ngày trên kết quả
583 * có thể được điều chỉnh một chút: 31 tháng 5 + 1 tháng = 30 tháng 6.
584 *
585 * @param tháng số tháng cần thêm (có thể là số âm).
586 * @param lấy ngày gốc.
587 *
588 * @ quay lại một ngày mới.
589 * /
590 public static SerialDate addMonths (các tháng cuối cùng,
591
căn cứ SerialDate cuối cùng) {
592
593
cuối cùng int yy = (12 * base.getYYYY () + base.getMonth () + tháng - 1)
594
/ 12;
595
cuối cùng int mm = (12 * base.getYYYY () + base.getMonth () + tháng - 1)
596
% 12 + 1;
597
cuối cùng int dd = Math.min (
598
base.getDayOfMonth (), SerialDate.lastDayOfMonth (mm, yy)
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 390
359
Phụ lục B: org.jfree.date.SerialDate
599
);
600
return SerialDate.createInstance (dd, mm, yy);
601
602}
603
604 / **
605 * Tạo một ngày mới bằng cách thêm số năm được chỉ định vào gốc
Ngày 606 *.
607 *
608 * @param năm số năm cần thêm (có thể là số âm).
609 * @param lấy ngày gốc.
610 *
611 * @return Một ngày mới.
612 * /
613 public static SerialDate addYears (các năm cuối cùng, cơ sở SerialDate cuối cùng) {
614
615
cuối cùng int baseY = base.getYYYY ();
616
cuối cùng int baseM = base.getMonth ();
617
cuối cùng int baseD = base.getDayOfMonth ();
618
619
int targetY cuối cùng = baseY + năm;
620
end int targetD = Math.min (
621
baseD, SerialDate.lastDayOfMonth (baseM, targetY)
622
);
623
624
return SerialDate.createInstance (targetD, baseM, targetY);
625
626}
627
628 / **
629 * Trả về ngày mới nhất rơi vào ngày trong tuần được chỉ định và
630 * là TRƯỚC ngày cơ sở.
631 *
632 * @param targetWeekday mã cho ngày-trong-tuần mục tiêu.
633 * @param lấy ngày gốc.
634 *
635 * @ quay lại ngày mới nhất rơi vào ngày cụ thể trong tuần và
636 *
là TRƯỚC ngày cơ sở.
637 * /
638 public static SerialDate getPreviousDayOfWeek (last int targetWeekday,
639
căn cứ SerialDate cuối cùng) {
640
641
// kiểm tra các đối số ...
642
if (! SerialDate.isValidWeekdayCode (targetWeekday)) {
643
ném IllegalArgumentException mới (
644
"Mã ngày trong tuần không hợp lệ."
645
);
646
}
647
648
// tìm ngày ...
649
int cuối cùng điều chỉnh;
650
cuối cùng int baseDOW = base.getDayOfWeek ();
651
if (baseDOW> targetWeekday) {
652
điều chỉnh = Math.min (0, targetWeekday - baseDOW);
653
}
654
khác {
655
điều chỉnh = -7 + Math.max (0, targetWeekday - baseDOW);
656
}
657
658
return SerialDate.addDays (điều chỉnh, căn cứ);
659
660}
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 391
360
Phụ lục B: org.jfree.date.SerialDate
661
662 / **
663 * Trả về ngày sớm nhất rơi vào ngày trong tuần được chỉ định
664 * và là SAU ngày cơ sở.
665 *
666 * @param targetWeekday mã cho ngày trong tuần mục tiêu.
667 * @param lấy ngày gốc.
668 *
669 * @ quay lại ngày sớm nhất rơi vào ngày trong tuần được chỉ định
670 *
và là SAU ngày cơ sở.
671 * /
672 public static SerialDate getFollowingDayOfWeek (last int targetWeekday,
673
căn cứ SerialDate cuối cùng) {
674
675
// kiểm tra các đối số ...
676
if (! SerialDate.isValidWeekdayCode (targetWeekday)) {
677
ném IllegalArgumentException mới (
678
"Mã ngày trong tuần không hợp lệ."
679
);
680
}
681
682
// tìm ngày ...
683
int cuối cùng điều chỉnh;
684
cuối cùng int baseDOW = base.getDayOfWeek ();
685
if (baseDOW> targetWeekday) {
686
điều chỉnh = 7 + Math.min (0, targetWeekday - baseDOW);
687
}
688
khác {
689
điều chỉnh = Math.max (0, targetWeekday - baseDOW);
690
}
691
692
return SerialDate.addDays (điều chỉnh, căn cứ);
693}
694
695 / **
696 * Trả về ngày rơi vào ngày trong tuần được chỉ định và là
697 * ĐÓNG CỬA đến ngày gốc.
698 *
699 * @param target XEM mã cho mục tiêu ngày trong tuần.
700 * @param lấy ngày gốc.
701 *
702 * @ quay lại ngày rơi vào ngày trong tuần được chỉ định và là
703 *
ĐÓNG CỬA đến ngày cơ sở.
704 * /
705 public static SerialDate getNearestDayOfWeek (last int targetDOW,
706
căn cứ SerialDate cuối cùng) {
707
708
// kiểm tra các đối số ...
709
if (! SerialDate.isValidWeekdayCode (targetDOW)) {
710
ném IllegalArgumentException mới (
711
"Mã ngày trong tuần không hợp lệ."
712
);
713
}
714
715
// tìm ngày ...
716
cuối cùng int baseDOW = base.getDayOfWeek ();
717
int điều chỉnh = -Math.abs (targetDOW - baseDOW);
718
if (điều chỉnh> = 4) {
719
điều chỉnh = 7 - điều chỉnh;
720
}
721
if (điều chỉnh <= -4) {
722
điều chỉnh = 7 + điều chỉnh;
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 392
361
Phụ lục B: org.jfree.date.SerialDate
723
}
724
return SerialDate.addDays (điều chỉnh, căn cứ);
725
726}
727
728 / **
729 * Chuyển ngày tới ngày cuối cùng của tháng.
730 *
731 * @param lấy ngày gốc.
732 *
733 * @ quay trở lại ngày nối tiếp mới.
734 * /
735 public SerialDate getEndOfCurrentMonth (cơ sở SerialDate cuối cùng) {
736
last int last = SerialDate.lastDayOfMonth (
737
base.getMonth (), base.getYYYY ()
738
);
739
return SerialDate.createInstance (last, base.getMonth (), base.getYYYY ());
740}
741
742 / **
743 * Trả về một chuỗi tương ứng với mã tuần trong tháng.
744 * <P>
745 * Cần tìm cách tiếp cận tốt hơn.
746 *
747 * @param đếm một mã số nguyên đại diện cho tuần trong tháng.
748 *
749 * @ quay lại một chuỗi tương ứng với mã tuần trong tháng.
750 * /
751 public static String weekInMonthToString (số int cuối cùng) {
752
753
chuyển đổi (đếm) {
754
case SerialDate.FIRST_WEEK_IN_MONTH: return "Đầu tiên";
755
case SerialDate.SECOND_WEEK_IN_MONTH: trả về "Thứ hai";
756
case SerialDate.THIRD_WEEK_IN_MONTH: return "Thứ ba";
757
case SerialDate.FOURTH_WEEK_IN_MONTH: return "Thứ tư";
758
case SerialDate.LAST_WEEK_IN_MONTH: return "Last";
759
mặc định :
760
return "SerialDate.weekInMonthToString (): mã không hợp lệ.";
761
}
762
763}
764
765 / **
766 * Trả về một chuỗi đại diện cho 'tương đối' được cung cấp.
767 * <P>
768 * Cần tìm cách tiếp cận tốt hơn.
769 *
770 * @param tương đối một hằng số đại diện cho 'tương đối'.
771 *
772 * @ quay lại một chuỗi đại diện cho 'họ hàng' được cung cấp.
773 * /
774 public static String relativeToString (cuối cùng int tương đối) {
775
776
chuyển đổi (tương đối) {
777
case SerialDate.PRECEDING: return "Preceding";
778
case SerialDate.NEAREST: return "Gần nhất";
779
case SerialDate.FOLLOWING: return "Đang theo dõi";
780
default: return "ERROR: Relative To String";
781
}
782
783}
784
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 393
362
Phụ lục B: org.jfree.date.SerialDate
785 / **
786 * Phương thức Factory trả về một phiên bản của một số lớp con cụ thể của
787 * {@link SerialDate}.
788 *
789 * @param ngày trong ngày (1-31).
790 * @param tháng trong tháng (1-12).
791 * @param yyyy năm (trong khoảng 1900 đến 9999).
792 *
793 * @return Một bản sao của {@link SerialDate}.
794 * /
795 public static SerialDate createInstance (ngày cuối cùng, tháng cuối cùng,
796
int yyyy cuối cùng) {
797
trả về SpreadsheetDate mới (ngày, tháng, yyyy);
798}
799
800 / **
801 * Phương thức Factory trả về một phiên bản của một số lớp con cụ thể của
802 * {@link SerialDate}.
803 *
804 * @param đánh số sê-ri trong ngày (1 tháng 1 năm 1900 = 2).
805 *
806 * @ quay lại một phiên bản của SerialDate.
807 * /
808 public static SerialDate createInstance (end int serial) {
809
trả về SpreadsheetDate mới (nối tiếp);
810}
811
812 / **
813 * Phương thức Factory trả về một thể hiện của lớp con của SerialDate.
814 *
815 * @param date Một đối tượng ngày trong Java.
816 *
817 * @ quay lại một phiên bản của SerialDate.
818 * /
819 public static SerialDate createInstance (ngày cuối cùng của java.util.Date) {
820
821
lịch GregorianCalendar cuối cùng = new GregorianCalendar ();
822
Calendar.setTime (ngày tháng);
823
trả về SpreadsheetDate mới (calendar.get (Calendar.DATE),
824
Calendar.get (Calendar.MONTH) + 1,
825
Calendar.get (Lịch.YEAR));
826
827}
828
829 / **
830 * Trả về số sê-ri cho ngày, trong đó ngày 1 tháng 1 năm 1900 = 2 (cái này
831 * gần như tương ứng với hệ thống đánh số được sử dụng trong Microsoft Excel cho
832 * Windows và Lotus 1-2-3).
833 *
834 * @ quay lại số sê-ri cho ngày.
835 * /
836 public abstract int toSerial ();
837
838 / **
839 * Trả về java.util.Date. Vì java.util.Date có độ chính xác cao hơn
840 * SerialDate, chúng ta cần xác định một quy ước cho 'thời gian trong ngày'.
841 *
842 * @ quay lại địa chỉ này là <code> java.util.Date </code>.
843 * /
844 public abstract java.util.Date toDate ();
845
846 / **
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 394
363
Phụ lục B: org.jfree.date.SerialDate
847 * Trả về mô tả ngày tháng.
848 *
849 * @ quay lại mô tả về ngày tháng.
850 * /
851 public String getDescription () {
852
trả lại this.description;
853}
854
855 / **
856 * Đặt mô tả cho ngày.
857 *
858 * @param mô tả mô tả mới cho ngày.
859 * /
860 public void setDescription (mô tả chuỗi cuối cùng) {
861
this.description = mô tả;
862}
863
864 / **
865 * Chuyển đổi ngày thành chuỗi.
866 *
867 * @ quay trở lại biểu diễn chuỗi của ngày.
868 * /
869 public String toString () {
870
trả về getDayOfMonth () + "-" + SerialDate.monthCodeToString (getMonth ())
871
+ "-" + getYYYY ();
872}
873
874 / **
875 * Trả về năm (giả sử phạm vi hợp lệ từ 1900 đến 9999).
876 *
877 * @ quay lại năm.
878 * /
879 public abstract int getYYYY ();
880
881 / **
882 * Trả về tháng (tháng 1 = 1, tháng 2 = 2, tháng 3 = 3).
883 *
884 * @ quay trở lại tháng trong năm.
885 * /
886 public abstract int getMonth ();
887
888 / **
889 * Trả về ngày trong tháng.
890 *
891 * @ quay trở lại ngày trong tháng.
892 * /
893 public abstract int getDayOfMonth ();
894
895 / **
896 * Trả về ngày trong tuần.
897 *
898 * @ quay trở lại ngày trong tuần.
899 * /
900 public abstract int getDayOfWeek ();
901
902 / **
903 * Trả về sự khác biệt (tính theo ngày) giữa ngày này và ngày được chỉ định
904 * ngày 'khác'.
905 * <P>
906 * Kết quả là dương tính nếu ngày này sau ngày 'kia' và
907 * âm nếu nó trước ngày 'khác'.
908 *
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 395
364
Phụ lục B: org.jfree.date.SerialDate
909 * @param khác ngày được so sánh với.
910 *
911 * @ trả lại sự khác biệt giữa ngày này và ngày kia.
912 * /
913 public abstract int so sánh (SerialDate other);
914
915 / **
916 * Trả về true nếu SerialDate này đại diện cho cùng một ngày với
917 * SerialDate được chỉ định.
918 *
919 * @param khác ngày được so sánh với.
920 *
921 * @return <code> true </code> nếu SerialDate này đại diện cho cùng một ngày với
922 *
SerialDate được chỉ định.
923 * /
924 public trừu tượng boolean isOn (SerialDate other);
925
926 / **
927 * Trả về true nếu SerialDate này biểu thị một ngày sớm hơn so với
928 * SerialDate được chỉ định.
929 *
930 * @param khác Ngày được so sánh với.
931 *
932 * @return <code> true </code> nếu SerialDate này đại diện cho một ngày trước đó
933 *
so với SerialDate được chỉ định.
934 * /
935 public trừu tượng boolean isBefore (SerialDate other);
936
937 / **
938 * Trả về true nếu SerialDate này đại diện cho cùng một ngày với
939 * SerialDate được chỉ định.
940 *
941 * @param khác ngày được so sánh với.
942 *
943 * @return <code> true <code> nếu SerialDate này đại diện cho cùng một ngày
944 *
như SerialDate được chỉ định.
945 * /
946 public abstract boolean isOnOrBefore (SerialDate other);
947
948 / **
949 * Trả về true nếu SerialDate này đại diện cho cùng một ngày với
950 * SerialDate được chỉ định.
951 *
952 * @param khác ngày được so sánh với.
953 *
954 * @return <code> true </code> nếu SerialDate này đại diện cho cùng một ngày
955 *
như SerialDate được chỉ định.
956 * /
957 public abstract boolean isAfter (SerialDate other);
958
959 / **
960 * Trả về true nếu SerialDate này đại diện cho cùng một ngày với
961 * SerialDate được chỉ định.
962 *
963 * @param khác ngày được so sánh với.
964 *
965 * @return <code> true </code> nếu SerialDate này đại diện cho cùng một ngày
966 *
như SerialDate được chỉ định.
967 * /
968 public abstract boolean isOnOrAfter (SerialDate other);
969
970 / **
971 * Trả về <code> true </code> nếu {@link SerialDate} này nằm trong
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 396
365
Phụ lục B: org.jfree.date.SerialDate
972 * phạm vi được chỉ định (BAO GỒM). Thứ tự ngày của d1 và d2 không phải là
973 * quan trọng.
974 *
975 * @param d1 một ngày ranh giới cho phạm vi.
976 * @param d2 ngày ranh giới khác cho phạm vi.
977 *
978 * @return Một boolean.
979 * /
980 public trừu tượng boolean isInRange (SerialDate d1, SerialDate d2);
981
982 / **
983 * Trả về <code> true </code> nếu {@link SerialDate} này nằm trong
984 * phạm vi được chỉ định (người gọi chỉ định có hay không các điểm cuối
985 * bao gồm). Thứ tự ngày của d1 và d2 không quan trọng.
986 *
987 * @param d1 một ngày ranh giới cho phạm vi.
988 * @param d2 ngày ranh giới khác cho phạm vi.
989 * @param bao gồm một mã kiểm soát việc bắt đầu và kết thúc hay không
990 *
ngày được bao gồm trong phạm vi.
991 *
992 * @return Một boolean.
993 * /
994 public abstract boolean isInRange (SerialDate d1, SerialDate d2,
995
int bao gồm);
996
997 / **
998 * Trả về ngày mới nhất rơi vào ngày trong tuần được chỉ định và
999 * là TRƯỚC ngày này.
1000 *
1001 * @param target XEM mã cho mục tiêu ngày trong tuần.
1002 *
1003 * @ quay lại ngày mới nhất rơi vào ngày cụ thể trong tuần và
1004 *
là TRƯỚC ngày này.
1005 * /
1006 public SerialDate getPreviousDayOfWeek (last int targetDOW) {
1007
trả về getPreviousDayOfWeek (targetDOW, this);
1008}
1009
1010 / **
1011 * Trả về ngày sớm nhất rơi vào ngày trong tuần được chỉ định
1012 * và là SAU ngày này.
1013 *
1014 * @param target XEM mã cho mục tiêu ngày trong tuần.
1015 *
1016 * @ quay lại ngày sớm nhất rơi vào ngày trong tuần được chỉ định
1017 *
và là SAU ngày này.
1018 * /
1019 public SerialDate getFollowingDayOfWeek (final int targetDOW) {
1020
trả về getFollowingDayOfWeek (targetDOW, this);
1021}
1022
1023 / **
1024 * Trả về ngày gần nhất rơi vào ngày trong tuần được chỉ định.
1025 *
1026 * @param target XEM mã cho mục tiêu ngày trong tuần.
1027 *
1028 * @ quay lại ngày gần nhất rơi vào ngày trong tuần được chỉ định.
1029 * /
1030 public SerialDate getNearestDayOfWeek (last int targetDOW) {
1031
trả về getNearestDayOfWeek (targetDOW, this);
1032}
1033
1034}
Liệt kê B-1 (tiếp theo)
SerialDate.Java
www.it-ebooks.info

Trang 397
366
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-2
SerialDateTest.java
1 / * ================================================= =========================
2 * JCommon: thư viện lớp đa năng miễn phí cho nền tảng Java (tm)
3 * ================================================ ========================
4*
Bản quyền 5 * (C) 2000-2005, bởi Object Refinery Limited và những người đóng góp.
6*
7 * Thông tin dự án: http://www.jfree.org/jcommon/index.html
số 8 *
9 * Thư viện này là phần mềm miễn phí; bạn có thể phân phối lại nó và / hoặc sửa đổi nó
10 * theo các điều khoản của Giấy phép Công cộng Ít hơn GNU do
11 * Tổ chức Phần mềm Tự do; phiên bản 2.1 của Giấy phép hoặc
12 * (theo tùy chọn của bạn) bất kỳ phiên bản nào mới hơn.
13 *
14 * Thư viện này được phân phối với hy vọng rằng nó sẽ hữu ích, nhưng
15 * KHÔNG CÓ BẤT KỲ BẢO HÀNH NÀO; thậm chí không có bảo đảm ngụ ý về KHẢ NĂNG LẠNH
16 * hoặc PHÙ HỢP VỚI MỤC ĐÍCH CỤ THỂ. Xem GNU Lesser General Public
17 * Giấy phép để biết thêm chi tiết.
18 *
19 * Bạn nên nhận được một bản sao của GNU Lesser General Public
20 * Giấy phép cùng với thư viện này; nếu không, hãy viết thư cho Phần mềm Miễn phí
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22 * Hoa Kỳ.
23 *
24 * [Java là nhãn hiệu hoặc nhãn hiệu đã đăng ký của Sun Microsystems, Inc.
25 * tại Hoa Kỳ và các quốc gia khác.]
26 *
27 * --------------------
28 * SerialDateTests.java
29 * --------------------
30 * (C) Bản quyền 2001-2005, bởi Object Refinery Limited.
31 *
32 * Tác giả gốc: David Gilbert (cho Object Refinery Limited);
33 * (Các) người đóng góp: -;
34 *
35 * $ Id: SerialDateTests.java, v 1.6 2005/11/16 15:58:40 taqua Exp $
36 *
37 * Thay đổi
38 * -------
39 * 15-11-2001: Phiên bản 1 (DG);
40 * 25-Tháng Sáu-2002: Loại bỏ nhập không cần thiết (DG);
41 * 24-10-2002: Sửa lỗi được Checkstyle (DG) báo cáo;
42 * 13-Tháng Ba-2003: Thêm thử nghiệm tuần tự hóa (DG);
43 * 05-01-2005: Thêm thử nghiệm cho báo cáo lỗi 1096282 (DG);
44 *
45 * /
46
47 gói org.jfree.date.junit;
48
49 nhập java.io.ByteArrayInputStream;
50 nhập java.io.ByteArrayOutputStream;
51 nhập java.io.ObjectInput;
52 nhập java.io.ObjectInputStream;
53 nhập java.io.ObjectOutput;
54 nhập java.io.ObjectOutputStream;
55
56 nhập junit.framework.Test;
57 nhập junit.framework.TestCase;
58 nhập junit.framework.TestSuite;
59
60 nhập khẩu org.jfree.date.MonthConstants;
61 nhập khẩu org.jfree.date.SerialDate;
62
www.it-ebooks.info

Trang 398
367
Phụ lục B: org.jfree.date.SerialDate
63 / **
64 * Một số bài kiểm tra JUnit cho lớp {@link SerialDate}.
65 * /
66 lớp công khai SerialDateTests mở rộng TestCase {
67
68 / ** Ngày đại diện cho ngày 9 tháng 11. * /
69 riêng SerialDate nov9Y2001;
70
71 / **
72 * Tạo một trường hợp thử nghiệm mới.
73 *
74 * @param đặt tên cho tên.
75 * /
76 công khai SerialDateTests (tên chuỗi cuối cùng) {
77
siêu (tên);
78}
79
80 / **
81 * Trả về một bộ thử nghiệm cho người chạy thử nghiệm JUnit.
82 *
83 * @return Bộ thử nghiệm.
84 * /
85 Bộ thử nghiệm tĩnh công cộng () {
86
trả về TestSuite mới (SerialDateTests.class);
87}
88
89 / **
90 * Đặt vấn đề.
91 * /
92 được bảo vệ void setUp () {
93
this.nov9Y2001 = SerialDate.createInstance (9, MonthConstants.NOVEMBER, 2001);
94}
95
96 / **
97 * 9 tháng 11 năm 2001 cộng với hai tháng sẽ là ngày 9 tháng 1 năm 2002.
98 * /
99 public void testAddMonthsTo9Nov2001 () {
100
cuối cùng SerialDate jan9Y2002 = SerialDate.addMonths (2, this.nov9Y2001);
101
cuối cùng câu trả lời SerialDate = SerialDate.createInstance (9, 1, 2002);
102
khẳng địnhEquals (đáp án, jan9Y2002);
103}
104
105 / **
106 * Một trường hợp thử nghiệm cho một lỗi được báo cáo, hiện đã được sửa.
107 * /
108 public void testAddMonthsTo5Oct2003 () {
109
cuối cùng SerialDate d1 = SerialDate.createInstance (5, MonthConstants.OCTOBER, 2003);
110
cuối cùng SerialDate d2 = SerialDate.addMonths (2, d1);
111
khẳng địnhEquals (d2, SerialDate.createInstance (5, MonthConstants.DECEMBER, 2003));
112}
113
114 / **
115 * Một trường hợp thử nghiệm cho một lỗi được báo cáo, hiện đã được sửa.
116 * /
117 public void testAddMonthsTo1Jan2003 () {
118
cuối cùng SerialDate d1 = SerialDate.createInstance (1, MonthConstants.JANUARY, 2003);
119
cuối cùng SerialDate d2 = SerialDate.addMonths (0, d1);
120
khẳng địnhEquals (d2, d1);
121}
122
123 / **
124 * Thứ Hai trước Thứ Sáu ngày 9 tháng 11 năm 2001 phải là ngày 5 tháng Mười Một.
Liệt kê B-2 (tiếp theo)
SerialDateTest.java
www.it-ebooks.info

Trang 399
368
Phụ lục B: org.jfree.date.SerialDate
125 * /
126 public void testMondayPrecedingFriday9Nov2001 () {
127
SerialDate mondayBefore = SerialDate.getPreviousDayOfWeek (
128
SerialDate.MONDAY, this.nov9Y2001
129
);
130
khẳng địnhEquals (5, mondayBefore.getDayOfMonth ());
131}
132
133 / **
134 * Thứ Hai tuần sau Thứ Sáu ngày 9 tháng 11 năm 2001 phải là ngày 12 tháng Mười Một.
135 * /
136 public void testMondayFollowingFriday9Nov2001 () {
137
SerialDate mondayAfter = SerialDate.getFollowingDayOfWeek (
138
SerialDate.MONDAY, this.nov9Y2001
139
);
140
khẳng địnhEquals (12, mondayAfter.getDayOfMonth ());
141}
142
143 / **
144 * Thứ Hai gần nhất Thứ Sáu ngày 9 tháng 11 năm 2001 phải là ngày 12 tháng Mười Một.
145 * /
146 public void testMondayNearestFriday9Nov2001 () {
147
SerialDate mondayNearest = SerialDate.getNearestDayOfWeek (
148
SerialDate.MONDAY, this.nov9Y2001
149
);
150
khẳng địnhEquals (12, mondayNearest.getDayOfMonth ());
151}
152
153 / **
154 * Thứ Hai gần nhất với ngày 22 tháng 1 năm 1970 rơi vào ngày 19.
155 * /
156 public void testMondayNearest22Jan1970 () {
157
SerialDate jan22Y1970 = SerialDate.createInstance (22, MonthConstants.JANUARY, 1970);
158
SerialDate mondayNearest = SerialDate.getNearestDayOfWeek (SerialDate.MONDAY, jan22Y1970);
159
khẳng địnhEquals (19, mondayNearest.getDayOfMonth ());
160}
161
162 / **
163 * Vấn đề là việc chuyển đổi ngày thành chuỗi trả về kết quả đúng. Trên thực tế, điều này
Kết quả 164 * phụ thuộc vào Ngôn ngữ, vì vậy thử nghiệm này cần được sửa đổi.
165 * /
166 public void testWeekdayCodeToString () {
167
168
kiểm tra chuỗi cuối cùng = SerialDate.weekdayCodeToString (SerialDate.SATURDAY);
169
khẳng địnhEquals ("Thứ bảy", kiểm tra);
170
171}
172
173 / **
174 * Kiểm tra việc chuyển đổi một chuỗi thành một ngày trong tuần. Lưu ý rằng thử nghiệm này sẽ không thành công nếu
175 * ngôn ngữ mặc định không sử dụng tên các ngày trong tuần bằng tiếng Anh ... hãy tạo ra một bài kiểm tra tốt hơn!
176 * /
177 public void testStringToWeekday () {
178
179
int weekday = SerialDate.stringToWeekdayCode ("Thứ Tư");
180
khẳng địnhEquals (SerialDate.WEDNESDAY, ngày trong tuần);
181
182
ngày trong tuần = SerialDate.stringToWeekdayCode ("Thứ Tư");
183
khẳng địnhEquals (SerialDate.WEDNESDAY, ngày trong tuần);
184
Liệt kê B-2 (tiếp theo)
SerialDateTest.java
www.it-ebooks.info

Trang 400
369
Phụ lục B: org.jfree.date.SerialDate
185
ngày trong tuần = SerialDate.stringToWeekdayCode ("Thứ tư");
186
khẳng địnhEquals (SerialDate.WEDNESDAY, ngày trong tuần);
187
188}
189
190 / **
191 * Kiểm tra việc chuyển đổi một chuỗi thành một tháng. Lưu ý rằng thử nghiệm này sẽ không thành công nếu
Ngôn ngữ mặc định 192 * không sử dụng tên tháng tiếng Anh ... hãy tạo ra một bài kiểm tra tốt hơn!
193 * /
194 public void testStringToMonthCode () {
195
196
int m = SerialDate.stringToMonthCode ("Tháng Giêng");
197
khẳng địnhEquals (MonthConstants.JANUARY, m);
198
199
m = SerialDate.stringToMonthCode ("Tháng 1");
200
khẳng địnhEquals (MonthConstants.JANUARY, m);
201
202
m = SerialDate.stringToMonthCode ("Tháng 1");
203
khẳng địnhEquals (MonthConstants.JANUARY, m);
204
205}
206
207 / **
208 * Kiểm tra việc chuyển đổi mã tháng thành một chuỗi.
209 * /
210 public void testMonthCodeToStringCode () {
211
212
kiểm tra chuỗi cuối cùng = SerialDate.monthCodeToString (MonthConstants.DECEMBER);
213
khẳng địnhEquals ("tháng 12", thử nghiệm);
214
215}
216
217 / **
218 * 1900 không phải là một năm nhuận.
219 * /
220 public void testIsNotLeapYear1900 () {
221
khẳng địnhTrue (! SerialDate.isLeapYear (1900));
222}
223
224 / **
225 * 2000 là một năm nhuận.
226 * /
227 public void testIsLeapYear2000 () {
228
khẳng địnhTrue (SerialDate.isLeapYear (2000));
229}
230
231 / **
232 * Số năm nhuận từ năm 1900 đến năm 1899 là 0.
233 * /
234 public void testLeapYearCount1899 () {
235
khẳng địnhEquals (SerialDate.leapYearCount (1899), 0);
236}
237
238 / **
239 * Số năm nhuận từ năm 1900 đến năm 1903 là 0.
240 * /
241 public void testLeapYearCount1903 () {
242
khẳng địnhEquals (SerialDate.leapYearCount (1903), 0);
243}
244
245 / **
246 * Số năm nhuận từ năm 1900 đến trước năm 1904 là 1.
247 * /
Liệt kê B-2 (tiếp theo)
SerialDateTest.java
www.it-ebooks.info

Trang 401
370
Phụ lục B: org.jfree.date.SerialDate
248 public void testLeapYearCount1904 () {
249
khẳng địnhEquals (SerialDate.leapYearCount (1904), 1);
250}
251
252 / **
253 * Số năm nhuận từ năm 1900 đến năm 1999 là 24 năm.
254 * /
255 public void testLeapYearCount1999 () {
256
khẳng địnhEquals (SerialDate.leapYearCount (1999), 24);
257}
258
259 / **
260 * Số năm nhuận từ năm 1900 đến năm 2000 là 25.
261 * /
262 public void testLeapYearCount2000 () {
263
khẳng địnhEquals (SerialDate.leapYearCount (2000), 25);
264}
265
266 / **
267 * Tuần tự hóa một thể hiện, khôi phục nó và kiểm tra tính bình đẳng.
268 * /
269 public void testSerialization () {
270
271
SerialDate d1 = SerialDate.createInstance (15, 4, 2000);
272
SerialDate d2 = null;
273
274
thử {
275
Bộ đệm ByteArrayOutputStream = new ByteArrayOutputStream ();
276
ObjectOutput out = new ObjectOutputStream (bộ đệm);
277
out.writeObject (d1);
278
out.close ();
279
280
ObjectInput in = new ObjectInputStream (
new ByteArrayInputStream (buffer.toByteArray ()));
281
d2 = (SerialDate) in.readObject ();
282
ghim theo();
283
}
284
bắt (Ngoại lệ e) {
285
System.out.println (e.toString ());
286
}
287
khẳng địnhEquals (d1, d2);
288
289}
290
291 / **
292 * Kiểm tra báo cáo lỗi 1096282 (hiện đã được khắc phục).
293 * /
294 public void test1096282 () {
295
SerialDate d = SerialDate.createInstance (29, 2, 2004);
296
d = SerialDate.addYears (1, d);
297
SerialDate dự kiến = SerialDate.createInstance (28, 2, 2005);
298
khẳng địnhTrue (d.isOn (dự kiến));
299}
300
301 / **
302 * Các bài kiểm tra khác cho phương thức addMonths ().
303 * /
304 public void testAddMonths () {
305
SerialDate d1 = SerialDate.createInstance (31, 5, 2004);
306
Liệt kê B-2 (tiếp theo)
SerialDateTest.java
www.it-ebooks.info

Trang 402
371
Phụ lục B: org.jfree.date.SerialDate
307
SerialDate d2 = SerialDate.addMonths (1, d1);
308
khẳng địnhEquals (30, d2.getDayOfMonth ());
309
khẳng địnhEquals (6, d2.getMonth ());
310
khẳng địnhEquals (2004, d2.getYYYY ());
311
312
SerialDate d3 = SerialDate.addMonths (2, d1);
313
khẳng địnhEquals (31, d3.getDayOfMonth ());
314
khẳng địnhEquals (7, d3.getMonth ());
315
khẳng địnhEquals (2004, d3.getYYYY ());
316
317
SerialDate d4 = SerialDate.addMonths (1, SerialDate.addMonths (1, d1));
318
khẳng địnhEquals (30, d4.getDayOfMonth ());
319
khẳng địnhEquals (7, d4.getMonth ());
320
khẳng địnhEquals (2004, d4.getYYYY ());
321}
322}
Liệt kê B-2 (tiếp theo)
SerialDateTest.java
www.it-ebooks.info

Trang 403
372
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-3
MonthConstants.java
1 / * ================================================= =========================
2 * JCommon: thư viện lớp đa năng miễn phí cho nền tảng Java (tm)
3 * ================================================ ========================
4*
Bản quyền 5 * (C) 2000-2005, bởi Object Refinery Limited và những người đóng góp.
6*
7 * Thông tin dự án: http://www.jfree.org/jcommon/index.html
số 8 *
9 * Thư viện này là phần mềm miễn phí; bạn có thể phân phối lại nó và / hoặc sửa đổi nó
10 * theo các điều khoản của Giấy phép Công cộng Ít hơn GNU do
11 * Tổ chức Phần mềm Tự do; phiên bản 2.1 của Giấy phép hoặc
12 * (theo tùy chọn của bạn) bất kỳ phiên bản nào mới hơn.
13 *
14 * Thư viện này được phân phối với hy vọng rằng nó sẽ hữu ích, nhưng
15 * KHÔNG CÓ BẤT KỲ BẢO HÀNH NÀO; thậm chí không có bảo đảm ngụ ý về KHẢ NĂNG LẠNH
16 * hoặc PHÙ HỢP VỚI MỤC ĐÍCH CỤ THỂ. Xem GNU Lesser General Public
17 * Giấy phép để biết thêm chi tiết.
18 *
19 * Bạn nên nhận được một bản sao của GNU Lesser General Public
20 * Giấy phép cùng với thư viện này; nếu không, hãy viết thư cho Phần mềm Miễn phí
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22 * Hoa Kỳ.
23 *
24 * [Java là nhãn hiệu hoặc nhãn hiệu đã đăng ký của Sun Microsystems, Inc.
25 * tại Hoa Kỳ và các quốc gia khác.]
26 *
27 * -------------------
28 * MonthConstants.java
29 * -------------------
30 * (C) Bản quyền 2002, 2003, của Object Refinery Limited.
31 *
32 * Tác giả gốc: David Gilbert (cho Object Refinery Limited);
33 * (Các) người đóng góp: -;
34 *
35 * $ Id: MonthConstants.java, v 1.4 2005/11/16 15:58:40 taqua Exp $
36 *
37 * Thay đổi
38 * -------
39 * 29 tháng 5 năm 2002: Phiên bản 1 (mã chuyển từ lớp SerialDate) (DG);
40 *
41 * /
42
43 gói org.jfree.date;
44
45 / **
46 * Hằng số hữu ích cho tháng. Lưu ý rằng chúng KHÔNG tương đương với
47 * hằng số được định nghĩa bởi java.util.Calendar (trong đó JANUARY = 0 và DECEMBER = 11).
48 * <P>
49 * Được sử dụng bởi các lớp SerialDate và RegularTimePeriod.
50 *
51 * @author David Gilbert
52 * /
53 giao diện công khai MonthConstants {
54
55 / ** Không đổi cho tháng Giêng. * /
56 public static final int JANUARY = 1;
57
58 / ** Không đổi cho tháng Hai. * /
59 public static final int FEBRUARY = 2;
60
www.it-ebooks.info

Trang 404
373
Phụ lục B: org.jfree.date.SerialDate
61 / ** Không đổi cho tháng Ba. * /
62 public static final int MARCH = 3;
63
64 / ** Không đổi cho tháng Tư. * /
65 public static final int APRIL = 4;
66
67 / ** Không đổi cho tháng Năm. * /
68 public static final int MAY = 5;
69
70 / ** Không đổi cho tháng Sáu. * /
71 public static final int JUNE = 6;
72
73 / ** Không đổi cho tháng Bảy. * /
74 public static final int JULY = 7;
75
76 / ** Không đổi cho tháng Tám. * /
77 public static final int AUGUST = 8;
78
79 / ** Không đổi cho tháng Chín. * /
80 public static final int SEPTEMBER = 9;
81
82 / ** Không đổi cho tháng Mười. * /
83 public static final int OCTOBER = 10;
84
85 / ** Không đổi cho tháng 11. * /
86 public static final int NOVEMBER = 11;
87
88 / ** Không đổi cho tháng Mười Hai. * /
89 public static final int DECEMBER = 12;
90
91}
Liệt kê B-3 (tiếp theo)
MonthConstants.java
www.it-ebooks.info

Trang 405
374
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-4
BobsSerialDateTest.java
1 gói org.jfree.date.junit;
2
3 nhập junit.framework.TestCase;
4 nhập org.jfree.date. *;
5 nhập static org.jfree.date.SerialDate. *;
6
7 nhập java.util. *;
số 8
9 lớp công khai BobsSerialDateTest mở rộng TestCase {
10
11 public void testIsValidWeekdayCode () ném Exception {
12 cho (int day = 1; day <= 7; day ++)
13 khẳng địnhTrue (isValidWeekdayCode (ngày));
14 khẳng địnhFalse (isValidWeekdayCode (0));
15 khẳng địnhFalse (isValidWeekdayCode (8));
16}
17
18 public void testStringToWeekdayCode () ném Exception {
19
20 khẳng địnhEquals (-1, stringToWeekdayCode ("Xin chào"));
21 khẳng địnhEquals (MONDAY, stringToWeekdayCode ("Thứ Hai"));
22 statementEquals (MONDAY, stringToWeekdayCode ("Thứ Hai"));
23 // todo confirmEquals (MONDAY, stringToWeekdayCode ("monday"));
24 // statementEquals (MONDAY, stringToWeekdayCode ("MONDAY"));
25 // khẳng địnhEquals (MONDAY, stringToWeekdayCode ("mon"));
26
27 khẳng địnhEquals (TUESDAY, stringToWeekdayCode ("Thứ Ba"));
28 khẳng địnhEquals (TUESDAY, stringToWeekdayCode ("Tue"));
29 // khẳng địnhEquals (TUESDAY, stringToWeekdayCode ("tuesday"));
30 // statementEquals (TUESDAY, stringToWeekdayCode ("TUESDAY"));
31 // khẳng địnhEquals (TUESDAY, stringToWeekdayCode ("tue"));
32 // statementEquals (TUESDAY, stringToWeekdayCode ("tues"));
33
34 khẳng địnhEquals (THỨ TƯ, stringToWeekdayCode ("Thứ Tư"));
35 khẳng địnhEquals (THỨ TƯ, stringToWeekdayCode ("Thứ Tư"));
36 // statementEquals (WEDNESDAY, stringToWeekdayCode ("wednesday"));
37 // statementEquals (WEDNESDAY, stringToWeekdayCode ("WEDNESDAY"));
38 // khẳng địnhEquals (WEDNESDAY, stringToWeekdayCode ("wed"));
39
40 khẳng địnhEquals (THURSDAY, stringToWeekdayCode ("Thứ Năm"));
41 khẳng địnhEquals (THURSDAY, stringToWeekdayCode ("Thu"));
42 // khẳng địnhEquals (THURSDAY, stringToWeekdayCode ("thursday"));
43 // khẳng địnhEquals (THURSDAY, stringToWeekdayCode ("THURSDAY"));
44 // khẳng địnhEquals (THURSDAY, stringToWeekdayCode ("thu"));
45 // khẳng địnhEquals (THURSDAY, stringToWeekdayCode ("thurs"));
46
47 statementEquals (FRIDAY, stringToWeekdayCode ("Thứ sáu"));
48 khẳng địnhEquals (FRIDAY, stringToWeekdayCode ("Thứ sáu"));
49 // khẳng địnhEquals (FRIDAY, stringToWeekdayCode ("thứ sáu"));
50 // statementEquals (FRIDAY, stringToWeekdayCode ("FRIDAY"));
51 // khẳng địnhEquals (FRIDAY, stringToWeekdayCode ("fri"));
52
53 khẳng địnhEquals (THỨ BẢY, stringToWeekdayCode ("Thứ Bảy"));
54 statementEquals (SATURDAY, stringToWeekdayCode ("Sat"));
55 // statementEquals (SATURDAY, stringToWeekdayCode ("saturday"));
56 // statementEquals (SATURDAY, stringToWeekdayCode ("SATURDAY"));
57 // statementEquals (SATURDAY, stringToWeekdayCode ("sat"));
58
59 khẳng địnhEquals (SUNDAY, stringToWeekdayCode ("Chủ nhật"));
60 khẳng địnhEquals (CHỦ NHẬT, stringToWeekdayCode ("CN"));
61 // statementEquals (SUNDAY, stringToWeekdayCode ("sunday"));
62 // statementEquals (SUNDAY, stringToWeekdayCode ("SUNDAY"));
63 // statementEquals (SUNDAY, stringToWeekdayCode ("sun"));
64}
65
www.it-ebooks.info

Trang 406
375
Phụ lục B: org.jfree.date.SerialDate
66 public void testWeekdayCodeToString () ném Exception {
67 khẳng địnhEquals ("Chủ nhật", ngày trong tuầnCodeToString (CHỦ NHẬT));
68 khẳng địnhEquals ("Thứ Hai", ngày trong tuầnCodeToString (THỨ HAI));
69 khẳng địnhEquals ("Thứ Ba", ngày trong tuầnCodeToString (THỨ TƯ));
70 khẳng địnhEquals ("Thứ Tư", ngày trong tuầnCodeToString (THỨ TƯ));
71 khẳng địnhEquals ("Thứ Năm", ngày trong tuầnCodeToString (THURSDAY));
72 khẳng địnhEquals ("Thứ sáu", ngày trong tuầnCodeToString (THỨ SÁU));
73 khẳng địnhEquals ("Thứ bảy", ngày trong tuầnCodeToString (THỨ BẢY));
74}
75
76 public void testIsValidMonthCode () ném Exception {
77 cho (int i = 1; i <= 12; i ++)
78 khẳng định (isValidMonthCode (i));
79 khẳng địnhFalse (isValidMonthCode (0));
80 khẳng địnhFalse (isValidMonthCode (13));
81}
82
83 public void testMonthToQuarter () ném Exception {
84 statementEquals (1, monthCodeToQuarter (THÁNG 1));
85 khẳng địnhEquals (1, monthCodeToQuarter (THÁNG 2));
86 khẳng địnhEquals (1, monthCodeToQuarter (THÁNG 3));
87 khẳng địnhEquals (2, monthCodeToQuarter (APRIL));
88 khẳng địnhEquals (2, monthCodeToQuarter (MAY));
89 khẳng địnhEquals (2, monthCodeToQuarter (JUNE));
90 khẳng địnhEquals (3, monthCodeToQuarter (THÁNG 7));
91 khẳng địnhEquals (3, monthCodeToQuarter (AUGUST));
92 statementEquals (3, monthCodeToQuarter (THÁNG 9));
93 khẳng địnhEquals (4, monthCodeToQuarter (THÁNG 10));
94 statementEquals (4, monthCodeToQuarter (THÁNG 11));
95 khẳng địnhEquals (4, monthCodeToQuarter (THÁNG 12));
96
97 hãy thử {
98 thángCodeToQuarter (-1);
99 fail ("Mã tháng không hợp lệ nên ném ngoại lệ");
100} catch (IllegalArgumentException e) {
101}
102}
103
104 public void testMonthCodeToString () ném Exception {
105 khẳng địnhEquals ("tháng 1", thángCodeToString (THÁNG 1));
106 khẳng địnhEquals ("tháng hai", thángCodeToString (THÁNG 2));
107 khẳng địnhEquals ("tháng 3", thángCodeToString (tháng ba));
108 khẳng địnhEquals ("tháng 4", thángCodeToString (tháng 4));
109 khẳng địnhEquals ("tháng 5", thángCodeToString (có thể));
110 khẳng địnhEquals ("tháng 6", thángCodeToString (tháng 6));
111 khẳng địnhEquals ("tháng bảy", thángCodeToString (tháng bảy));
112 khẳng địnhEquals ("tháng 8", thángCodeToString (tháng 8));
113 khẳng địnhEquals ("tháng 9", thángCodeToString (tháng 9));
114 khẳng địnhEquals ("tháng 10", thángCodeToString (THÁNG 10));
115 khẳng địnhEquals ("tháng 11", thángCodeToString (tháng 11));
116 khẳng địnhEquals ("tháng 12", thángCodeToString (tháng 12));
117
118 khẳng địnhEquals ("Jan", monthCodeToString (THÁNG 1, true));
119 khẳng địnhEquals ("tháng 2", thángCodeToString (tháng 2, đúng));
120 khẳng địnhEquals ("Mar", monthCodeToString (THÁNG 3, true));
121 khẳng địnhEquals ("tháng 4", thángCodeToString (tháng 4, đúng));
122 khẳng địnhEquals ("Tháng Năm", monthCodeToString (CÓ THỂ, true));
123 khẳng địnhEquals ("Jun", monthCodeToString (JUNE, true));
124 statementEquals ("Tháng 7", monthCodeToString (THÁNG 7, true));
125 khẳng địnhEquals ("Tháng 8", monthCodeToString (THÁNG 8, true));
126 khẳng địnhEquals ("Tháng 9", monthCodeToString (THÁNG 9, true));
127 khẳng địnhEquals ("Tháng 10", monthCodeToString (THÁNG 10, true));
Liệt kê B-4 (tiếp theo)
BobsSerialDateTest.java
www.it-ebooks.info
Trang 407
376
Phụ lục B: org.jfree.date.SerialDate
128 khẳng địnhEquals ("Tháng 11", monthCodeToString (THÁNG 11, true));
129 khẳng địnhEquals ("Tháng 12", monthCodeToString (Tháng 12, true));
130
131 thử {
132 thángCodeToString (-1);
133 fail ("Mã tháng không hợp lệ nên ném ngoại lệ");
134} catch (IllegalArgumentException e) {
135}
136
137}
138
139 public void testStringToMonthCode () ném Exception {
140 khẳng địnhEquals (THÁNG 1, stringToMonthCode ("1"));
141 khẳng địnhEquals (FEBRUARY, stringToMonthCode ("2"));
142 khẳng địnhEquals (THÁNG 3, stringToMonthCode ("3"));
143 khẳng địnhEquals (APRIL, stringToMonthCode ("4"));
144 khẳng địnhEquals (CÓ THỂ, stringToMonthCode ("5"));
145 khẳng địnhEquals (JUNE, stringToMonthCode ("6"));
146 khẳng địnhEquals (THÁNG 7, stringToMonthCode ("7"));
147 khẳng địnhEquals (AUGUST, stringToMonthCode ("8"));
148 khẳng định Equals (SEPTEMBER, stringToMonthCode ("9"));
149 statementEquals (THÁNG 10, stringToMonthCode ("10"));
150 khẳng địnhEquals (THÁNG 11, stringToMonthCode ("11"));
151 khẳng địnhEquals (THÁNG 12, stringToMonthCode ("12"));
152
153 // todo khẳng địnhEquals (-1, stringToMonthCode ("0"));
154 // khẳng địnhEquals (-1, stringToMonthCode ("13"));
155
156 khẳng địnhEquals (-1, stringToMonthCode ("Xin chào"));
157
158 cho (int m = 1; m <= 12; m ++) {
159 khẳng địnhEquals (m, stringToMonthCode (monthCodeToString (m, false)));
160 khẳng địnhEquals (m, stringToMonthCode (monthCodeToString (m, true)));
161}
162
163 // khẳng địnhEquals (1, stringToMonthCode ("jan"));
164 // khẳng địnhEquals (2, stringToMonthCode ("feb"));
165 // khẳng địnhEquals (3, stringToMonthCode ("mar"));
166 // khẳng địnhEquals (4, stringToMonthCode ("apr"));
167 // khẳng địnhEquals (5, stringToMonthCode ("may"));
168 // khẳng địnhEquals (6, stringToMonthCode ("jun"));
169 // khẳng địnhEquals (7, stringToMonthCode ("jul"));
170 // khẳng địnhEquals (8, stringToMonthCode ("aug"));
171 // khẳng địnhEquals (9, stringToMonthCode ("sep"));
172 // khẳng địnhEquals (10, stringToMonthCode ("oct"));
173 // khẳng địnhEquals (11, stringToMonthCode ("nov"));
174 // khẳng địnhEquals (12, stringToMonthCode ("dec"));
175
176 // khẳng địnhEquals (1, stringToMonthCode ("JAN"));
177 // khẳng địnhEquals (2, stringToMonthCode ("FEB"));
178 // khẳng địnhEquals (3, stringToMonthCode ("MAR"));
179 // khẳng địnhEquals (4, stringToMonthCode ("APR"));
180 // khẳng địnhEquals (5, stringToMonthCode ("CÓ THỂ"));
181 // khẳng địnhEquals (6, stringToMonthCode ("JUN"));
182 // khẳng địnhEquals (7, stringToMonthCode ("JUL"));
183 // khẳng địnhEquals (8, stringToMonthCode ("AUG"));
184 // khẳng địnhEquals (9, stringToMonthCode ("SEP"));
185 // khẳng địnhEquals (10, stringToMonthCode ("OCT"));
186 // khẳng địnhEquals (11, stringToMonthCode ("NOV"));
187 // khẳng địnhEquals (12, stringToMonthCode ("DEC"));
188
189 // khẳng địnhEquals (1, stringToMonthCode ("january"));
190 // khẳng địnhEquals (2, stringToMonthCode ("tháng hai"));
Liệt kê B-4 (tiếp theo)
BobsSerialDateTest.java
www.it-ebooks.info

Trang 408
377
Phụ lục B: org.jfree.date.SerialDate
191 // khẳng địnhEquals (3, stringToMonthCode ("diễu hành"));
192 // khẳng địnhEquals (4, stringToMonthCode ("april"));
193 // khẳng địnhEquals (5, stringToMonthCode ("may"));
194 // khẳng địnhEquals (6, stringToMonthCode ("tháng sáu"));
195 // khẳng địnhEquals (7, stringToMonthCode ("july"));
196 // khẳng địnhEquals (8, stringToMonthCode ("august"));
197 // khẳng địnhEquals (9, stringToMonthCode ("tháng chín"));
198 // khẳng địnhEquals (10, stringToMonthCode ("tháng mười"));
199 // khẳng địnhEquals (11, stringToMonthCode ("tháng mười một"));
200 // khẳng địnhEquals (12, stringToMonthCode ("tháng mười hai"));
201
202 // khẳng địnhEquals (1, stringToMonthCode ("THÁNG 1"));
203 // khẳng địnhEquals (2, stringToMonthCode ("FEBRUARY"));
204 // khẳng địnhEquals (3, stringToMonthCode ("MAR"));
205 // khẳng địnhEquals (4, stringToMonthCode ("APRIL"));
206 // khẳng địnhEquals (5, stringToMonthCode ("CÓ THỂ"));
207 // khẳng địnhEquals (6, stringToMonthCode ("JUNE"));
208 // khẳng địnhEquals (7, stringToMonthCode ("THÁNG 7"));
209 // khẳng địnhEquals (8, stringToMonthCode ("AUGUST"));
210 // khẳng địnhEquals (9, stringToMonthCode ("SEPTEMBER"));
211 // khẳng địnhEquals (10, stringToMonthCode ("THÁNG 10"));
212 // khẳng địnhEquals (11, stringToMonthCode ("THÁNG 11"));
213 // khẳng địnhEquals (12, stringToMonthCode ("THÁNG 12"));
214}
215
216 public void testIsValidWeekInMonthCode () ném Exception {
217 cho (int w = 0; w <= 4; w ++) {
Chương 218 khẳng định (isValidWeekInMonthCode (w));
219}
220 khẳng địnhFalse (isValidWeekInMonthCode (5));
221}
222
223 public void testIsLeapYear () ném Exception {
224 khẳng địnhFalse (isLeapYear (1900));
225 khẳng địnhFalse (isLeapYear (1901));
Chương 226 khẳng định (isLeapYear (1902));
Chương 227 khẳng định (isLeapYear (1903));
Chương 228 khẳng định (isLeapYear (1904));
Chương 229 khẳng định (isLeapYear (1908));
230 khẳng địnhFalse (isLeapYear (1955));
231 khẳng định (isLeapYear (1964));
232 khẳng định (isLeapYear (1980));
Chương 233 khẳng định (isLeapYear (2000));
234 khẳng địnhFalse (isLeapYear (2001));
Chương 235 khẳng định (isLeapYear (2100));
236}
237
238 public void testLeapYearCount () ném Exception {
239 khẳng địnhEquals (0, leapYearCount (1900));
240 khẳng địnhEquals (0, leapYearCount (1901));
241 khẳng địnhEquals (0, leapYearCount (1902));
242 khẳng địnhEquals (0, leapYearCount (1903));
243 khẳng địnhEquals (1, leapYearCount (1904));
244 khẳng địnhEquals (1, leapYearCount (1905));
245 khẳng địnhEquals (1, leapYearCount (1906));
246 khẳng địnhEquals (1, leapYearCount (1907));
247 khẳng địnhEquals (2, leapYearCount (1908));
248 khẳng địnhEquals (24, leapYearCount (1999));
249 khẳng địnhEquals (25, leapYearCount (2001));
250 khẳng địnhEquals (49, leapYearCount (2101));
251 khẳng địnhEquals (73, leapYearCount (2201));
Liệt kê B-4 (tiếp theo)
BobsSerialDateTest.java
www.it-ebooks.info

Trang 409
378
Phụ lục B: org.jfree.date.SerialDate
252 khẳng địnhEquals (97, leapYearCount (2301));
253 khẳng địnhEquals (122, leapYearCount (2401));
254}
255
256 public void testLastDayOfMonth () ném Exception {
257 khẳng địnhEquals (31, lastDayOfMonth (THÁNG 1, 1901));
258 khẳng định (28, lastDayOfMonth (THÁNG 2, 1901));
259 khẳng địnhEquals (31, lastDayOfMonth (THÁNG 3, 1901));
260 khẳng địnhEquals (30, lastDayOfMonth (tháng 4 năm 1901));
261 khẳng địnhEquals (31, lastDayOfMonth (MAY, 1901));
262 khẳng địnhEquals (30, lastDayOfMonth (JUNE, 1901));
263 khẳng địnhEquals (31, lastDayOfMonth (tháng 7, 1901));
264 khẳng địnhEquals (31, lastDayOfMonth (THÁNG 8, 1901));
265 khẳng địnhEquals (30, lastDayOfMonth (SEPTEMBER, 1901));
266 khẳng địnhEquals (31, lastDayOfMonth (THÁNG 10, 1901));
Chương 267 khẳng định (30, lastDayOfMonth (THÁNG 11, 1901));
Chương 268 khẳng định (31, lastDayOfMonth (DECEMBER, 1901));
269 khẳng địnhEquals (29, lastDayOfMonth (THÁNG 2, 1904));
270}
271
272 public void testAddDays () ném Exception {
273 SerialDate newYears = d (1, THÁNG 1, 1900);
274 statementEquals (d (2, JANUARY, 1900), addDays (1, newYears));
275 khẳng địnhEquals (d (1, FEBRUARY, 1900), addDays (31, newYears));
276 confirmEquals (d (1, JANUARY, 1901), addDays (365, newYears));
277 khẳng địnhEquals (d (31, DECEMBER, 1904), addDays (5 * 365, newYears));
278}
279
280 private static SpreadsheetDate d (int day, int month, int year) {return new
SpreadsheetDate (ngày, tháng, năm);}
281
282 public void testAddMonths () ném Exception {
283 khẳng địnhEquals (d (1, FEBRUARY, 1900), addMonths (1, d (1, JANUARY, 1900)));
284 statementEquals (d (28, FEBRUARY, 1900), addMonths (1, d (31, JANUARY, 1900)));
285 statementEquals (d (28, FEBRUARY, 1900), addMonths (1, d (30, JANUARY, 1900)));
286 khẳng địnhEquals (d (28, THÁNG 2, 1900), addMonths (1, d (29, tháng 1, 1900)));
287 statementEquals (d (28, FEBRUARY, 1900), addMonths (1, d (28, JANUARY, 1900)));
288 statementEquals (d (27, FEBRUARY, 1900), addMonths (1, d (27, JANUARY, 1900)));
289
290 khẳng định (d (30, JUNE, 1900), addMonths (5, d (31, JANUARY, 1900)));
291 khẳng địnhEquals (d (30, JUNE, 1901), addMonths (17, d (31, JANUARY, 1900)));
292
293 allowEquals (d (29, FEBRUARY, 1904), addMonths (49, d (31, JANUARY, 1900)));
294
295}
296
297 public void testAddYears () ném Exception {
298 khẳng địnhEquals (d (1, JANUARY, 1901), addYears (1, d (1, JANUARY, 1900)));
299 khẳng địnhEquals (d (28, FEBRUARY, 1905), addYears (1, d (29, FEBRUARY, 1904)));
300 khẳng địnhEquals (d (28, FEBRUARY, 1905), addYears (1, d (28, FEBRUARY, 1904)));
301 khẳng địnhEquals (d (28, FEBRUARY, 1904), addYears (1, d (28, FEBRUARY, 1903)));
302}
303
304 public void testGetPreviousDayOfWeek () ném Exception {
305 khẳng địnhEquals (d (24, THÁNG 2, 2006), getPreviousDayOfWeek (THỨ SÁU, d (1, THÁNG 3, 2006)));
306 khẳng địnhEquals (d (22, THÁNG 2, 2006), getPreviousDayOfWeek (THỨ TƯ, ngày 1 tháng 3, 2006)));
307 ensureEquals (d (29, FEBRUARY, 2004), getPreviousDayOfWeek (SUNDAY, d (3, March, 2004)));
308 confirmEquals (d (29, DECEMBER, 2004), getPreviousDayOfWeek (WEDNESDAY, d (5, JANUARY, 2005)));
309
310 thử {
311 getPreviousDayOfWeek (-1, d (1, THÁNG 1, 2006));
312 fail ("Mã ngày trong tuần không hợp lệ nên ném ngoại lệ");
Liệt kê B-4 (tiếp theo)
BobsSerialDateTest.java
www.it-ebooks.info

Trang 410
379
Phụ lục B: org.jfree.date.SerialDate
313} catch (IllegalArgumentException e) {
314}
315}
316
317 public void testGetFollowingDayOfWeek () ném Exception {
318 // statementEquals (d (1, JANUARY, 2005), getFollowingDayOfWeek (SATURDAY, d (25, DECEMBER, 2004)));
319 confirmEquals (d (1, JANUARY, 2005), getFollowingDayOfWeek (SATURDAY, d (26, DECEMBER, 2004)));
320 khẳng địnhEquals (d (3, 3, 2004), getFollowingDayOfWeek (THỨ TƯ, d (28, THÁNG 2, 2004)));
321
322 hãy thử {
323 getFollowingDayOfWeek (-1, d (1, THÁNG 1, 2006));
324 fail ("Mã ngày trong tuần không hợp lệ nên ném ngoại lệ");
325} bắt (IllegalArgumentException e) {
326}
327}
328
329 public void testGetNearestDayOfWeek () ném Exception {
330 khẳng định (d (16, 4 tháng 4, 2006), getNearestDayOfWeek (CHỦ NHẬT, d (16, 4, 2006)));
331 khẳng địnhEquals (d (16, APRIL, 2006), getNearestDayOfWeek (SUNDAY, d (17, 4, 2006)));
332 khẳng địnhEquals (d (16, APRIL, 2006), getNearestDayOfWeek (SUNDAY, d (18, APRIL, 2006)));
333 defineEquals (d (16, APRIL, 2006), getNearestDayOfWeek (SUNDAY, d (19, APRIL, 2006)));
334 confirmEquals (d (23, 4, 2006), getNearestDayOfWeek (SUNDAY, d (20, 4, 2006)));
335 confirmEquals (d (23, 4, 2006), getNearestDayOfWeek (SUNDAY, d (21, APRIL, 2006)));
336 khẳng địnhEquals (d (23, APRIL, 2006), getNearestDayOfWeek (SUNDAY, d (22, APRIL, 2006)));
337
338 // todo khẳng địnhEquals (d (17, APRIL, 2006), getNearestDayOfWeek (MONDAY, d (16, 4, 2006)));
339 ensureEquals (d (17, 4, 2006), getNearestDayOfWeek (MONDAY, d (17, APRIL, 2006)));
340 khẳng định (d (17, 4 tháng 4, 2006), getNearestDayOfWeek (THỨ HAI, d (18, 4, 2006)));
341 khẳng địnhEquals (d (17, APRIL, 2006), getNearestDayOfWeek (MONDAY, d (19, 4, 2006)));
342 khẳng địnhEquals (d (17, 4, 2006), getNearestDayOfWeek (THỨ HAI, d (20, 4, 2006)));
343 confirmEquals (d (24, 4, 2006), getNearestDayOfWeek (MONDAY, d (21, 4, 2006)));
344 statementEquals (d (24, APRIL, 2006), getNearestDayOfWeek (MONDAY, d (22, APRIL, 2006)));
345
346 // khẳng địnhEquals (d (18, 4, 2006), getNearestDayOfWeek (TUESDAY, d (16, 4, 2006)));
347 // khẳng địnhEquals (d (18, APRIL, 2006), getNearestDayOfWeek (TUESDAY, d (17, APRIL, 2006)));
348 khẳng địnhEquals (d (18, APRIL, 2006), getNearestDayOfWeek (TUESDAY, d (18, 4, 2006)));
349 khẳng địnhEquals (d (18, APRIL, 2006), getNearestDayOfWeek (TUESDAY, d (19, APRIL, 2006)));
350 khẳng địnhEquals (d (18, APRIL, 2006), getNearestDayOfWeek (TUESDAY, d (20, APRIL, 2006)));
351 khẳng địnhEquals (d (18, APRIL, 2006), getNearestDayOfWeek (TUESDAY, d (21, APRIL, 2006)));
352 confirmEquals (d (25, 4, 2006), getNearestDayOfWeek (TUESDAY, d (22, 4, 2006)));
353
354 // statementEquals (d (19, 4, 2006), getNearestDayOfWeek (WEDNESDAY, d (16, 4, 2006)));
355 // statementEquals (d (19, 4, 2006), getNearestDayOfWeek (WEDNESDAY, d (17, 4, 2006)));
356 // khẳng địnhEquals (d (19, APRIL, 2006), getNearestDayOfWeek (WEDNESDAY, d (18, APRIL, 2006)));
357 statementEquals (d (19, 4, 2006), getNearestDayOfWeek (WEDNESDAY, d (19, 4, 2006)));
358 confirmEquals (d (19, 4, 2006), getNearestDayOfWeek (WEDNESDAY, d (20, 4, 2006)));
359 ensureEquals (d (19, 4, 2006), getNearestDayOfWeek (WEDNESDAY, d (21, 4, 2006)));
360 khẳng địnhEquals (d (19, 4, 2006), getNearestDayOfWeek (WEDNESDAY, d (22, 4, 2006)));
361
362 // statementEquals (d (13, APRIL, 2006), getNearestDayOfWeek (THURSDAY, d (16, APRIL, 2006)));
363 // statementEquals (d (20, APRIL, 2006), getNearestDayOfWeek (THURSDAY, d (17, APRIL, 2006)));
364 // statementEquals (d (20, APRIL, 2006), getNearestDayOfWeek (THURSDAY, d (18, APRIL, 2006)));
365 // statementEquals (d (20, APRIL, 2006), getNearestDayOfWeek (THURSDAY, d (19, APRIL, 2006)));
366 khẳng địnhEquals (d (20, APRIL, 2006), getNearestDayOfWeek (THURSDAY, d (20, APRIL, 2006)));
367 statementEquals (d (20, APRIL, 2006), getNearestDayOfWeek (THURSDAY, d (21, APRIL, 2006)));
368 statementEquals (d (20, APRIL, 2006), getNearestDayOfWeek (THURSDAY, d (22, APRIL, 2006)));
369
370 // statementEquals (d (14, APRIL, 2006), getNearestDayOfWeek (FRIDAY, d (16, APRIL, 2006)));
371 // statementEquals (d (14, 4, 2006), getNearestDayOfWeek (FRIDAY, d (17, 4, 2006)));
372 // statementEquals (d (21, APRIL, 2006), getNearestDayOfWeek (FRIDAY, d (18, APRIL, 2006)));
373 // statementEquals (d (21, APRIL, 2006), getNearestDayOfWeek (FRIDAY, d (19, APRIL, 2006)));
374 // statementEquals (d (21, APRIL, 2006), getNearestDayOfWeek (FRIDAY, d (20, APRIL, 2006)));
Liệt kê B-4 (tiếp theo)
BobsSerialDateTest.java
www.it-ebooks.info

Trang 411
380
Phụ lục B: org.jfree.date.SerialDate
375 khẳng địnhEquals (d (21, APRIL, 2006), getNearestDayOfWeek (FRIDAY, d (21, APRIL, 2006)));
376 khẳng địnhEquals (d (21, APRIL, 2006), getNearestDayOfWeek (FRIDAY, d (22, APRIL, 2006)));
377
378 // statementEquals (d (15, APRIL, 2006), getNearestDayOfWeek (SATURDAY, d (16, APRIL, 2006)));
379 // statementEquals (d (15, APRIL, 2006), getNearestDayOfWeek (SATURDAY, d (17, APRIL, 2006)));
380 // statementEquals (d (15, APRIL, 2006), getNearestDayOfWeek (SATURDAY, d (18, APRIL, 2006)));
381 // statementEquals (d (22, APRIL, 2006), getNearestDayOfWeek (SATURDAY, d (19, APRIL, 2006)));
382 // statementEquals (d (22, APRIL, 2006), getNearestDayOfWeek (SATURDAY, d (20, APRIL, 2006)));
383 // statementEquals (d (22, APRIL, 2006), getNearestDayOfWeek (SATURDAY, d (21, APRIL, 2006)));
384 ensureEquals (d (22, 4, 2006), getNearestDayOfWeek (SATURDAY, d (22, APRIL, 2006)));
385
386 thử {
387 getNearestDayOfWeek (-1, d (1, THÁNG 1, 2006));
388 fail ("Mã ngày trong tuần không hợp lệ nên ném ngoại lệ");
389} catch (IllegalArgumentException e) {
390}
391}
392
393 public void testEndOfCurrentMonth () ném Exception {
Chương 394: SerialDate d = SerialDate.createInstance (2);
395 confirmEquals (d (31, JANUARY, 2006), d.getEndOfCurrentMonth (d (1, JANUARY, 2006)));
396 confirmEquals (d (28, FEBRUARY, 2006), d.getEndOfCurrentMonth (d (1, FEBRUARY, 2006)));
397 ensureEquals (d (31, MARCH, 2006), d.getEndOfCurrentMonth (d (1, MARCH, 2006)));
398 statementEquals (d (30, APRIL, 2006), d.getEndOfCurrentMonth (d (1, APRIL, 2006)));
399 khẳng địnhEquals (d (31, MAY, 2006), d.getEndOfCurrentMonth (d (1, MAY, 2006)));
400 khẳng định (d (30, JUNE, 2006), d.getEndOfCurrentMonth (d (1, JUNE, 2006)));
401 khẳng địnhEquals (d (31, JULY, 2006), d.getEndOfCurrentMonth (d (1, JULY, 2006)));
402 khẳng địnhEquals (d (31, AUGUST, 2006), d.getEndOfCurrentMonth (d (1, AUGUST, 2006)));
403 confirmEquals (d (30, SEPTEMBER, 2006), d.getEndOfCurrentMonth (d (1, SEPTEMBER, 2006)));
404 khẳng địnhEquals (d (31, tháng 10, 2006), d.getEndOfCurrentMonth (d (1, tháng 10, 2006)));
405 khẳng địnhEquals (d (30, 11/11, 2006), d.getEndOfCurrentMonth (d (1, 11/11, 2006)));
406 khẳng địnhEquals (d (31, DECEMBER, 2006), d.getEndOfCurrentMonth (d (1, DECEMBER, 2006)));
407 confirmEquals (d (29, FEBRUARY, 2008), d.getEndOfCurrentMonth (d (1, FEBRUARY, 2008)));
408}
409
410 public void testWeekInMonthToString () ném Exception {
411 khẳng địnhEquals ("Đầu tiên", weekInMonthToString (FIRST_WEEK_IN_MONTH));
412 khẳng địnhEquals ("Thứ hai", tuầnInMonthToString (SECOND_WEEK_IN_MONTH));
413 khẳng địnhEquals ("Thứ ba", weekInMonthToString (THIRD_WEEK_IN_MONTH));
414 statementEquals ("Thứ tư", weekInMonthToString (FOURTH_WEEK_IN_MONTH));
415 khẳng địnhEquals ("Cuối cùng", tuầnInMonthToString (LAST_WEEK_IN_MONTH));
416
417 // việc cần làm hãy thử {
418 // tuầnInMonthToString (-1);
419 // fail ("Mã tuần không hợp lệ nên ném ngoại lệ");
420 //} catch (IllegalArgumentException e) {
421 //}
422}
423
424 public void testRelativeToString () ném Exception {
425 khẳng địnhEquals ("Trước", tương đốiToString (CHÍNH XÁC));
426 khẳng địnhEquals ("Gần nhất", tương đốiToString (GẦN NHẤT));
427 khẳng địnhEquals ("Đang theo dõi", relativeToString (THEO DÕI));
428
429 // việc cần làm hãy thử {
430 // tương đốiToString (-1000);
431 // fail ("Mã tương đối không hợp lệ nên ném ngoại lệ");
432 //} catch (IllegalArgumentException e) {
433 //}
434}
435
Liệt kê B-4 (tiếp theo)
BobsSerialDateTest.java
www.it-ebooks.info

Trang 412
381
Phụ lục B: org.jfree.date.SerialDate
436 public void testCreateInstanceFromDDMMYYY () ném Exception {
437 SerialDate date = createInstance (1, THÁNG 1, 1900);
Chương 438 khẳng định (1, date.getDayOfMonth ());
439 khẳng địnhEquals (THÁNG 1, date.getMonth ());
440 khẳng địnhEquals (1900, date.getYYYY ());
441 khẳng địnhEquals (2, date.toSerial ());
442}
443
444 public void testCreateInstanceFromSerial () ném Exception {
445 khẳng địnhEquals (d (1, THÁNG 1, 1900), createInstance (2));
446 khẳng địnhEquals (d (1, THÁNG 1, 1901), createInstance (367));
447}
448
449 public void testCreateInstanceFromJavaDate () ném Exception {
450 khẳng định (d (1, THÁNG 1, 1900),
createInstance (GregorianCalendar mới (1900,0,1) .getTime ()));
451 khẳng địnhEquals (d (1, THÁNG 1, 2006),
createInstance (GregorianCalendar mới (2006,0,1) .getTime ()));
452}
453
454 public static void main (String [] args) {
455 junit.textui.TestRunner.run (BobsSerialDateTest.class);
456}
457}
Liệt kê B-4 (tiếp theo)
BobsSerialDateTest.java
www.it-ebooks.info
Trang 413
382
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-5
SpreadsheetDate.java
1 / * ================================================= =========================
2 * JCommon: thư viện lớp đa năng miễn phí cho nền tảng Java (tm)
3 * ================================================ ========================
4*
Bản quyền 5 * (C) 2000-2005, bởi Object Refinery Limited và những người đóng góp.
6*
7 * Thông tin dự án: http://www.jfree.org/jcommon/index.html
số 8 *
9 * Thư viện này là phần mềm miễn phí; bạn có thể phân phối lại nó và / hoặc sửa đổi nó
10 * theo các điều khoản của Giấy phép Công cộng Ít hơn GNU do
11 * Tổ chức Phần mềm Tự do; phiên bản 2.1 của Giấy phép hoặc
12 * (theo tùy chọn của bạn) bất kỳ phiên bản nào mới hơn.
13 *
14 * Thư viện này được phân phối với hy vọng rằng nó sẽ hữu ích, nhưng
15 * KHÔNG CÓ BẤT KỲ BẢO HÀNH NÀO; thậm chí không có bảo đảm ngụ ý về KHẢ NĂNG LẠNH
16 * hoặc PHÙ HỢP VỚI MỤC ĐÍCH CỤ THỂ. Xem GNU Lesser General Public
17 * Giấy phép để biết thêm chi tiết.
18 *
19 * Bạn nên nhận được một bản sao của GNU Lesser General Public
20 * Giấy phép cùng với thư viện này; nếu không, hãy viết thư cho Phần mềm Miễn phí
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22 * Hoa Kỳ.
23 *
24 * [Java là nhãn hiệu hoặc nhãn hiệu đã đăng ký của Sun Microsystems, Inc.
25 * tại Hoa Kỳ và các quốc gia khác.]
26 *
27 * --------------------
28 * SpreadsheetDate.java
29 * --------------------
30 * (C) Bản quyền 2000-2005, bởi Object Refinery Limited và những người đóng góp.
31 *
32 * Tác giả gốc: David Gilbert (cho Object Refinery Limited);
33 * (Các) người đóng góp: -;
34 *
35 * $ Id: SpreadsheetDate.java, v 1.8 2005/11/03 09:25:39 mungady Exp $
36 *
37 * Thay đổi
38 * -------
39 * 11-10-2001: Phiên bản 1 (DG);
40 * 05-tháng 11 năm 2001: Đã thêm phương thức getDescription () và setDescription () (DG);
41 * 12-11-2001: Đổi tên từ ExcelDate.java thành SpreadsheetDate.java (DG);
42 *
Sửa lỗi tính toán ngày, tháng và năm từ sê-ri
43 *
số (DG);
44 * 24 tháng 1 năm 2002: Sửa một lỗi trong việc tính toán số sê-ri từ ngày,
45 *
tháng và năm. Cảm ơn Trevor Hills về báo cáo (DG);
46 * 29 tháng 5 năm 2002: Đã thêm phương thức bằng (Đối tượng) (SourceForge ID 558850) (DG);
47 * 03-10-2002: Đã sửa các lỗi được Checkstyle (DG) báo cáo;
48 * 13-03-2003: Thực hiện Serializable (DG);
49 * 04-09-2003: Hoàn thành các phương thức isInRange () (DG);
50 * 05-09-2003: Thực hiện So sánh (DG);
51 * 21-10-2003: Thêm phương thức hashCode () (DG);
52 *
53 * /
54
55 gói org.jfree.date;
56
57 nhập java.util.Calendar;
58 nhập java.util.Date;
59
60 / **
61 * Đại diện cho một ngày sử dụng một số nguyên, theo kiểu tương tự như
62 * thực hiện trong Microsoft Excel. Phạm vi ngày được hỗ trợ là
www.it-ebooks.info

Trang 414
383
Phụ lục B: org.jfree.date.SerialDate
63 * 1-Jan-1900 đến 31-Dec-9999.
64 * <P>
65 * Hãy lưu ý rằng có một lỗi cố ý trong Excel nhận dạng năm
66 * 1900 là một năm nhuận trong khi thực tế nó không phải là một năm nhuận. Bạn có thể tìm thêm
67 * thông tin trên trang web của Microsoft trong bài viết Q181370:
68 * <P>
69 * http://support.microsoft.com/support/kb/articles/Q181/3/70.asp
70 * <P>
71 * Excel sử dụng quy ước 1-Jan-1900 = 1. Lớp này sử dụng
72 * quy ước 1-Jan-1900 = 2.
73 * Kết quả là số ngày trong lớp này sẽ khác với
74 * Số liệu Excel cho tháng 1 và tháng 2 năm 1900 ... nhưng sau đó Excel thêm vào
75 * ngày (29-02-1900 không thực sự tồn tại!) Và từ thời điểm đó trở đi
76 * các con số trong ngày sẽ khớp.
77 *
78 * @author David Gilbert
79 * /
80 public class SpreadsheetDate mở rộng SerialDate {
81
82 / ** Để tuần tự hóa. * /
83 private static cuối cùng dài serialVersionUID = -2039586705374454461L;
84
85 / **
86 * Số ngày (1-Jan-1900 = 2, 2-Jan-1900 = 3, ..., 31-Dec-9999 =
87 * 2958465).
88 * /
89 private int nối tiếp;
90
91 / ** Ngày trong tháng (1 đến 28, 29, 30 hoặc 31 tùy theo tháng). * /
92 private int day;
93
94 / ** Tháng trong năm (1 đến 12). * /
95 private int tháng;
96
97 / ** Năm (1900 đến 9999). * /
98 private int năm;
99
100 / ** Một mô tả tùy chọn cho ngày. * /
101 mô tả chuỗi riêng tư;
102
103 / **
104 * Tạo một phiên bản ngày mới.
105 *
106 * @param ngày trong ngày (trong phạm vi 1 đến 28/29/30/31).
107 * @param tháng trong tháng (trong phạm vi từ 1 đến 12).
108 * @param năm trong năm (trong khoảng 1900 đến 9999).
109 * /
110 public SpreadsheetDate (ngày cuối cùng, tháng cuối cùng, năm cuối) {
111
112
if ((year> = 1900) && (year <= 9999)) {
113
this.year = năm;
114
}
115
khác {
116
ném IllegalArgumentException mới (
117
"Đối số 'năm' phải nằm trong khoảng từ 1900 đến 9999."
118
);
119
}
120
121
if ((tháng> = MonthConstants.JANUARY)
122
&& (tháng <= MonthConstants.DECEMBER)) {
123
this.month = tháng;
124
}
Liệt kê B-5 (tiếp theo)
SpreadsheetDate.java
www.it-ebooks.info
Trang 415
384
Phụ lục B: org.jfree.date.SerialDate
125
khác {
126
ném IllegalArgumentException mới (
127
"Đối số 'tháng' phải nằm trong phạm vi từ 1 đến 12."
128
);
129
}
130
131
if ((ngày> = 1) && (ngày <= SerialDate.lastDayOfMonth (tháng, năm))) {
132
this.day = ngày;
133
}
134
khác {
135
ném mới IllegalArgumentException ("Đối số 'ngày' không hợp lệ.");
136
}
137
138
// số sê-ri cần được đồng bộ với ngày-tháng-năm ...
139
this.serial = calcSerial (ngày, tháng, năm);
140
141
this.description = null;
142
143}
144
145 / **
146 * Hàm tạo chuẩn - tạo một đối tượng ngày mới đại diện cho
147 * số ngày được chỉ định (phải nằm trong khoảng 2 đến 2958465.
148 *
149 * @param đánh số sê-ri trong ngày (phạm vi: 2 đến 2958465).
150 * /
151 public SpreadsheetDate (end int serial) {
152
153
if ((serial> = SERIAL_LOWER_BOUND) && (serial <= SERIAL_UPPER_BOUND)) {
154
this.serial = nối tiếp;
155
}
156
khác {
157
ném IllegalArgumentException mới (
158
"SpreadsheetDate: Serial phải nằm trong khoảng từ 2 đến 2958465.");
159
}
160
161
// ngày-tháng-năm cần được đồng bộ với số sê-ri ...
162
calcDayMonthYear ();
163
164}
165
166 / **
167 * Trả về mô tả được đính kèm với ngày tháng. Không phải vậy
168 * yêu cầu ngày phải có mô tả, nhưng đối với một số ứng dụng thì
169 * rất hữu ích.
170 *
171 * @return Mô tả được đính kèm với ngày tháng.
172 * /
173 public String getDescription () {
174
trả lại this.description;
175}
176
177 / **
178 * Đặt mô tả cho ngày tháng.
179 *
180 * @param description the description for this date (<code> null </code>
181 *
được phép).
182 * /
183 public void setDescription (mô tả chuỗi cuối cùng) {
184
this.description = mô tả;
185}
186
Liệt kê B-5 (tiếp theo)
SpreadsheetDate.java
www.it-ebooks.info

Trang 416
385
Phụ lục B: org.jfree.date.SerialDate
187 / **
188 * Trả về số sê-ri cho ngày, trong đó ngày 1 tháng 1 năm 1900 = 2
189 * (gần như tương ứng với hệ thống đánh số được sử dụng trong Microsoft
190 * Excel dành cho Windows và Lotus 1-2-3).
191 *
192 * @return Số sê-ri của ngày này.
193 * /
194 public int toSerial () {
195
trả lại this.serial;
196}
197
198 / **
199 * Trả về <code> java.util.Date </code> tương đương với ngày này.
200 *
201 * @return Ngày.
202 * /
203 public Date toDate () {
204
lịch cuối cùng Lịch = Calendar.getInstance ();
205
Calendar.set (getYYYY (), getMonth () - 1, getDayOfMonth (), 0, 0, 0);
206
trả về lịch.getTime ();
207}
208
209 / **
210 * Trả về năm (giả sử phạm vi hợp lệ từ 1900 đến 9999).
211 *
212 * @return Năm.
213 * /
214 public int getYYYY () {
215
trả lại this.year;
216}
217
218 / **
219 * Trả về tháng (tháng 1 = 1, tháng 2 = 2, tháng 3 = 3).
220 *
221 * @return Tháng trong năm.
222 * /
223 public int getMonth () {
224
trả lại this.month;
225}
226
227 / **
228 * Trả về ngày trong tháng.
229 *
230 * @return Ngày trong tháng.
231 * /
232 public int getDayOfMonth () {
233
return this.day;
234}
235
236 / **
237 * Trả về mã đại diện cho ngày trong tuần.
238 * <P>
239 * Các mã được định nghĩa trong lớp {@link SerialDate} là:
240 * <code> CHỦ NHẬT </code>, <code> THỨ HAI </code>, <code> THỨ TƯ </code>,
241 * <code> WEDNESDAY </code>, <code> THURSDAY </code>, <code> FRIDAY </code> và
242 * <code> THỨ BẢY </code>.
243 *
244 * @return Một mã đại diện cho ngày trong tuần.
245 * /
246 public int getDayOfWeek () {
247
return (this.serial + 6)% 7 + 1;
248}
Liệt kê B-5 (tiếp theo)
SpreadsheetDate.java
www.it-ebooks.info

Trang 417
386
Phụ lục B: org.jfree.date.SerialDate
249
250 / **
251 * Kiểm tra tính bình đẳng của ngày này với một đối tượng tùy ý.
252 * <P>
253 * Phương thức này CHỈ trả về true nếu đối tượng là một phiên bản của
Lớp cơ sở 254 * {@link SerialDate} và nó đại diện cho cùng một ngày với ngày này
255 * {@link SpreadsheetDate}.
256 *
257 * @param đối tượng đối tượng để so sánh (được phép <code> null </code>).
258 *
259 * @return Một boolean.
260 * /
261 public boolean bằng (đối tượng cuối cùng của Object) {
262
263
if (object instanceof SerialDate) {
264
đối tượng cuối cùng SerialDate s = (SerialDate);
265
return (s.toSerial () == this.toSerial ());
266
}
267
khác {
268
trả về sai;
269
}
270
271}
272
273 / **
274 * Trả về mã băm cho cá thể đối tượng này.
275 *
276 * @return Một mã băm.
277 * /
278 public int hashCode () {
279
quay lại toSerial ();
280}
281
282 / **
283 * Trả về sự khác biệt (tính theo ngày) giữa ngày này và ngày được chỉ định
284 * ngày 'khác'.
285 *
286 * @param khác ngày được so sánh với.
287 *
288 * @return Sự khác biệt (tính theo ngày) giữa ngày này và ngày được chỉ định
289 *
ngày 'khác'.
290 * /
291 public int so sánh (ngày cuối cùng của SerialDate khác) {
292
trả về this.serial - other.toSerial ();
293}
294
295 / **
296 * Triển khai phương thức được yêu cầu bởi giao diện So sánh.
297 *
298 * @param khác đối tượng khác (thường là Ngày nối tiếp khác).
299 *
300 * @return Một số nguyên âm, 0 hoặc một số nguyên dương làm đối tượng này
301 *
nhỏ hơn, bằng hoặc lớn hơn đối tượng được chỉ định.
302 * /
303 public int so sánhTo (đối tượng cuối cùng khác) {
304
return so sánh ((SerialDate) other);
305}
306
307 / **
308 * Trả về true nếu SerialDate này đại diện cho cùng một ngày với
309 * SerialDate được chỉ định.
310 *
Liệt kê B-5 (tiếp theo)
SpreadsheetDate.java
www.it-ebooks.info

Trang 418
387
Phụ lục B: org.jfree.date.SerialDate
311 * @param khác ngày được so sánh với.
312 *
313 * @return <code> true </code> nếu SerialDate này đại diện cho cùng một ngày với
314 *
SerialDate được chỉ định.
315 * /
316 công khai boolean isOn (SerialDate cuối cùng khác) {
317
return (this.serial == other.toSerial ());
318}
319
320 / **
321 * Trả về true nếu SerialDate này biểu thị một ngày sớm hơn so với
322 * SerialDate được chỉ định.
323 *
324 * @param khác ngày được so sánh với.
325 *
326 * @return <code> true </code> nếu SerialDate này đại diện cho một ngày trước đó
327 *
so với SerialDate được chỉ định.
328 * /
329 public boolean isBefore (cuối cùng của SerialDate khác) {
330
return (this.serial <other.toSerial ());
331}
332
333 / **
334 * Trả về true nếu SerialDate này đại diện cho cùng một ngày với
335 * SerialDate được chỉ định.
336 *
337 * @param khác ngày được so sánh với.
338 *
339 * @return <code> true </code> nếu SerialDate này đại diện cho cùng một ngày
340 *
như SerialDate được chỉ định.
341 * /
342 public boolean isOnOrBefore (cuối cùng của SerialDate khác) {
343
return (this.serial <= other.toSerial ());
344}
345
346 / **
347 * Trả về true nếu SerialDate này đại diện cho cùng một ngày với
348 * SerialDate được chỉ định.
349 *
350 * @param khác ngày được so sánh với.
351 *
352 * @return <code> true </code> nếu SerialDate này đại diện cho cùng một ngày
353 *
như SerialDate được chỉ định.
354 * /
355 public boolean isAfter (cuối cùng của SerialDate khác) {
356
return (this.serial> other.toSerial ());
357}
358
359 / **
360 * Trả về true nếu SerialDate này đại diện cho cùng một ngày với
361 * SerialDate được chỉ định.
362 *
363 * @param khác ngày được so sánh với.
364 *
365 * @return <code> true </code> nếu SerialDate này đại diện cho cùng một ngày với
366 *
SerialDate được chỉ định.
367 * /
368 public boolean isOnOrAfter (cuối cùng của SerialDate khác) {
369
return (this.serial> = other.toSerial ());
370}
371
372 / **
373 * Trả về <code> true </code> nếu {@link SerialDate} này nằm trong
Liệt kê B-5 (tiếp theo)
SpreadsheetDate.java
www.it-ebooks.info

Trang 419
388
Phụ lục B: org.jfree.date.SerialDate
374 * phạm vi được chỉ định (BAO GỒM). Thứ tự ngày của d1 và d2 không phải là
375 * quan trọng.
376 *
377 * @param d1 ngày ranh giới cho phạm vi.
378 * @param d2 ngày ranh giới khác cho phạm vi.
379 *
380 * @return Một boolean.
381 * /
382 public boolean isInRange (cuối cùng SerialDate d1, cuối cùng SerialDate d2) {
383
return isInRange (d1, d2, SerialDate.INCLUDE_BOTH);
384}
385
386 / **
387 * Trả về true nếu SerialDate này nằm trong phạm vi được chỉ định (người gọi
388 * chỉ định xem có bao gồm các điểm cuối hay không). Thứ tự của d1
389 * và d2 không quan trọng.
390 *
391 * @param d1 một ngày ranh giới cho phạm vi.
392 * @param d2 ngày ranh giới thứ hai cho phạm vi.
393 * @param bao gồm một mã kiểm soát việc bắt đầu và kết thúc hay không
394 *
ngày được bao gồm trong phạm vi.
395 *
396 * @return <code> true </code> nếu SerialDate này nằm trong quy định
397 *
phạm vi.
398 * /
399 public boolean isInRange (cuối cùng SerialDate d1, cuối cùng SerialDate d2,
400
int cuối cùng bao gồm) {
401
cuối cùng int s1 = d1.toSerial ();
402
end int s2 = d2.toSerial ();
403
end int start = Math.min (s1, s2);
404
end int end = Math.max (s1, s2);
405
406
cuối cùng int s = toSerial ();
407
if (include == SerialDate.INCLUDE_BOTH) {
408
return (s> = start && s <= end);
409
}
410
khác nếu (bao gồm == SerialDate.INCLUDE_FIRST) {
411
return (s> = start && s <end);
412
}
413
else if (include == SerialDate.INCLUDE_SECOND) {
414
return (s> bắt đầu && s <= end);
415
}
416
khác {
417
return (s> start && s <end);
418
}
419}
420
421 / **
422 * Tính số thứ tự ngày, tháng, năm.
423 * <P>
424 * 1-Jan-1900 = 2.
425 *
426 * @param d trong ngày.
427 * @param m tháng.
428 * @param y năm.
429 *
430 * @ trả lại số sê-ri từ ngày, tháng và năm.
431 * /
432 private int calcSerial (cuối cùng int d, cuối cùng int m, cuối cùng int y) {
433
end int yy = ((y - 1900) * 365) + SerialDate.leapYearCount (y - 1);
434
int mm = SerialDate.AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH [m];
435
if (m> MonthConstants.FEBRUARY) {
Liệt kê B-5 (tiếp theo)
SpreadsheetDate.java
www.it-ebooks.info

Trang 420
389
Phụ lục B: org.jfree.date.SerialDate
436
if (SerialDate.isLeapYear (y)) {
437
mm = mm + 1;
438
}
439
}
440
cuối cùng int dd = d;
441
trả về yy + mm + dd + 1;
442}
443
444 / **
445 * Tính ngày, tháng, năm từ dãy số.
446 * /
447 private void calcDayMonthYear () {
448
449
// lấy năm từ ngày nối tiếp
450
int days = this.serial - SERIAL_LOWER_BOUND;
451
// được đánh giá quá cao vì chúng tôi đã bỏ qua ngày nhuận
452
int cuối cùng được đánh giá quá caoYYYY = 1900 + (ngày / 365);
453
last int leaps = SerialDate.leapYearCount (đánh giá quá caoYYYY);
454
cuối cùng int nonleapdays = ngày - bước nhảy vọt;
455
// bị đánh giá thấp vì chúng tôi đã đánh giá quá cao các năm
456
int bị đánh giá thấpYYYY = 1900 + (nonleapdays / 365);
457
458
if (đánh giá thấpYYYY == đánh giá quá thấpYYYY) {
459
this.year = đánh giá thấpYYYY;
460
}
461
khác {
462
int ss1 = calcSerial (1, 1, đánh giá thấpYYYY);
463
while (ss1 <= this.serial) {
464
bị đánh giá thấpYYYY = bị đánh giá thấpYYYY + 1;
465
ss1 = calcSerial (1, 1, đánh giá thấpYYYY);
466
}
467
this.year = bị đánh giá thấpYYYY - 1;
468
}
469
470
end int ss2 = calcSerial (1, 1, this.year);
471
472
int [] daysToEndOfPrecedingMonth
473
= AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH;
474
475
if (isLeapYear (this.year)) {
476
daysToEndOfPrecedingMonth
477
= LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH;
478
}
479
480
// lấy tháng từ ngày nối tiếp
481
int mm = 1;
482
int sss = ss2 + daysToEndOfPrecedingMonth [mm] - 1;
483
while (sss <this.serial) {
484
mm = mm + 1;
485
sss = ss2 + daysToEndOfPrecedingMonth [mm] - 1;
486
}
487
this.month = mm - 1;
488
489
// những gì còn lại là d (+1);
490
this.day = this.serial - ss2
491
- daysToEndOfPrecedingMonth [this.month] + 1;
492
493}
494
495}
Liệt kê B-5 (tiếp theo)
SpreadsheetDate.java
www.it-ebooks.info
Trang 421
390
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-6
RelativeDayOfWeekRule.java
1 / * ================================================= =========================
2 * JCommon: thư viện lớp đa năng miễn phí cho nền tảng Java (tm)
3 * ================================================ ========================
4*
Bản quyền 5 * (C) 2000-2005, bởi Object Refinery Limited và những người đóng góp.
6*
7 * Thông tin dự án: http://www.jfree.org/jcommon/index.html
số 8 *
9 * Thư viện này là phần mềm miễn phí; bạn có thể phân phối lại nó và / hoặc sửa đổi nó
10 * theo các điều khoản của Giấy phép Công cộng Ít hơn GNU do
11 * Tổ chức Phần mềm Tự do; phiên bản 2.1 của Giấy phép hoặc
12 * (theo tùy chọn của bạn) bất kỳ phiên bản nào mới hơn.
13 *
14 * Thư viện này được phân phối với hy vọng rằng nó sẽ hữu ích, nhưng
15 * KHÔNG CÓ BẤT KỲ BẢO HÀNH NÀO; thậm chí không có bảo đảm ngụ ý về KHẢ NĂNG LẠNH
16 * hoặc PHÙ HỢP VỚI MỤC ĐÍCH CỤ THỂ. Xem GNU Lesser General Public
17 * Giấy phép để biết thêm chi tiết.
18 *
19 * Bạn nên nhận được một bản sao của GNU Lesser General Public
20 * Giấy phép cùng với thư viện này; nếu không, hãy viết thư cho Phần mềm Miễn phí
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
22 * Hoa Kỳ.
23 *
24 * [Java là nhãn hiệu hoặc nhãn hiệu đã đăng ký của Sun Microsystems, Inc.
25 * tại Hoa Kỳ và các quốc gia khác.]
26 *
27 * --------------------------
28 * RelativeDayOfWeekRule.java
29 * --------------------------
30 * (C) Bản quyền 2000-2003, bởi Object Refinery Limited và những người đóng góp.
31 *
32 * Tác giả gốc: David Gilbert (cho Object Refinery Limited);
33 * (Các) người đóng góp: -;
34 *
35 * $ Id: RelativeDayOfWeekRule.java, v 1.6 2005/11/16 15:58:40 taqua Exp $
36 *
37 * Thay đổi (từ 26-10-2001)
38 * --------------------------
39 * 26-10-2001: Đã thay đổi gói thành com.jrefinery.date. *;
40 * 03-10-2002: Đã sửa các lỗi do Checkstyle (DG) báo cáo;
41 *
42 * /
43
44 gói org.jfree.date;
45
46 / **
47 * Quy tắc ngày hàng năm trả về ngày cho mỗi năm dựa trên (a) a
48 * quy tắc tham chiếu; (b) một ngày trong tuần; và (c) một tham số lựa chọn
49 * (SerialDate.PRECEDING, SerialDate.NEAREST, SerialDate.FOLLOWING).
50 * <P>
51 * Ví dụ: Thứ Sáu Tuần Thánh có thể được chỉ định là 'Thứ Sáu Lễ Phục Sinh CHÍNH XÁC
52 * Chủ nhật '.
53 *
54 * @author David Gilbert
55 * /
56 lớp công khai RelativeDayOfWeekRule mở rộng Quy tắc hàng năm {
57
58 / ** Tham chiếu đến quy tắc ngày hàng năm dựa trên quy tắc này. * /
59 trang con riêng của Quy tắc hàng năm;
60
61 / **
62 * Ngày trong tuần (SerialDate.MONDAY, SerialDate.TUESDAY, v.v.).
www.it-ebooks.info

Trang 422
391
Phụ lục B: org.jfree.date.SerialDate
63 * /
64 private int dayOfWeek;
65
66 / ** Chỉ định ngày nào trong tuần (CHÍNH XÁC, GẦN NHẤT hoặc SAU ĐÂY). * /
67 private int tương đối;
68
69 / **
70 * Hàm tạo mặc định - xây dựng quy tắc cho Thứ Hai sau ngày 1 tháng Một.
71 * /
72 public RelativeDayOfWeekRule () {
73
this (DayAndMonthRule mới (), SerialDate.MONDAY, SerialDate.FOLLOWING);
74}
75
76 / **
77 * Hàm tạo chuẩn - xây dựng quy tắc dựa trên quy tắc phụ được cung cấp.
78 *
79 * @param phụ quy tắc xác định ngày tham chiếu.
80 * @param dayOfXem ngày trong tuần so với ngày tham chiếu.
81 * @param tương đối cho biết * nào * ngày trong tuần (trước, gần nhất
82 *
hoặc sau đây).
83 * /
84 public RelativeDayOfWeekRule (phân loại cuối cùng của YearDateRule,
85
cuối cùng int dayOfWeek, int tương đối cuối cùng) {
86
this.subrule = subrule;
87
this.dayOfWeek = dayOfWeek;
88
this.relative = tương đối;
89}
90
91 / **
92 * Trả về quy tắc phụ (còn được gọi là quy tắc tham chiếu).
93 *
94 * @return Quy tắc ngày hàng năm xác định ngày tham chiếu cho việc này
95 *
qui định.
96 * /
97 public dailyDateRule getSubrule () {
98
trả lại this.subrule;
99}
100
101 / **
102 * Đặt quy tắc phụ.
103 *
104 * @param phụ quy tắc ngày hàng năm xác định ngày tham chiếu
105 *
cho quy tắc này.
106 * /
107 public void setSubrule (trang con cuối cùng của Quy tắc hàng năm) {
108
this.subrule = subrule;
109}
110
111 / **
112 * Trả về ngày trong tuần cho quy tắc này.
113 *
114 * @ quay lại ngày trong tuần cho quy tắc này.
115 * /
116 public int getDayOfWeek () {
117
trả về this.dayOfWeek;
118}
119
120 / **
121 * Đặt ngày trong tuần cho quy tắc này.
122 *
123 * @param dayOfXem các ngày trong tuần (SerialDate.MONDAY,
124 *
SerialDate.TUESDAY, v.v.).
Liệt kê B-6 (tiếp theo)
RelativeDayOfWeekRule.java
www.it-ebooks.info
Trang 423
392
Phụ lục B: org.jfree.date.SerialDate
125 * /
126 public void setDayOfWeek (final int dayOfWeek) {
127
this.dayOfWeek = dayOfWeek;
128}
129
130 / **
131 * Trả về thuộc tính 'tương đối', xác định * cái nào *
132 * ngày trong tuần mà chúng tôi quan tâm (SerialDate .RECEDING,
133 * SerialDate.NEAREST hoặc SerialDate.FOLLOWING).
134 *
135 * @return Thuộc tính 'tương đối'.
136 * /
137 public int getRelative () {
138
trả về this.relative;
139}
140
141 / **
142 * Đặt thuộc tính 'tương đối' (SerialDate.PRECEDING, SerialDate.NEAREST,
143 * SerialDate.FOLLOWING).
144 *
145 * @param tương đối xác định * * ngày trong tuần được chọn bởi cái này
146 *
qui định.
147 * /
148 public void setRelative (cuối cùng int tương đối) {
149
this.relative = tương đối;
150}
151
152 / **
153 * Tạo bản sao của quy tắc này.
154 *
155 * @ quay lại bản sao của quy tắc này.
156 *
157 * @throws CloneNotSupportedException điều này sẽ không bao giờ xảy ra.
158 * /
159 public Object clone () ném CloneNotSupportedException {
160
bản sao cuối cùng RelativeDayOfWeekRule
161
= (RelativeDayOfWeekRule) super.clone ();
162
trùng lặp.subrule = (Quy tắc hàng năm) trùng lặp.getSubrule (). clone ();
163
trả lại bản sao;
164}
165
166 / **
167 * Trả về ngày được tạo bởi quy tắc này, cho năm đã chỉ định.
168 *
169 * @param năm năm (1900 & lt; = year & lt; = 9999).
170 *
171 * @return Ngày được tạo bởi quy tắc cho năm nhất định (có thể
172 *
<code> null </code>).
173 * /
174 public SerialDate getDate (cuối năm) {
175
176
// kiểm tra đối số ...
177
if ((năm <SerialDate.MINIMUM_YEAR_SUPPORTED)
178
|| (năm> SerialDate.MAXIMUM_YEAR_SUPPORTED)) {
179
ném IllegalArgumentException mới (
180
"RelativeDayOfWeekRule.getDate (): năm ngoài phạm vi hợp lệ.");
181
}
182
183
// tính toán ngày ...
184
Kết quả SerialDate = null;
185
cuối cùng SerialDate base = this.subrule.getDate (năm);
186
Liệt kê B-6 (tiếp theo)
RelativeDayOfWeekRule.java
www.it-ebooks.info

Trang 424
393
Phụ lục B: org.jfree.date.SerialDate
187
if (base! = null) {
188
chuyển đổi (this.relative) {
189
trường hợp (SerialDate.PRECEDING):
190
result = SerialDate.getPreviousDayOfWeek (this.dayOfWeek,
191
căn cứ);
192
phá vỡ;
193
trường hợp (SerialDate.NEAREST):
194
result = SerialDate.getNearestDayOfWeek (this.dayOfWeek,
195
căn cứ);
196
phá vỡ;
197
trường hợp (SerialDate.FOLLOWING):
198
result = SerialDate.getFollowingDayOfWeek (this.dayOfWeek,
199
căn cứ);
200
phá vỡ;
201
mặc định:
202
phá vỡ;
203
}
204
}
205
trả về kết quả;
206
207}
208
209}
Liệt kê B-6 (tiếp theo)
RelativeDayOfWeekRule.java
www.it-ebooks.info

Trang 425
394
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-7
DayDate.java (Cuối cùng)
1 / * ================================================= =========================
2 * JCommon: thư viện lớp đa năng miễn phí cho nền tảng Java (tm)
3 * ================================================ ========================
4*
Bản quyền 5 * (C) 2000-2005, bởi Object Refinery Limited và những người đóng góp.
...
36 * /
37 gói org.jfree.date;
38
39 nhập java.io.Serializable;
40 nhập java.util. *;
41
42 / **
43 * Một lớp trừu tượng đại diện cho ngày tháng không thay đổi với độ chính xác là
44 * một ngày. Việc triển khai sẽ ánh xạ mỗi ngày thành một số nguyên
45 * đại diện cho số ngày theo thứ tự từ một số điểm gốc cố định.
46 *
47 * Tại sao không chỉ sử dụng java.util.Date? Chúng tôi sẽ làm, khi nó có ý nghĩa. Đôi khi,
48 * java.util.Date có thể * quá * chính xác - nó đại diện cho một thời điểm tức thì,
49 * chính xác đến 1/1000 giây (với chính ngày tháng tùy thuộc vào
50 * múi giờ). Đôi khi chúng ta chỉ muốn đại diện cho một ngày cụ thể (ví dụ: 21
51 * Tháng 1 năm 2015) mà không liên quan đến bản thân về thời gian trong ngày, hoặc
Múi giờ 52 * hoặc bất kỳ thứ gì khác. Đó là những gì chúng tôi đã xác định DayDate cho.
53 *
54 * Sử dụng DayDateFactory.makeDate để tạo một phiên bản.
55 *
56 * @author David Gilbert
57 * @author Robert C. Martin đã tái cấu trúc rất nhiều.
58 * /
59
60 lớp trừu tượng công khai DayDate triển khai Có thể so sánh, Có thể nối tiếp {
61 public abstract int getOrdinalDay ();
62 public abstract int getYear ();
63 public abstract Tháng getMonth ();
64 public abstract int getDayOfMonth ();
65
66 Ngày trừu tượng được bảo vệ getDayOfWeekForOrdinalZero ();
67
68 ngày công khai Ngày cộng với Ngày (số ngày) {
69 return DayDateFactory.makeDate (getOrdinalDay () + ngày);
70}
71
72 ngày công khai Ngày cộng với tháng (trong tháng) {
73 int thisMonthAsOrdinal = getMonth (). ToInt () - Month.JANUARY.toInt ();
74 int thisMonthAndYearAsOrdinal = 12 * getYear () + thisMonthAsOrdinal;
75 int resultMonthAndYearAsOrdinal = thisMonthAndYearAsOrdinal + tháng;
76 int resultYear = resultMonthAndYearAsOrdinal / 12;
77 int resultMonthAsOrdinal = resultMonthAndYearAsOrdinal% 12 + Tháng.JANUARY.toInt ();
78 Tháng kết quảMonth = Month.fromInt (resultMonthAsOrdinal);
79 int resultDay = correctLastDayOfMonth (getDayOfMonth (), resultMonth, resultYear);
80 return DayDateFactory.makeDate (resultDay, resultMonth, resultYear);
81}
82
83 Ngày công cộng Ngày cộng năm (các năm) {
84 int resultYear = getYear () + năm;
85 int resultDay = correctLastDayOfMonth (getDayOfMonth (), getMonth (), resultYear);
86 return DayDateFactory.makeDate (resultDay, getMonth (), resultYear);
87}
88
89 private int correctLastDayOfMonth (int day, int month, int year) {
90 int lastDayOfMonth = DateUtil.lastDayOfMonth (tháng, năm);
91 if (day> lastDayOfMonth)
www.it-ebooks.info

Trang 426
395
Phụ lục B: org.jfree.date.SerialDate
92
ngày = lastDayOfMonth;
93 ngày trở lại;
94}
95
96 ngày công khai Ngày nhậnPre trướcDayOfWeek (Mục tiêu ngàyDayOfWeek) {
97 int offsetToTarget = targetDayOfWeek.toInt () - getDayOfWeek (). ToInt ();
98 if (offsetToTarget> = 0)
99 offsetToTarget - = 7;
100 trả về plusDays (offsetToTarget);
101}
102
103 public DayDate getFollowingDayOfWeek (Mục tiêu ngàyDayOfWeek) {
104 int offsetToTarget = targetDayOfWeek.toInt () - getDayOfWeek (). ToInt ();
105 if (offsetToTarget <= 0)
106 offsetToTarget + = 7;
107 return plusDays (offsetToTarget);
108}
109
110 Ngày công khai Ngày nhậnNearestDayOfWeek (Mục tiêu ngàyDayOfWeek) {
111 int offsetToThisWeeksTarget = targetDayOfWeek.toInt () - getDayOfWeek (). ToInt ();
112 int offsetToFutureTarget = (offsetToThisWeeksTarget + 7)% 7;
113 int offsetToPreviousTarget = offsetToFutureTarget - 7;
114
115 nếu (offsetToFutureTarget> 3)
116 return plusDays (offsetToPreviousTarget);
117 khác
118 return plusDays (offsetToFutureTarget);
119}
120
121 public DayDate getEndOfMonth () {
122 Tháng tháng = getMonth ();
123 int year = getYear ();
124 int lastDay = DateUtil.lastDayOfMonth (tháng, năm);
125 return DayDateFactory.makeDate (Ngày cuối cùng, tháng, năm);
126}
127
128 public Date toDate () {
129 lịch cuối cùng Lịch = Calendar.getInstance ();
130 int ordinalMonth = getMonth (). ToInt () - Tháng.JANUARY.toInt ();
131 calendar.set (getYear (), ordinalMonth, getDayOfMonth (), 0, 0, 0);
132 trả về lịch.getTime ();
133}
134
135 chuỗi công khai toString () {
136 return String.format ("% 02d-% s-% d", getDayOfMonth (), getMonth (), getYear ());
137}
138
139 ngày công nhận getDayOfWeek () {
140 Ngày bắt đầuDay = getDayOfWeekForOrdinalZero ();
141 int startOffset = startDay.toInt () - Day.SUNDAY.toInt ();
142 int ordinalOfDayOfWeek = (getOrdinalDay () + startOffset)% 7;
143 return Day.fromInt (ordinalOfDayOfWeek + Day.SUNDAY.toInt ());
144}
145
146 public int daysSince (DayDate ngày) {
147 trả về getOrdinalDay () - date.getOrdinalDay ();
148}
149
150 public boolean isOn (DayDate khác) {
151 trả về getOrdinalDay () == other.getOrdinalDay ();
152}
153
Liệt kê B-7 (tiếp theo)
DayDate.java (Cuối cùng)
www.it-ebooks.info

Trang 427
396
Phụ lục B: org.jfree.date.SerialDate
154 public boolean isBefore (DayDate khác) {
155 trả về getOrdinalDay () <other.getOrdinalDay ();
156}
157
158 public boolean isOnOrBefore (DayDate khác) {
159 trả về getOrdinalDay () <= other.getOrdinalDay ();
160}
161
162 public boolean isAfter (Ngày khác) {
163 trả về getOrdinalDay ()> other.getOrdinalDay ();
164}
165
166 public boolean isOnOrAfter (Ngày khác) {
167 trả về getOrdinalDay ()> = other.getOrdinalDay ();
168}
169
170 public boolean isInRange (DayDate d1, DayDate d2) {
171 trả về isInRange (d1, d2, DateInterval.CLOSED);
172}
173
174 public boolean isInRange (DayDate d1, DayDate d2, DateInterval khoảng thời gian) {
175 int left = Math.min (d1.getOrdinalDay (), d2.getOrdinalDay ());
176 int right = Math.max (d1.getOrdinalDay (), d2.getOrdinalDay ());
177 trả về khoảng thời gian.isIn (getOrdinalDay (), left, right);
178}
179}
Liệt kê B-7 (tiếp theo)
DayDate.java (Cuối cùng)
www.it-ebooks.info

Trang 428
397
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-8
Month.java (Cuối cùng)
1 gói org.jfree.date;
2
3 nhập java.text.DateFormatSymbols;
4
5 tháng enum công khai {
6 THÁNG 1 (1), THÁNG 2 (2), THÁNG 3 (3),
7 THÁNG 4 (4), THÁNG 5 (5), THÁNG 6 (6),
8 JULY (7), August (8), SEPTEMBER (9),
9 THÁNG 10 (10), THÁNG 11 (11), THÁNG 12 (12);
10 private static DateFormatSymbols dateFormatSymbols = new DateFormatSymbols ();
11 private static final int [] LAST_DAY_OF_MONTH =
12 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
13
14 private int index;
15
16 Tháng (int index) {
17 this.index = index;
18}
19
20 tháng tĩnh công khai từInt (int monthIndex) {
21 cho (Tháng m: Tháng.values ()) {
22 if (m.index == monthIndex)
23
trả lại m;
24}
25 ném mới IllegalArgumentException ("Chỉ mục tháng không hợp lệ" + monthIndex);
26}
27
28 public int lastDay () {
29 trả về LAST_DAY_OF_MONTH [chỉ mục];
30}
31
32 public int 1/4 () {
33 return 1 + (chỉ mục - 1) / 3;
34}
35
36 chuỗi công khai toString () {
37 return dateFormatSymbols.getMonths () [index - 1];
38}
39
40 chuỗi công khai toShortString () {
41 return dateFormatSymbols.getShortMonths () [index - 1];
42}
43
44 phân tích cú pháp tháng tĩnh công khai (Chuỗi) {
45 s = s.trim ();
46 cho (Tháng m: Tháng. Giá trị ())
47 nếu (m.m phù hợp)
48
trả lại m;
49
50 thử {
51 return fromInt (Integer.parseInt (s));
52}
53 catch (NumberFormatException e) {}
54 ném mới IllegalArgumentException ("Tháng không hợp lệ" + s);
55}
56
57 trận đấu boolean riêng tư (Chuỗi) {
58 trả về s.equalsIgnoreCase (toString ()) ||
59
s.equalsIgnoreCase (toShortString ());
60}
61
62 public int toInt () {
63 chỉ số trả lại;
64}
65}
www.it-ebooks.info

Trang 429
398
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-9
Day.java (Cuối cùng)
1 gói org.jfree.date;
2
3 nhập java.util.Calendar;
4 nhập java.text.DateFormatSymbols;
5
6 ngày công khai {
7 THỨ HAI (Calendar.MONDAY),
8 THỨ TƯ (Lịch. NGÀY HÔM NAY),
9 THỨ TƯ (Lịch. NGÀY THỨ TƯ),
10 THỨ NĂM (Lịch. THỨ NĂM),
11 THỨ SÁU (Calendar.FRIDAY),
12 THỨ BẢY (Lịch. NGÀY THỨ BẢY),
13 CHỦ NHẬT (Dương lịch);
14
15 private end int index;
16 private static DateFormatSymbols dateSymbols = new DateFormatSymbols ();
17
18 ngày (ngày nguyên) {
19 chỉ số = ngày;
20}
21
22 public static Day fromInt (int index) ném IllegalArgumentException {
23 cho (Day d: Day.values ())
24 if (d.index == index)
25
trả lại d;
26 ném IllegalArgumentException mới (
27 String.format ("Chỉ mục ngày bất hợp pháp:% d.", Chỉ mục));
28}
29
30 phân tích cú pháp ngày tĩnh công khai (Các chuỗi) ném IllegalArgumentException {
31 Chuỗi [] shortWeekdayNames =
32 dateSymbols.getShortWeekdays ();
33 String [] weekDayNames =
34 dateSymbols.getWeekdays ();
35
36 s = s.trim ();
37 cho (Day day: Day.values ()) {
38 if (s.equalsIgnoreCase (shortWeekdayNames [day.index]) ||
39
s.equalsIgnoreCase (weekDayNames [day.index])) {
40
ngày trở về;
41}
42}
43 ném IllegalArgumentException mới (
44 String.format ("% s không phải là chuỗi ngày trong tuần hợp lệ", s));
45}
46
47 public String toString () {
48 ngày trả vềSymbols.getWeekdays () [index];
49}
50
51 public int toInt () {
52 chỉ số trả lại;
53}
54}
www.it-ebooks.info

Trang 430
399
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-10
DateInterval.java (Cuối cùng)
1 gói org.jfree.date;
2
3 public enum DateInterval {
4 MỞ {
5 public boolean isIn (int d, int left, int right) {
6 return d> left && d <right;
7}
số 8 },
9 CLOSED_LEFT {
10 public boolean isIn (int d, int left, int right) {
11 return d> = left && d <right;
12}
13},
14 CLOSED_RIGHT {
15 public boolean isIn (int d, int left, int right) {
16 return d> left && d <= right;
17}
18},
19 ĐÃ ĐÓNG CỬA {
20 public boolean isIn (int d, int left, int right) {
21 return d> = left && d <= right;
22}
23};
24
25 public abstract boolean isIn (int d, int left, int right);
26}
www.it-ebooks.info

Trang 431
400
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-11
WeekInMonth.java (Cuối cùng)
1 gói org.jfree.date;
2
3 public enum WeekInMonth {
4 FIRST (1), SECOND (2), THIRD (3), FOURTH (4), LAST (0);
5 private end int index;
6
7 WeekInMonth (int index) {
8 this.index = index;
9}
10
11 public int toInt () {
12 chỉ số trở lại;
13}
14}
www.it-ebooks.info

Trang 432
401
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-12
WeekdayRange.java (Cuối cùng)
1 gói org.jfree.date;
2
3 public enum WeekdayRange {
4 CUỐI CÙNG, GẦN NHẤT, TIẾP THEO
5}
www.it-ebooks.info

Trang 433
402
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-13
DateUtil.java (Cuối cùng)
1 gói org.jfree.date;
2
3 nhập java.text.DateFormatSymbols;
4
5 lớp công khai DateUtil {
6 private static DateFormatSymbols dateFormatSymbols = new DateFormatSymbols ();
7
8 chuỗi tĩnh công khai [] getMonthNames () {
9 ngày trả vềFormatSymbols.getMonths ();
10}
11
12 public static boolean isLeapYear (int year) {
13 boolean thứ tư = năm% 4 == 0;
14 boolean trămth = năm% 100 == 0;
15 boolean fourH trăm = năm% 400 == 0;
16 trả về thứ tư && (! Trămth || bốnH trăm);
17}
18
19 public static int lastDayOfMonth (Tháng tháng, năm) {
20 if (tháng == Tháng.FEBRUARY && isLeapYear (năm))
21 return month.lastDay () + 1;
22 người khác
23 return month.lastDay ();
24}
25
26 public static int leapYearCount (int year) {
27 int leap4 = (năm - 1896) / 4;
28 int leap100 = (năm - 1800) / 100;
29 int leap400 = (năm - 1600) / 400;
30 trở lại leap4 - nhuận 100 + nhuận 400;
31}
32}
www.it-ebooks.info

Trang 434
403
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-14
DayDateFactory.java (Cuối cùng)
1 gói org.jfree.date;
2
3 lớp trừu tượng công khai DayDateFactory {
4 private static DayDateFactory factory = new SpreadsheetDateFactory ();
5 public static void setInstance (nhà máy DayDateFactory) {
6 DayDateFactory.factory = nhà máy;
7}
số 8
9 trừu tượng được bảo vệ DayDate _makeDate (int ordinal);
10 trừu tượng được bảo vệ DayDate _makeDate (ngày, tháng tháng, năm);
11 trừu tượng được bảo vệ DayDate _makeDate (int day, int month, int year);
12 ngày trừu tượng được bảo vệ DayDate _makeDate (ngày java.util.Date);
13 trừu tượng được bảo vệ int _getMinimumYear ();
14 trừu tượng được bảo vệ int _getMaximumYear ();
15
16 public static DayDate makeDate (int ordinal) {
17 return factory._makeDate (thứ tự);
18}
19
20 public static DayDate makeDate (ngày, tháng tháng, năm) {
21 return factory._makeDate (ngày, tháng, năm);
22}
23
24 public static DayDate makeDate (int day, int month, int year) {
25 return factory._makeDate (ngày, tháng, năm);
26}
27
28 public static DayDate makeDate (java.util.Date date) {
29 return factory._makeDate (ngày);
30}
31
32 public static int getMinimumYear () {
33 return factory._getMinimumYear ();
34}
35
36 public static int getMaximumYear () {
37 return factory._getMaximumYear ();
38}
39}
www.it-ebooks.info

Trang 435
404
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-15
SpreadsheetDateFactory.java (Cuối cùng)
1 gói org.jfree.date;
2
3 nhập java.util. *;
4
5 lớp công khai SpreadsheetDateFactory mở rộng DayDateFactory {
6 Ngày công khai _makeDate (int ordinal) {
7 trả về SpreadsheetDate mới (thứ tự);
số 8 }
9
10 public DayDate _makeDate (ngày, tháng tháng, năm) {
11 trả về SpreadsheetDate mới (ngày, tháng, năm);
12}
13
14 public DayDate _makeDate (int day, int month, int year) {
15 trả về SpreadsheetDate mới (ngày, tháng, năm);
16}
17
18 public DayDate _makeDate (Ngày tháng) {
19 lịch GregorianCalendar cuối cùng = new GregorianCalendar ();
20 calendar.setTime (ngày);
21 trả về SpreadsheetDate mới (
22 calendar.get (Calendar.DATE),
23 Tháng.fromInt (calendar.get (Calendar.MONTH) + 1),
24 Calendar.get (Lịch.YEAR));
25}
26
27 được bảo vệ int _getMinimumYear () {
28 trả về SpreadsheetDate.MINIMUM_YEAR_SUPPORTED;
29}
30
31 được bảo vệ int _getMaximumYear () {
32 trả về SpreadsheetDate.MAXIMUM_YEAR_SUPPORTED;
33}
34}
www.it-ebooks.info

Trang 436
405
Phụ lục B: org.jfree.date.SerialDate
Liệt kê B-16
SpreadsheetDate.java (Cuối cùng)
1 / * ================================================= =========================
2 * JCommon: thư viện lớp đa năng miễn phí cho nền tảng Java (tm)
3 * ================================================ ========================
4*
Bản quyền 5 * (C) 2000-2005, bởi Object Refinery Limited và những người đóng góp.
6*
...
52 *
53 * /
54
55 gói org.jfree.date;
56
57 nhập static org.jfree.date.Month.FEBRUARY;
58
59 nhập java.util. *;
60
61 / **
62 * Đại diện cho một ngày bằng cách sử dụng một số nguyên, theo kiểu tương tự như
63 * thực hiện trong Microsoft Excel. Phạm vi ngày được hỗ trợ là
64 * 1-Jan-1900 đến 31-Dec-9999.
65 * <p />
66 * Hãy lưu ý rằng có một lỗi cố ý trong Excel nhận dạng năm
67 * 1900 là một năm nhuận trong khi thực tế nó không phải là một năm nhuận. Bạn có thể tìm thêm
68 * thông tin trên trang web của Microsoft trong bài viết Q181370:
69 * <p />
70 * http://support.microsoft.com/support/kb/articles/Q181/3/70.asp
71 * <p />
72 * Excel sử dụng quy ước 1-Jan-1900 = 1. Lớp này sử dụng
73 * quy ước 1-Jan-1900 = 2.
74 * Kết quả là số ngày trong lớp này sẽ khác với
75 * Số liệu Excel cho tháng 1 và tháng 2 năm 1900 ... nhưng sau đó Excel thêm vào
76 * ngày (29-02-1900 không thực sự tồn tại!) Và từ thời điểm đó trở đi
77 * các con số trong ngày sẽ khớp.
78 *
79 * @author David Gilbert
80 * /
81 public class SpreadsheetDate mở rộng DayDate {
82 public static final int EARLIEST_DATE_ORDINAL = 2; // 1/1/1900
83 public static final int LATEST_DATE_ORDINAL = 2958465; // 12/31/9999
84 public static final int MINIMUM_YEAR_SUPPORTED = 1900;
85 public static final int MAXIMUM_YEAR_SUPPORTED = 9999;
86 static cuối cùng int [] AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH =
87 {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
88 tĩnh cuối cùng int [] LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH =
89 {0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
90
91 private int ordinalDay;
92 private int day;
93 tháng riêng tư tháng;
94 private int năm;
95
96 public SpreadsheetDate (ngày, tháng tháng, năm) {
97 nếu (năm <MINIMUM_YEAR_SUPPORTED || năm> MAXIMUM_YEAR_SUPPORTED)
98 ném IllegalArgumentException mới (
99
"Đối số 'năm' phải nằm trong phạm vi" +
100
MINIMUM_YEAR_SUPPORTED + "đến" + MAXIMUM_YEAR_SUPPORTED + ".");
101 if (day <1 || day> DateUtil.lastDayOfMonth (tháng, năm))
102 ném IllegalArgumentException mới ("Đối số 'ngày' không hợp lệ.");
103
104 this.year = năm;
105 this.month = tháng;
www.it-ebooks.info

Trang 437
406
Phụ lục B: org.jfree.date.SerialDate
106 this.day = ngày;
107 ordinalDay = calcOrdinal (ngày, tháng, năm);
108}
109
110 public SpreadsheetDate (int day, int month, int year) {
111 this (day, Month.fromInt (tháng), năm);
112}
113
114 public SpreadsheetDate (int serial) {
115 nếu (nối tiếp <EARLIEST_DATE_ORDINAL || nối tiếp> LATEST_DATE_ORDINAL)
116 ném IllegalArgumentException mới (
117
"SpreadsheetDate: Serial phải nằm trong khoảng từ 2 đến 2958465.");
118
119 ordinalDay = nối tiếp;
120 calcDayMonthYear ();
121}
122
123 public int getOrdinalDay () {
Chương 124 trở về thứ tự ngày;
125}
126
127 public int getYear () {
128 năm trở lại;
129}
130
131 tháng công khai getMonth () {
132 tháng trở lại;
133}
134
135 public int getDayOfMonth () {
136 ngày trở lại;
137}
138
139 Ngày được bảo vệ getDayOfWeekForOrdinalZero () {return Day.SATURDAY;}
140
141 public boolean bằng (Đối tượng đối tượng) {
142 if (! (Object instanceof DayDate))
Chương 143
144
145 đối tượng DayDate date = (DayDate);
146 return date.getOrdinalDay () == getOrdinalDay ();
147}
148
149 public int hashCode () {
150 trả về getOrdinalDay ();
151}
152
153 public int so sánhTo (Đối tượng khác) {
154 ngày trở lạiSince ((DayDate) other);
155}
156
157 private int calcOrdinal (int day, int month, int year) {
158 int leapDaysForYear = DateUtil.leapYearCount (năm - 1);
159 int daysUpToYear = (year - MINIMUM_YEAR_SUPPORTED) * 365 + leapDaysForYear;
160 int daysUpToMonth = AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH [month.toInt ()];
161 if (DateUtil.isLeapYear (year) && month.toInt ()> FEBRUARY.toInt ())
162 ngàyUpToMonth ++;
163 int daysInMonth = day - 1;
164 ngày quay lạiUpToYear + ngàyUpToMonth + ngàyInMonth + EARLIEST_DATE_ORDINAL;
165}
166
Liệt kê B-16 (tiếp theo)
SpreadsheetDate.java (Cuối cùng)
www.it-ebooks.info

Trang 438
407
Phụ lục B: org.jfree.date.SerialDate
167 private void calcDayMonthYear () {
168 int days = ordinalDay - EARLIEST_DATE_ORDINAL;
169 int quá mức ước tínhYear = MINIMUM_YEAR_SUPPORTED + ngày / 365;
170 int nonleapdays = days - DateUtil.leapYearCount (đánh giá quá cao năm);
171 int bị đánh giá thấpYear = MINIMUM_YEAR_SUPPORTED + nonleapdays / 365;
172
173 năm = hunterForYearContainer (ordinalDay, đánh giá thấp hơnYear);
174 int firstOrdinalOfYear = firstOrdinalOfYear (năm);
175 tháng = hunterForMonthContaining (ordinalDay, firstOrdinalOfYear);
176 ngày = ordinalDay - firstOrdinalOfYear - daysBeforeThisMonth (month.toInt ());
177}
178
179 tháng riêng tư HuntForMonthContaining (int anOrdinal, int firstOrdinalOfYear) {
180 int daysIntoThisYear = anOrdinal - firstOrdinalOfYear;
181 int aMonth = 1;
182 while (daysBeforeThisMonth (aMonth) <daysIntoThisYear)
Chương 183 một tháng ++;
184
185 trả lại Month.fromInt (aMonth - 1);
186}
187
188 private int daysBeforeThisMonth (int aMonth) {
189 if (DateUtil.isLeapYear (năm))
190 lượt trả lại LEAP_YEAR_AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH [aMonth] - 1;
191 khác
192 trả về AGGREGATE_DAYS_TO_END_OF_PRECEDING_MONTH [aMonth] - 1;
193}
194
195 private int hunterForYearContaining (int anOrdinalDay, int startedYear) {
196 int aYear = startYear;
197 trong khi (firstOrdinalOfYear (aYear) <= anOrdinalDay)
198 năm ++;
199
200 trả về aYear - 1;
201}
202
203 private int firstOrdinalOfYear (int year) {
204 return calcOrdinal (1, Month.JANUARY, year);
205}
206
207 public static DayDate createInstance (Ngày tháng) {
208 Lịch GregorianCalendar = new GregorianCalendar ();
209 calendar.setTime (ngày);
210 trả về SpreadsheetDate mới (calendar.get (Calendar.DATE),
211
Month.fromInt (calendar.get (Calendar.MONTH) + 1),
212
Calendar.get (Lịch.YEAR));
213
214}
215}
Liệt kê B-16 (tiếp theo)
SpreadsheetDate.java (Cuối cùng)
www.it-ebooks.info

Trang 439
Trang này cố ý để trống
www.it-ebooks.info

Trang 440
409

Phụ lục C
Tham khảo chéo của Heuristics
Tham khảo chéo về Mùi và phương pháp chẩn đoán. Tất cả các tham chiếu chéo khác có thể bị xóa.
C1 ................................................... ...................... 16-276, 16-279, 17-292
C2 ................................................... ........ 16-279, 16-285, 16-295, 17-292
C3 ................................................... ........ 16-283, 16-285, 16-288, 17-293
C4 ... ................................................ 17- 293
C5 ... ................................................ 17- 293
E1 ................................................. ................................................ 17- 294
E2 ................................................... ................................................ 17- 294
F1 ... ................................... 14-239, 17-295
F2 ................................................... ................................................ 17- 295
F3 ... ................................................ 17- 295
F4 ............................... 14-289, 16-273, 16-285, 16-287, 16- 288, 17-295
G1 ................................................... ................................... 16-276, 17-295
G2 ................................................... ...................... 16-273, 16-274, 17-296
Phòng 3 ................................................... ................................... 16-274, 17-296
G4 ............................................................................................... 9 -31, 16-279, 16-286, 16-291,
17-297
G5 ................................... 9-31, 16-279, 16-286, 16- 291, 16-296, 17-297
G6 ................................ 6-106, 16-280, 16-283, 16-284, 16 -289, 16-293,
16-294, 16-296, 17-299
G7 ................................................... ...................... 16-281, 16-283, 17-300
G8 ................................................... ................................... 16-283, 17-301
G9 ............................................ 16-283, 16 -285, 16-286, 16-287, 17-302
G10 .............................................. 5-86 , 15-264, 16-276, 16-284, 17-302
G11 .......................................... 15-264, 16-284 , 16-288, 16-292, 17-302
G12 ............... 16-284, 16-285, 16-286, 16-287, 16-288, 16-295, 17-303
G13 ................................................... .................... 16-286, 16-288, 17-303
G14 ................................................... .................... 16-288, 16-292, 17-304
www.it-ebooks.info

Trang 441
410
Phụ lục C: Tham khảo chéo về Heuristics
G15 ................................................... ................................. 16-288, 17-305
G16 ................................................... ................................. 16-289, 17-306
G17 ................................................... .................... 16-289, 17-307, 17-312
G18 ................................................... ...... 16-289, 16-290, 16-291, 17-308
G19 ................................................... ...... 16-290, 16-291, 16-292, 17-309
G20 ................................................... ................................. 16-290, 17-309
G21 ................................................... ................................. 16-291, 17-310
G22 ................................................... ................................. 16-294, 17-322
G23 ................................................... ......... ?? - 44, 14-239, 16-295, 17-313
G24 ................................................... ................................. 16-296, 17-313
G25 ................................................... ................................. 16-296, 17-314
G26 ................................................... .............................................. 17-316
G27 ................................................... .............................................. 17-316
G28 ................................................... ................................. 15-262, 17-317
G29 ................................................... ................................. 15-262, 17-317
G30 ................................................... ................................. 15-263, 17-317
G31 ................................................... ................................. 15-264, 17-318
G32 ................................................... ................................. 15-265, 17-319
G33 ................................................... .................... 15-265, 15-266, 17-320
G34 ................................................... .......................... 1-40, 6-106, 17-321
G35 ................................................... ..................................... 5-90, 17-323
G36 ................................................... ................................... 6-103, 17-324
J1 ... .................................... 16-276, 17-325
J2 ................................................... ....................... 16-278, 16-285, 17-326
J3 ................................................... ....................... 16-283, 16-285, 17-327
N1 .............................. 15-264, 16-277, 16-279, 16-282, 16-287 , 16-288,
16-289, 16-290, 16-294, 16-296, 17-328
N2 ................................................... ................................... 16-277, 17-330
N3 ................................................... ...................... 16-284, 16-288, 17-331
N4 ................................................... ...................... 15-263, 16-291, 17-332
N5 ................................................... ............ 2-26, 14-221, 15-262, 17-332
Số 6: ................................................... ................................... 15-261, 17-333
N7 ................................................... ................................... 15-263, 17-333
T1 ... ...................... 16-273, 16-274, 17-334
Ngày 2 tháng ...... năm ...... ................................... 16-273, 17-334
T3 ................................................... ................................... 16-274, 17-334
T4 ................................................... ................................................ 17- 334
T5 ................................................... ...................... 16-274, 16-275, 17-335
T6 ................................................... ................................... 16-275, 17-335
T7 ................................................... ................................... 16-275, 17-335
T8 ................................................... ................................... 16-275, 17-335
T9 ................................................... ................................................ 17- 336
www.it-ebooks.info

Trang 442
411

Phần kết
Năm 2005, khi tham dự buổi họp Agile tại Denver, Elisabeth Hedrickson 1 đưa cho tôi
dây đeo cổ tay màu xanh lục tương tự như loại mà Lance Armstrong đã làm rất phổ biến. Cái này
cho biết "Thử nghiệm bị ám ảnh" trên đó. Tôi vui mừng mặc nó vào và mặc nó một cách tự hào. Kể từ khi học TDD
từ
Kent Beck vào năm 1999, tôi thực sự đã bị ám ảnh bởi sự phát triển dựa trên thử nghiệm.
Nhưng rồi một điều kỳ lạ đã xảy ra. Tôi thấy tôi không thể gỡ bỏ ban nhạc. không phải
bởi vì nó đã bị mắc kẹt về thể chất, nhưng vì nó đã bị mắc kẹt về mặt đạo đức . Ban nhạc đã vượt qua
tuyên bố về đạo đức nghề nghiệp của tôi. Đó là một dấu hiệu rõ ràng về cam kết của tôi đối với
viết mã tốt nhất tôi có thể viết. Cởi bỏ nó dường như là một sự phản bội lại những đạo đức
của cam kết đó.
Vì vậy, nó vẫn còn trên cổ tay của tôi. Khi tôi viết mã, tôi nhìn thấy nó ở đó trong tầm nhìn ngoại vi của tôi. Nó là
một lời nhắc nhở liên tục về lời hứa mà tôi đã hứa với bản thân là viết mã sạch.
1. http://www.qualitytree.com/
www.it-ebooks.info

Trang 443
Trang này cố ý để trống
www.it-ebooks.info

Trang 444
413

Mục lục
## phát hiện, 237 –238
Toán tử ++ (tăng trước hoặc tăng sau),
325, 326
A
tính toán bị hủy bỏ, 109
các lớp trừu tượng, 149 , 271, 290
Mẫu NHÀ MÁY TÓM TẮT, 38 , 156,
273, 274
giao diện trừu tượng, 94
phương pháp trừu tượng
thêm vào ArgumentMarshaler , 234– 235
sửa đổi, 282
thuật ngữ trừu tượng, 95
sự trừu tượng
các lớp tùy thuộc, 150
mã ở mức sai, 290 –291
giảm dần từng cấp độ tại một thời điểm, 37
các chức năng chỉ giảm dần một cấp
của, 304- 306
mức trộn, 36 –37
tên ở cấp độ thích hợp của, 311
phân tách các mức, 305
gói một triển khai, 11
mức độ trừu tượng
nâng cao, 290
tách biệt, 305
hàm truy cập, Định luật Demeter
và, 98
người truy cập, đặt tên, 25
Bản ghi hoạt động, 101
máy chủ thích ứng, 185
mối quan hệ, 84
Phát triển phần mềm Agile: Nguyên tắc,
Mô hình, Thực hành (PPP) , 15
thuật toán
sửa chữa, 269– 270
lặp lại, 48
hiểu biết, 297– 298
sự mơ hồ
trong mã, 301
các thử nghiệm bị bỏ qua như, 313
bình luận khuếch đại, 59
chức năng phân tích, 265
"Dạng chú thích", của AspectJ, 166
Dự án Ant, 76, 77
AOP (lập trình hướng khía cạnh),
160, 163
API. Xem thêm các API công khai
gọi một phương thức null -returning
từ, 110
chuyên dùng cho các bài kiểm tra, 127
gói bên thứ ba, 108
các ứng dụng
tách khỏi mùa xuân, 164
tách khỏi xây dựng
chi tiết, 156
cơ sở hạ tầng của, 163
giữ mã liên quan đến đồng tiền
riêng biệt, 181
cấu trúc tùy ý , 303 –304
mảng args , chuyển đổi thành danh sách , 231– 232
args lớp
xây dựng, 194
thi hành, 194- 200
bản nháp thô của , 201 –212, 226–231
www.it-ebooks.info

Trang 445
414
Mục lục
ArgsException lớp
niêm yết , 198 –200
hợp nhất các ngoại lệ thành, 239 –242
tranh luận)
cờ, 41
cho một chức năng, 40
trong các chức năng, 288
các hình thức đơn nguyên, 41
giảm, 43
danh sách đối số, 43
đối tượng đối số, 43
các loại đối số
thêm, 200, 237
tác động tiêu cực của, 208
Lớp ArgumentMarshaler
thêm bộ xương của, 213 –214
sinh năm 212
Giao diện ArgumentMarshaler , 197 –198
mảng, di chuyển, 279
nghệ thuật, mã sạch, 6- 7
khớp nối nhân tạo, 293
Ngôn ngữ AspectJ, 166
lập trình hướng khía cạnh (AOP),
160, 163
các khía cạnh
trong AOP, 160– 161
Hỗ trợ "hạng nhất" cho, 166
tuyên bố khẳng định , 130 –131
khẳng địnhEquals , 42
khẳng định, sử dụng tập hợp, 111
bài tập, unaligned, 87- 88
hoạt động nguyên tử, 323 –324
thuộc tính, 68
các tác giả
của JUnit, 252
lập trình viên như, 13- 14
tuyên bố về quyền tác giả, 55
thiết bị đo mã tự động, 189 –190
bộ tự động, kiểm tra đơn vị, 124
B
mã xấu, 3–4 . Xem thêm mã bẩn;
mã lộn xộn
làm giảm tác dụng của, 250
ví dụ, 71– 72
kinh nghiệm làm sạch, 250
không bù đắp, 55
nhận xét xấu, 59– 74
biểu ngữ, tập hợp các chức năng bên dưới, 67
các lớp cơ sở, 290, 291
BDUF (Mặt trước thiết kế lớn), 167
bean, biến riêng được thao tác,
100– 101
Beck, Kent, 3, 34, 71, 171, 252,
289, 296
hành vi, 288– 289
Thiết kế lớn phía trước (BDUF), 167
dòng trống, trong mã, 78- 79
khối, gọi hàm trong, 35
Booch, Grady, 8- 9
boolean, truyền vào một hàm, 41
đối số boolean, 194, 288
bản đồ boolean , xóa, 224
đầu ra boolean, trong số các bài kiểm tra, 132
tài nguyên ràng buộc, 183, 184
ranh giới
sạch sẽ, 120
khám phá và học hỏi, 116
hành vi không chính xác tại, 289
tách biệt đã biết với chưa biết,
118– 119
lỗi điều kiện biên, 269
điều kiện biên
đóng gói, 304
thử nghiệm, 314
kiểm tra ranh giới, nới lỏng di chuyển, 118
“Trò chơi bowling”, 312
Quy tắc hướng đạo sinh, 14-15 , 257
theo dõi, 284
thỏa mãn, 265
phép ẩn dụ cửa sổ vỡ, 8
lữ đoàn xô, 303
Mẫu XÂY DỰNG-VẬN HÀNH-KIỂM TRA, 127
bản dựng, 287
logic kinh doanh, tách biệt khỏi lỗi
xử lý, 109
số dòng, 68
thư viện thao tác byte, 161 ,
162– 163
www.it-ebooks.info

Trang 446
415
Mục lục
C
Ngôn ngữ lập trình C ++ , 7
tính toán, đột nhập vào trung gian
giá trị, 296
ngăn xếp cuộc gọi, 324
Callable giao diện, 326
người gọi, lộn xộn, 104
gọi thứ bậc, 106
cuộc gọi, tránh chuỗi, 98
quan tâm, cho mã, 10
Điểm Descartes, 42
Hoạt động CAS, dưới dạng nguyên tử, 328
thay đổi (các)
cô lập từ, 149 –150
số lượng lớn rất nhỏ, 213
tổ chức cho, 147– 150
cho phép thử nghiệm, 124
thay đổi lịch sử, xóa, 270
kiểm tra các ngoại lệ, trong Java, 106
chờ vòng tròn, 337 , 338–339
làm rõ, nhận xét như, 57
rõ ràng, 25, 26
tên lớp, 25
các lớp học
sự gắn kết của, 140– 141
tạo cho các khái niệm lớn hơn, 28- 29
khai báo các biến cá thể, 81
thực thi thiết kế và kinh doanh
quy tắc, 115
để lộ nội bộ của, 294
công cụ vào ConTest, 342
giữ nhỏ, 136, 175
giảm thiểu số lượng, 176
đặt tên, 25, 138
nonthread -safe, 328– 329
như danh từ của một ngôn ngữ, 49
tổ chức của, 136
tổ chức để giảm rủi ro
thay đổi, 147
hỗ trợ đồng thời nâng cao
thiết kế, 183
phân loại, lỗi, 107
ranh giới rõ ràng, 120
mã sạch
nghệ thuật, 6- 7
mô tả, 7- 12
viết, 6- 7
kiểm tra sạch, 124 –127
sạch sẽ
cảm giác mua của, 6- 7
gắn liền với các bài kiểm tra, 9
dọn dẹp, mã, 14- 15
tên thông minh, 26
khách hàng, sử dụng hai phương pháp, 330
mã máy khách, kết nối với máy chủ, 318
khóa dựa trên máy khách, 185 , 329, 330–332
clientScheduler , 320
ứng dụng máy khách / máy chủ, đồng thời trong,
317– 321
Máy khách / Máy chủ chưa đọc, mã cho,
343– 346
máy khách-máy chủ sử dụng chuỗi, thay đổi mã,
346– 347
ClientTest.java , 318 , 344–346
đóng dấu ngoặc nhọn, nhận xét về, 67– 68
Cỏ ba lá, 268, 269
lộn xộn
Javadocs as, 276
giữ miễn phí, 293
mã, 2
xấu, 3– 4
Quy tắc của Beck về, 10
đã nhận xét-ra, 68– 69, 287
chết, 292
giải thích bản thân trong, 55
thể hiện bản thân trong, 54
định dạng của, 76
implicity của, 18- 19
nhạc cụ, 188, 342
lắc lư, 190
làm cho dễ đọc, 311
sự cần thiết của, 2
đọc từ trên xuống dưới, 37
đơn giản của, 18, 19
kỹ thuật khâm liệm, 20
www.it-ebooks.info

Trang 447
416
Mục lục
mã, tiếp tục
bên thứ ba, 114– 115
chiều rộng của dòng trong, 85- 90
ở mức trừu tượng sai , 290 –291
cơ sở mã, bị chi phối bởi lỗi
xử lý, 103
thay đổi mã, nhận xét không phải lúc nào
theo dõi, 54
hoàn thành mã, tự động, 20
phân tích vùng phủ mã, 254– 256
thiết bị đo mã, 188– 190
"Code sense", 6, 7
mã mùi, danh sách , 285 –314
tiêu chuẩn mã hóa, 299
sự gắn kết
trong số các lớp, 140– 141
duy trì, 141– 146
đối số dòng lệnh, 193– 194
lệnh, tách mình ra khỏi các truy vấn, 45- 46
tiêu đề tiêu đề nhận xét, 55– 56
tiêu đề bình luận, thay thế, 70
mã đã nhận xét, 68– 69, 287
cho ý kiến phong cách, ví dụ về xấu, 71- 72
bình luận
khuếch đại tầm quan trọng của
gì đó, 59
xấu, 59– 74
xóa, 282
như thất bại, 54
tốt, 55- 59
heuristics trên, 286 –287
HTML, 69
không chính xác, 54
nhiều thông tin, 56
tạp chí, 63– 64
hợp pháp, 55– 56
ủy thác, 63
gây hiểu lầm, 63
lầm bầm, 59– 60
như một điều ác cần thiết, 53– 59
tiếng ồn, 64 66
không bù đắp cho mã xấu, 55
lỗi thời, 286
viết kém, 287
sử dụng hợp lý, 54
dư thừa, 60– 62, 272, 275, 286–287
nói lại điều hiển nhiên, 64
tách khỏi mã, 54
TODO , 58– 59
quá nhiều thông tin trong, 70
xông vào, 65
viết lách, 287
"Khoảng cách giao tiếp", giảm thiểu, 168
So sánh và hoán đổi (CAS) hoạt động,
327– 328
Mô-đun ComparisonCompactor , 252– 265
khử cấu trúc, 256– 261
chung cuộc, 263– 265
tạm thời, 261– 263
mã gốc, 254– 256
cảnh báo trình biên dịch, tắt, 289
mã phức tạp, thể hiện lỗi
trong, 341
phức tạp, quản lý, 139– 140
thuật ngữ khoa học máy tính (CS), sử dụng cho
tên, 27
các khái niệm
giữ gần nhau, 80
đặt tên, 19
mỗi từ một, 26
phân tách ở các cấp độ khác nhau, 290
đánh vần tương tự tương tự, 20
cởi mở dọc giữa, 78- 79
mối quan hệ khái niệm, của mã, 84
mối quan tâm
cắt ngang, 160– 161
tách, 154, 166, 178, 250
lớp bê tông, 149
chi tiết bê tông, 149
điều khoản cụ thể, 94
đồng thời
nguyên tắc quốc phòng, 180 –182
các vấn đề, 190
động cơ nhận con nuôi, 178– 179
lầm tưởng và quan niệm sai lầm về,
179– 180
mã đồng thời
so với không liên quan đến tiền tệ
mã, 181
lấy nét, 321
www.it-ebooks.info

Trang 448
417
Mục lục
thuật toán đồng thời, 179
ứng dụng đồng thời, phân vùng
hành vi, 183
mã đồng thời
phá vỡ, 329– 333
bảo vệ khỏi các vấn đề của, 180
lỗ hổng ẩn trong, 188
lập trình đồng thời, 180
Lập trình đồng thời trong Java: Thiết kế
Nguyên tắc và khuôn mẫu , 182, 342
chương trình đồng thời, 178
sự cố cập nhật đồng thời, 341
Triển khai ConcurrentHashMap , 183
điều kiện
tránh tiêu cực, 302
đóng gói, 257– 258, 301
dữ liệu có thể định cấu hình, 306
hằng số cấu hình, 306
hậu quả, cảnh báo, 58
Tính nhất quán
trong mã, 292
trong tổng số enums, 278
đứng tên, 40
quy ước nhất quán, 259
hằng số
so với enums , 308- 309
trốn, 308
kế thừa, 271 , 307–308
giữ ở mức thích hợp, 83
để lại dưới dạng số nguyên, 300
không kế thừa, 307 –308
chuyển dưới dạng ký hiệu, 276
biến thành enums, 275– 276
xây dựng
chuyển tất cả sang main , 155, 156
tách biệt với nhà máy, 156
của một hệ thống, 154
đối số của hàm tạo, 157
hàm tạo, quá tải, 25
chủ đề người tiêu dùng, 184
Công cụ ConTest, 190, 342
bối cảnh
thêm ý nghĩa, 27- 29
không thêm cho không, 29- 30
cung cấp các ngoại lệ, 107
người đọc liên tục, 184
các biến điều khiển, trong các câu lệnh vòng lặp,
80– 81
thành ngữ tiện lợi, 155
quy ước
theo tiêu chuẩn, 299– 300
vượt quá cấu hình, 164
cấu trúc hơn, 301
sử dụng nhất quán, 259
mã phức tạp, 175
tuyên bố bản quyền, 55
các tia vũ trụ. Xem một lần
Lớp CountDownLatch , 183
khớp nối. Xem thêm tách; thời gian
khớp nối; khớp nối chặt chẽ
nhân tạo, 293
thời gian ẩn, 302– 303
thiếu, 150
các mẫu bao phủ, thử nghiệm, 314
công cụ bảo hiểm, 313
“Trừu tượng sắc nét”, 8- 9
mối quan tâm xuyên suốt, 160
Cunningham, Phường, 11 12
dễ thương, trong mã, 26
D
lập luận sai lầm lủng lẳng , 294
dữ liệu
trừu tượng, 93- 95
bản sao, tới 181 182
đóng gói, 181
giới hạn phạm vi, 181
các bộ được xử lý song song, 179
loại, 97, 101
cấu trúc dữ liệu. Xem thêm (các) cấu trúc
so với các đối tượng, 95, 97
xác định, 95
giao diện đại diện, 94
coi các Bản ghi Hoạt động là, 101
đối tượng truyền dữ liệu (DTO),
100– 101, 160
cơ sở dữ liệu dạng bình thường, 48
DateInterval enum, 282– 283
Bảng kê DAY , 277
www.it-ebooks.info

Trang 449
418
Mục lục
DayDate lớp, chạy SerialDate
như, 271
DayDateFactory , 273- 274
mã chết, 288, 292
các chức năng chết, 288
bế tắc, 183 , 335–339
cái ôm chết người. Xem vòng chờ đợi
gỡ lỗi, tìm bế tắc, 336
ra quyết định, tối ưu hóa, 167 –168
quyết định, hoãn lại, 168
khai báo, không dấu, 87 –88
Đồ vật DECORATOR, 164
DECORATOR mẫu, 274
kiến trúc tách rời, 167
tách rời, khỏi xây dựng
chi tiết, 156
chiến lược tách, đồng thời
như, 178
hàm tạo mặc định, xóa, 276
suy thoái, ngăn ngừa, 14
xóa, như phần lớn
thay đổi, 250
mật độ, dọc trong mã, 79 –80
sự phụ thuộc
tìm kiếm và phá vỡ, 250
tiêm, 157
lôgic, 282
làm cho vật lý logic, 298 –299
giữa các phương pháp, 329– 333
giữa đồng bộ hóa
phương pháp, 185
Tiêm phụ thuộc (DI), 157
Nguyên tắc đảo ngược phụ thuộc (DIP),
15, 150
nam châm phụ thuộc, 47
chức năng phụ thuộc, định dạng, 82– 83
các dẫn xuất
các lớp cơ sở tùy thuộc vào, 291
các lớp cơ bản biết về, 273
của lớp ngoại lệ, 48
chuyển các hàm đã đặt vào, 232 ,
233– 235
đẩy chức năng vào, 217
sự miêu tả
của một lớp, 138
quá tải cấu trúc của mã
thành, 310
tên mô tả
lựa chọn, 309– 310
sử dụng, 39- 40
(các) thiết kế
thuật toán đồng thời, 179
ghép nối tối thiểu, 167
nguyên tắc của, 15
mẫu thiết kế, 290
chi tiết, chú ý đến, 8
DI (Tiêm phụ thuộc), 157
Dijkstra, Edsger, 48 tuổi
mô hình thực hiện các triết gia ăn uống,
184- 185
DIP (Nguyên tắc đảo ngược phụ thuộc),
15, 150
mã bẩn. Xem thêm mã xấu;
mã lộn xộn
mã bẩn, làm sạch, 200
kiểm tra bẩn, 123
sai lệch, tránh, 19– 20
khoảng cách, chiều dọc trong mã, 80 –84
sự phân biệt, làm cho ý nghĩa, 20- 21
ngôn ngữ dành riêng cho miền (DSL),
168– 169
ngôn ngữ thử nghiệm miền cụ thể, 127
Lớp DoubleArgumentMarshaler , 238
Nguyên tắc KHÔ (Không lặp lại chính mình),
181, 289
DTO (đối tượng truyền dữ liệu), 100– 101, 160
phạm vi giả, 90
lặp lại nếu báo cáo, 276
sự trùng lặp
mã, 48
trong mã, 289 –290
loại bỏ, 173 –175
tập trung vào, 10
hình thức, 173, 290
giảm, 48
chiến lược để loại bỏ, 48
www.it-ebooks.info

Trang 450
419
Mục lục
đối số dyadic, 40
chức năng dyadic, 42
proxy động, 161
E
e , như một tên biến, 22
Nhật thực, 26
chỉnh sửa phiên, phát lại, 13 –14
hiệu quả, của mã, 7
Kiến trúc EJB, sớm nhất là được thiết kế kỹ lưỡng,
167
Tiêu chuẩn EJB, đại tu hoàn chỉnh, 164
Đậu EJB2, 160
EJB3, đối tượng Ngân hàng được viết lại vào, 165 –166
Mã "tao nhã", 7
thiết kế nổi, 171 –176
sự đóng gói, 136
điều kiện biên, 304
phá vỡ, 106- 107
trong số các điều kiện, 301
mã hóa, tránh, 23 –24, 312–313
đậu thực thể, 158 –160
enum (các)
thay đổi MonthConstants thành, 272
đang sử dụng, 308– 309
liệt kê, di chuyển, 277
môi trường, heuristics trên, 287
hệ thống kiểm soát môi trường, 128 –129
envying, phạm vi của một lớp, 293
kiểm tra lỗi, ẩn một tác dụng phụ, 258
Lớp lỗi , 47– 48
hằng số mã lỗi, 198 –200
mã lỗi
ngụ ý một lớp hoặc enum, 47- 48
ưu tiên các ngoại lệ hơn, 46
trở lại, 103 –104
sử dụng lại cũ, 48
tách khỏi mô-đun Args ,
242– 250
phát hiện lỗi, đẩy ra các cạnh, 109
cờ lỗi , 103 –104
xử lý lỗi, 8 , 47–48
thông báo lỗi, 107, 250
xử lý lỗi, kiểm tra , 238 –239
phương thức errorMessage , 250
lỗi. Xem thêm lỗi điều kiện biên;
lỗi chính tả; so sánh chuỗi
sai sót
phân loại, 107
Evans, Eric, 311
sự kiện, 41
phân loại ngoại lệ, 107
mệnh đề ngoại lệ, 107 –108
mã quản lý ngoại lệ, 223
ngoại lệ
thay vì mã trả lại , 103 –105
thu hẹp loại, 105 –106
thích mã lỗi, 46
cung cấp ngữ cảnh với, 107
tách từ args , 242- 250
ném, 104– 105, 194
được kiểm soát, 106- 107
thực thi, các đường dẫn có thể có , 321 –326
mô hình thực hiện, 183- 185
Khung chấp hành , 326– 327
ExecutorClientScheduler.java , 321
giải thích, về ý định, 56 –57
biến giải thích, 296 –297
tính rõ ràng, của mã, 19
mã biểu cảm, 295
biểu cảm
trong mã, 10 11
đảm bảo, 175 –176
Tái cấu trúc phương pháp trích xuất, 11
Những cuộc phiêu lưu mạo hiểm về lập trình
trong C # , 10
Đã cài đặt lập trình cực đoan , 10
"Mãn nhãn", mã vừa vặn , 79 –80
F
nhà máy , 155 –156
các lớp nhà máy, 273 –275
sự thất bại
để thể hiện bản thân bằng mã, 54
www.it-ebooks.info

Trang 451
420
Mục lục
thất bại, tiếp tục
mẫu của, 314
khoan dung mà không có hại, 330
đối số sai , 294
kiểm tra nhanh, 132
chủ đề chạy nhanh, đói lâu hơn
đang chạy, 183
sợ đổi tên, 30
Feathers, Michael, 10
tính năng ghen tị
loại bỏ, 293 –294
mùi, 278
kích thước tệp, trong Java, 76
từ khóa cuối cùng , 276
Từ viết tắt FIRST, 132 –133
Luật thứ nhất, của TDD, 122
Dự án FitNesse
phong cách mã hóa cho, 90
kích thước tệp, 76 , 77
hàm trong, 32 –33
gọi tất cả các bài kiểm tra, 224
đối số cờ, 41, 288
mã tiêu điểm, 8
mã nước ngoài. Xem mã của bên thứ ba
định dạng
ngang, 85– 90
mục đích của, 76
Các quy tắc của Uncle Bob, 90– 92
dọc, 76 –85
phong cách định dạng, cho một nhóm
nhà phát triển, 90
Fortran, buộc mã hóa, 23
Fowler, Martin, 285, 293
khung, 324
đối số chức năng, 40- 45
phụ thuộc cuộc gọi hàm, 84– 85
tiêu đề hàm, 70
chữ ký hàm, 45
chức năng, vị trí của , 295 –296
chức năng
phá vỡ thành nhỏ hơn, 141 –146
gọi trong một khối, 35
chết, 288
xác định riêng tư, 292
giảm dần một cấp độ trừu tượng,
304– 306
làm một việc, 35– 36, 302
dyadic, 42
loại bỏ các câu lệnh if không liên quan ,
262
thiết lập bản chất thời gian
trong số, 260
định dạng phụ thuộc, 82– 83
tập hợp bên dưới biểu ngữ, 67
heuristics trên, 288
tiết lộ ý định, 19
giữ nhỏ, 175
chiều dài, 34 –35
di chuyển, 279
đặt tên, 39, 297
số đối số trong, 288
một mức trừu tượng trên mỗi, 36 –37
thay cho bình luận, 67
đổi tên cho rõ ràng, 258
viết lại cho rõ ràng , 258 –259
phần bên trong, 36
nhỏ càng tốt, 34
lập trình có cấu trúc với, 49
hiểu biết, 297 –298
như động từ của một ngôn ngữ, 49
viết lách, 49
tương lai, 326
G
Gamma, Eric, 252
heuristics tổng quát , 288 –307
mã byte được tạo, 180
generic, cải thiện khả năng đọc mã, 115
nhận các chức năng, 218
Hàm getBoolean , 224
Hướng dẫn GETFIELD , 325 , 326
phương thức getNextId , 326
Hàm getState , 129
Gilbert, David, 267, 268
quy ước cho trước khi-thì, 130
trục trặc. Xem một lần
www.it-ebooks.info

Trang 452
421
Mục lục
chiến lược thiết lập toàn cầu, 155
“Lớp học của Chúa”, 136– 137
ý kiến tốt, 55- 59
câu lệnh goto , tránh, 48, 49
thiết kế lại lớn, 5
bối cảnh cho không, 29- 30
H
thiết bị đo đạc mã hóa bằng tay, 189
HashTable , 328– 329
tiêu đề. Xem tiêu đề bình luận; chức năng
tiêu đề
heuristics
tham chiếu chéo của, 286, 409
nói chung, 288– 307
danh sách , 285 –314
khớp nối thời gian ẩn, 259 , 302–303
những thứ ẩn, trong một hàm, 44
trốn
thực hiện, 94
cấu trúc, 99
phân cấp phạm vi, 88
HN. Xem ký hiệu Hungary
sự liên kết ngang, mã, 87- 88
định dạng ngang, 85- 90
khoảng trắng ngang, 86
HTML, trong mã nguồn, 69
Hungary Notation (HN), 23–24 , 295
Hunt, Andy, 8, 289
cấu trúc lai, 99
Tôi
nếu báo cáo
trùng lặp, 276
loại bỏ, 262
chuỗi if-else
xuất hiện lặp đi lặp lại, 290
loại bỏ, 233
các bài kiểm tra bị bỏ qua, 313
thực hiện
sao chép, 173
mã hóa, 24
phơi bày, 94
trốn, 94
gói một sự trừu tượng, 11
Các mẫu triển khai , 3 , 296
sự đơn giản, mã, 18
danh sách nhập khẩu
tránh lâu, 307
rút ngắn trong SerialDate , 270
nhập khẩu, như là phụ thuộc cứng, 307
không chính xác, trong mã, 301
nhận xét không chính xác, 54
thông tin không phù hợp, trong
bình luận, 286
phương thức tĩnh không phù hợp, 296
bao gồm phương pháp, 48
không nhất quán, trong mã, 292
cách viết không nhất quán, 20
incrementalism, 212- 214
mức thụt lề, của một hàm, 35
thụt lề, của mã, 88– 89
quy tắc thụt lề, 89
kiểm tra độc lập, 132
thông tin
không thích hợp, 286
quá nhiều, 70, 291–292
bình luận nhiều thông tin, 56
hệ thống phân cấp kế thừa, 308
kết nối rõ ràng, giữa một bình luận
và mã, 70
đối số đầu vào, 41
Biến thể hiện
trong các lớp học, 140
tuyên bố, 81
giấu khai báo về, 81– 82
chuyển dưới dạng hàm
đối số, 231
sự gia tăng của, 140
lớp học nhạc cụ, 342
không đủ bài kiểm tra, 313
(các) đối số nguyên
xác định, 194
tích phân, 224 –225
chức năng đối số số nguyên ,
chuyển sang ArgumentMarshaler ,
215– 216
www.it-ebooks.info

Trang 453
422
Mục lục
kiểu đối số số nguyên, thêm
tới Args , 212
số nguyên, kiểu thay đổi đối với, 220
IntelliJ, 26 tuổi
ý định
giải thích trong mã, 55
giải thích về, 56– 57
bị che khuất, 295
chức năng tiết lộ ý định, 19
tên ý-lộ, 18- 19
(các) giao diện
xác định cục bộ hoặc từ xa, 158 –160
mã hóa, 24
thực hiện, 149– 150
đại diện cho các mối quan tâm trừu tượng, 150
biến ArgumentMarshaler thành, 237
được xác định rõ, 291– 292
viết, 119
cấu trúc bên trong, vật thể ẩn, 97
giao điểm, của các miền, 160
trực giác, không dựa vào, 289
người phát minh ra C ++, 7
Đảo ngược kiểm soát (IoC), 157
Đối tượng InvocationHandler , 162
I / O ràng buộc, 318
cô lập, khỏi thay đổi, 149 –150
phương pháp isxxxArg , 221– 222
quy trình lặp lại, tái cấu trúc thành, 265
J
tệp jar, triển khai các dẫn xuất và cơ sở
trong, 291
Java
khía cạnh hoặc cơ chế giống khía cạnh,
161– 166
heuristics trên , 307 –309
như một ngôn ngữ nhiều chữ, 200
Java 5, cải tiến cho đồng thời
phát triển, 182- 183
Khung công tác Java 5 Executor, 320– 321
Java 5 VM, giải pháp không chặn trong,
327– 328
Các khuôn khổ Java AOP, 163– 166
Lập trình viên Java, không mã hóa
cần thiết, 24
Các proxy Java, 161 –163
Tệp nguồn Java, 76 –77
javadocs
như lộn xộn, 276
trong mã không công khai, 71
giữ nguyên định dạng trong, 270
trong các API công khai, 59
yêu cầu cho mọi chức năng, 63
gói java.util.concurrent , bộ sưu tập
trong, 182 –183
JBoss AOP, proxy trong, 163
Thư viện JCommon, 267
Bài kiểm tra đơn vị JCommon , 270
Dự án JDepend, 76, 77
Proxy JDK, cung cấp hỗ trợ liên tục,
161– 163
Jeffries, Ron, 10 11, 289
chiến lược lắc lư, 190
Tra cứu JNDI, 157
bình luận tạp chí, 63– 64
JUnit , 34 tuổi
Khung JUnit, 252– 265
Dự án Junit, 76, 77
Trình biên dịch Just-In-Time, 180
K
dạng từ khóa, của một tên hàm, 43
L
L , chữ thường trong tên biến, 20
thiết kế ngôn ngữ, nghệ thuật lập trình, 49
ngôn ngữ
có vẻ đơn giản, 12
mức độ trừu tượng, 2
nhiều trong một tệp nguồn, 288
bội số trong một nhận xét, 270
cấu trúc dữ liệu cuối cùng vào, ra trước (LIFO),
toán hạng ngăn xếp dưới dạng, 324
Định luật Demeter, 97– 98, 306
www.it-ebooks.info

Trang 454
423
Mục lục
LAZY BAN ĐẦU /
Thành ngữ EVALUATION, 154
LAZY-INITIALIZATION, 157
Lea, Doug, 182, 342
kiểm tra học tập, 116, 118
Định luật LeBlanc, 4
mã kế thừa, 307
ý kiến pháp lý, 55– 56
mức độ trừu tượng, 36- 37
mức độ chi tiết, 99
từ vựng, có một nhất quán, 26
dòng mã
sao chép, 173
chiều rộng, 85
(các) danh sách
trong số các đối số, 43
nghĩa là cụ thể cho các lập trình viên, 19
trả về bất biến được xác định trước, 110
mã biết chữ, 9
lập trình biết chữ, 9
Lập trình Literate , 141
sống động, 183, 338
ý kiến địa phương, 69- 70
biến cục bộ, 324
tuyên bố, 292
ở đầu mỗi chức năng, 80
khóa và chờ, 337, 338
ổ khóa, giới thiệu, 185
gói log4j , 116– 118
phụ thuộc logic, 282 , 298–299
Ngôn ngữ LOGO, 36
tên mô tả dài, 39
tên dài, cho phạm vi dài, 312
bộ đếm vòng lặp, tên một chữ cái cho, 25
M
những con số kỳ diệu
che khuất ý định, 295
thay thế bằng các hằng số được đặt tên,
300– 301
chức năng chính , chuyển công trình đến,
155, 156
người quản lý, vai trò của, 6
ý kiến bắt buộc, 63
điều khiển thủ công, qua một ID nối tiếp, 272
Bản đồ
thêm cho ArgumentMarshaler , 221
phương pháp, 114
bản đồ, phá vỡ việc sử dụng, 222 –223
thực hiện điều phối,
214– 215
bối cảnh có ý nghĩa, 27- 29
biến thành viên
tiền tố f cho, 257
tiền tố, 24
đổi tên cho rõ ràng, 259
lập bản đồ tinh thần, tránh, 25
mã lộn xộn. Xem thêm mã xấu; mã bẩn
tổng chi phí sở hữu, 4 12
lời gọi phương thức, 324
tên phương thức, 25
phương pháp
ảnh hưởng đến trình tự thực hiện, 188
gọi một cặp song sinh với một lá cờ, 278
thay đổi từ tĩnh sang phiên bản, 280
trong số các lớp học, 140
phụ thuộc giữa, 329– 333
loại bỏ sự trùng lặp giữa,
173- 174
giảm thiểu các tuyên bố khẳng định trong, 176
đặt tên, 25
kiểm tra phát hiện lỗi trong, 269
mã tối thiểu, 9
nhận xét sai lệch, 63
đặt sai trách nhiệm, 295– 296, 299
MOCK OBJECT, ấn định, 155
lập luận đơn nguyên, 40
các hình thức đơn nguyên, lập luận, 41
monads, chuyển đổi dyads thành, 42
Thử nghiệm Monte Carlo, 341
Tháng enum, 278
Lớp MonthConstants , 271
nhận biết đa luồng, 332
tính toán đa luồng, thông lượng,
335
www.it-ebooks.info

Trang 455
424
Mục lục
mã đa luồng, 188 , 339–342
lầm bầm, 59– 60
người đột biến, đặt tên, 25
loại trừ lẫn nhau, 183, 336, 337
N
các hằng số được đặt tên, thay thế phép thuật
số, 300– 301
ngôn ngữ được thử thách về độ dài tên, 23
tên
trừu tượng, mức độ thích hợp của, 311
đang thay đổi, 40
lựa chọn, 175, 309–310
trong số các lớp học, 270– 271
thông minh, 26
mô tả, 39- 40
trong số các chức năng, 297
heuristics trên, 309 –313
tầm quan trọng của, 309– 310
ý-lộ, 18- 19
chiều dài tương ứng với phạm vi,
22- 23
tên dài cho phạm vi dài, 312
làm cho rõ ràng, 258
miền vấn đề, 27
phát âm được, 21- 22
quy tắc để tạo, 18- 30
tìm kiếm, 22- 23
ngắn hơn thường tốt hơn dài hơn, 30
miền giải pháp, 27
với sự khác biệt tinh tế, 20
rõ ràng, 312
ở mức trừu tượng sai, 271
đặt tên, các lớp, 138
quy ước đặt tên, kém hơn
cấu trúc, 301
phương pháp điều hướng, trong Active
Hồ sơ, 101
gần lỗi, thử nghiệm, 314
điều kiện phủ định, tránh, 302
phủ định, 258
cấu trúc lồng nhau, 46
Newkirk, Jim, 116 tuổi
ẩn dụ báo chí, 77– 78
đối số niladic, 40
không có quyền ưu tiên, 337
tiếng ồn
nhận xét, 64– 66
đáng sợ, 66
từ, 21
danh pháp, sử dụng tiêu chuẩn, 311– 312
giải pháp không chặn, 327– 328
mã không liên quan đến tiền tệ, 181
tên không có định dạng, 21
thông tin không cục bộ, 69- 70
mã không công khai, javadocs in, 71
các phương thức nonstatic, ưu tiên cho static, 296
mã chưa đọc, đang hoạt động
đầu tiên, 187
các lớp an toàn không đọc, 328– 329
dòng chảy bình thường, 109
vô giá trị
không chuyển vào các phương thức, 111– 112
không trở lại, 109 –110
vô tình đi ngang qua một người gọi, 111
logic phát hiện null, cho ArgumentMarshaler ,
214
NullPointerException , 110, 111
đặt tên chuỗi số, 21
O
Phân tích và thiết kế hướng đối tượng với
Ứng dụng , 8
thiết kế hướng đối tượng, 15
các đối tượng
so với cấu trúc dữ liệu, 95, 97
so với các kiểu dữ liệu và quy trình
dures, 101
sao chép ở chế độ chỉ đọc, 181
xác định, 95
ý định bị che khuất, 295
bình luận lỗi thời, 286
hành vi rõ ràng, 288– 289
mã rõ ràng, 12
www.it-ebooks.info

Trang 456
425
Mục lục
Nguyên tắc "Một lần và chỉ một lần", 289
Quy tắc "MỘT CHUYỂN ĐỔI" , 299
một điều, các hàm đang làm, 35 –36, 302
một lần, 180, 187, 191
Mã OO, 97
Thiết kế OO, 139
Nguyên tắc đóng mở (OCP), 15, 38
bởi các ngoại lệ đã kiểm tra, 106
hỗ trợ, 149
ngăn xếp toán hạng, 324
hệ điều hành, chính sách phân luồng, 188
toán tử, ưu tiên của, 86
khóa lạc quan, 327
tối ưu hóa, LAZY-EVALUATION
như, 157
tối ưu hóa, ra quyết định, 167 –168
orderings, tính toán có thể , 322 –323
cơ quan
để thay đổi, 147 –150
trong số các lớp học, 136
quản lý độ phức tạp, 139– 140
kiểm tra bên ngoài, thực hiện một giao diện, 118
đối số đầu ra, 41, 288
tránh, 45
cần biến mất, 45
đầu ra, đối số là, 45
chi phí chung, phát sinh bởi đồng thời, 179
quá tải, mã có mô tả, 310
P
mô hình bìa mềm, như một học thuật
người mẫu, 27
tham số, thực hiện theo hướng dẫn, 324
phân tích cú pháp , ném một
ngoại lệ, 220
phân vùng, 250
đường dẫn thực thi, 321 –326
đường dẫn, qua các đoạn quan trọng, 188
tên mẫu, sử dụng tiêu chuẩn, 175
hoa văn
thất bại, 314
như một loại tiêu chuẩn, 311
hiệu suất
của một cặp máy khách / máy chủ, 318
cải thiện đồng thời, 179
khóa dựa trên máy chủ, 333
hoán vị, tính toán, 323
bền bỉ, 160, 161
bi quan khóa, 327
cụm từ, với tên gọi tương tự, 40
vật lý hóa, một sự phụ thuộc, 299
Đối tượng Java thuần túy cũ. Xem POJOs
nền tảng, chạy mã luồng, 188
mã đẹp, 7
mã dựa trên chuỗi có thể cắm được, 187
Hệ thống POJO, sự nhanh nhẹn được cung cấp bởi, 168
POJO (Đối tượng Java thuần túy cũ)
tạo, 187
triển khai logic kinh doanh, 162
phân tách mã nhận biết luồng, 190
vào mùa xuân, 163
viết logic miền ứng dụng, 166
đối số đa đa, 40
hành vi đa hình, của các chức năng, 296
thay đổi đa hình, 96- 97
đa hình, 37, 299
điểm đánh dấu vị trí, 67
tích cực
dễ hiểu hơn, 258
biểu thị các điều kiện như, 302
trong số các quyết định, 301 chính xác
là điểm của mọi cách đặt tên, 30
vị ngữ, đặt tên, 25
preemption, phá vỡ, 338
tiền tố
đối với các biến thành viên, 24
như vô dụng trong môi trường ngày nay,
312– 313
toán tử tăng trước, ++ , 324, 325, 326
"Prequel", cuốn sách này với tên, 15
nguyên tắc ít bất ngờ nhất , 288 –289, 295
nguyên tắc thiết kế, 15
Chương trình PrintPrimes , dịch sang
Java, 141
hành vi riêng tư, cô lập , 148 –149
www.it-ebooks.info

Trang 457
426
Mục lục
chức năng riêng, 292
hành vi phương pháp riêng, 147
tên miền có vấn đề, 27
mã thủ tục, 97
ví dụ về hình dạng thủ tục, 95– 96
thủ tục, so với đối tượng, 101
chức năng xử lý, phân vùng lại , 319 –320
phương pháp xử lý , I / O ràng buộc, 319
quy trình, cạnh tranh để giành tài nguyên, 184
giới hạn bộ xử lý, mã là, 318
mô hình thực hiện người tiêu dùng của nhà sản xuất, 184
chủ đề nhà sản xuất, 184
môi trường sản xuất, 127 –130
năng suất, giảm do mã lộn xộn, 4
lập trình viên chuyên nghiệp, 25
đánh giá chuyên nghiệp, mã, 268
lập trình viên
với tư cách là tác giả, 13 –14
câu hỏi hóc búa phải đối mặt, 6
trách nhiệm đối với các vụ lộn xộn, 5– 6
không chuyên nghiệp, 5– 6
lập trình
xác định, 2
có cấu trúc, 48 –49
chương trình, làm cho chúng hoạt động, 201
tên phát âm được, 21- 22
các biến được bảo vệ, tránh, 80
proxy, nhược điểm của, 163
API công khai, javadocs in, 59
chơi chữ, tránh, 26- 27
Lệnh PUTFIELD , dưới dạng nguyên tử, 325
Q
truy vấn, tách khỏi lệnh, 45 –46
R
lắc lư ngẫu nhiên, chạy thử nghiệm, 190
phạm vi, bao gồm cả ngày điểm cuối năm, 276
khả năng đọc
trong số các bài kiểm tra sạch, 124
mã, 76
Dave Thomas trên, 9 tuổi
cải thiện việc sử dụng thuốc generic, 115
quan điểm dễ đọc, 8
độc giả
mã, 13- 14
liên tục, 184
mô hình thực thi người đọc-người viết, 184
đọc hiểu
mã sạch, 8
mã từ trên xuống dưới, 37
so với viết, 14
khởi động lại, như một giải pháp khóa, 331
khuyến nghị, trong cuốn sách này, 13
thiết kế lại, theo yêu cầu của nhóm, 5
dư thừa, các từ nhiễu, 21
nhận xét thừa, 60– 62, 272, 275,
286– 287
Lớp ReentrantLock , 183
các chương trình được cấu trúc lại, dài hơn, 146
tái cấu trúc
Args , 212
mã tăng dần, 172
như một quá trình lặp đi lặp lại, 265
đưa những thứ vào để lấy ra, 233
mã kiểm tra, 127
Tái cấu trúc (Fowler), 285
đổi tên, sợ hãi, 30
khả năng lặp lại, lỗi đồng thời, 180
kiểm tra lặp lại, 132
yêu cầu, chỉ định, 2
resetId , mã byte được tạo cho , 324 –325
tài nguyên
ràng buộc, 183
các quy trình cạnh tranh, 184
chủ đề đồng ý về một đơn đặt hàng toàn cầu
trong tổng số 338
trách nhiệm
đếm trong lớp, 136
định nghĩa của, 138
xác định, 139
thất lạc, 295– 296, 299
tách một chương trình thành chính, 146
trả lại mã, sử dụng các ngoại lệ để thay thế,
103– 105
www.it-ebooks.info

Trang 458
427
Mục lục
tái sử dụng, 174
rủi ro thay đổi, giảm, 147
mã rõ ràng mạnh mẽ, viết, 112
bản nháp thô, viết, 200
giao diện chạy được , 326
biểu thức run-on, 295
chạy trên các bút toán, 63- 64
logic thời gian chạy, tách khởi động khỏi, 154
S
cơ chế an toàn, bị ghi đè, 289
mở rộng quy mô, 157– 161
tiếng ồn đáng sợ, 66
lược đồ của một lớp, 194
trường phái tư tưởng, về mã sạch,
12- 13
quy tắc kéo, trong C ++, 81
(các) phạm vi
được xác định bởi các ngoại lệ, 105
hình nộm, 90
envying, 293
mở rộng và thụt vào, 89
phân cấp trong tệp nguồn, 88
giới hạn cho dữ liệu, 181
tên liên quan đến độ dài của,
22– 23, 312
trong tổng số các biến được chia sẻ, 333
tên tìm kiếm, 22- 23
Luật thứ hai, của TDD, 122
các phần, trong các chức năng, 36
đối số bộ chọn, tránh, 294 –295
kiểm tra tự xác thực, 132
Lớp Semaphore , 183
dấu chấm phẩy, hiển thị, 90
“Số sê-ri”, Ngày sử dụng SerialDate , 271
SerialDate lớp
làm cho nó đúng, 270 –284
đặt tên cho, 270– 271
tái cấu trúc, 267 –284
Lớp SerialDateTests , 268
tuần tự hóa, 272
máy chủ, chuỗi được tạo bởi, 319 –321
ứng dụng máy chủ , 317 –318, 343–344
mã máy chủ, trách nhiệm của, 319
khóa dựa trên máy chủ, 329
như được ưu tiên, 332 –333
với các phương pháp đồng bộ, 185
Mô hình “Servlet”, của các ứng dụng Web, 178
Servlet , sự cố đồng bộ hóa, 182
thiết lập các chức năng, di chuyển vào thích hợp
các dẫn xuất, 232 , 233–235
setArgument , thay đổi, 232– 233
Hàm setBoolean , 217
phương pháp setter, chèn phụ thuộc,
157
chiến lược thiết lập, 155
Danh sách SetupTeardownIncluder.java ,
50– 52
các lớp hình dạng, 95– 96
dữ liệu được chia sẻ, giới hạn quyền truy cập, 181
biến chia sẻ
cập nhật phương pháp, 328
giảm phạm vi, 333
cách tiếp cận súng ngắn, dụng cụ mã hóa bằng tay-
tation as, 189
mã tắt, 186
tắt máy, duyên dáng, 186
phản ứng phụ
không có, 44
tên mô tả, 313
Simmons, Robert, 276
mã đơn giản, 10, 12
Thiết kế đơn giản, quy tắc của, 171 –176
đơn giản, mã, 18, 19
quy tắc khẳng định duy nhất , 130 –131
các khái niệm đơn lẻ, trong mỗi chức năng kiểm tra,
131– 132
Nguyên tắc trách nhiệm đơn lẻ (SRP), 15 ,
138– 140
áp dụng, 321
phá vỡ, 155
như một nguyên tắc bảo vệ đồng thời,
181
công nhận vi phạm của, 174
máy chủ vi phạm, 320
www.it-ebooks.info

Trang 459
428
Mục lục
Nguyên tắc Trách nhiệm Đơn lẻ (SRP),
tiếp tục
Sql vi phạm lớp, 147
hỗ trợ, 157
trong các lớp thử nghiệm phù hợp với, 172
vi phạm, 38
giá trị đơn, các thành phần có thứ tự của, 42
tên đơn, 22, 25
tính toán đơn luồng, thông lượng,
334
Mẫu SINGLETON, 274
lớp học nhỏ, 136
Các mẫu thực hành tốt nhất cho Smalltalk , 296
lập trình viên thông minh, 25
dự án phần mềm, bảo trì, 175
hệ thống phần mềm. Xem thêm (các) hệ thống
so với hệ thống vật lý, 158
Nguyên tắc thiết kế lớp SOLID, 150
tên miền giải pháp, 27
hệ thống điều khiển mã nguồn, 64, 68, 69
tập tin nguồn
so với các bài báo,
77– 78
nhiều ngôn ngữ trong, 288
Chương trình lấp lánh , 34
chủ đề sinh sản, bị khóa, 186
đối tượng trường hợp đặc biệt, 110
MẪU TRƯỜNG HỢP ĐẶC BIỆT, 109
thông số kỹ thuật, mục đích của, 2
lỗi chính tả, sửa chữa, 20
SpreadsheetDateFactory , 274– 275
Spring AOP, proxy trong, 163
Khung mùa xuân, 157
Mô hình mùa xuân, theo EJB3, 165
Tệp cấu hình Spring V2.5, 163 –164
lỗi giả, 187
Lớp Sql , đang thay đổi, 147 –149
căn bậc hai, là giới hạn lặp, 74
SRP. Xem Nguyên tắc Trách nhiệm Đơn lẻ
quy ước tiêu chuẩn, 299 –300
danh pháp chuẩn, 175 , 311–312
tiêu chuẩn, sử dụng một cách khôn ngoan, 168
quá trình khởi động, tách khỏi thời gian chạy
logic, 154
chết đói, 183, 184, 338
hàm tĩnh, 279
nhập tĩnh, 308
phương thức tĩnh, không phù hợp, 296
Quy tắc hạ bậc , 37
câu chuyện, chỉ triển khai hôm nay, 158
Mô hình CHIẾN LƯỢC, 290
đối số chuỗi , 194, 208–212, 214–225
lỗi so sánh chuỗi, 252
StringBuffers , 129
Stroustrup, Bjarne, 7- 8
cấu trúc). Xem thêm cấu trúc dữ liệu
trốn, 99
hybrid, 99
thực hiện các thay đổi lớn đối với, 212
vượt quá quy ước, 301
lập trình có cấu trúc, 48- 49
Lớp SuperDashboard , 136 –137
hoán đổi, như hoán vị, 323
chuyển đổi câu lệnh
chôn, 37, 38
xem xét tính đa hình
trước đây, 299
lý do để dung nạp, 38 –39
công tắc / chuỗi hộp , 290
vấn đề đồng bộ hóa, tránh với
Servlets , 182
khối đồng bộ , 334
từ khóa được đồng bộ hóa , 185
thêm, 323
luôn luôn có được một khóa, 328
giới thiệu một khóa qua, 331
bảo vệ một phần quan trọng
trong mã, 181
phương pháp đồng bộ, 185
đồng bộ hóa, tránh, 182
chức năng tổng hợp, 265
(các) hệ thống. Xem thêm hệ thống phần mềm
kích thước tệp đáng kể, 77
tiếp tục chạy trong quá trình phát triển,
213
cần tên miền cụ thể, 168
kiến trúc hệ thống, lái thử,
166- 167
www.it-ebooks.info

Trang 460
429
Mục lục
lỗi hệ thống, không bỏ qua
một lần, 187
cấp hệ thống, giữ sạch ở, 154
thông tin toàn hệ thống, trong một địa phương
bình luận, 69– 70
T
bảng, di chuyển, 275
mục tiêu nền tảng triển khai, chạy thử nghiệm
trên, 341
hoán đổi nhiệm vụ, khuyến khích, 188
TDD (Phát triển theo hướng thử nghiệm), 213
logic xây dựng, 106
là kỷ luật cơ bản, 9
luật, 122– 123
quy tắc đội, 90
đội
tiêu chuẩn mã hóa cho mọi, 299 –300
bị chậm bởi mã lộn xộn, 4
tên kỹ thuật, lựa chọn, 27
ghi chú kỹ thuật, bảo lưu ý kiến
cho, 286
Mẫu phương pháp TEMPLATE
giải quyết sự trùng lặp, 290
loại bỏ trùng lặp cấp cao hơn,
174– 175
đang sử dụng, 130
khớp nối thời gian. Xem thêm khớp nối
phơi bày, 259– 260
ẩn, 302– 303
tạo hiệu ứng phụ, 44
các biến tạm thời, giải thích, 279– 281
trường hợp thử nghiệm
thêm để kiểm tra các đối số, 237
trong ComparisonCompactor , 252– 254
các kiểu thất bại, 269 , 314
tắt, 58
mã kiểm tra, 124, 127
KIỂM TRA NHÂN ĐÔI, ấn định, 155
Hướng phát triển thử nghiệm. Xem TDD
lái thử, kiến trúc , 166 –167
môi trường thử nghiệm, 127– 130
kiểm tra các hàm, các khái niệm đơn lẻ trong , 131 –132
triển khai thử nghiệm, của một giao diện, 150
bộ thử nghiệm
tự động, 213
trong số các bài kiểm tra đơn vị, 124, 268
xác minh hành vi chính xác, 146
hệ thống có thể kiểm tra, 172
hướng phát triển thử nghiệm. Xem TDD
thử nghiệm
lập luận khó hơn, 40
logic xây dựng trộn với
thời gian chạy, 155
ngôn ngữ thử nghiệm, miền cụ thể, 127
dự án testNG, 76, 77
bài kiểm tra
sạch, 124– 127
sạch sẽ gắn liền với, 9
đã nhận xét cho SerialDate ,
268– 270
bẩn, 123
cho phép -các tính năng, 124
nhanh, 132
nhanh và chậm, 314
heuristics trên, 313– 314
bỏ qua, 313
độc lập, 132
không đủ, 313
giữ sạch sẽ, 123– 124
giảm thiểu các tuyên bố khẳng định trong,
130– 131
không ngừng tầm thường, 313
tái cấu trúc, 126 –127
có thể lặp lại, 132
yêu cầu nhiều hơn một bước, 287
đang chạy, 341
tự xác thực, 132
thiết kế đơn giản chạy tất cả, 172
bộ tự động, 213
đúng lúc, 133
viết mã đa luồng,
339– 342
viết cho mã luồng, 186 –190
viết tốt, 122– 123
www.it-ebooks.info
Trang 461
430
Mục lục
Luật thứ ba, của TDD, 122
mã của bên thứ ba
tích hợp, 116
học tập, 116
sử dụng, 114– 115
bài kiểm tra viết cho, 116
biến này , 324
Thomas, Dave, 8 , 9, 289
(các) chủ đề
thêm vào một phương thức, 322
giao thoa với nhau, 330
làm cho độc lập như
có thể, 182
dẫm lên nhau, 180, 326
lấy tài nguyên từ người khác
chủ đề, 338
chiến lược quản lý luồng, 320
nhóm chủ đề, 326
mã dựa trên luồng, thử nghiệm, 342
mã luồng
làm cho có thể cắm được, 187
làm du dương, 187- 188
các triệu chứng của lỗi trong, 187
thử nghiệm, 186 –190
viết bằng Java 5, 182 –183
xâu chuỗi
thêm vào ứng dụng máy khách / máy chủ,
319, 346–347
các vấn đề trong hệ thống phức tạp, 342
bộ sưu tập an toàn theo chuỗi, 182 –183, 329
thông lượng
gây chết đói, 184
cải thiện, 319
đang tăng, 333– 335
xác thực, 318
mệnh đề ném , 106
đội hổ, 5
khớp nối chặt chẽ, 172
thời gian, đi nhanh, 6
Dự án Thời gian và Tiền bạc, 76
kích thước tệp, 77
kiểm tra kịp thời, 133
chương trình hẹn giờ, thử nghiệm, 121 –122
Từ khóa “TO”, 36
ĐẾN đoạn, 37
TODO nhận xét, 58– 59
mã thông báo, được sử dụng làm số ma thuật, 300
Dự án Tomcat, 76, 77
công cụ
Công cụ ConTest, 190, 342
phạm vi bảo hiểm, 313
xử lý bản ghi proxy, 163
kiểm tra mã dựa trên luồng, 342
đào tạo những xác tàu, 98- 99
các phép biến đổi, dưới dạng giá trị trả về, 41
điều hướng bắc cầu , tránh , 306 –307
đối số bộ ba, 40
bộ ba, 42
thử khối, 105
thử / bắt khối, 46– 47, 65–66
câu lệnh try-catch- last , 105– 106
thể điều hướng luồng dựa trên mã, 187- 188
loại mã hóa, 24
U
ngôn ngữ phổ biến, 311– 312
tên rõ ràng, 312
trường hợp ngoại lệ được kiểm soát, 106- 107
không được đóng gói có điều kiện, đóng gói,
257
kiểm tra đơn vị, bị cô lập là khó, 160
kiểm tra đơn vị, 124 , 175, 268
lập trình không chuyên nghiệp, 5 6
chữ hoa C , trong tên biến, 20
khả năng sử dụng, của báo, 78
sử dụng, của một hệ thống, 154
người dùng, xử lý đồng thời, 179
V
xác nhận, thông lượng, 318
tên biến, một chữ cái, 25
www.it-ebooks.info

Trang 462
431
Mục lục
biến
1 dựa trên so với 0 dựa trên, 261
khai báo, 80, 81, 292
giải thích tạm thời, 279– 281
giải thích, 296– 297
giữ riêng tư, 93
địa phương, 292, 324
chuyển sang một lớp khác, 273
thay cho bình luận, 67
thúc đẩy các biến ví dụ của
lớp học, 141
với ngữ cảnh không rõ ràng, 28
trút giận, trong bình luận, 65
động từ, từ khóa và, 43
Lớp phiên bản , 139
phiên bản, không deserializing trên 272
mật độ thẳng đứng, trong mã, 79- 80
khoảng cách dọc, theo mã, 80– 84
định dạng dọc, 76– 85
mở dọc, giữa các khái niệm,
78– 79
đặt hàng dọc, theo mã, 84– 85
tách dọc, 292
W
lội nước, thông qua mã xấu, 3
Vùng chứa web, bộ tách được cung cấp
bởi, 178
cái gì, tách ra từ khi nào, 178
khoảng trắng, sử dụng chiều ngang, 86
ký tự đại diện, 307
Làm việc hiệu quả với Legacy
Mã , 10
Chương trình "làm việc", 201
tay nghề, 176
giấy gói, 108
gói, 108
nhà văn, chết đói, 184
“Viết mã nhút nhát”, 306
X
XML
bộ mô tả triển khai, 160
Cấu hình được chỉ định "chính sách"
tệp, 164
www.it-ebooks.info

You might also like