Tôi cho rằng phần lớn sự đối lập giữa hai trường phái trên là do Microsoft thiên về thiết kế hướng dữ liệu mặc dù trên thực tế nó không tương thích tốt với những gì mà những nhà phát tri[r]
Trang 1BẢN QUYỀN
Quyển sách Foundation of Programming được đăng kí bản quyền với tên gọi Attribution-NonCommercial-NoDerivs 3.0 Unported
Độc giả được phép sao chép, phân phối và sử dụng quyển sách Tuy nhiên, bạn cần ghi chú tên tác giả là Karl Seguin và không được sử dụng quyển sách với mục đích thương mại cũng như không được thay đổi nội dung của sách
Bản quyền đầy đủ của sách được thể hiện tại:
http://creativecommons.org/licences/by-nc-nd/3.0/legalcode
SỰ GHI NHẬN
Có rất nhiều người xứng đáng nhận lời cảm ơn từ tác giả Quyển sách này chỉ là một sự đóng góp nhỏ nhoi cho sự phát triển và kho kiến thức khổng lồ của cộng đồng phần mềm Nếu không có những quyển sách chất lượng cùng các diễn đàn, những bài của các newsgroup, blog, thư viện và các dự án mã nguồn mở, tôi sẽ vẫn còn phải tìm hiểu tại sao các đoạn mã ASP của tôi bị timing-out khi thực hiện vòng lặp trên những tập hợp các bản ghi/đối tượng - recordset(sự phiền toái của MoveNext)
Không ngạc nhiên rằng cộng đồng phần mềm đã sử dụng tính đại chúng của Internet nhiều hơn bất
kì ngành nghề nào khác để phát triển Điều đáng ngạc nhiên là cái cách mà điều đó đã xảy ra mà không gây nhiều chú ý Rất tốt!
Dĩ nhiên, có một ngừơi đặc biệt mà nếu không có thì sẽ không thể có quyển sách này
Gửi Wendy,
Mọi người bảo anh là may mắn vì được ở cùng một người thông minh và xinh đẹp như em Tuy nhiên họ không biết hết về em Em không chỉ xinh đẹp và thông minh, em còn cho phép anh dành quá nhiều thời gian bên chiếc máy tính, cho dù là để làm việc, học tập, viết code hoặc chơi game
Em còn rất sẵn lòng đọc lại bản thảo của anh hoặc lắng nghe anh chuyện trò những điều vô nghĩa Anh cần phải biết ơn em nhiều hơn nữa
Mục lục
BẢN QUYỀN 1
SỰ GHI NHẬN 1
VỀ TÁC GIẢ 3
CHƯƠNG 1 ALT.NET 4
Mục tiêu 4
Sự đơn giản 5
YAGNI 5
Thời điểm hồi đáp cuối cùng(Last Responsible Moment) 6
Tránh lặp lại (DRY) 6
Rõ ràng và gắn kết 6
Cặp đôi 6
Unit Tests và Continuous Integration 6
Trang 2Trong chương này 6
Chương 2 THIẾT KẾ HƯỚNG LĨNH VỰC 8
Thiết kế hướng dữ liệu/ lĩnh vực 8
Ngừơi dùng, khách hàng và Các bên liên quan 9
Hướng lĩnh vực (The domain object) 9
Giao diện người dùng (UI) 12
Bí quyết và thủ thuật 13
Factory Pattern 13
Chỉ định truy cập – Access Modifier 13
Interface 13
Che dấu thông tin và tính đóng gói 14
Tổng kết chương 15
CHƯƠNG 3 Persistence 16
Gap 16
DataMapper 16
Vấn đề 19
Các hạn chế 20
Tổng kết chương 21
Dependency Injection 22
Sneak Peak trong Unit Testing 23
Don’t avo id Coupling lik e the Plag ue 24
Dependency Injection 24
Constructor Injection 24
Framework 26
Sự cải tiến cuối cùng 28
Tổng kết chương 29
CHƯƠNG 5 Unit Testing 30
Tại sao tôi đã không dùng Unit Test trong 3 năm trước? 30
The Tools 31
nUnit 32
Unit Test là gì? 33
Mocking 33
More on nUnit and RhinoMocks 36
UI and Database Testing 37
Trong chương này 37
CHƯƠNG 6 Object Relational Mappers 38
Infamous Inline SQL vs Stored Procedure Debate 38
Stored Procedures are More Secure 38
Thủ tục lưu trữ nhanh hơn 39
NHibernate 40
Configuration 40
Relationships 42
Querying 43
Lazy Loading 45
Download 46
Trong chương này 46
CHƯƠNG 7 Trở lại căn bản: Bộ nhớ 47
Cấp phát bộ nhớ: 47
Trang 3Heap 47
Con trỏ 48
Mô hình bộ nhớ trong thực tế 50
Đóng hộp (Boxing) 50
ByRef 51
Quản lý thất thoát bộ nhớ 53
Phân mảnh 53
Kết dính bộ nhớ (Pinning) 54
Xác lập giá trị null 54
Kết thúc tiền định 55
Trong chương này 55
CHƯƠNG 8 Trở lại căn bản: Exception 56
Xử lý exception 56
Logging 57
Cleaning (làm sạch) 57
Ném ngoại lệ 58
Cơ chế ném ngoại lệ 58
Khi nào phải ném ngoại lệ 59
Tạo những ngoại lệ riêng 60
Trong chương này 62
CHƯƠNG 9 Basic : Proxy This and Proxy That 63
Proxy Domain Pattern 63
Interception 64
In This Chapter 66
VỀ TÁC GIẢ
Karl Seguin là một nhà phát triển phần mềm tại Fuel Industries, từng là MVP của Microsoft, là một thành viên có nhiều ảnh hưởng của cộng đồng CodeBetter.com và là một biên tập viên của DotNetSlackers Ông đã viết rất nhiều bài báo và là một thành viên tích cực của nhiều cộng đồng thông tin Microsoft Ông hiện sống tại Ottawa, Ontario Canada
Trang web cá nhân của Karl Seguin là http://www.openmymind.net/
Blog của tác giả, cùng với blog của nhiều chuyên gia nổi tiếng khác, ở tại địa chỉ:
http://www.codebetter.com/
Trang 4CHƯƠNG 1 ALT.NET
NẾU CÓ SỰ KhÔNG HÀI LÒNG VỚI HIỆN TẠI, TỐT THÔI NẾU CÓ SỰ KHUẤY ĐỘNG, CÀNGTỐT HƠN NỮA NẾU CÓ SỰ KIÊN TRÌ KHÔNG NGỪNG NGHỈ, TÔI HÀI LÒNG VÀ TIẾP ĐÓHÃY ĐỂ NHỮNG Ý TƯỞNG, NHỮNG SUY NGHĨ, VIỆC LÀM TÍCH CỰC ĐƯỢC NẢY NỞ NẾUMỘT NGƯỜI CẢM THẤY NHỎ BÉ, HÃY ĐỂ ANH TA TỰ LÀM MÌNH LỚN HƠN - HUBERT HHUMPHREY
Vài năm về trước tôi đã có may mắn để chuyển hướng trong nghề lập trình của mình Một cơ hộilàm hướng dẫn xuất hiện và tôi tận dụng nó một cách tối đa Trong vòng vài tháng, kĩ năng lậptrình của tôi tăng theo hàm mũ, và vài năm gần đây, tôi tiếp tục tinh lọc kĩ năng của mình Khôngnghi ngờ rằng tôi còn nhiều điều cần học, và 5 năm sau, tôi sẽ xấu hổ khi nhìn lại những đoạn codemình viết lúc này Tôi đã từng tự tin ở khả năng lập trình của mình, Nhưng chỉ khi tôi chấp nhậnrằng mình biết rất ít, và thường là tôi luôn như vậy, tôi mới bắt đầu thật sự hiểu
Cuốn sách này là sự bộ sưu tập các bài viết mà mục đích chính là để giúp cho các lập trình viênnhiệt huyết tự luyện tập Xuyên suốt cuốn sách này, chúng ta sẽ đề cập những vấn đề thường đượctrình bày quá chuyên sâu với mọi người, ngoại trừ những người đã tiếp cận chúng từ trước Tôiluôn thấy có 2 phần nổi trội trong thế giới Net Một được ủng hộ mạnh mẽ bởi Microsoft như sựphát triển tự nhiên của VB6 và ASP cổ điển( thường được biết tới như MSDN Way) Cái còn lạiđược ủng hộ bởi những xu hướng chính trong hướng đối tượng và bị ảnh hưởng bời những kháiniệm/ dự án lớn của Java (được biết đến như là ALT.NET)
Trong thực tế, hai phần không thể so sánh với nhau được MSDN Way hướng người sử dụng vàomột phương thức đặc biệt để chia nhỏ chương trình thành từng câu lệnh riêng biệt (sau cùng, chẳngphải những ghi chú tham khảo của API là lí do duy nhất mà chúng ta phải ghé thăm MSDN?) Trong khi đó, ALT.NET chú trọng vào những chủ đề mang tính trừu tượng và cũng đồng thời cungcấp một sự hiện thực cụ thể Jeremy Miller đã cho rằng: Cộng đồng Net đã chú trọng quá nhiềuvào API và framework, trong khi không đủ nhấn mạnh vào nền tảng của thiết kế và lập trình Đểđưa ví dụ liên quan và cụ thể, MSDN Way ủng hộ mạnh mẽ việc sử dụng Datasets va DataTablescho việc liên lạc/kết nối cơ sở dữ liệu ALT.NET, tuy nhiên, chú ý vào những cuộc bàn luận vềnhững mẫu thiết kế ổn định, những vấn đề không tương thích trước mắt giữa các đối tượng cũngnhư là những cách hiện thực đặc biệt, như NHibernate (O/R Mapping), MonoRail(ActiveRecord)cũng như DataSets and DataTables Nói một cách khác, khác với điều nhiều người nghĩ, ALT.NETkhông phải là một sự thay thế cho MSDN Way, những người phát triền phần mềm cần phải biết vàhiểu rằng những phương án thay thế cho những giải pháp và cách tiếp cận mà MSDN Way là mộtphần trong đó
Dĩ nhiên, từ những miêu tả trên, ta có thể thấy được rằng để đi theo con đường ALT.NET cần phải
có niềm đam mê lớn hơn cũng như cần một nền tảng kiến thức sâu rộng hơn Con đường học vấnnhiều trắc trở và những nguồn tài liệu bổ ích mới bắt đầu được tập hợp lại (đây cũng là lí do tôiviết cuốn sách này) Tuy nhiên, những gì nhận được thì rất xứng đáng Với tôi, thành công trongnghề nghiệp đã đem đến những hạnh phúc to lớn trong đời sống cá nhân
Mục tiêu
Mặc dù đơn giản, nhưng những ý tưởng ở đây chủ yếu dựa trên khả năng có thể bảo trì được Bảotrì là phần nền tảng của việc phát triển phần mềm Những độc giả trung thành của CodeBetter cóthể nghe điều này nhiều lần, nhưng có một lí do quan trọng để vấn đề trên được nhắc lại thườngxuyên: đây chính là chìa khóa để trở thành một nhà phát triển phần mềm xuất sắc.Tôi có thể nghĩ
ra một số lí do vì sao nó là một yếu tố quan trong trong quá trình thiết kế Đầu tiên, cả nghiên cứu
Trang 5và kinh nghiệm thực tế cho chúng tôi thấy rằng, các hệ thống sự dụng phần lớn thời gian (trên50%) ở trạng thái bảo trì – thay đổi, sửa lỗi, nâng cấp Thứ hai là, sự gia tăng của cách phát triểnlặp (iterative) làm cho những thay đổi và tính chất mới liên tục được tiến hành trên những đoạn mã
có sẵn (thậm chí nếu bạn chưa bao giờ trải nghiệm qua một quá trình phát triển lặp, ví dụ nhưAgile, khách hàng của bạn vẫn có nhiều khả năng sẽ yêu cầu bạn đủ kiểu thay đổi) Tóm lại, giảipháp bảo trì không chỉ hạ thấp chi phí mà còn làm tăng chất lượng cũng như số lượng thành phẩm
mà bạn có thể bán ra
Thậm chí nếu bạn còn khá xa lạ với lập trình, rất có thể bạn cũng đã từng định hình những suynghĩ về việc những gì có thể được bảo trì và những gì không thể, dựa trên kinh nghiệm làm việcnhóm, sử dụng những ứng dụng của người khác, hay thậm chí là sửa lại những gì mà bạn đã codevài tháng trước Một trong số những điều quan trọng nhất mà bạn có thể làm là ghi chú lại tất cảnhững gì có vẻ không đúng và google (hay MSN cũng được) một giải pháp tốt hơn xung quanh Ví
dụ, các lập trình viên đã bỏ ra nhiều năm làm việc với ASP cổ điển có thể biết rằng sự tương thíchchặt chẽ giữa code và HTML chưa thật sự lý tưởng
Viết ra những đoạn mã có thể bảo trì không phải là một điều dễ dàng Khi bạn bắt đầu, bạn sẽ phảisiêng năng hơn cho tới khi mọi việc đi vào quỹ đạo Bạn có thể nghi ngờ, nhưng chúng tôi khôngphải là người đầu tiên đặt suy nghĩ vào việc tạo ra những chương trình có thể bảo trì Cuối cùng,
có một số ý niệm bạn nên quen dần Khi chúng ta lướt qua một điều gí đó, hãy bỏ thời gian nghiêncứu chúng thật kĩ và tìm kiếm thêm thông tin để có được cái nhìn sâu sắc và nền tảng vững vànghơn Và, quan trọng hơn hết, hãy cố tìm hiểu xem chúng ứng dụng vào dự án hiện tại mà bạn đangthực hiện như thế nào
Sự đơn giản
Công cụ tốt nhất để làm cho chương trình của bạn có thể bảo trì được là làm cho nó đơn giản nhất
có thể được Theo quan điểm thông thường thì để có thể bảo trì được, trước nhất hệ thống phảiđược xây dựng để có thể đáp ứng cho tất cả các yêu cầu thay đổi khả dĩ Tôi đã thấy các hệ thốngđược xây dựng dựa trên meta-repositories (những bảng với những cột khóa – key và cột giá trị -value) hoặc cấu hình XML phức tạp, nhằm mục đích giải quyết các thay đổi mà khách hàng yêucầu Những hệ thống này không chỉ có xu hướng vấp phải những giới hạn kĩ thuật nghiêm trọng(thể hiện của hệ thống sẽ chậm hơn do sự cồng kềnh) mà chúng còn hầu như luôn không đạt đượcmục đích (chúng ta sẽ đề cập vấn đề này kĩ hơn khi nói về YAGNI) Theo kinh nghiệm bản thân,cách đúng đắn để đặt được sự mềm dẻo là giữ cho hệ thống càng đơn giản càng tốt, để cho bạn,hoặc một lập trình viên khác có thể đọc và hiểu đoạn code một cách dễ dàng cũng như có thể thựchiện một số thay đổi cần thiết Vì sao phải xây dựng cả một tập hợp những luật cấu hình trong khi
ta chỉ cần kiểm tra chiều dài của tên người dùng hệ thống đạt độ dài cần thiết hay không? Trongchương sau, ta sẽ xem mô hình “Phát triển định hướng testcase” - Test Driven Development - cóthể giúp chúng ta đạt tới mức độ cao hơn của sự đơn giản bằng cách đảm bảo rằng ta tập trung vàonhững gì ma khách hàng yêu cầu như thế nào
YAGNI
Bạn sẽ không phải cần nó - you aren’t going to need it - là một quan điểm lập trình cực đoan Theo
đó, bạn không nên xây dựng một chương trình nào đó bây giờ bởi vì bạn sẽ không cần tới nó trongtương lai Kinh nghiệm cho thấy rằng bạn có thể sẽ không thực sự cần đến nó hoặc cần phải có đôichút thay đổi Bạn có thể mất cá tháng để xây dựng một hệ thống linh động một cách đáng kinhngạc nhưng chỉ cần 2 dòng email đơn giản của khách hàng để làm cho cả hệ thống đó trở nên hoàntoàn vô dụng Có một lần, khi tôi đã bắt tay vào thiết kế một máy báo cáo không giới hạn thời gian(open-ended reporting engine), thì tôi nhận ra rằng mình đã hiểu sai ý khách hàng Những gì họthật sự muốn có là một thiết bị báo cáo hằng ngày (single daily report), và thực ra tôi chỉ cần 15phút để làm điều đó
Thời điểm hồi đáp cuối cùng(Last Responsible Moment)
Trang 6Ý tưởng đằng sau thời điểm hồi đáp cuối cùng là bạn cần trì hoãn việc xây dựng bất cứ điều gì chođến khi bạn buộc phải làm như vậy Thực sự là, trong một số trường hợp, thời điểm này có thể xuấthiện rất sớm trong quá trình phát triển phần mềm Ý tưởng này rất gần gũi với YAGNI, với ýnghĩa rằng cho dù bạn thật sự cần nó, bạn vẫn nên trì hoãn việc xây dựng nó cho đến khi bạnkhông thể chờ hơn nữa Điều này sẽ giúp bạn, và khách hàng của bạn, có thời gian để chắc chắnrằng bạn thật sự cần nó, đồng thời cũng có thể sẽ giúp bạn giảm thiểu số thay đổi mà bạn cần làmtrong khi và sau khi phát triển sản phẩm.
Cặp đôi
Cặp đôi xảy ra khi có 2 class phụ thuộc lẫn nhau Bất cứ khi nào có thể, bạn cần phải giảm thiểucặp đôi để giảm thiểu ảnh hưởng của những thay đổi và gia tăng khả năng kiểm tra của code Giảmthiểu và thậm chí là loại bỏ cặp đôi thực ra dễ dàng hơn nhiều người nghĩ; có những thủ thuật vàcông cụ giúp bạn hoàn thành việc đó Cái khó là phát hiện ra những cặp đôi không mong muốn.Chúng ta sẽ bàn đến cặp đôi chi tiết hơn ỡ những chương sau
Unit Tests và Continuous Integration
Kiểm tra đơn vị và sự tích hợp liên tục, Unit Tests và Continuous Integration, thường được biếtđến như là CI, là một đề tài nữa sẽ được đề cập sau Có hai điều bạn cần phải nắm trước Đầu tiên,
cả hai đều hiệu quả trong việc giúp ta có được code có tính bảo trì cao Unit tests giúp cho ngườiphát triển có được một sự tự tin lớn lao Số lượng những thay đổi về việc tái phân rã – refactoring -
và tính năng mà bạn sẵn sàng tạo ra là vô cùng lớn khi bạn có một tấm lưới an toàn là hàng trăm,hàng ngàn test tự động để chắc rằng bạn chưa hề phá vỡ chương trình Thứ hai là, nếu bạn khôngmuốn chấp nhận, hoặc ít nhất là thử, việc tiến hành kiểm tra đơn vị, bạn đang lãng phí thời gianđọc những điều này Phần lớn những gì sẽ được đề cập sẽ chú trọng về nâng cao khả năng testđược của code
Trong chương này
Mặc dù chương này không có bất cứ dòng code nào, chúng ta đã đề cập đến một số vấn đề Bởi vìtôi muốn cuốn sách này sẽ thiên về thực hành hơn là lí thuyết, chúng ta sẽ chú trọng nhiều hơn vềcode kể từ đây Hy vọng là tôi đã làm sang tỏ nhiếu từ thuật ngữ mà bạn thường nghe tới Một vàichương tới sẽ tạo nền tảng cho các phần còn lại bằng cách trình bày OOP và tính vững bền -
Trang 7persistence - ở một mức độ cao hơn Cho tới đó, tôi hy vọng các bạn dành thời gian nghiên cứu vềnhững thuật ngữ tôi đã trình bày Bởi vì kinh nghiệm của chính bạn chính là công cụ đắc lực nhất,hãy nghĩ về những dự án vừa qua và hiện tại và liệt kê những điều không thành công cũng nhưthành công của bạn trong suốt thời gian đó
Trang 8
Chương 2 THIẾT KẾ HƯỚNG LĨNH VỰC
Thiết kế là gì? Đó là nơi mà bạn đứng giữa hai thế giới – thế giới của công nghệ và thếgiới của con người và những mục đích của họ - và bạn cố gắng mang hai thế giới lại cạnhnhau – Mitchell Kapor
Có thể đoán được rằng chúng ta sẽ bắt đầu bằng Domain driven design (thiết kế định hướng theolĩnh vực) và lập trình hướng đối tượng (OOP) Ban đầu tôi nghĩ là tôi nên bỏ qua một số phầntrong chủ đề này, nhưng điều đó có thể gây khó khăn cho cả tôi và các bạn Có một số hữu hạncách thiết thực để thiết kế phần cốt lõi của chương trình Cách tiếp cận phổ biến với NET củanhững nhà phát triền là dùng mô hình dữ liệu tập trung(data-centric model) Có nhiều khả năngbạn là một chuyên gia trong cách tiếp cận này – thành thạo sử dụng những repeater lồng nhau, kiểuevent hữu dụng ItemDataBoundvà điều chỉnh thuần thụcDataRelations. Một giải pháp khác, vốnrất thông dụng với các nhà phát triển Java và nhanh chóng phổ biến trong cộng đồng NET, làthiên về cách tiếp cận tập trung lĩnh vực
Thiết kế hướng dữ liệu/ lĩnh vực
Cách tiếp cận tập trung vào dữ liệu và hướng lĩnh vực là gì? Tập trung dữ liệu nói chung có nghĩa
là bạn xây dựng một hệ thống dựa trên những hiểu biết của bạn về dữ liệu mà bạn sẽ phải tươngtác Cách tiếp cận tiêu biểu nhất là trước hết bạn xây dựng mẫu cơ sở dữ liệu bằng cách tạo ra cácbảng quan hệ, cột quan hệ và các khóa ngoại; sau đó mô phỏng nó trên ngôn ngữ C# hoặcVB.NET Lí do phương pháp này trở nên rất thông dụng trong giới phát triền NET là Microsoft đãtốn nhiều thời gian để tự động hóa quá trình dịch sang ngôn ngữ lập trình với những lớpDataAdapters, DataSets và DataTables Chúng ta đều biết rằng với một bảng với dữliệu, chúng ta có thể có một website hoặc một ứng dụng windows hoạt động trong chưa đầy nămphút với chỉ vài dòng code.Điểm mấu chốt là tập trung vào dữ liệu – một ý kiến tốt trong hầu hếtcác trường hợp Cách tiếp cận này đôi khi được gọi là “phát triển hướng dữ liệu”
Domain-centric, hay thường gọi hơn là thiết kế hướng lĩnh vực (DDD), tập trung chủ yếu về vấn
đề về lĩnh vực Vấn đề đó không chỉ bao gồm dữ liệu mà còn có cả cách hoạt động Vì thế, ví dụ,
ta không chỉ chú trọng TênRiêng của một nhân viên mà còn phải chú ý đến khả năng đượcTăngLương Vấn đề về lĩnh vực chỉ là một cách nói tương trưng cho nghiệp vụ mà bạn đang xâydựng hệ thống để quản lí.Công cụ chúng ta sử dụng là lập trình hướng đối tượng (OOP), và việcbạn chỉ biết sử dụng những ngôn ngữ hướng đối tượng như C# hay VB.NET không có nghĩa là bạnđang lập trình hướng đối tượng
Những mô tả trên ít nhiều làm bạn hiểu lầm – trên khía cạnh nào đó nó có nghĩa là nếu bạn sửdụng DataSets bạn sẽ không cần quan tâm hoặc không thể cung cấp mô phỏng hoạt động tănglương cho một nhân viên Tất nhiên điều đó không hoàn toàn đúng – trên thực tế vấn đề đó rất dễthực hiện Một hệ thống dữ liệu tập trung không hề thiếu những hoạt động (behaviour) cũng nhưkhông hề xem chúng như là những phần phụ.DDD chỉ đơn giản là tốt hơn khi xử lý các hệ thốngphức tạp bằng một cách dễ bảo trì với nhiều lí do – tất cả những lí do đó sẽ được trình bày trongchương sau Điều đó không có nghĩa là “hướng lĩnh vực” tốt hơn “hướng dữ liệu” Nó chỉ đơn giản
làm cho “hướng lĩnh vực” tốt hơn “hướng dữ liệu” trong một số trường hợp, và điều ngược lại
cũng đúng Bạn có thể đã từng đọc tất cả những điều trên, và cuối cùng, bạn đơn giản chỉ cần tintưởng đôi chút và cố gắng chấp nhận những gì tôi đã nói – ít nhất cũng đủ để bạn có thể tự tư duy (Những điều trên có vẻ hơi thô và đối lập đôi chút với những gì tôi đã nói trong phần giới thiệu,nhưng những tranh luận giữa MSDN Way và ALT.NET có thể được tóm tắt như sự tranh luận giữahướng lĩnh vực và hướng dữ liệu Mặc dù vậy, những người thực sự theo ALT.NET cần phải cho
Trang 9rằng hướng dữ liệu thực sự là một sự lựa chọn đúng đắn trong một số trường hợp Tôi cho rằngphần lớn sự đối lập giữa hai trường phái trên là do Microsoft thiên về thiết kế hướng dữ liệu mặc
dù trên thực tế nó không tương thích tốt với những gì mà những nhà phát triển NET đang làm(phát triển ứng dụng doanh nghiệp), và khi không sử dụng đúng cách, kết quả tạo ra là những dòngcode khó được bảo trì hơn Nhiều lập trình viên, cả trong và ngoài cộng đồng NET đều gãi đầusuy nghĩ tại sao Microsoft kiên định làm ngược lại với những điều thông thường và phải luôn luôn
cố gắng bắt kịp một cách vụng về )
Ngừơi dùng, khách hàng và Các bên liên quan
“ Trong quá khứ, tôi thường xuyên bực mình với khách hàng Họ khó chịu, không biết họ cần gì vàluôn luôn có những quyết định sai lầm Nhưng khi tôi thật sự nghĩ lại về điều đó, tôi nhận ra rằngtôi đã không thông minh như tôi nghĩ Khách hàng biết rõ về công việc của họ hơn tôi rất nhiều.Không chỉ thế, mà đó còn là tiền của họ và công việc của tôi là giúp họ có được nhiều lợi nhuậnnhất từ đồng vốn ấy Và khi mối quan hệ với khách hàng trở nên tích cực và có mối cộng tác tốt,không chỉ kết quả công việc tăng lên đáng kể mà việc lập trình cũng trở nên thú vị trở lại.”
Vài điều tôi rút ra một cách nghiêm túc từ sự phát triển của Agile là sự ảnh hưởng lẫn nhau của độiphát triển phần mềm, người dùng và khách hàng Trên thực tế, bất cứ khi nào có thể, tôi khôngphân biệt nhà phát triển hay khách hàng, tất cả là một thực thể thống nhất : đội phát triển Bất kểbạn có đủ may mắn hay không trong tình huống này (đôi khi bạn dính đến luật pháp hoặc đôi khikhách hàng không sẵn sàng hợp tác chặt chẽ), hiểu được chức năng của từng người là rất quantrọng Khách hàng là người trả tiền và vì vậy cũng là người đưa ra những quyết định cuối cùng vềnhững vấn đề chủ chốt và độ ưu tiên Người dùng là những người sử dụng hệ thống Khách hàngthường là người dùng nhưng hiếm khi là người dùng duy nhất Ví dụ một website có thể có nhữngngười dùng ẩn danh, người dùng chính thức, người điều hành và người quản lý Cuối cùng, cácbên liên quan là bất cứ ai có liên quan đến hệ thống Một trang web có thể có những trang “cha” vàtrang “anh chị” , các nhà quảng cáo, PR hoặc chuyên gia lĩnh vực
Khách hàng có một công việc rất khó khăn Họ phải dành ưu tiên một cách khách quan cho điều
mà mọi người muốn, bao gồm cả họ, thu xếp với một ngân quỹ có hạn Rõ ràng, họ sẽ phạm sailầm, có thể vì họ không hiểu hoàn toàn những nhu cầu của người dùng, có thể vì họ hiểu lầmnhững thông tin mà bạn cung cấp, cũng có thể do họ dành quá ưu tiên cho nhu cầu của họ hơn làcủa những người khác Là một nhà phát triển phần mềm, công việc của bạn là giúp họ tránh sailầm càng nhiều càng tốt và đồng thời đáp ứng nhu cầu của họ
Bất kể bạn có xây dựng một hệ thống vì mục đích thương mại hay không, tiêu chí đánh giá cuốicùng cho sự thành công của hệ thống đó là cách mà người dùng cảm nhận về nó Vì thế, trong lúcbạn và khách hàng làm việc với nhau, mong rằng cả hai đều hướng tới nhu cầu của người dùng.Nếu bạn và khách hàng của bạn xem việc xây dựng hệ thống cho người dùng là quan trọng, tôithực sự khuyên bạn nên đọc thật kĩ User Stories – một nơi tốt để bắt đầu là Mike Cohn’s excellentUser Stories Applied1
Cuối cùng, lí do chính cho sự có mặt của chương này là các chuyên gia lĩnh vực Chuyên gia lĩnhvực là những người biết rõ về đầu vào và đầu ra của cái thế giới mà hệ thống của bạn đang tồn tại.Cách đây không lâu, tôi là một thành viên của một dự án phát triển rất lớn của tổ hợp tài chính, và
ở đó thật sự đã có hàng trăm chuyên viên lĩnh vựcmà phần lớn trong số đó là nhà kinh tế hoặc nhà
kế toán Họ là những người nhiệt tình với công việc của mình cũng giống như bạn say mê lậptrình Bất cứ ai cũng có thể trở thành một chuyên gia lĩnh vực – một khách hàng, một người dùng,một cổ đông và thậm chí là bạn Sự phụ thuộc của bạn vào các chuyên gia lĩnh vực tăng cùng với
sự phức tạp của hệ thống
Hướng lĩnh vực (The domain object)
Như tôi đã nói từ trước, lập trình hướng đối tượng là một công cụ chúng ta sẽ dùng để đưa thiết kếtập trung lĩnh vực vào thực tế Chi tiết hơn, chúng ta sẽ dựa vào vào sức mạnh của lớp và sự đónggói Trong chương này, chúng ta sẽ tập trung vào những phần cơ bản của lớp và vài thủ thuật khác
Trang 10để bắt đầu – nhiều người có thể đã biết hết những gì đề cập ở đây Chúng ta sẽ không đề cập vềtính bền vững - persistence - bây giờ (có thể xem phần cơ sở dữ liệu) Nếu bạn còn xa lạ với cáchthiết kế này, bạn có thể luôn cảm thấy thắc mắc về cơ sở dữ liệu và các đoạn code truy xuất dữliệu Hãy cố đừng quá lo lắng Trong chương tiếp theo ta sẽ đề cập tới phần cơ bản của tính bềnvững, và ở chương tiếp theo nữa, ta sẽ nghiên cứu nó sâu hơn nữa.
Ý tưởng đằng sau thiết kế hướng lĩnh vực là xây dựng một hệ thống phản ánh lại lĩnh vực của vấn
đề mà bạn đang cố giải quyết Đây sẽ là nơi mà các chuyên gia lĩnh vực nhập cuộc – họ sẽ giúpbạn hiểu được hệ thống đang hoạt động như thế nào (thậm chí nếu nó chỉ là quá trình thực hiệnbằng tay trên giấy) và hệ thống đúng ra phải hoạt động như thế nào Ban đầu bạn sẽ bị ngợp trong
sự hiểu biết của họ - họ sẽ nói những thứ mà bạn chưa từng biết đến và họ sẽ phải ngạc nhiên trướcánh nhìn ngơ ngác của bạn Họ cũng sẽ dùng những từ viết tắt, những từ đặc biệt và điều đó sẽ làmcho bạn phải tự hỏi rằng liệu mình có thể đảm đương được công việc này hay không Cuối cùng,đây chính là mục đích thực sự của một nhà phát triển ứng dụng doanh nghiệp - hiểu về lĩnh vựccủa vấn đề Bạn đã biết lập trình như thế nào, nhưng bạn có biết lập trình trong một hệ thống kiểm
kê cụ thể mà bạn được yêu cầu? Một ai đó phải hiểu được ngôn ngữ của người khác, và nếu nhữngchuyên gia lĩnh vực học cách lập trình thì chúng ta sẽ không còn việc làm
Với tất cả những ai đã đọc qua những phần trên đều hiểu rằng học về một lĩnh vực mới là phầnphức tạp nhất trong công việc lập trình Vì thế, làm cho code của bạn tương đồng nhiều nhất có thểđược với lĩnh vực ấy sẽ rất có ích Về cơ bản, điều tôi nói đến là sự giao tiếp Nếu khách hàng củabạn nói về Mục đích chiến lược, điều mà một tháng trước đây chẳng có ý nghĩa gì với bạn, và codecủa bạn là MụcĐíchChiếnLược thì một số điểm mơ hồ, nguy cơ hiểu lầm sẽ được dẹp bỏ Nhiềungười, kể cả tôi, cho rằng một nơi tốt để bắt đầu công việc là học những cụm danh từ mà nhữngchuyên gia trong lĩnh vực ấy cũng như người dùng hay sử dụng Ví dụ nếu bạn xây dựng một hệthống cho một công ty mua bán xe và bạn nói chuyện với người bán hàng (người đó có thể vừa làngười dùng vừa là chuyên gia trong ngành), anh ấy chắc chắn sẽ nói về Khách hàng, Xe (car), Mo-del (model), Kiện hàng (package) và Nâng Cấp (upgrade), Tiền thanh toán (payment), v v Nhữngđiều ấy là nồng cốt trong công việc của anh ấy, nên tất nhiên cũng sẽ là phần chính trong hệ thốngcủa bạn Ngoài những cụm danh từ thì điều cần lưu ý là sự giao hội tụ của ngôn ngữ kinh doanh,vốn được biết tới như ngôn ngữ thống nhất Ý tưởng dùng một ngôn ngữ duy nhất cho người dùng
và hệ thống sẽ giúp cho việc bảo trì dễ dàng hơn cũng như giảm thiểu những sai lầm
Chính xác thì bắt đầu công việc như thế nào là hoàn toàn phụ thuộc vào bạn Thiết kế hướng lĩnhvực không nhất thiết phải bắt đầu bằng việc mô phong lĩnh vực đó (mặc dù đó lá một ý kiến hay!),
mà nó có nghĩa là bạn nên tập trung vào lĩnh vực đó và để nó điều khiển quyết định của bạn Đầutiên, bạn hoàn toàn có thể bắt đầu với mô hình dữ liệu của bạn, khi chúng ta khám phá mô hìnhphát triển định hướng testcase chúng ta sẽ sử dụng một cách tiếp cận khác rất thích hợp với DDD.Tuy nhiên, bây giờ chúng ta giả sử rằng chúng ta đã nói chuyện với khách hàng và vài người bánhàng, và nhận ra rằng điều khó khăn chủ yếu là kiểm soát sự phụ thuộc lẫn nhau giữa những khảnăng nâng cấp hệ thống Công việc đầu tiên mà chúng ta làm là thiết kế bốn class:
public class Car{}
public class Model{}
public class Package{}
public class Upgrade{}
Tiếp theo chúng ta sẽ thêm vào một ít code dựa trên một vài giả thiết khá tin cậy:
public class Car
{
private Model _model;
private List<Upgrade> _upgrades;
Trang 11public void Add(Upgrade upgrade){ //todo }
}
public class Model
{
private int _id;
private int _year;
private string _name;
public ReadOnlyCollection<Upgrade> GetAvailableUpgrades()
private int _id;
private string _name;
public ReadOnlyCollection<Upgrade> RequiredUpgrades
{
get {
return null; //todo }
}
}
Công việc hoàn toàn đơn giản Chúng ta sẽ thêm vào vài thông tin truyền thống ( id, tên), vài thamchiếu (cả Cars và Models đều có Upgrades), và hàm Add vào class Car Bây giờ ta có thể thay đốimột ít và bắt đầu viết một ít về những hoạt động thực tế
public class Car
{
private Model _model;
//todo where to initialize this?
private List<Upgrade> _upgrades;
public void Add(Upgrade upgrade)
List<Upgrade> missingUpgrades = new List<Upgrade>();
foreach (Upgrade upgrade in _upgrades) {
foreach (Upgrade dependentUpgrade in upgrade.RequiredUpgrades) {
} return missingUpgrades.AsReadOnly();
}
}
Đầu tiên chúng ta sẽ hiện thực phương thức Add. Tiếp theo ta sẽ hiện thực phương thức cho phép
ta phục hồi lại những phần nâng cấp bị mất Cần lặp lại rằng đây mới chỉ là bước đầu Bước tiếp
Trang 12theo có thể là tìm xem nâng cấp nào đã gây ra việc làm mất các nâng cấp khác, nói cách khac, bạnphải lựa chọn 4 WheelDrive cùng với Traction Control của bạn ; tuy nhiên, chúng ta sẽ dừng lại ởđây Mục đích của phần trên chỉ là đưa ra cách chúng ta có thể bắt đầu và phần mở đầu có thể rasao
Giao diện người dùng (UI)
Bạn có thể thấy rằng là chúng ta chưa hề nhắc tới UIs Bởi vì lĩnh vực của ta độc lập với lớppresentation – nó có thể dùng để làm mạnh website, ứng dụng windows, dịch vụ windows Việccuối cùng mà bạn muốn làm là trộn lẫn lớp presentation của bạn và logic của lĩnh vực Làm nhưthế không những tạo ra những đoạn code khó đọc và khó kiểm tra mà còn làm cho ta không thể tái
sử dụng logic của chúng ta giữa các UI (có thể đây không phải là vấn đề quá quan trọg, nhưng tính
dễ đọc và dễ bảo trì thì khác) Tiếc thay, nhiều nhà phát triển ASP.NET lại trộn lẫn UI và lớp củalĩnh vực Thậm chí tôi có thể nói rằng, chúng ta rất thường thấy những hoạt động trông suốt quátrình của một hàm xử lý sự kiện ấn nút hay sự kiện tải trang – page load – trong ASP.NET Nhữngkhung trang ASP.NET điều khiển ASP.NET UI – không phải là hiện thực những hoạt động NútSave không nên thông qua những quy tắc kinh doanh rắc rối (hoặc tệ hơn là truy cập cơ sỡ dữ liệumột cách trực tiếp), mà mục đích của nó là sửa lại trang ASP.NET dựa trên kết quả của lớp lĩnhvực – có thể nó nên thông qua một trang khác, đưa ra vài tin báo lỗi hoặc yêu cầu thêm thông tin.Hãy nhớ rằng bạn muốn viết những đoạn code liền lạc với nhau Logic của ứng dụng ASP.NETcủa bạn nên tập trung vào một việc và làm việc đó cho tốt – Tôi tin rằng sẽ không ai phản đối rằngnếu một đối tượng cần phải quản lí page, điều đó có nghĩa là nó không thể thực thi chức năng liênquan tới lĩnh vực.Thêm vào đó, đặt logic trongnhững file code behind thường vi phạm nguyên tắc
“Không tự lặp lại”, đơn giản chỉ vì việc tái sử dụng code trong file aspx.cs rất khó
Chính vì những điều trên, bạn không thể chờ quá lâu để bắt đầu UI Trước hết, ta nên lấy phản hồicủa khách hàng càng sớm và càng thường xuyên càng tốt Tôi không cho rằng họ sẽ ấn tượng nếu
ta gửi cho họ một loạt file cs/vb cùng với các class Thứ hai, khi đưa lớp của lĩnh vực ra thực tế sẽgiúp ta thấy được vài chỗ hỏng và vài điều không hợp lí Ví dụ, sự ngưng kết nối tự nhiên củatrang web của nghĩa là ta phải chỉnh sửa đôi chút trong thế giới hướng đối tượng thuần khiết củachúng ta để có được những trải nghiệm tốt hơn của người dùng Theo kinh nghiệm của tôi thì unittest còn quá hẹp để thấy được những khuyết tật này
Bạn sẽ hài lòng khi biết rằng ASP.NET và WinForms xử lý những đoạn mã tập trung lĩnh vựccũng tốt như những lớp tập trung dữ liệu Bạn có thể ràng buộc dữ liệu – data binding với bất kìtập hợp NET nào và sử dụng các phiên - session và kỹ thuật đệm - cache như những gì bạn thườnglàm, và làm bất cứ gì bạn vẫn hay làm Trên thực tế, ngoài ra, ảnh hưởng đối với UI có thể là ítnghiêm trọng nhất Tất nhiên bạn không nên ngạc nhiên khi biết rằng các lập trình viên ALT.NETcho rằng bạn nên mở rộng đầu óc khi làm việc với bộ công cụ hỗ trợ trình bày ASP.NET PageFramework không nhất thiết phải là công cụ tốt nhất cho công việc của bạn – phần đông chúng tôicho rằng nó phức tạp một cách không cần thiết và cũng không bền vững Chúng ta sẽ đề cập vấn
đề này kĩ hơn trong chương sau, nhưng nếu bạn muốn tìm hiểu rõ hơn, tôi khuyên bạn nên đọc vềMonoRails ( nó là khung cho NET) hoặc khung giá MVC mới được Microsoft tung ra Việc cuốicùng tôi muốn là dành cho những người đã chán nản với sự rộng lớn của những thay đổi, vì vậybây giờ hãy trở lại đề tái chính
Bí quyết và thủ thuật
Ta sẽ kết thúc chương này bằng việc lướt qua vài điều có ích mà ta có thể làm với class Ta chỉmới bước trên bề mặt của tảng băng, nhưng hi vọng những thông tin này sẽ giúp bạn bước đi đúngcách
Factory Pattern
Trang 13Chúng ta sẽ làm gì khi Khách Hàng mua một chiếc Xe mới? Rõ ràng ta cần tạo ra một đối tượngkiều Xe và chỉ rõ mo-del Một cách truyền thống để làm việc này là sử dụng hàm tạo – constructor
và đơn giản tạo ra đối tượng mới với từ khóa new Một cách tiếp cận khác là sử dụng cách tiếp cận
factory pattern để tạo đối tượng:
public class Xe
{
private Model _model;
private List<Upgrade> _upgrades;
ra những tên hàm có ý nghĩa hơn Ví dụ đầu tiên xuất hiện trong đầu là khi bạn muốn tạo ra mộtđối tượng của class NgườiDùng, bạn có thể có NgườiDùng.XácĐịnhBởiThôngTin (string tên,string mật-mã), NgườiDùng XácĐịnhBởiId ( int id) và Người Dùng XácĐịnhBởiVịTrí (string vị-trí) Bạn có thể tạo được những hàm giống nhau với cách nạp chồng constructor, nhưng rất hiếmkhi tạo được sự rõ ràng như nhau Thành thật mà nói, tôi luôn phải rất khó khăn để chọn lựa cáinào để sử dụng, vì thế đó là một vấn đề của lựa chọn và sở thích riêng
Chỉ định truy cập – Access Modifier
Khi bạn tập trung viết những class đóng gói những hoạt động của nghiệp vụ, một API phong phú
sẽ được tạo ra để UI của bạn sử dụng Giữ cho API trong sáng và rõ ràng luôn là một ý tưởng tốt.Một cách đơn giản nhất là làm cho API nhỏ lại bằng cách giấu tất cả trừ những phương thức cầnthiết nhất Vài phương thức rõ ràng phải public và một vài cái khác phải private, nhưng nếu bạnkhông chắc chắn, hãy để quyền truy cập hạn chế và chỉ thay đổi khi cần thiết Tôi ưa chuộngphương thức internal trong phần lớn các phương thức và thuộc tính Thành phần internal chỉ có thểđược thấy bởi các thành viên trong cùng một khối hợp ngữ - vì thế nếu bạn thực sự phân chia cáclớp của bạn giữa nhiều khối hợp ngữ (vốn thường là một ý tưởng hay), bạn sẽ thu nhỏ API đángkể
Interface
Giao diện giữ một vai trò quan trọng trong việc giúp ta tạo ra những đoạn code dễ bảo trì Ta sửdụng nó để tách rời code cũng như tạo những class giả để dùng cho kiểm tra đơn vị - unit testing.Một giao diện là một giao kèo trước mà các class thực thi phải tuân thủ Giả sử chúng ta muốn tómgọn tất cả dữ liệu trong một class tên SqlServerDataAccess:
internal class SqlServerDataAccess
Trang 14}
public void ASampleMethod()
{
SqlServerDataAccess da = new SqlServerDataAccess();
List<Upgrade> upgrades = da.RetrieveAllUpgrades();
}
Bạn có thể thấy rằng phần code mẫu ở cuối trong ví dụ trên có tham chiếu trực tiếp tới
dữ liệu Những đoạn code “cặp đôi” chặt chẽ này rất khó thay đổi cũng như khó kiểm tra (takhông thể kiểm tra ASampleMethod mà không biết đầy đủ phương thức RetrieveAllUpgrades).
Ta có thể làm giảm bớt sự liên hệ chặt chẽ này bằng cách lập trình một giao diện thay thế:
internal interface IDataAccess
Đây chỉ là một ví dụ đơn giản cho việc sử dụng giao diện để hỗ trợ chúng ta Chúng ta có thể làmcải thiện đoạn code hơn nữa bằng cách tạo ra những ví dụ của các class thông qua dữ liệu cấu hìnhhoặc tạo ra những khung được dành riêng cho công việc (đó chính xác là điều chúng ta chuẩn bịlàm) Chúng ta sẽ thường xuyên thiên về việc lập trình với các giao diện hơn là với các lớp thực
sự, vì thế nếu bạn chưa quen với điều đó hãy cố tìm đọc thêm
Che dấu thông tin và tính đóng gói
Ẩn thông tin là nguyên tắc trong đó những quyết định thiết kế nên được che giấu khỏi các phầncòn lại của hệ thống Nói chung khi xây dựng các class và thành phần ta càng giữ kín càng tốt,điều đó sẽ giúp cho sự thay đổi trong thực thi sẽ không ảnh hưởng tới các class và thành phầnkhác Sự đóng gói là phần hiện thực hướng đối tượng của việc che dấu thông tin Về cơ bản cónghĩa là những dữ liệu của đối tượng (các trường - field) và phần thực thi không nên để nhữngclass khác có thể truy cập được càng nhiều càng tốt Một ví dụ phổ biến nhất là làm cho các trườngprivate nhưng với thuộc tính public Tốt hơn nữa là nên hỏi chính bạn liệu những trường địnhdanh (id) có thật sự cần thuộc tính public
Trang 16CHƯƠNG 3 Persistence
CODD'S AIM WAS TO FREE PROGRAMMERS FROM HAVING TO
KNOW THE PHYSICAL STRUCTURE OF DATA OUR AIM IS TO
FREE THEM IN ADDITION FROM HAVING TO
KNOW ITS LOGICAL STRUCTURE – LAZY SOFTWARE
Trong chương trước, chúng ta đã thảo luận về DDD mà không nói nhiều đến cơ sở dữ liệu Nếu bạn đã thường lập trình với DataSet, bạn chắc chắn sẽ có nhiều câu hỏi về việc nó làm việc như thếnào DataSet rất lớn nên bạn cần phải cực kỳ cẩn thận Trong chương này, chúng ta sẽ bắt đầu thảothuận quanh việc làm thế nào để giải quyết persistence sử dụng DDD Chúng ta sẽ viết code để bắt cầu giữa các đối tượng C# và các bảng SQL Trong phần sau, chúng ta sẽ xem xét về các vấn đề cao hơn (hai hướng mapping O/R khác nhau), như DataSet- giúp chúng ta làm nhiều việc phức tạp.Chương này đóng lại vấn đề đã thảo luận từ trước và mở ra vấn đề mới với nhiều mẫu persistence
Gap
Như chúng ta đã biết, chương trình chạy trên bộ nhớ và yêu cầu một không gian để lưu trữ hay persist thông tin Ngày nay, giải pháp lựa chọn là cơ sở dữ liệu quan hê Persistence thực sự là một chủ đề khá lớn trong lĩnh vực phá triển phần mềm bởi vì, không có sự giúp đỡ của các pattern và công cụ thì đó không phải là cách đơn giản nhất để thực hiện thành công Đối với lập trình hướng đối tượng, thách thức được đưa ra với một cái tên: Object-Relational Impedance Mismatch Khá nhiều nghĩa cho rằng dữ liệu quan hệ (relational data) không ánh xạ hoàn toàn với các đối tượng vàcác đối tượng cũng không ánh xạ hoàn toàn Microsoft cơ bản đã cố gắng lờ đi vấn đề này và tạo đơn giản một biểu diễn quan hệ (relational representation) với code hướng đối tượng- một cách tiếp cận khéo léo, nhưng không phải không có sai sót như hiệu suất kém, các lỗ hổng, khả năng kiểm tra kém, rắc rối, và bảo trì kém (Cơ sở dữ liệu hướng đối tượng, cái tốt nhất theo kiến thức của tôi, cũng không thể làm giảm.)
Tốt hơn cách lờ đi vấn đề này, chúng ta có thể và nên đối mặt với nó Chúng ta đối mặt với nó để
có thể tận chụng cái tốt nhất giữa hai world – các quy tắc business phức tạp được hiện thực trong OOP và lưu trữ dữ liệu và lấy ra thong qua cơ sở dữ liệu quan hệ Dĩ nhiên, chúng bắt cầu cho chúng ta vượt qua gap Nhưng gap thực sự là gì? Impedance Mismatch này là gì? Bạn hầu như đang nghĩa rằng nó không thể khó hơn để việc đẩy dữ liệu quan hệ ra đối tượng và đưa chúng trở lại các bảng (Hầu hết là đúng trong bất cứ trường hợp nào… và bây giờ giả định nó luôn là một quá trình đơn giản)
DataMapper
Với những dự án nhỏ chỉ vài lớp domain và bảng dữ liệu nhỏ, nhFor small projects with only a handful of small domain classes and database tables, ưu tiên của tôi là viết code bằng tay để ánh xạgiữa hai bên Hãy xem ví dụ đơn giản sau Việc đầu tiên là chúng ta sẽ mở rộng lớp Upgrade của chúng ta ( chúng ta chỉ tập trung vào phần dữ liệu của lớp (các trường) bởi vì đó là những gì sẽ nhận persist):
Trang 17public class Upgrade
{
private int _id;
private string _name;
private string _description;
private decimal _price;
private List<Upgrade> _requiredUpgrades;
public int Id
{
get { return _id; }
internal set { _id = value; }
}
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Description
{
get { return _description; }
set { _description = value; }
}
public decimal Price
{
get { return _price; }
set { _price = value; }
Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
[Name] VARCHAR(64) NOT NULL,
Description VARCHAR(512) NOT NULL,
Price MONEY NOT NULL,
)
Không có điều gì ngạc nhiên ở đây Bây giờ đến phần thú vị, chúng ta sẽ bắt đầu xây dựng tầng Data Access nằm giữa Domain và các mô hình quan hệ (relational model)
Trang 18internal class SqlServerDataAccess
{
private readonly static string _connectionString = "FROM_CONFIG"
internal List<Upgrade> RetrieveAllUpgrades()
{
//use a sproc if you prefer
string sql = "SELECT Id, Name, Description, Price FROM Upgrades";
using (SqlCommand command = new SqlCommand(sql))
using (SqlDataReader dataReader = ExecuteReader(command))
ExecuteReader là một phương thức giúp giảm nhẹ các đoạn mã thừa của chúng ta
RetrieveAllUpgrades chọn tất cả các upgrade và lưu chúng vào một danh sách thông qua
DataMapper Hàm CreateUpgrade (xem ở dưới) là một đoạn code có thể tái sử dụng mà chúng ta dùng để ánh xạ (map) thông tin upgrade vào cơ sở dữ liệu trong domain của chúng ta Nó không phức tạp vì mô hình Domain và mô hình dữ liệu là tương tự nhau
internal static class DataMapper
{
internal static Upgrade CreateUpgrade(IDataReader dataReader)
{
Upgrade upgrade = new Upgrade();
upgrade.Id = Convert.ToInt32(dataReader["Id"]); upgrade.Name =
Trang 19Nếu chúng ta cần làm, chúng ta có thể tái sử dụng hàm CreateUpgrade lúc cần thiết Ví dụ nếu cần lấy thông tin upgrade bằng id hay price– cả hai là các phương thức mới trong lớp
SqlParameter[] parameters = new SqlParameter[4];
parameters[0] = new SqlParameter("Id", SqlDbType.Int);
(handle) phức tạp nhất là các quan hệ Trong domain world là các tham khảo ( hay tập các tham khảo)đến các đối tượng khác; nơi mà relational world sử dụng các khóa ngoại Sự khác nhau này là một trở ngại bất biến Việc sửa đổi không phải là quá khó Đầu tiên, chúng ta thêm một bảng join many-to-many mà nó liên kết tới một upgrade với các upgrade khác mà chúng cần ( có thể là 0,1 hay nhiều hơn nữa)
CREATE TABLE UpgradeDepencies
(
UpgradeId INT NOT NULL,
RequiredUpgradeId INT NOT NULL,
)
Kế tiếp, chúng ta cần chỉnh sửa RetrieveAllUpgrade để load các upgrade cần thiết.:
Trang 20internal List<Upgrade> RetrieveAllUpgrades()
{
string sql = @"SELECT Id, Name, Description, Price FROM Upgrades;
SELECT UpgradeId, RequiredUpgradeId FROM UpgradeDepencies"; using (SqlCommand command = new SqlCommand(sql))
using (SqlDataReader dataReader = ExecuteReader(command))
{
List<Upgrade> upgrades = new List<Upgrade>();
Dictionary<int, Upgrade> localCache = new Dictionary<int, Upgrade>();
int upgradeId = dataReader.GetInt32(0);
int requiredUpgradeId = dataReader.GetInt32(1);
Upgrade upgrade;
Upgrade required;
if (!localCache.TryGetValue(upgradeId, out upgrade)
|| !localCache.TryGetValue(requiredUpgradeId, out required)) {
//probably should throw an exception //since our db is in a weird state continue;
Chúng ta kéo các thông tin extra join table cùng với câu query ban đầu và tạo một từ điển tìm kiếm
để truy cập nhanh chóng các upgrade bằng id của chúng Tiếp thep là lặp thông qua join table, lấy các upgrade thích hợp từ từ điển tìm kiếm và thêm chúng vào các tập hợp
Đó không phải là giải pháp tốt nhất nhưng làm việc tốt Chúng ta có thể refactor hàm một ít để làm
nó dễ đọc hơn, nhưng bây giờ và trong trường hợp đơn giản này, chúng ta sẽ do the job
Các hạn chế
Mặc dù chúng ta chỉ đang bắt đầu tìm kiếm việc ánh xạ (mapping), đáng để tìm những hạn chế màchúng ta tự đặt ra cho chính mình Bạn một lần đi xuống còn đường tự viết loại code này mà nó cóthể thoát khỏi sự kiểm soát nhanh chóng Nếu muốn thêm các phương thức sắp xếp hay lọc chúng
ta phải viết SQL động hoặc phải viết rất nhiều phương thức Chúng ta sẽ kết thúc việc viết một nhóm các phương thức RetrieveUpgradeByX mà nó tương tự nhau
Thông thường bạn sẽ muốn lazy-load các quan hệ Đó là thay vì load tất cả các upgrade yêu cầu lên trước, có lẽ chúng ta muốn load chúng chỉ khi cần thiết Trong trường hợp này, đó không phải
là một ý tưởng lớn bởi vì chỉ là một tham khảo 32 bit Một ví dụ tốt hơn là quan hệ của các Model
để upgrade Tương đối dễ để hiện thực các lazy load
Trang 21Vấn đề quan trọng nhất là phải làm việc với id Nếu chúng ta gọi RetrieveAllUpgrades hai lần, chúng ta sẽ nhận được một phiên bản riêng biệt của mọi upgrade Điều này có thể cho kết quả mâu thuẫn:
SqlServerDataAccess da = new SqlServerDataAccess();
Upgrade upgrade1a = da.RetrieveAllUpgrades()[0];
Upgrade upgrade1b = da.RetrieveAllUpgrades()[0];
upgrade1b.Price = 2000;
upgrade1b.Save();
Giá cả thay đổi để việc upgrade đầu tiên sẽ không bị phản chiếu đến một thể hiện trỏ tới bởi upgrade1a Trong một số trường hợp đó không phải là một vấn đề Tuy nhiên, trong nhiều trường hợp, bạn sẽ muốn tầng Data Acess theo dõi id của các thể hiện (instance) mà nó tạo ra và thực hiện một số điều khiển (bạn có thể đọc thêm bằng cách tìm kiếm mẫu Identify Map)
Chắc chắn là có thêm một số hạn chế, nhưng điều cuối chúng ta sẽ nói về việc phải làm việc với các Unit of work (một lần nữa bạn có thể đọc thêm bằng cách tra Google với cụm Unit of Work) Khi bạn viết code trên tầng Data Access, bạn cần chắc chắn rằng khi bạn persist một đối tượng, bạn cũng persisttất cả các đối tượng tham khảo được cập nhật, nếu cần thiết Nếu bạn đang làm việc trên phần admin của hệ thống bán xe hơi, bạn có thể tạo một Model mới và tạo một Upgrade Nếu bạn gọi Save trên Model của bạn, bạn cần chắc chắn là Updare của bạn cũng được lưu Giải pháp đơn giản nhất là gọi Save thường xuyên với mỗi hành động - nhưng cảThe simplest solution is to call save often for each individual action – nhưng điều này vừa khó (các quan hệ có thể sâu vài cấp) và không hiệu quả Một cách tương tự bạn có thể chỉ thay đổi một ít các thuộc tính và sau đó phải quyết định giữa việc lưu lại tất cả các trường hay theo dõi sự thay đổi các thuộc tính và chỉ cập nhật chúng Một lần nữa, đối với các
hệ thống nhỏ, đây không phải là vấn đề lớn Đối với các hệ thống lớn hơn, gần như là bất khả thi để thực hiện bằng tay (ngoài ra, tốt hơn là việc tốn thời gian xây dựng Unit of work của bạn, có thể bạn nên viết các chức năng yêu cầu của client)
Tổng kết chương
Cuối cùng, chúng ta sẽ không dựa vào việc ánh xạ bằng tay, nó vừa không đủ linh hoạt và chúng ta kết thúc việc tốn thời gian quá nhiều để viết những đoạn code không được sử dụng ởclient Tuy nhiên, thật quan trọng để xem việc ánh xạ trong hành động và mặc dù chúng ta lấymột ví dụ đơn giản, chúng ta vẫn gặp một số vấn đề Bởi vì mapping là không phức tạp, điều quan trọng nhất là giới hạn của cách tiếp cận này Thử suy nghĩ những gì có thể xảy ra nếu hai instance riêng biệt của một dữ liệu giống nhau đang lơ lửng trong code của bạn, hay làm thế nào mà tầng Data Acess của bạn phồng lên nhanh chóng như một yêu cầu mới Chúng ta
sẽ không xem lại persistence trong hai chương cuối nhưng khi chúng ta định vị lại nó, chúng
ta sẽ xem xét đầy đủ tất cả
Trang 22Dependency Injection
I WOULD SAY THAT MODERN SOFTWARE ENGINEERING IS THE
ONGOING REF INEMENT OF THE EVER-INCREASING DEGREES OF
DECOUPLING YET, WHILE THE HISTORY OF SOFTWARE SHOWS
THAT COUPLING IS BAD, IT ALSO SUGGESTS THAT COUPLING IS
UNAVOIDABLE A N ABSOLUTELY DECOUPLED APPLICATION IS
US ELESS BECAUSE IT ADDS NO VALUE DEVELOPERS CAN ONLY
ADD VALUE BY COUPLING THINGS TOGETHER THE VERY ACT
OF WRITING CODE IS COUPLING ONE THING TO ANOTHER THE
REAL QUESTION IS HOW TO WISELY CHOOSE WHAT TO BE
COUPLED TO - JUVAL LÖWY
t’s common to hear developers promote layering as a means to provide
extensibility The most common example, and one I used in Chapter 2 when
we looked at interfaces, is the ability to switch out your data access layer in
order to connect to a different database If your projects are anything
like mine, you know upfront what database you’re going to use and you know you aren’tgoing to have to change it Sure, you could build that flexibility upfront - just in case - butwhat about keeping things simple and You Aren’t Going To Need IT (YAGNI)?
I used to write about the importance of domain layers in order to have re-use across multiple presentation layers: website, windows applications and web services Ironically, I’ve rarely
had to write multiple front-ends for a given domain layer I still think layering is important,
but my reasoning has changed I now see layering as a natural by-product of highly cohesive code with at least some thought put into coupling That is, if you build things right, it should automatically come out layered
The real reason we’re spending a whole chapter on decoupling (which layering is a high-level implementation of) is because it’s a key ingredient in writing testable code It w asn’t until I started unit testing that I realized how tangled and fragile my code was I quickly became frustrated because method X relied on a function in class Y which needed a database up and running In order to avoid the headaches I went through, we’ll first cover coupling and then look at unit testing in the next chapter
(A point about YAGNI While many developers consider it a hard rule, I rather think of it as a general guideline There are good reasons why you want to ignore YAGNI, the most obvi ous
is your own experience If you know that something will be hard to implement later, it might
be a good idea to build it now, or at least put hooks in place This is something I frequently do with caching, building an ICacheProvider and a NullCacheProvider implementation that does nothing, except provide the necessary hooks for a real implementation later on That said, of the numerous guidelines out there, AY G NI , RD Y a nd S u s t a i n a b l e P a c e a re easily the three I consider the most important.)
Trang 23Sneak Peak trong Unit Testing
Nói về việc bắt cặp có liên quan đến kiểm tra đơn vị (unit testing) thì cũng tương tự như vấn đề con gà
và quả trứng – cái nào có trước Tôi nghĩ tốt nhất là nên tiếp tục với kiểm tra đơn vị Điều quan trọng nhất đó là kiểm tra đơn vị thì chỉ bao gồm về đơn vị Bạn không nên tập trung kiểm tra từ đầu đến cuối, chỉ nên tập trung vào thuộc tính độc lập Ý tưởng là nếu bạn kiểm tra mỗi thuộc tính qua mỗi phương pháp và kiểm tra tính tương tác của chúng với những cái khác, toàn bộ hệ thống của bạn sẽ cứng nhắc Phương pháp mà bạn muốn dùng để kiểm tra đơn vị có thể có sự độc lập với những lớp khác, những cái
mà không thể dễ dàng thực thi trong vòng nội dung của một sự kiểm tra (ví dụ như cơ sở dữ liệu, hay một thành phần của trình duyệt web) Vì lý do này, kiểm tra đơn vị sử dụng những lớp giả (mock) – hay lớp tưởng tượng
Xem ví dụ sau, lưu trạng thái của xe hơi:
public class Car
{
private int _id;
public void Save()
{
if (!IsValid())
{
//todo: come up with a better exception
throw new InvalidOperationException("The car must be in a valid
Để kiểm tra hiệu quả phương thức Save, có 3 điều chúng ta phải làm:
1 Chắc chắn rằng exception được ném ra khi cố lưu một chiếc xe là trạng thái hợp lệ
2 Chắc chắn rằng phương thức Save của tầng Data Access được gọi khi có một chiếc xe mới
3 Chắc chắn rằng Updatemethod đuợc gọi khi có một chiếc xe tồn tại
Những điều mà chúng ta không muốn làm (cũng quan trọng không kém những thứ ta muốn làm)
là kiểm tra chức năng của IsValid hay chức năng Save và Update của tầng Data Access Vấn đề quan trọng cuối cùng đó là tất cả những gì chúng ta muốn làm là chắc chắn rằng tất cả những chức năng này được gọi với các tham số thích hợp và giá trị trở về (nếu có) được nắm giữ Thật khó khi bạn phải suy nghĩ với những ví dụ trừu tượng, nhưng mocking framework sẽ cho bạn
Trang 24khái niệm về Save và Update, chắc chắn rằng những argument chính xác phải được kiểm tra và đạt, và trả về bất cứ giá trị nào chúng ta mong muốn Mocking framework thì khá thú vị và hữu ích bởi vì đoạn code của bạn sẽ chặt chẽ theo từng cặp.
Don’t avo id Coupling lik e the Plag ue
Trong trường hợp bạn quên những gì đã nói ở chương 1, bắt cặp (coupling) thì đơn giản là những
gì những ta gọi khi một lớp đòi hỏi một lớp khác để hoạt động Điều quan trọng nhất của đoạn code
là sự độc lập với những lớp khác Nếu bạn viết string site = “CodeBetter”, bạn đã thực hiện việc bắt cặp với lớp System.String – nếu nó thay đổi, đoạn code của bạn có khả năng break rất cao Dĩ nhiên, điều đầu tiên bạn nên biết đó là trong những trường hợp đa số, ví dụ như những ví dụ với chuỗi, bắt cặp không phải là một điều không tốt
Chúng ta không muốn tạo các interface và provider cho mỗi và mọi lớp Có thể chấp nhận cho lớp Car giữ một tham khảo trực tiếp của lớp Upgrade Điều không thể được là bất cứ sự bắt cặpnào tới thành phần bên ngoài ( dữ liệu, state server, cache server, dịch vụ web), bất cứ đoạn code nào đòi hỏi thiết lập mở rộng ( khung dữ liệu) và, như tôi đã nói ở project trước, bất cứ đoạn code nào phát triển output ngẫu nhiên (password, key) Điều đó có thể là một sự miên tả gây nhầm lẫn, nhưng sau chapter này và chapter sau,l và một khi bạn đã sử dụng unit testing, bạn sẽ cảm thấy rằng điều gì nên làm và điều gì nên tránh
Sẽ là một ý kiến hay nếu bạn tách cặp dữ liệu từ domain, chúng ta sẽ có một ví dụ trong chương này
Dependency Injection
Trong chương 2, chúng ta đã thấy được giao diện có thể giúp cho trườngause của chúng ta như thế nào – tuy nhiên, đoạn code được cúng cấp không cho phép chúng ta có thể cung cấp động một hiện thực giả của IdataAccess cho lớp DataAccessfactory để trả về.Nói cách khác để giải quyết điều này, chúng ta sẽ dựa vào một pattern gọi là Dependency Injection (DI) DI là một giải pháp cụ thể cho tình huống này, bởi vì, như tên gọi, đó là một pattern chuyển một
dependency được code cứng thành được đưau vào trong lúc chạy Chúng ta sẽ xem xét 2 mẫu DI, một cái chúng ta có thể tự làm, một cái dựa vào một thư viện bên thứ ba third party library
Constructor Injection
Hình thức đơn giản nhất của Di là constructor injection – đó là, thêm vào một sự phụ thuộc qua con trỏ của lớp Đầu tiên, nhìn vào giao diện DataAccess một lần nữa :
Trang 25internal interface IDataAccess
{
int Save(Car car);
void Update(Car car);
}
internal class MockDataAccess : IDataAccess
{
private readonly List<Car> _cars = new List<Car>();
public int Save(Car car)
Mặc dù chức năng upgrade mock của chúng có thể được cải thiện, nó sẽ được làm sau Chỉ
có một sự thay đổi nhỏ so với lớp Car nguyên bản:
public class Car
{
private int _id;
private IDataAccess _dataProvider;
public Car() : this(new SqlServerDataAccess())
//todo: come up with a better exception
throw new InvalidOperationException("The car must be in a valid
state");
}
if (_id == 0)
{
Trang 26Hãy nhìn vào đoạn code trên và cả bên dưới Chú ý rằng việc sử dụng con trỏ có nghĩa là DI không
có ảnh hưởng gì đến đoạn code hiện hành – Nếu bạn chọn không thêm vào IdataAccess Nếu bạn muốn thêm vào một thuộc tính, ví dụ như trong MockDataAccess, ta có thể:
public void AlmostATest()
to go into setting up classes for unit testing Since DI is so critical to unit testing, and most unit testers love their open-source tools, it should come as no surprise that a number of
frameworks exist to help automate DI The rest of this chapter will focus on StructureMap,
an open source Dependency Injection framework created by fellow CodeBetter blogger
Jeremy Miller ( h t t p : / / s t r u c t u r e m a p s ou r c e f o r g e n e t / )
Trước khi sử dụng StructureMap bạn phải cấu hình nó sử dụng một file XML (được gọi là
StructureMap.config) hay thêm những thuộc tính vào class của bạn Những cấu hình cần thiết này nói lên đây là interface mà tôi muốn để chương trình đối diện và đây là những hiện thực mặc định Cấu hình đơn giản nhất để nhận StructureMap và chạy sẽ giống như sau:
Trang 27đề nghị bạn hãy vào StructureMap website)
Một khi đã configurate, bạn có thể undo tất cả những thay đổi thực hiện ở lớp Car để cho phép việc thêm vào con trỏ (gỡ bỏ the_dataProvider và tất cả những con trỏ) Để có thể có được việc thêm vào IDataAccess chính xác, chúng ta chỉ cần yêu cầu StrutureMap, phương pháp Save bây giờ trông như thế này::
public class Car
{
private int _id;
public void Save()
{
if (!IsValid())
{
//todo: come up with a better exception
throw new InvalidOperationException("The car must be in a valid
Trang 28Để sử dụng mock hơn là việc thêm vào chuẩn, chúng ta chỉ cần thêm mock vào StructureMap:
public void AlmostATest()
{
ObjectFactory.InjectStub(typeof(IDataAccess), new MockDataAccess()); Car
car = new Car();
DI frameworks như StructureMap thì rất dễ sử dụng và cũng rất hữu ích Với một cặp lệnh
configuration và một vài thay đổi nho nhỏ trong đoạn code của chúng ta, chúng ta có thể giảm
đáng kể việc bắt cặp và tăng khả năng kiểm tra Trước đây, tôi đã từng giới thiệu StructureMap với một luợng lớn codebase, tuy nhiên tầm ảnh hưởng thì không đáng kể
Sự cải tiến cuối cùng
Bằng việc giới thiệu lớp IDataAccess cũng như việc sử dụng DI Framework, chúng ta đã giải quyết việc gỡ bỏ nhiều việc bắt cặp dở trong đoạn code ví dụ Chúng ta có thể thực hiện tiếp vài bước nữa, thậm chí tới một điểm mà có thể mang lại hại nhiều hơn lợi Tuy nhiên, có một sự phụ thuộc mà tôi muốn tránh đi - những object bussiness thì thường không biết về việc thêm vào DI Thay vì gọi
StructureMap's ObjectFactory trực tiếp, chúng ta sẽ thêm vào nhiều cấp độ gián tiếp:
public static class DataFactory
{
public static IDataAccess CreateInstance
{
get { } }
}
Trang 29return ObjectFactory.GetInstance<IDataAccess>();
Một lần nữa, nhờ vào vài thay đổi đơn giản, chúng ta có thể tạo nên những thay đổi lớn (chọnmột DI framework khác) tốt hơn vào việc phát triển ứng dụng của chúng ta đơn giản
Trang 30Tổng kết chương
Giảm việc bắt cặp là một trong những khá đơn giản để thực hiện mà mang lại kết quả tốt Tất cả những thứ cần là kiến thức và phương pháp, và dĩ nhiên, cả công cụ Có thể dễ dàng thấy được rằngtại sao bạn muốn giảm sự phục thuộc giữa các component trong đoạn code - đặc biệt là giữa những component chịu trách nhiệm nhiều lĩnh vực trong hệ thống (UI, Domain và dữ liệu) Trong chương
kế chúng ta sẽ tìm hiểu unit testing ảnh hưởng tới lợi ích của sự thêm vào phụ thuộc (dependency injection) Nếu bạn không thấy có vấn đề gì thắc mắc với DI, hãy tìm hiểu chi tiết thêm tại
DotNetSlackers
Trang 31CHƯƠNG 5 Unit Testing
Chúng ta sẽ không chọn unit testing nếu nó không cho chúng ta những lợi ích hơn
Trong suốt quyển sách này, chúng ta đã nói về tầm quan trọng của khả năng kiểm
tra(testability ) và chúng ta cũng đã xem qua nhiều kĩ thuật để làm đơn giản hóa việc kiểm tra hệ thống Không cần phải nói rằng lợi ích lớn của việc viết test cho hệ thống của chúng ta là khả năng mang tới sản phẩm tốt hơn cho khách hàng mặc dù điều này thì cũng đúng cho unit testing, nhưng lý do mà tôi viết unit test là không có gì có thể tới gần hơn tới việc cải thiện khả năng quản lý của hệ thống nhiều hơn việc phét triển suite cho unit test, Bạn sẽ thường nghe rằng unit testing hỗ trợ speak của unit test - và đó thực sự làcái nó bao gồm Dựa trên 1 dự án mà tôi đang làm, chúng ta sẽ tiếp tục thực hiện thay đổi
đẻ cải thiện hệ thống ( thay đổi về chức năng, performance, refactoring) Nếu đó là một
hệ thống lớn, chúng ta thỉnh thoảng sẽ được yêu cầu tạo ra những sự thay đổi có thể làm chúng ta phát hoảng Liệu có thể làm được? Liệu nó sẽ tạo ra những hiệu ứng dữ dội? Những lỗi gì sẽ được đưa ra? Nếu không có unit test, chúng ta sẽ từ chối tạo ra những sự thay đổi cao hơn Nhưng chúng ta biết rằng, và khách hàng của chúng ta cũng biết, rằng những sự thay đổi với nguy cơ cao sẽ là những cái mang lại thành công lớn Nếu có hơn
100 unit test chạy trong vòng vài phút, chúng ta có thể tách rời component, sắp xếp code
và xây dựng tính năng mà chúng ta không bao giờ nghĩ đến 1 năm trước, và cũng không cần phải lo lắng gí về nó Bởi vì chúng ta tự tin rằng với sự hoàn chỉnh của unit test, chúng ta biết rằng chúng nó không đưa ra lỗi
Kiểm tra đơn vị không chỉ làm nhẹ bớt các thay dổi rủi ro cao Trong cuộc đời lập trình, tôi đã từng nhiều lần xử lý các lỗi quan trọng gây nên bởi những sự thay đổi có vẻ mang rủi ro thấp Một điểm là tôi cần tạo một thay đổi cơ bản cho hệ thống của chúng ta, click chuột phải lên solution, chọn “Run Test” và trong 2 phút sẽ biết chúng ta đang ở đâu Tôi không thể đơn giản nhấn mạnh kiểm tra đơn vị quan trọng như thế nào Chắc chắn rằng chúng hữu ích để tìm kiếm lỗi và kiểm tra những code của tôi có nên vậy hay không, nhưng quan trọng hơn là khả năng ma thuật để phát hiện lỗi nghiêm trọng trong thiết kế của một hệ thống Tôi phấn khích bất cứ khi nào lướt ngang một phương thức hay hành
vi mà khó để kiểm tra Nghĩa là tôi tìm thấy một thiếu sót trong một phần cơ bản của hệ thống Một cách tương tự, bất cứ khi nào tôi đặt cùng 1 test vào 1 cặp giây cho một điều nào đó mà tôi khá chắc chắn là sẽ khó, tôi biết một số người trong team viết code có lẽ sửdụng lại từ dự án khác
Tại sao tôi đã không dùng Unit Test trong 3 năm trước?
Với những người đã tìm thấy được tiện lợi của unit testing, thật là khó hiểu tại sao mọi người lại không sử dụng nó Với những người chưa chấp nhận nó, bạn chắc chắn muốn chúng ta sẽ chấm dứt nói về điều đó Trong nhiều năm tôi đã đọc blog và nói chuyện với những đồng nghiệp đã từng làm việc trên unit test, nhưng chưa từng tự tay thực hành lần nào
Nhìn lại, đây là một số điều khiến tôi mất một thời gian để đi theo phong trào:
Trang 321 Tôi đã có quan niệm sai về mục đích của unit testing Như tôi đã từng nói, unit testing cải thiện chất lượng của hệ thống, nhưng nó thật sự làm dễ dàng để chuyển hoặc bảo trì
hệ thống sau này Hơn thế nữa, nếu bạn tiến đến bước tiếp theo và adopt Test Driven Development (TDD), unit testing thật sự trở thành phần thiết kế Theo diễn giải của ScottBellware, TDD không phải là testing bởi vè bạn không phải suy nghĩ như một tester khi thực hiện TDD mà bạn suy nghĩ như một designer
2 Giống như nhiều người, tôi thường nghĩ những người phát triển phần mềm thì không nên viết những phần test Tôi không biết câu chuyện đằng sau sự tin tưởng này, nhưng hiện tại tôi nghĩ nó là một cái cớ của những lập trình viên kém Testing là một quá trình của cả tìm lỗi trong một hệ thống cũng như xác thực rằng hệ thống làm việc như mong đợi Có lẽ các nhà phát triển phần mềm không đươc giỏi trong việc tìm lỗi trong code của họ, nhưng họ là những người thích hợp nhất để chắc chắn rằng hệ thống làm việc theo hướng mà họ mong muốn và người sử dụng là người thích hợp để kiểm tra rằng nó
là việc như nó nên làm.Thậm chí nếu unit testing không thật sự là testing đi chăng nữa, thì những người phát triển phần mềm nào mà không tin rằng họ nên test code thì thật sự
là người không có trách nhiệm
3 Testing chẳng có gì vui vẻ Ngồi trước màn hình, nhập dữ liệu và chắc chắn mọi thứ đều ok Nhưng unit testing lại là coding, cũng có nghĩa là có rất nhiều trở ngại để thành công Đôi khi, cũng như coding, testing cũng rất bình thường, nhưng nhìn chung nó chẳng khác gì việc lập trình bạn làm hằng ngày
4 Nó tốn thời gian Vài người sẽ nói rằng unit testing không hề tốn thời gian, mà nó tiết kiệm thời gian cho bạn Đó là sự thật đúng vì thời gian bạn bỏ ra để viết unit test chỉ là một phần nhỏ so với thời gian bạn giành để đổi các yêu cầu và chỉnh lỗi Thành thật mà nói, unit testing cũng tốn khá nhiều thời gian (đặc biệt khi bạn chỉ mới bắt đầu) Bạn có thể không có đủ thời gian cho unit test hoặc khách hang của bạn có thể cảm thấy không được thoả mãn Trong những trường hợp này, tôi khuyên bạn xác định phần code then chốt nhất và test nó, thậm chí có phải trải qua hằng giờ để viết unit test cũng có thể có một ảnh hưởng lớn
Rốt cuộc, unit testing trông giống như một vật phức tạp và huyền bí, chỉ được sử dụng trong những trường hợp cần thiết Những lợi ích được xem là không thể đạt tới đươc và thời gian dường như không cho phép cho nó Nó cũng phải luyện tập rất nhiều (tuôi cũng
đã có thời gian khó khăn để học về unit test và cách để dung nó), nhưng những lợi ích hầu như có thể thấy được trưc tiếp
nUnit là testing framework chúng ta sẽ sử dụng Có rất nhiều thay đổi, như là mbUnit, nhưng tôi không biết nhiều về chúng lắm
Trang 33RhinoMocks là mocking framework chúng ta sẽ sử dụng Trong phần trước, chúng ta tạo mock 1 cách thủ công – nó vừa có nhiều giới hạn, vừa tốn thời gian RhinoMocks sẽ tự động tạo lớp mock từ một giao diện và cho phép chúng ta thẩm tra và điều khiển tương tác với nó.
nUnit
Điều đầu tiên phải làm là thêm liên kết vào nunit.framework.dll va Rhino.Mocks.dll Theo sở thích của riêng tôi là đặt các unit tests vào bộ phận riêng của chúng Ví dụ, nếu lớp domain của tôi được đặt trong CodeBetter.Foundations, tôi sẽ tạo 1 bộ phận mới gọi
là CodeBetter.Foundations.Tests Nó có nghĩa là chúng ta sẽ không có thể test những phương thức private Trong NET 2.0+, chúng ta có thể sử dụng
InternalsVisibleToAttribute để cho phép để cho phép bộ phận Test có thể truy cập vào các phương thức bên trong (mở Properties/AssemblyInfo.cs và thêm
[assembly:InternalsVisibleTo(“CodeBetter.Foundations.Test”)]
đó là 1 vài thứ mà tôi phải làm.)
Có 2 việc bạn cần phải biết về nUnit Đầu tiên, bạn điều chỉnh việc test băng cách sử dụng các thuộc tính TestFixtureAttribute được áp dụng vào class chứa việc kiểm tra của bạn, thiết lập và teardown phương thức SetupAttribute được áp dụng vào các phương thức mà bạn muốn thực hiện trước mỗi lần test - bạn sẽ không thường xuyên cần chúng Tương tự, TearDownAttribute được áp dụng vào các phương thức bạn muốn thực thi saukhi test Cuối cùng, TestAttribute được áp dụng vào các unit test của bạn (còn có các thuộc tính khác, nhưng đây là 4 thuộc tính quan trọng nhất) Nó sẽ trông giống như:
Điều thứ hai cần phải biết về nUnit là bạn xác thực việc kiểm tra của bạn thực thi như mong muốn thông qua việc sử dụng lớp Assert và rất nhiều phương thức của nó Tôi biết điều này là không thoả đáng, nhưng nếu chúng ta có một phương thức
nhận đối số int[] và trả về tổng của nó, unit test của chúng ta sẽ trông như sau:
Trang 34public void MathUtilityReturnsValueWhenPassedOneValue()
Bạn sẽ không thể nào biết về chúng từ ví dụ trên, nhưng lớp Assert có nhiều hơn 1
hàm,như là: Assert.IsFalse, Assert.IsTrue, Assert.IsNull, Assert.IsNotNull,
Assert.AreSame, Assert.AreNotEqual, Assert.Greater, Assert.IsInstanceOfType,
Unit Test là gì?
Unit tests là phương thức dùng để kiểm tra cách hoạt động ở múc rất chi tiết Người phát triển mới với việc unit test thường để phạm vi kiểm tra của họ tăng khá lớn Hầu hết unit test cho phép các thành phần giống nhau: thực thi vài code từ hệ thống của bạn và xác nhận nó hoạt động như mong muốn Mục đích của unit test là để kiểm chứng các hoạt động đặc biệt Nếu chúng ta viết test cho phương thức Save của lớp Car, chúng ta sẽ không viết test chứa hết tất cả mà chúng ta sẽ viết một test cho mỗi tác vụ mà nó chứa - thất bại khi đối tượng trong trạng thái invalid, gọi đến phương thức Save của lớp Data Access , thiết lập ID và gọi phương thức Update của lớp Data Access Một điều quan trọng là unit test của chúng ta có thể chỉ ra lỗi Tôi chắc rằng một vài người trong số các bạn sẽ thấy 4 tests được dung để cover phương thức MathUtility.Add là dư thừa Bạn có thể nghĩ rằng 4 tests đó có thể nhóm lại thành một – và trong trường hợp nhỏ này, tôi sẽ nói rằng bất cứ thứ gì bạn thích hơn Tuy nhiên, khi tôi bắt đầu sử dụng unit test, tôi đã rơi vào thói quen xấu là để phạm vi test khá lớn Test của tôi sẽ tạo một đối tượng, thực thi vài thành phần và xác nhận chức năng Nhưng như tôi vẫn thường nói, khi tôi vẫn còn
ở đây, tôi sẽ qua 1 cách tốt đẹp trong việc xác nhận thêm để chắc rằng các field này được thiết đặt đúng theo cách mà chúng phải được làm Nó rất nguy hiểm bởi vì một vài thay đổi trong code của bạn có thể phá vỡ hang loạt các test không lien quan với nhau - dứt khoát rằng dấu hiệu mà bạn đưa cho test của bạn phải tập trung trong một phạm vi rất nhỏ
Nó mang chúng ta quay lại với việc kiểm tra với các phương thức private Nếu bạn google, bạn sẽ tìm thấy khá nhiều thảo luận về chủ đề này, nhưng nhất trí chung là bạn không nên kiểm tra các phương thức private Tôi nghĩ 1 lý do thuyết phục để không test các phương thức private là mục đích của chúng ta không phải là để kiểm tra phương thức hay từng dòng code, mà là để kiểm tra cách hoạt động Đó là điều mà các bạn phải luôn luôn nhớ Nếu bạn đã test code của bạn thông qua public interface, thì các phương thức private cũng đã tự động được test Một lý lẽ khác chống lại việc kiểm tra các phương thức private là nó phá vỡ tính đóng gói Chúng ta đã nói về sự quan trong của việc che giấu thông tin Các phương tức private chứa các chi tiết hiện thực mà chúng ta muốn có thể thay đổi mà không cần phá vỡ việc gọi trong code Nếu chúng ta kiểm tra các phươngthức private 1 cách trực tiếp, những thay đổi hiện thực sẽ phá hỏng các test của chúng ta,
mà điều này không báo trước tốt cho khả năng bảo trì cao hơn
Mocking