Thứ Sáu, 27 tháng 1, 2012

OOP - Bài 2: Phân loại kết quả xét tuyển




Center of Excellence, SaigonTech


Bài 2
Phân loại kết quả xét tuyển

Một trong những nguyên tắc thiết kế software giá trị nhất
là phải tránh tình trạng dư thừa mã (code duplication).
-- Martin Fowler

I. Bài toán

Kết quả xét tuyển vào Học viện SaigonTech của một thí sinh bao gồm ba điểm: toán (math), viết luận (essay), và phỏng vấn (interview). Các điểm này được chấm trên thang số nguyên từ 0 đến 100. Hãy phát triển chương trình phân loại (grade) kết quả xét tuyển cho một thí sinh, biết rằng kết quả này được tính dựa trên điểm trung bình (average) của ba môn như sau
  • Loại A nếu average từ 90.0 trở lên,
  • Loại B nếu average từ 80.0 đến dưới 90.0,
  • Loại C nếu average từ 70.0 đến dưới 80.0,
  • Loại D nếu average từ 60.0 đến dưới 70.0, và
  • Loại F nếu average dưới 60.0

Bạn không phải phát triển phần UI, nhưng để hiểu rõ hơn yêu cầu bài toán, bạn có thể tham khảo một UI mẫu dưới đây.

Hình 1



II. Giải pháp

Ta tiếp tục vận dụng qui trình giải quyết vấn đề gồm 4 bước như đã làm quen ở bài trước.

Bước 1. Phát biểu lại yêu cầu bài toán

Phân loại kết quả xét tuyển
  • Input: ba điểm math, essay, và interview
  • Output: grade

Bước 2. Thiết kế sơ bộ class để giải quyết bài toán đã đặt ra
  • Xác định tên method và tên parameters
Hình 2
  • Thử chuyển parameters thành fields của class
    Hình 3
  • Xác định tên class dựa vào fields và method
    Ta cần trả lời câu hỏi: Đối tượng nào có ba điểm math, essay, và interview, rồi từ đó tính được grade? Đó chính là các điểm số (test scores).
    Hình 4

Bước 3. Xây dựng test cases

Để xác định số trường hợp kiểm thử, ta cần dựa vào phương pháp phân loại. Theo mô tả bài toán, kết quả phân loại là một trong năm ký tự (char) từ A đến F, nên bộ dữ liệu kiểm thử phải chứa tối thiểu năm trường hợp này. Ngoài ra, cũng cần chú ý đến các giá trị biên (boundary values) của average là 0, 60, 70, 80, 90, và 100. Như vậy, ta sẽ có bộ dữ liệu kiểm thử bao gồm 11 trường hợp.

                                     0                                      60     70      80      90      100   average
Hình 5. Mười một vị trí của average cần được kiểm thử

grade()
No
math
essay
interview
sum?
average?
grade?
1
0
0
0
0
0.0
F
2
50
50
50
150
50.0
F
3
60
60
60
180
60.0
D
4
65
65
65
195
65.0
D
5
70
70
70
210
70.0
C
6
75
75
75
225
75.0
C
7
80
80
80
240
80.0
B
8
85
85
85
255
85.0
B
9
90
90
90
270
90.0
A
10
95
95
95
285
95.0
A
11
100
100
100
300
100.0
A

Thêm vào đó, biết rằng kết quả phân loại phải dựa vào tổng (sum) và average của ba điểm, ta bổ sung thêm các methods sum()average() vào thiết kế, đồng thời xác định data types cho fields và kết quả trả về của các methods.

Hình 6

Lưu ý rằng sum()average() chỉ có chức năng bổ trợ cho method chính, đó là grade(), nên trong thiết kế ta không đặt dấu cộng (+) ở đằng trước các methods này.



Bước 4: Phát triển test class

Trình tự thực hiện sẽ tương tự như Bài 1.
  • Project: TestScores
  • Package: testscores
  • Test class: TestScoresTest
Hình 7

Do grade() phụ thuộc vào sum()average() nên ta phát triển sum() trước, sau đó là average(), và cuối cùng là grade().

Phát triển sum()

Tổng của ba điểm là một số nguyên nằm trong đoạn từ 0 đến 300. Ta tạo ra ba test cases cho method này, bao gồm hai trường hợp biên (0 và 300) kèm theo một trường hợp không phải là biên (ở đây ta chọn giá trị 150).

Hình 8

Sau đó, tương tự như Bài 1, ta tiến hành loại bỏ hết lỗi trong test class bằng cách tạo ra TestScores class và phát triển sum() method.

Hình 9

Lưu ý rằng do sum() chỉ là method bổ trợ (helper hay auxiliary method), ta không cần dùng từ khóa (keyword) public khi phát triển method này.

Đến đây ta cần thi hành test class để nhận được tín hiệu xanh, từ đó tự tin tiếp tục phát triển giải pháp.

Hình 10


Phát triển average()

Do kết quả của average() phải là một số thực trong đoạn từ 0.0 đến 100.0, ta tạo ra ba test cases cho method này, bao gồm hai trường hợp biên (0.0 và 100.0) và một trường hợp không phải là biên (ở đây ta chọn giá trị 50.0).

Hình 11

Quan sát rằng khi tạo test method cho average(), ta đã lặp lại việc tạo ra ba TestScores objects hoàn toàn giống với ba objects bên trong test method cho sum(). Đây là tình trạng dư thừa mã (code duplication). Khi phát hiện tình trạng này, ta cần loại bỏ nó để tạo thuận lợi cho việc bảo trì (maintenance) về sau. Tuy nhiên, trước khi nghĩ đến việc tái cấu trúc (refactor) code, ta cần hoàn thành nhiệm vụ trước mắt, đó là phát triển average().

