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

Kiến trúc tiến hóa và thiết kế nổi dần: Thiết kế hướng theo kiểm thử, phần 2 Bàn luận thêm về việc cho phép dùng kiểm thử để định hướng và cải thiện thiết kế của bạn ppt

18 354 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Kiến trúc tiến hóa và thiết kế nổi dần: Thiết kế hướng theo kiểm thử, phần 2 bàn luận thêm về việc cho phép dùng kiểm thử để định hướng và cải thiện thiết kế của bạn
Tác giả Neal Ford
Người hướng dẫn Kiến Trúc Phần Mềm, ThoughtWorks
Trường học ThoughtWorks
Chuyên ngành Kiến trúc phần mềm
Thể loại bài viết
Định dạng
Số trang 18
Dung lượng 1,27 MB

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

Nội dung

Phần thứ 2 này của bài viết Kiến trúc tiến hóa và thiết kế nổi dần sẽ hoàn tất các bước hướng dẫn về một ví dụ được mở rộng, cho thấy cách làm thế nào để thiết kế có thể xuất hiện dần t

Trang 1

Kiến trúc tiến hóa và thiết kế nổi dần: Thiết kế hướng theo kiểm thử, phần 2

Bàn luận thêm về việc cho phép dùng kiểm thử để định hướng và cải thiện thiết kế của bạn

Neal Ford, Kiến trúc phần mềm, ThoughtWorks

Tóm tắt: Kiểm thử chỉ là một tác dụng phụ của việc phát triển hướng theo kiểm

thử (TDD - test-driven development); khi được thực hiện đúng cách, TDD sẽ cải

thiện thiết kế tổng thể của mã của bạn Phần thứ 2 này của bài viết Kiến trúc tiến hóa và thiết kế nổi dần sẽ hoàn tất các bước hướng dẫn về một ví dụ được mở

rộng, cho thấy cách làm thế nào để thiết kế có thể xuất hiện dần từ các mối quan tâm nảy sinh trong quá trình kiểm thử

Đây là phần thứ hai của bài viết gồm hai phần, nghiên cứu cách sử dụng TDD như thế nào để cho phép làm nổi dần các bước thiết kế tốt hơn từ quá trình viết kiểm thử trước khi bạn viết mã Tại phần 1, tôi đã viết một phiên bản của trình tìm số hoàn hảo (perfect numbers), sử dụng cách phát triển kiểm thử sau (viết các phép kiểm thử sau khi viết mã) Sau đó, tôi đã viết một phiên bản sử dụng TDD (viết các phép kiểm thử trước khi viết mã, cho phép kiểm thử chi phối thiết kế mã lệnh)

Ở cuối phần 1, tôi thấy rằng tôi đã mắc phải một lỗi cơ bản khi suy nghĩ về loại cấu trúc dữ liệu được sử dụng để lưu giữ danh sách các số hoàn hảo: bản năng mách bảo tôi bắt đầu bằng một danh sách mảng (ArrayList), nhưng tôi thấy rằng phép trừu tượng hóa thành kiểu tập hợp (Set) Tôi sẽ bắt đầu từ điểm này, mở rộng các thảo luận theo cách mà bạn có thể cải thiện chất lượng của các phép kiểm thử của bạn và kiểm tra chất lượng của mã lệnh cuối cùng

Chất lượng kiểm thử

Phép kiểm thử sử dụng cách trừu tượng hóa thành kiểu Set tốt hơn có trong liệt kê 1:

Liệt kê 1 Kiểm thử đơn vị với cách trừu tượng hóa thành Set tốt hơn

@Test public void add_factors() {

Set<Integer> expected =

Trang 2

new HashSet<Integer>(Arrays.asList(1, 2, 3, 6));

Classifier4 c = new Classifier4(6);

c.addFactor(2);

c.addFactor(3);

assertThat(c.getFactors(), is(expected));

}

Mã này kiểm thử một trong những phần quan trọng nhất trong miền bài toán của tôi: lấy các ước số của một số Tôi muốn kiểm tra hành vi đó một cách kỹ lưỡng bởi vì nó là phần phức tạp nhất của bài toán, dễ bị gặp lỗi nhất Tuy nhiên, nó chứa một cấu trúc cồng kềnh, đó là: new HashSet (Arrays.asList (1, 2, 3, 6)) Ngay cả với sự hỗ trợ của IDE hiện đại, cấu trúc này làm cho mã lệnh rắc rối: gõ nhập new,