Hình 12

Tương tự như sum(), average() chỉ là một helper, ta không cần dùng public keyword cho nó.

Một điểm cần lưu ý nữa là trong average(), ta đã dùng 3.0 thay vì 3, bởi vì nếu dùng số nguyên 3, kèm theo việc sum() sẽ trả về kết quả là một số nguyên, phép chia bên trong average() sẽ là một phép chia nguyên (bỏ qua phần thập phân), khiến kết quả tính toán sẽ thiếu chính xác (chẳng hạn 5 / 2 sẽ bằng 2, chứ không bằng 2.5, do đây là phép chia nguyên). Khi viết 3.0, ta muốn báo cho Java biết rằng đây phải là một phép chia số thực (không được là phép chia nguyên).

Đến đây ta thi hành test class một lần nữa để nhận được tín hiệu xanh, từ đó tự tin refactor code để loại bỏ tình trạng code duplication.



Loại bỏ code duplication trong TestScoresTest

Như đã đề cập ở trên, bên trong TestScoresTest class, ta đã tạo nhiều TestScores objects giống nhau một cách không cần thiết. Thấy rằng các objects này nằm trong hai methods khác nhau. Để loại bỏ code duplication, ta cần chuyển chúng ra bên ngoài để hai methods đều có thể sử dụng được chúng. Đồng thời, ta gán cho mỗi object một cái tên (testScores1, testScores2,testScores3) để dễ dàng đề cập đến chúng. Ta được code sau.

Hình 13

Sau khi refactoring, ta cần thi hành test class để chắc chắn rằng chương trình vẫn hoạt động tốt.



Phát triển grade()

Ta có tổng cộng 11 test cases để kiểm tra tính đúng đắn của grade(). Đây là một lượng không nhỏ, có thể gây khó khăn cho quá trình tìm kiếm giải pháp. Ta quyết định tạm thời chỉ dùng vài test cases, rồi sẽ bổ sung đầy đủ ngay sau khi hoàn thành việc phát triển code.

Do test class đã có sẵn ba TestScores objects, ta dùng luôn những objects này vào mục đích kiểm thử grade().

Hình 14

Chú ý rằng ta đã bao quanh các ký tự FA bằng các dấu nháy đơn (single quote). Đây là qui ước của ngôn ngữ Java.

Lỗi của test class được loại bỏ bằng cách tạo ra grade() method bên trong TestScores class, với data type của kết quả trả về là một ký tự (char).

Hình 15

Bây giờ ta tiến hành lý luận trên grade(). Dựa vào phát biểu bài toán và test cases, biết rằng grade() chỉ có thể trả về một trong năm ký tự từ A đến F, ta dùng cấu trúc điều kiện IF như sau

Hình 16

Tất nhiên kết quả trả về của grade() sẽ phụ thuộc vào giá trị trung bình nên ta đưa vào biến cục bộ (local variable) average để lưu giữ giá trị này, rồi sử dụng nó để hoàn tất cấu trúc điều kiện IF.
Hình 17

Ở trên ta đã sử dụng toán tử so sánh (comparison operators) lớn hơn hay bằng >= để so sánh average với một số cụ thể. Ngoài >=, Java còn có các comparison operators khác: lớn hơn >, nhỏ hơn <, nhỏ hơn hay bằng <=, bằng ==, và không bằng !=. Cần chú ý phân biệt toán tử so sánh bằng (gồm hai dấu bằng) với toán tử gán (chỉ gồm một dấu bằng).

Đến đây ta thi hành test class để nhận được tín hiệu xanh, từ đó có thể tự tin bổ sung các test cases còn thiếu.

Hình 18

Cuối cùng, ta kết thúc quá trình tìm kiếm giải pháp bằng cách thi hành một lần nữa TestScoresTest để được tín hiệu xanh.



III. Tổng kết

Bài viết minh họa việc vận dụng cấu trúc điều kiện IF để giải quyết vấn đề phân loại kết quả xét tuyển. Trong quá trình tìm ra giải pháp cho nhiệm vụ chính là vấn đề phân loại (grade), ta cần phải tính toán giá trị trung bình (average), và để tính average, ta lại cần tính tổng (sum). Với từng tính toán như vậy, ta luôn sử dụng các test cases và tín hiệu xanh để hỗ trợ quá trình tư duy.

Ngoài ra, khi phát triển giải pháp, ta cần chú ý loại bỏ tình trạng trùng lặp mã (code duplication). Đây là một trong những nguyên tắc quan trọng nhất trong phát triển software. Kỹ thuật loại bỏ code duplication sẽ còn được trình bày chi tiết hơn trong các bài tiếp theo.



IV. Tài liệu tham khảo
  1. Gaddis T. (2010) Starting Out with Java From Control Structures Through Objects, (4th edition), Pearson Education, Boston. Chương 3.

V. Thuật ngữ tiếng Anh

average
trung bình
auxiliary method
phương thức bổ trợ, phương thức phụ
boundary value
giá trị biên
char
kiểu ký tự
code duplication
trùng lặp mã, dư thừa mã
comparison operator
toán tử so sánh
grade
điểm phân loại, là một chữ thuộc tập { A, B, C, D, F }
helper
phương thức bổ trợ
keyword
từ khóa, từ dành riêng
local variable
biến cục bộ
maintenance
bảo trì
operator
toán tử
refactor
tái cấu trúc mã (động từ)
refactoring
việc tái cấu trúc mã (danh từ)
score
điểm số
single quote
dấu nháy đơn
sum
tổng

Không có nhận xét nào:

Đăng nhận xét