gõ nhập Has và để mã bên trong tiếp tục; gõ nhập <Int và để mã bên trong tiếp tục, thật chán Tôi sẽ làm cho điều này trở nên dễ dàng hơn

Về loạt bài viết này

Loạt bài viết này nhằm cung cấp một phối cảnh tươi mới về các khái niệm thường được thảo luận nhưng khó nắm bắt về kiến trúc và thiết kế phần mềm Thông qua các ví dụ cụ thể, Neal Ford mang đến cho bạn một nền tảng vững chắc cho cách

làm thực tế lanh lẹn của kiến trúc tiến hóa và thiết kế nổi dần Bằng cách trì hoãn

các quyết định quan trọng về thiết kế và kiến trúc cho đến thời điểm chịu trách nhiệm cuối cùng, bạn có thể ngăn ngừa được những phức tạp không cần thiết không để chúng ngầm phá hoại các dự án phần mềm của bạn

Kiểm thử theo nguyên tắc Moist

Một trong những câu “châm ngôn” để viết mã lệnh tốt có trong cuốn The

Pragmatic Programmer (Lập trình viên thực dụng) của các tác giả Andy Hunt và

Dave Thomas (xem mục Tài nguyên) — là nguyên tắc DRY (Don't Repeat

Yourself – Đừng lặp lại chính mình) Nó khuyên nhủ bạn tránh mọi sự lặp lại mã của bạn vì điều này thường gây ra các vấn đề Tuy nhiên, nguyên tác DRY không

áp dụng cho các kiểm thử đơn vị Các kiểm thử đơn vị thường phải kiểm tra các sắc thái hành vi của mã được kiểm thử, dẫn đến các tình huống tương tự và trùng lặp nhau Mã sao chép và dán để tạo ra kết quả mong đợi trong Liệt kê 1 (hàm

Trang 3

HashSet (Arrays.asList (1, 2, 3, 6)) mới) là một ví dụ tuyệt vời về điều này bởi vì bạn sẽ muốn có rất nhiều biến thể của nó trong các phép kiểm thử khác nhau (N.D: tác giả chơi chữ ở đây khi đưa ra nguyên tắc Moist “Moist” – nghĩa là “ẩm ướt” đối lập với DRY- nghĩa là “khô”)

Quy tắc ngón tay cái TDD của tôi là các kiểm thử chỉ là ẩm (moist) chứ không phải là ướt sũng nước (drenched) Ý tôi muốn nói là một số trùng lắp trong các phép kiểm thử có thể chấp nhận được (và không tránh khỏi), nhưng bạn không nên

đi quá xa, tạo ra các cấu trúc cồng kềnh lặp đi lặp lại Để đạt mục đích này, tôi sẽ tái cấu trúc phép kiểm thử của mình để cung cấp một phương thức phụ trợ riêng tư (private) để giúp tôi xử lý cách viết hàm tạo phổ biến này, nó có trong liệt kê 2:

Liệt kê 2 Phương thức phụ trợ để giữ cho phép thử của tôi ở mức “ẩm”

private Set<Integer> expectationSetWith(Integer numbers) {

return new HashSet<Integer>(Arrays.asList(numbers));

}

Mã trong Liệt kê 2 làm cho tất cả các phép kiểm thử của tôi về các ước số trở nên sạch hơn nhiều, như đã thấy trong phép kiểm thử thể hiện trong Liệt kê 3, được viết lại từ liệt kê 1:

Liệt kê 3 Phép kiểm thử “ẩm hơn” để kiểm tra các ước số của một số

@Test public void factors_for_6() {

Set<Integer> expected = expectationSetWith(1, 2, 3, 6);

Classifier4 c = new Classifier4(6);

c.calculateFactors();

Trang 4

assertThat(c.getFactors(), is(expected));

}

Bởi vì bạn đang viết các phép kiểm thử không có nghĩa là bạn phải vứt bỏ đi các nguyên tắc thiết kế tốt Phép kiểm thử là các loại mã lệnh khác, nhưng các nguyên tắc tốt (mặc dù khác) cũng được áp dụng đối với chúng

Các điều kiện biên

TDD khuyến khích các nhà phát triển phần mềm viết một phép kiểm thử không thực hiện được khi viết phép kiểm thử đầu tiên cho một chức năng mới nào đó Điều này tránh việc phép kiểm thử vô tình chạy thông suốt trong mọi trường hợp, làm cho phép kiểm thử thực sự không kiểm tra bất cứ điều gì (phép kiểm thử thừa – tautology test) Các phép kiểm thử cũng có thể xác minh hành vi mà bạn nghĩ rằng bạn là đúng nhưng chưa kiểm tra đủ để tự tin Các phép kiểm thử này không nhất thiết phải là trước tiên thất bại (mặc dù thất bại khi bạn nghĩ rằng phép kiểm thử sẽ thông suốt là điều hoàn toàn tốt bởi vì bạn đã tìm ra một lỗi tiềm tàng) Suy nghĩ về việc kiểm thử dẫn bạn đến xem xét những gì có thể kiểm thử được

Một số trường hợp kiểm thử thường không được chú ý là các điều kiện biên: mã của bạn sẽ làm gì khi phải đối mặt với đầu vào bất thường? Khi viết nhiều phép kiểm thử đối với phương thức getFactors() sẽ mở ra cho bạn suy nghĩ về những đầu vào hợp lý và không hợp lý nào có thể xảy ra

Với mục đích này, tôi sẽ bổ sung một số phép thử dành cho các điều kiện biên đáng chú ý, được thể hiện trong liệt kê 4:

Liệt kê 4 Các điều kiện biên cho phân tích ước số

@Test public void factors_for_100() {

Classifier5 c = new Classifier5(100);

c.calculateFactors();

Trang 5

assertThat(c.getFactors(),

is(expectationSetWith(1, 100, 2, 50, 4, 25, 5, 20, 10)));

}

@Test(expected = InvalidNumberException.class)

public void cannot_classify_negative_numbers() {

new Classifier5(-20);

}

@Test public void factors_for_max_int() {

Classifier5 c = new Classifier5(Integer.MAX_VALUE);

c.calculateFactors();

assertThat(c.getFactors(), is(expectationSetWith(1, 2147483647)));

}

Con số 100 dường như thú vị bởi vì nó có rất nhiều ước số Bằng cách kiểm thử cho các số khác nhau, tôi nhận ra rằng trong miền bài toán việc có các số âm là vô nghĩa, do đó, tôi đã viết một phép kiểm thử (và thực sự phép thử này đã thất bại trước khi tôi sửa lỗi ấy) để loại trừ các số âm Nghĩ về các số âm cũng làm cho tôi nghĩ về MAX_INT: Phải chăng giải pháp của tôi nên xem xét những gì sẽ xảy ra nếu người sử dụng hệ thống cần các số lớn, kiểu long? Giả định ban đầu của tôi chỉ giới hạn ở các số kiểu interger, nhưng tôi cần phải chắc chắn rằng đây là một giả định hợp lệ

Trang 6

Thu thập các yêu cầu là quá trình nén chịu thiệt (lossy compression – khi nén sẽ

bị mất thông tin)

Bạn hãy nhìn xung quanh mình và tìm một bức tranh hoặc tác phẩm nghệ thuật Giả sử rằng bức tranh đó chứa 2 triệu điểm ảnh (pixel) Điều gì sẽ xảy ra nếu bạn nén bức tranh đó để chỉ có 2.000 điểm ảnh? Bức tranh đó vẫn còn trông như cũ không? (Có lẽ thế nếu đó là một bức tranh của Rothko (N.D: hoạ sĩ theo trường phái trừu tượng, tranh của ông chỉ gồm các mảng mầu), nhưng đó là một trường hợp hiếm hoi) Thao tác nén bằng cách loại bỏ các thông tin là một thuật toán nén chịu thiệt Nếu bạn dùng phiên bản đã nén và cố gắng khôi phục lại nó thành 2 triệu điểm ảnh, thì bạn sẽ cần phải thực hiện một số ngón nghề Đôi khi bạn có thể đoán đúng, nhưng không phải trong mọi trường hợp

Các phiên làm việc yêu cầu “big design up front" (N.D: phương thức "thiết kế hoàn hảo trước, viết mã chương trình sau”, thường gắn với mô hình thác nước trong phát triển phần mềm) truyền thống là quá trình nén chịu thiệt đối với những

gì mà một ứng dụng cần làm Các nhà phân tích nghiệp vụ không thể lường trước mọi vấn đề sẽ phát sinh, do đó các nhà phát triển sẽ phải tạo ra các thông tin để điền vào các chi tiết Các nhà phát triển nổi tiếng là những người làm việc này rất

tệ, dẫn đến nhiều điều bực mình giữa những người xác định các yêu cầu và những người thực hiện các yêu cầu đó

Các quy trình lanh lẹn nỗ lực giảm bớt sự mất mát thông tin này bằng cách trì hoãn thuật toán giải nén càng muộn càng tốt và luôn luôn trông cậy vào một ai đó

có thể trả lời câu hỏi về những điều thực sự nên làm Thiết kế mà không có chi tiết thiết kế là điều không thể, vì vậy dù phương thức luận của bạn là gì, thì bạn phải tìm ra một cách hoàn toàn khả dĩ để điền vào các chi tiết chắc chắn bị loại bỏ bởi quá trình thu thập và xác định

Việc kiểm thử các điều kiện biên buộc bạn phải đặt dấu hỏi cho các giả định của bạn Rất dễ đưa ra các giả định không hợp lệ khi mã hóa một giải pháp Trong thực tế, đây là một trong những điểm yếu của việc thu thập các yêu cầu truyền thống - nó không bao giờ có thể tập hợp đủ chi tiết để loại bỏ các câu hỏi khi triển khai thực hiện, chắc chắn sẽ xảy ra Quá trình thu thập các yêu cầu là một dạng nén chịu thiệt

Bởi vì có quá nhiều điều bị bỏ sót bởi quá trình xác định những gì mà một phần mềm phải làm, bạn cần một cơ chế tại chỗ để giúp bạn tạo lại các câu hỏi mà bạn phải đưa ra để hiểu nó hoàn toàn Phỏng đoán về những gì những người kinh doanh thực sự mong muốn là điều nguy hiểm vì bạn sẽ nhận được phần lớn câu trả sai Sử dụng các phép kiểm thử để kiểm tra các điều kiện biên giúp bạn tìm ra các vấn đề để hỏi, mà hầu hết chúng là câu hỏi về cách hiểu vấn đề Việc tìm ra các câu hỏi đúng có ý nghĩa rất nhiều trong việc đạt được một thiết kế tốt

Trang 7

Các phép kiểm thử dương và âm

Khi bắt đầu việc khảo sát các vấn đề này, tôi phân rã nó thành nhiều tác vụ con Khi tôi viết các phép kiểm thử, tôi phát hiện một tác vụ phân rã quan trọng Sau đây là toàn bộ danh sách các tác vụ:

1 Tôi cần các ước số của số đang xét

2 Tôi cần phải xác định xem một số có phải là một ước số không

3 Tôi cần phải xác định làm thế nào để bổ sung các ước số vào danh sách các ước số

4 Tôi cần phải tính tổng các ước số

5 Tôi cần phải xác định xem một số có là hoàn hảo không

Hai tác vụ còn lại là tính tổng các ước số và kiểm tra tính hoàn hảo của số đang xét Không có gì ngạc nhiên xảy ra với hai tác vụ này; hai phép kiểm thử cuối cùng có trong liệt kê 5:

Liệt kê 5 Hai phép kiểm thử cuối cùng cho các số hoàn hảo

@Test public void sum() {

Classifier5 c = new Classifier5(20);

c.calculateFactors();

int expected = 1 + 2 + 4 + 5 + 10 + 20;

assertThat(c.sumOfFactors(), is(expected));

}

@Test public void perfection() {

Trang 8

int[] perfectNumbers =

new int[] {6, 28, 496, 8128, 33550336};

for (int number : perfectNumbers)

assertTrue(classifierFor(number).isPerfect());

}

Sau khi xem trang web Wikipedia để tìm một vài số hoàn hảo đầu tiên, tôi có thể viết một phép kiểm thử, kiểm tra xem tôi thực tế có thể tìm thấy các số hoàn hảo hay không Nhưng tôi chưa kết thúc Kiểm thử dương chỉ là một nửa công việc Tôi cũng cần một phép kiểm thử để kiểm tra xem liệu tôi có vô tình nhận nhầm một số không hoàn hảo Với mục đích này, tôi viết một phép thử âm, như trong liệt kê 6:

Liệt kê 6 Phép thử âm để đảm bảo rằng việc phân loại số hoàn hảo làm việc chính xác

@Test public void test_a_bunch_of_numbers() {

Set<Integer> expected = new HashSet<Integer>(

Arrays.asList(PERFECT_NUMS));

for (int i = 2; i < 33550340; i++) {

if (expected.contains(i))

assertTrue(classifierFor(i).isPerfect());

else

assertFalse(classifierFor(i).isPerfect());

Trang 9

}

}

Mã này cho biết rằng thuật toán số hoàn hảo của tôi làm việc một cách chính xác, nhưng nó rất chậm Tôi có thể đoán được lý do tại sao bằng cách xem phương thức calculateFactors() của tôi, hiển thị trong liệt kê 7:

Liệt kê 7 Phương thức getFactors() đơn sơ

public void calculateFactors() {

for (int i = 2; i < _number; i++)

if (isFactor(i))

addFactor(i);

}

Vấn đề biểu hiện trong Liệt kê 7 tương tự như vấn đề trong phiên bản mã kiểm thử sau trong Phần 1 của loạt bài: Mã lệnh thu thập các ước số đi suốt toàn bộ con đường cho đến tận chính số đó Tôi có thể cải thiện mã này bằng cách thu thập các ước số theo cặp, cho phép tôi chỉ phân tích tới căn bậc hai của số đang xét, như được thể hiện trong phiên bản mã đã tái cấu trúc trong liệt kê 8:

Liệt kê 8 Phiên bản đã tái cấu trúc, hoạt động tốt hơn của phương thức

calculateFactors()

Trang 10

public void calculateFactors() {

for (int i = 2; i < sqrt(_number) + 1; i++)

if (isFactor(i))

addFactor(i);

}

public void addFactor(int factor) {

_factors.add(factor);

_factors.add(_number / factor);

}

Đây là cách tái cấu trúc mã lệnh tương tự cách mà tôi đã làm trong phiên bản mã kiểm thử sau (trong Phần 1), nhưng lần này có sự thay đổi trong hai phương thức khác nhau Sự thay đổi ở đây đơn giản hơn vì tôi đã trừu tượng hóa chức năng addFactors() thành một phương thức riêng của nó, và phiên bản này sử dụng cách trừu tượng hóa thành Set, loại bỏ việc kiểm thử vụng về để chắc chắn rằng tôi không nhận các ước số hai lần như trong phiên bản kiểm thử sau

Nguyên tắc chỉ đạo của việc tối ưu hóa luôn luôn phải là làm cho đúng, sau đó làm cho nhanh Một bộ đầy đủ các phép kiểm thử đơn vị làm cho việc kiểm tra các

hành vi trở nên dễ dàng, cho phép bạn tự do chơi trò chơi “What if” với việc tối ưu hóa mà không cần lo lắng rằng bạn đã làm sai điều gì đó

Tôi đã làm xong với phiên bản mã hướng theo kiểm thử của trình tìm số hoàn hảo Toàn bộ mã của lớp này được hiển thị trong liệt kê 9

Liệt kê 9 Phiên bản TDD đầy đủ của trình phân loại số

Trang 11

public class Classifier6 {

private Set<Integer> _factors;

private int _number;

public Classifier6(int number) {

if (number < 1)

throw new InvalidNumberException( "Can't classify negative numbers"); _number = number;

_factors = new HashSet<Integer>(); _factors.add(1);

_factors.add(_number);

}

private boolean isFactor(int factor) { return _number % factor == 0;

}

public Set<Integer> getFactors() {

return _factors;

}

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

HÌNH ẢNH LIÊN QUAN

Hình 1. Các nút và các cung của phương thức doit () - Kiến trúc tiến hóa và thiết kế nổi dần: Thiết kế hướng theo kiểm thử, phần 2 Bàn luận thêm về việc cho phép dùng kiểm thử để định hướng và cải thiện thiết kế của bạn ppt
Hình 1. Các nút và các cung của phương thức doit () (Trang 16)
Hình 3. Độ phức tạp chu số của phiên bản TDD của trình tìm kiếm số hoàn  hảo - Kiến trúc tiến hóa và thiết kế nổi dần: Thiết kế hướng theo kiểm thử, phần 2 Bàn luận thêm về việc cho phép dùng kiểm thử để định hướng và cải thiện thiết kế của bạn ppt
Hình 3. Độ phức tạp chu số của phiên bản TDD của trình tìm kiếm số hoàn hảo (Trang 17)

TỪ KHÓA LIÊN QUAN

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

TÀI LIỆU LIÊN QUAN

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

w