Quản lý và tổ chức số lượng các lớp tăng lên ồ ạt sau khi chuyển sang RẮN?


50

Trong vài năm qua, chúng tôi đã dần dần chuyển đổi sang mã được viết tốt hơn, một vài bước một lần. Cuối cùng chúng tôi cũng bắt đầu thực hiện chuyển đổi sang một cái gì đó ít nhất giống với RẮN, nhưng chúng tôi chưa hoàn toàn ở đó. Kể từ khi thực hiện chuyển đổi, một trong những phàn nàn lớn nhất từ ​​các nhà phát triển là họ không thể xem xét ngang hàng và duyệt qua hàng chục và hàng chục tệp mà trước đây mọi tác vụ chỉ yêu cầu nhà phát triển chạm vào 5-10 tệp.

Trước khi bắt đầu thực hiện chuyển đổi, kiến ​​trúc của chúng tôi đã được tổ chức khá giống như sau (được cấp, với một hoặc hai đơn đặt hàng nhiều tệp hơn):

Solution
- Business
-- AccountLogic
-- DocumentLogic
-- UsersLogic
- Entities (Database entities)
- Models (Domain Models)
- Repositories
-- AccountRepo
-- DocumentRepo
-- UserRepo
- ViewModels
-- AccountViewModel
-- DocumentViewModel
-- UserViewModel
- UI

File khôn ngoan, mọi thứ đều cực kỳ tuyến tính và nhỏ gọn. Rõ ràng có rất nhiều sự trùng lặp mã, khớp nối chặt chẽ và đau đầu, tuy nhiên, mọi người đều có thể vượt qua nó và tìm ra nó. Những người mới hoàn thành, những người chưa bao giờ mở Visual Studio, có thể tìm ra nó chỉ trong vài tuần. Việc thiếu độ phức tạp của tệp tổng thể khiến cho các nhà phát triển mới làm quen và tuyển dụng mới bắt đầu đóng góp mà không mất quá nhiều thời gian. Nhưng điều này là khá nhiều khi bất kỳ lợi ích của phong cách mã đi ra ngoài cửa sổ.

Tôi hoàn toàn tán thành mọi nỗ lực của chúng tôi để cải thiện cơ sở mã hóa của chúng tôi, nhưng rất phổ biến để có được một số phản hồi từ phần còn lại của đội trong các thay đổi mô hình lớn như thế này. Một vài điểm gắn bó lớn nhất hiện nay là:

  • Bài kiểm tra đơn vị
  • Đếm lớp
  • Độ phức tạp đánh giá ngang hàng

Các bài kiểm tra đơn vị đã cực kỳ khó bán cho nhóm vì tất cả họ đều tin rằng họ lãng phí thời gian và họ có thể xử lý kiểm tra mã của họ nhanh hơn nhiều so với từng phần riêng lẻ. Sử dụng các bài kiểm tra đơn vị như một sự chứng thực cho RẮN hầu hết là vô ích và chủ yếu trở thành một trò đùa vào thời điểm này.

Số lượng lớp có lẽ là trở ngại lớn nhất để vượt qua. Các tác vụ được sử dụng để lấy 5-10 tệp giờ có thể mất 70-100! Mặc dù mỗi tệp này phục vụ một mục đích riêng biệt, khối lượng tệp tuyệt đối có thể áp đảo. Phản hồi từ đội chủ yếu là rên rỉ và gãi đầu. Trước đây một tác vụ có thể đã yêu cầu một hoặc hai kho lưu trữ, một hoặc hai mô hình, một lớp logic và một phương thức điều khiển.

Bây giờ, để xây dựng một ứng dụng lưu tệp đơn giản, bạn có một lớp để kiểm tra xem tệp đã tồn tại chưa, một lớp để viết siêu dữ liệu, một lớp để trừu tượng hóa DateTime.Nowđể bạn có thể tiêm thời gian để kiểm tra đơn vị, giao diện cho mọi tệp chứa logic, tệp để chứa các bài kiểm tra đơn vị cho từng lớp ngoài đó và một hoặc nhiều tệp để thêm mọi thứ vào thùng chứa DI của bạn.

Đối với các ứng dụng cỡ nhỏ đến trung bình, RẮN là một sản phẩm siêu dễ bán. Mọi người đều thấy lợi ích và dễ bảo trì. Tuy nhiên, họ chỉ không thấy một đề xuất có giá trị tốt cho RẮN trên các ứng dụng quy mô rất lớn. Vì vậy, tôi đang cố gắng tìm cách cải thiện tổ chức và quản lý để giúp chúng tôi vượt qua những cơn đau ngày càng tăng.


Tôi hình dung tôi sẽ đưa ra một chút mạnh mẽ hơn về một ví dụ về khối lượng tệp dựa trên một nhiệm vụ đã hoàn thành gần đây. Tôi đã được giao một nhiệm vụ để thực hiện một số chức năng trong một trong các dịch vụ siêu nhỏ mới hơn của chúng tôi để nhận được yêu cầu đồng bộ hóa tệp. Khi nhận được yêu cầu, dịch vụ sẽ thực hiện một loạt các tra cứu và kiểm tra, và cuối cùng lưu tài liệu vào ổ đĩa mạng, cũng như 2 bảng cơ sở dữ liệu riêng biệt.

Để lưu tài liệu vào ổ đĩa mạng, tôi cần một vài lớp cụ thể:

- IBasePathProvider 
-- string GetBasePath() // returns the network path to store files
-- string GetPatientFolderName() // gets the name of the folder where patient files are stored
- BasePathProvider // provides an implementation of IBasePathProvider
- BasePathProviderTests // ensures we're getting what we expect

- IUniqueFilenameProvider
-- string GetFilename(string path, string fileType);
- UniqueFilenameProvider // performs some filesystem lookups to get a unique filename
- UniqueFilenameProviderTests

- INewGuidProvider // allows me to inject guids to simulate collisions during unit tests
-- Guid NewGuid()
- NewGuidProvider 
- NewGuidProviderTests

- IFileExtensionCombiner // requests may come in a variety of ways, need to ensure extensions are properly appended.
- FileExtensionCombiner
- FileExtensionCombinerTests

- IPatientFileWriter
-- Task SaveFileAsync(string path, byte[] file, string fileType)
-- Task SaveFileAsync(FilePushRequest request) 
- PatientFileWriter
- PatientFileWriterTests

Vì vậy, đó là tổng cộng 15 lớp (không bao gồm POCO và giàn giáo) để thực hiện tiết kiệm khá đơn giản. Con số này tăng lên đáng kể khi tôi cần tạo POCO để đại diện cho các thực thể trong một vài hệ thống, xây dựng một số repos để liên lạc với các hệ thống bên thứ ba không tương thích với các ORM khác của chúng tôi và xây dựng các phương thức logic để xử lý các vấn đề phức tạp của một số hoạt động nhất định.


52
"Các tác vụ trước đây dùng để lấy 5-10 tệp có thể mất 70 - 100!" Làm thế nào trong địa ngục? Điều này là không có cách bình thường. Bạn đang thực hiện loại thay đổi nào yêu cầu thay đổi nhiều tệp đó ??
Euphoric

43
Việc bạn phải thay đổi nhiều tệp hơn cho mỗi tác vụ (nhiều hơn đáng kể!) Có nghĩa là bạn đang làm sai. Toàn bộ vấn đề là tổ chức mã của bạn (theo thời gian) theo cách phản ánh các mẫu thay đổi được quan sát, làm cho các thay đổi trở nên đơn giản hơn. Mọi nguyên tắc trong RẮN đều đi kèm với lý do nhất định đằng sau nó (khi nào và tại sao nên áp dụng nó); có vẻ như bạn đã nhận mình trong tình huống này bằng cách áp dụng những điều này một cách mù quáng. Điều tương tự với thử nghiệm đơn vị (TDD); Nếu bạn đang làm nó mà không nắm bắt được cách thiết kế / kiến ​​trúc, bạn sẽ tự đào một cái hố.
Filip Milovanović

60
Bạn rõ ràng đã chấp nhận RẮN như một tôn giáo chứ không phải là một công cụ thực dụng để giúp hoàn thành công việc. Nếu một cái gì đó trong RẮN làm cho nhiều công việc hơn hoặc làm cho mọi thứ trở nên khó khăn hơn, đừng làm điều đó.
whatsisname

25
@Euphoric: Vấn đề có thể xảy ra theo cả hai cách. Tôi nghi ngờ bạn đang phản ứng với khả năng 70-100 lớp là quá mức cần thiết. Nhưng không phải là không có khả năng đây chỉ là một dự án lớn được nhồi nhét vào 5-10 tệp (tôi đã làm việc trong các tệp 20KLOC trước đó ...) và 70-100 thực sự là số lượng tệp phù hợp.
Flater

18
Có một sự rối loạn trong suy nghĩ mà tôi gọi là "bệnh hạnh phúc đối tượng", đó là niềm tin rằng các kỹ thuật OO tự chấm dứt, thay vì chỉ là một trong nhiều kỹ thuật có thể để giảm chi phí làm việc trong một cơ sở mã lớn. Bạn có một hình thức đặc biệt tiên tiến, "bệnh hạnh phúc RẮN". RẮN không phải là mục tiêu. Giảm chi phí duy trì codebase là mục tiêu. Đánh giá các đề xuất của bạn trong bối cảnh đó, không phải là liệu đó có phải là học thuyết RẮN hay không. (Rằng các đề xuất của bạn có lẽ không thực sự là học thuyết RẮN cũng là một điểm tốt để xem xét.)
Eric Lippert

Câu trả lời:


104

Bây giờ, để xây dựng một ứng dụng lưu tệp đơn giản, bạn có một lớp để kiểm tra xem tệp đã tồn tại chưa, một lớp để viết siêu dữ liệu, một lớp để trừu tượng hóa DateTime. Làm thế nào bạn có thể tiêm thời gian để kiểm tra đơn vị, giao diện cho mọi tệp chứa logic, các tệp để chứa các bài kiểm tra đơn vị cho từng lớp ngoài đó và một hoặc nhiều tệp để thêm mọi thứ vào thùng chứa DI của bạn.

Tôi nghĩ rằng bạn đã hiểu nhầm ý tưởng về một trách nhiệm duy nhất. Trách nhiệm duy nhất của một lớp có thể là "lưu một tệp". Để làm điều đó, sau đó nó có thể chia trách nhiệm đó thành một phương thức kiểm tra xem một tệp có tồn tại hay không, một phương thức ghi siêu dữ liệu, v.v ... Mỗi phương thức đó có một trách nhiệm duy nhất, là một phần của trách nhiệm chung của lớp.

Một lớp để trừu tượng đi DateTime.Nowâm thanh tốt. Nhưng bạn chỉ cần một trong số đó và nó có thể được kết hợp với các tính năng môi trường khác thành một lớp duy nhất với trách nhiệm trừu tượng hóa các tính năng môi trường. Lại một trách nhiệm với nhiều trách nhiệm phụ.

Bạn không cần "giao diện cho mọi tệp chứa logic", bạn cần giao diện cho các lớp có tác dụng phụ, ví dụ: các lớp đọc / ghi vào tệp hoặc cơ sở dữ liệu; và thậm chí sau đó, chúng chỉ cần thiết cho các phần công khai của chức năng đó. Vì vậy, ví dụ trong AccountRepo, bạn có thể không cần bất kỳ giao diện nào, bạn có thể chỉ cần một giao diện để truy cập cơ sở dữ liệu thực tế được đưa vào repo đó.

Các bài kiểm tra đơn vị đã cực kỳ khó bán cho nhóm vì tất cả họ đều tin rằng họ lãng phí thời gian và họ có thể xử lý kiểm tra mã của họ nhanh hơn nhiều so với từng phần riêng lẻ. Sử dụng các bài kiểm tra đơn vị như một sự chứng thực cho RẮN hầu hết là vô ích và chủ yếu trở thành một trò đùa vào thời điểm này.

Điều này cho thấy rằng bạn đã hiểu nhầm bài kiểm tra đơn vị quá. "Đơn vị" của một bài kiểm tra đơn vị không phải là một đơn vị mã. Điều gì thậm chí là một đơn vị mã? Một lớp học? Một phương pháp? Một biến số? Một hướng dẫn máy duy nhất? Không, "đơn vị" chỉ một đơn vị cách ly, tức là mã có thể thực thi cách ly với các phần khác của mã. Một thử nghiệm đơn giản về việc thử nghiệm tự động có phải là thử nghiệm đơn vị hay không là liệu bạn có thể chạy thử nghiệm song song với tất cả các thử nghiệm đơn vị khác mà không ảnh hưởng đến kết quả của nó hay không. Có thêm một vài quy tắc về các bài kiểm tra đơn vị, nhưng đó là biện pháp chính của bạn.

Vì vậy, nếu các phần của mã của bạn thực sự có thể được kiểm tra toàn bộ mà không ảnh hưởng đến các phần khác, thì hãy làm điều đó.

Luôn luôn thực dụng và nhớ mọi thứ là một sự thỏa hiệp. Bạn càng tuân thủ DRY, mã của bạn càng trở nên gắn kết chặt chẽ. Bạn càng giới thiệu trừu tượng, mã càng dễ kiểm tra, nhưng càng khó hiểu. Tránh hệ tư tưởng và tìm sự cân bằng tốt giữa lý tưởng và giữ cho nó đơn giản. Có một điểm ngọt ngào của hiệu quả tối đa cả cho sự phát triển và bảo trì.


27
Tôi muốn nói thêm rằng một cơn đau đầu tương tự xuất hiện khi mọi người cố gắng tuân thủ câu thần chú lặp đi lặp lại quá nhiều về "phương pháp chỉ nên làm một việc" và kết thúc với hàng tấn phương pháp một dòng chỉ vì về mặt kỹ thuật có thể được thực hiện thành phương pháp .
Logarr

8
Re "Luôn luôn thực dụng và ghi nhớ mọi thứ là một sự thỏa hiệp" : Các môn đệ của chú Bob không được biết đến vì điều này (bất kể ý định ban đầu).
Peter Mortensen

13
Để tổng hợp phần đầu tiên, bạn thường có một thực tập cà phê, không phải là một bộ đầy đủ của trình cắm thêm, công tắc lật, kiểm tra nếu cần đường, nạp lại tủ lạnh, lấy sữa, lấy sữa -out-thìa, xuống cốc, rót cà phê, thêm đường, thêm sữa, khuấy cốc, và thực tập sinh cốc. ; P
Justin Time 2 Tái lập lại

12
Nguyên nhân cốt lõi của vấn đề của OP dường như là hiểu sai về sự khác biệt giữa các chức năng nên thực hiện một nhiệm vụ duy nhất và một lớp nên có một trách nhiệm
alephzero

6
"Các quy tắc là dành cho sự hướng dẫn của những người thông thái và sự vâng lời của những kẻ ngốc." - Douglas Bader
Calanus

29

Các tác vụ được sử dụng để lấy 5-10 tệp giờ có thể mất 70-100!

Điều này trái ngược với nguyên tắc trách nhiệm đơn lẻ (SRP). Để đi đến điểm đó, bạn phải phân chia chức năng của mình theo cách rất chi tiết, nhưng đó không phải là điều SRP nói về - làm điều đó bỏ qua ý tưởng chính về sự gắn kết .

Theo SRP, phần mềm nên được chia thành các mô-đun dọc theo các dòng được xác định bởi lý do có thể thay đổi của chúng, để thay đổi thiết kế có thể được áp dụng chỉ trong một mô-đun mà không yêu cầu sửa đổi ở nơi khác. Một "mô-đun" theo nghĩa này có thể tương ứng với nhiều hơn một lớp, nhưng nếu một thay đổi yêu cầu bạn phải chạm vào hàng chục tệp thì đó thực sự là nhiều thay đổi hoặc bạn đang làm sai SRP.

Bob Martin, người đầu tiên xây dựng SRP, đã viết một bài đăng trên blog vài năm trước để cố gắng làm rõ tình hình. Nó thảo luận ở một mức độ nào đó "lý do để thay đổi" là dành cho mục đích của SRP. Nó đáng để đọc toàn bộ, nhưng trong số những điều đáng chú ý đặc biệt là từ ngữ thay thế này của SRP:

Tập hợp những thứ thay đổi vì những lý do tương tự . Tách những thứ đó thay đổi vì những lý do khác nhau.

(nhấn mạnh của tôi). SRP không phải là về việc chia mọi thứ thành những phần nhỏ nhất có thể. Đó không phải là thiết kế tốt, và nhóm của bạn có quyền chống lại. Nó làm cho cơ sở mã của bạn khó cập nhật và bảo trì hơn. Có vẻ như bạn có thể đang cố gắng bán đội của mình dựa trên những cân nhắc thử nghiệm đơn vị, nhưng điều đó sẽ đặt giỏ hàng trước con ngựa.

Tương tự, nguyên tắc phân tách giao diện không nên được coi là tuyệt đối. Không có lý do gì để phân chia mã của bạn tốt hơn SRP, và nó thường phù hợp khá tốt với SRP. Giao diện chứa một số phương thức mà một số khách hàng không sử dụng không phải là lý do để phá vỡ nó. Bạn đang một lần nữa tìm kiếm sự gắn kết.

Ngoài ra, tôi khuyên bạn không nên lấy nguyên tắc đóng mở hoặc nguyên tắc thay thế Liskov làm lý do để ủng hộ hệ thống phân cấp thừa kế sâu. Không có khớp nối chặt chẽ hơn một lớp con với các siêu lớp của nó, và khớp nối chặt chẽ là một vấn đề thiết kế. Thay vào đó, ủng hộ thành phần hơn thừa kế bất cứ nơi nào có ý nghĩa để làm như vậy. Điều này sẽ làm giảm khớp nối của bạn và do đó, số lượng tệp mà một thay đổi cụ thể có thể cần phải chạm vào và nó sẽ phù hợp độc đáo với đảo ngược phụ thuộc.


1
Tôi đoán tôi chỉ đang cố gắng tìm ra dòng ở đâu. Trong một nhiệm vụ gần đây, tôi đã phải thực hiện một thao tác khá đơn giản, nhưng đó là trong một cơ sở mã mà không có nhiều giàn giáo hoặc chức năng hiện có. Như vậy, mọi thứ tôi cần làm rất đơn giản, nhưng tất cả đều khá độc đáo và dường như không phù hợp với các lớp học chung. Trong trường hợp của tôi, tôi cần lưu tài liệu vào ổ đĩa mạng và ghi nhật ký vào hai bảng cơ sở dữ liệu riêng biệt. Các quy tắc xung quanh mỗi bước là khá cụ thể. Ngay cả việc tạo tên tệp (một hướng dẫn đơn giản) cũng có một vài lớp để kiểm tra thuận tiện hơn.
JD Davis

3
Một lần nữa, @JDDavis, chọn nhiều lớp trên một lớp hoàn toàn cho mục đích kiểm tra là đặt giỏ hàng trước ngựa và nó đi thẳng vào SRP, trong đó kêu gọi nhóm các chức năng gắn kết lại với nhau. Tôi không thể tư vấn cho bạn về các chi tiết, nhưng vấn đề thay đổi chức năng cá nhân yêu cầu sửa đổi nhiều tệp là vấn đề mà bạn nên giải quyết (và cố gắng tránh), không phải là vấn đề mà bạn nên cố gắng biện minh.
John Bollinger

Đồng ý, tôi thêm cái này. Để trích dẫn Wikipedia, "Martin định nghĩa một trách nhiệm là một lý do để thay đổi và kết luận rằng một lớp hoặc mô-đun nên có một, và chỉ một, lý do cần thay đổi (nghĩa là viết lại)." và "gần đây ông đã tuyên bố" Nguyên tắc này là về con người. "" Trên thực tế, tôi tin rằng điều này có nghĩa là "trách nhiệm" trong SRP đề cập đến các bên liên quan, không phải chức năng. Một lớp phải chịu trách nhiệm cho những thay đổi được yêu cầu chỉ bởi một bên liên quan (người yêu cầu bạn thay đổi chương trình của bạn), để bạn thay đổi những điều HẤP DẪN nhất có thể để đáp ứng với các bên liên quan khác nhau yêu cầu thay đổi.
Corrodias

12

Các tác vụ được sử dụng để lấy 5-10 tệp giờ có thể mất 70-100!

Đây là một lời nói dối. Các tác vụ không bao giờ chỉ mất 5-10 tệp.

Bạn không giải quyết bất kỳ nhiệm vụ nào có ít hơn 10 tệp. Tại sao? Bởi vì bạn đang sử dụng C #. C # là một ngôn ngữ cấp cao. Bạn đang sử dụng hơn 10 tệp chỉ để tạo thế giới xin chào.

Ồ chắc chắn bạn không chú ý đến chúng vì bạn đã không viết chúng. Vì vậy, bạn không nhìn vào chúng. Bạn tin tưởng họ.

Vấn đề không phải là số lượng tập tin. Đó là bây giờ bạn có quá nhiều thứ đang diễn ra mà bạn không tin tưởng.

Vì vậy, hãy tìm ra cách để làm cho các thử nghiệm đó hoạt động đến mức một khi chúng vượt qua bạn tin tưởng các tệp này theo cách bạn tin tưởng các tệp trong .NET. Làm điều đó là điểm kiểm tra đơn vị. Không ai quan tâm đến số lượng tập tin. Họ quan tâm đến số lượng những thứ họ không thể tin tưởng.

Đối với các ứng dụng cỡ nhỏ đến trung bình, RẮN là một sản phẩm siêu dễ bán. Mọi người đều thấy lợi ích và dễ bảo trì. Tuy nhiên, họ chỉ không thấy một đề xuất có giá trị tốt cho RẮN trên các ứng dụng quy mô rất lớn.

Thay đổi là khó khăn trên các ứng dụng quy mô rất lớn không có vấn đề gì bạn làm. Sự khôn ngoan tốt nhất để áp dụng ở đây không đến từ chú Bob. Nó xuất phát từ Michael Feathers trong cuốn sách của ông làm việc hiệu quả với Bộ luật kế thừa.

Đừng bắt đầu một lễ hội viết lại. Các mã cũ đại diện cho kiến ​​thức chiến thắng khó khăn. Bỏ qua vì nó có vấn đề và không được thể hiện trong mô hình X mới và được cải thiện chỉ là yêu cầu một bộ vấn đề mới và không có kiến ​​thức nào khó thắng.

Thay vào đó, hãy tìm cách để kiểm tra mã cũ không thể kiểm tra của bạn (mã kế thừa trong Feathers speak). Trong mã ẩn dụ này giống như một chiếc áo sơ mi. Các bộ phận lớn được nối tại các đường nối tự nhiên có thể được hoàn tác để phân tách mã theo cách bạn loại bỏ các đường nối. Làm điều này để cho phép bạn đính kèm "tay áo" thử nghiệm cho phép bạn cách ly phần còn lại của mã. Bây giờ khi bạn tạo tay áo thử, bạn tự tin vào tay áo vì bạn đã làm điều này với một chiếc áo sơ mi công sở. (ow, ẩn dụ này đang bắt đầu đau).

Ý tưởng này xuất phát từ giả định rằng, giống như trong hầu hết các cửa hàng, các yêu cầu cập nhật duy nhất là trong mã làm việc. Điều này cho phép bạn khóa nó trong các thử nghiệm cho phép bạn thay đổi mã làm việc đã được chứng minh mà không làm mất đi một chút trạng thái làm việc đã được chứng minh. Bây giờ với làn sóng thử nghiệm đầu tiên này, bạn có thể bắt đầu thực hiện các thay đổi làm cho mã "di sản" (không thể kiểm chứng) có thể kiểm tra được. Bạn có thể in đậm vì các kiểm tra đường nối đang hỗ trợ bạn bằng cách nói đây là điều nó luôn làm và các kiểm tra mới cho thấy mã của bạn thực sự làm những gì bạn nghĩ.

Những gì trong số này có liên quan đến:

Quản lý và tổ chức số lượng các lớp tăng lên ồ ạt sau khi chuyển sang RẮN?

Trừu tượng.

Bạn có thể làm cho tôi ghét bất kỳ cơ sở mã với trừu tượng xấu. Một sự trừu tượng xấu là một cái gì đó làm cho tôi nhìn vào bên trong. Đừng làm tôi ngạc nhiên khi tôi nhìn vào bên trong. Được khá nhiều những gì tôi mong đợi.

Đặt cho tôi một cái tên hay, các bài kiểm tra có thể đọc được (ví dụ) cho biết cách sử dụng giao diện và sắp xếp nó để tôi có thể tìm thấy mọi thứ và tôi không quan tâm nếu chúng tôi sử dụng 10, 100 hoặc 1000 tệp.

Bạn giúp tôi tìm những thứ có tên mô tả tốt. Đặt những thứ có tên tốt trong những thứ có tên tốt.

Nếu bạn làm tất cả điều này đúng, bạn sẽ trừu tượng các tệp đến nơi hoàn thành một tác vụ chỉ có bạn phụ thuộc vào 3 đến 5 tệp khác. Các tập tin 70-100 vẫn còn đó. Nhưng họ đang ẩn sau 3 đến 5. Điều đó chỉ hiệu quả nếu bạn tin tưởng 3 đến 5 để làm điều đó đúng.

Vì vậy, những gì bạn thực sự cần là từ vựng để đưa ra những cái tên hay cho tất cả những điều này và các bài kiểm tra mà mọi người tin tưởng để họ sẽ ngừng lội qua mọi thứ. Không có điều đó bạn cũng sẽ làm tôi phát điên.

@Delioth làm cho một điểm tốt về đau ngày càng tăng. Khi bạn đã quen với các món ăn trong tủ phía trên máy rửa chén, sẽ có một số quen với việc chúng ở phía trên quầy bar ăn sáng. Làm cho một số điều khó khăn hơn. Làm cho một số điều dễ dàng hơn. Nhưng nó gây ra tất cả các loại ác mộng nếu mọi người không đồng ý các món ăn đi đâu. Trong một cơ sở mã lớn, vấn đề là bạn chỉ có thể di chuyển một số món ăn tại một thời điểm. Vì vậy, bây giờ bạn có món ăn ở hai nơi. Thật khó hiểu. Làm cho khó có thể tin rằng các món ăn là nơi họ cần phải có. Nếu bạn muốn vượt qua điều này mặc dù điều duy nhất cần làm là tiếp tục di chuyển các món ăn.

Vấn đề với điều đó là bạn thực sự muốn biết liệu có các món ăn trong quán ăn sáng có đáng không trước khi trải qua tất cả những điều vô nghĩa này. Vâng, tất cả những gì tôi có thể khuyên là đi cắm trại.

Khi thử một mô hình mới lần đầu tiên, nơi cuối cùng bạn nên áp dụng nó là trong một cơ sở mã lớn. Điều này đi cho mọi thành viên của đội. Không ai nên tin rằng RẮN hoạt động, OOP hoạt động hoặc lập trình chức năng hoạt động. Mỗi thành viên trong nhóm nên có cơ hội chơi với ý tưởng mới, bất kể đó là gì, trong một dự án đồ chơi. Nó cho phép họ xem ít nhất là cách nó hoạt động. Nó cho phép họ thấy những gì nó không làm tốt. Nó cho phép họ học cách làm điều đó ngay trước khi họ tạo ra một mớ hỗn độn lớn.

Cung cấp cho mọi người một nơi an toàn để chơi sẽ giúp họ áp dụng những ý tưởng mới và giúp họ tự tin rằng các món ăn thực sự có thể hoạt động trong ngôi nhà mới của họ.


3
Điều đáng nói là một số câu hỏi đau cũng có thể chỉ là nỗi đau ngày càng tăng - trong khi, vâng, họ có thể cần phải tạo 15 tệp cho điều này ... bây giờ họ không bao giờ phải viết lại GUIDProvider hay BasePathProvider hoặc ExtensionProvider, v.v ... Đó là một trở ngại tương tự mà bạn nhận được khi bắt đầu một dự án trường xanh mới - một loạt các tính năng hỗ trợ chủ yếu là tầm thường, ngu ngốc để viết và vẫn cần phải viết. Sucks để xây dựng chúng, nhưng một khi chúng ở đó, bạn không cần phải nghĩ về chúng .... bao giờ.
Delioth

@Delioth Tôi cực kỳ tin rằng đây là trường hợp. Trước đây, nếu chúng ta cần một số tập hợp con của chức năng (giả sử chúng ta chỉ muốn một URL được đặt trong AppSinstall), chúng ta chỉ cần có một lớp lớn được truyền qua và sử dụng. Với cách tiếp cận mới, không có lý do gì để vượt qua toàn bộ AppSettingschỉ để lấy url hoặc đường dẫn tệp.
JD Davis

1
Đừng bắt đầu một lễ hội viết lại. Các mã cũ đại diện cho kiến ​​thức chiến thắng khó khăn. Bỏ qua vì nó có vấn đề và không được thể hiện trong mô hình X mới và được cải thiện chỉ là yêu cầu một bộ vấn đề mới và không có kiến ​​thức nào khó giành được. Điều này. Chắc chắn rồi.
Flot2011

10

Nghe có vẻ như mã của bạn không được phân tách tốt và / hoặc kích thước tác vụ của bạn quá lớn.

Thay đổi mã phải là 5-10 tệp trừ khi bạn đang thực hiện tái cấu trúc mã hóa hoặc quy mô lớn. Nếu một thay đổi chạm vào nhiều tệp, điều đó có thể có nghĩa là các thay đổi của bạn xếp tầng. Một số trừu tượng được cải thiện (trách nhiệm đơn lẻ hơn, phân biệt giao diện, đảo ngược phụ thuộc) sẽ giúp ích. Cũng có thể bạn có thể đã chịu trách nhiệm quá đơn lẻ và có thể sử dụng tính thực dụng hơn một chút - hệ thống phân cấp loại ngắn hơn và mỏng hơn. Điều đó sẽ làm cho mã dễ hiểu hơn vì bạn không phải hiểu hàng tá tệp để biết mã đang làm gì.

Nó cũng có thể là một dấu hiệu cho thấy công việc của bạn quá lớn. Thay vì "hey, thêm tính năng này" (yêu cầu thay đổi giao diện người dùng và thay đổi api và thay đổi truy cập dữ liệu và thay đổi bảo mật và thay đổi thử nghiệm và ...) chia nó thành các phần dễ bảo trì hơn. Điều đó trở nên dễ dàng hơn để xem xét và dễ hiểu hơn bởi vì nó đòi hỏi bạn phải thiết lập các hợp đồng tốt giữa các bit.

Và tất nhiên, bài kiểm tra đơn vị giúp tất cả điều này. Họ buộc bạn phải làm cho giao diện tốt. Họ buộc bạn phải làm cho mã của mình đủ linh hoạt để tiêm các bit cần kiểm tra (nếu khó kiểm tra, sẽ khó sử dụng lại). Và họ đẩy mọi người ra khỏi những thứ kỹ thuật quá mức bởi vì bạn càng có nhiều kỹ sư thì bạn càng cần phải thử nghiệm.


2
5-10 tệp đến 70-100 tệp nhiều hơn một chút so với giả thuyết. Nhiệm vụ cuối cùng của tôi là tạo ra một số chức năng trong một trong những dịch vụ mới hơn của chúng tôi. Dịch vụ mới được cho là nhận được yêu cầu và lưu tài liệu. Khi làm như vậy, tôi cần các lớp để thể hiện các thực thể người dùng trong 2 cơ sở dữ liệu và repos riêng biệt cho mỗi thực thể. Repos để đại diện cho các bảng khác tôi cần viết. Các lớp chuyên dụng để xử lý kiểm tra dữ liệu tệp và tạo tên. Và danh sách được tiếp tục. Chưa kể, mỗi lớp có logic được biểu diễn bằng một giao diện để nó có thể được mô phỏng theo các bài kiểm tra đơn vị.
JD Davis

1
Theo như các cơ sở mã cũ của chúng tôi, tất cả chúng đều được liên kết chặt chẽ và nguyên khối vô cùng. Với cách tiếp cận RẮN, sự ghép nối duy nhất giữa các lớp là trong trường hợp POCO, mọi thứ khác được truyền qua DI và giao diện.
JD Davis

3
@JDDavis - chờ đã, tại sao một microservice lại hoạt động trực tiếp với nhiều cơ sở dữ liệu?
Telastyn

1
Đó là một sự thỏa hiệp với người quản lý dev của chúng tôi. Ông ồ ạt thích phần mềm nguyên khối và thủ tục. Như vậy, microservice của chúng tôi có nhiều vĩ mô hơn mức cần thiết. Khi cơ sở hạ tầng của chúng tôi trở nên tốt hơn, dần dần mọi thứ sẽ chuyển sang các dịch vụ siêu nhỏ của riêng họ. Hiện tại, chúng tôi phần nào tuân theo cách tiếp cận người lạ để chuyển chức năng nhất định ra microservice. Vì nhiều dịch vụ cần truy cập vào một tài nguyên cụ thể, chúng tôi cũng đang chuyển chúng vào các dịch vụ siêu nhỏ của riêng họ.
JD Davis

4

Tôi muốn giải thích về một số điều đã được đề cập ở đây, nhưng nhiều hơn từ góc độ nơi ranh giới đối tượng được vẽ. Nếu bạn đang theo dõi một cái gì đó giống với Thiết kế hướng tên miền, thì các đối tượng của bạn có thể sẽ đại diện cho các khía cạnh của doanh nghiệp của bạn. CustomerOrder, ví dụ, sẽ là đối tượng. Bây giờ, nếu tôi đoán dựa trên tên lớp mà bạn có làm điểm bắt đầu, AccountLogiclớp của bạn có mã sẽ chạy cho bất kỳ tài khoản nào . Tuy nhiên, trong OO, mỗi lớp có nghĩa là có bối cảnh và bản sắc. Bạn không nên lấy một Accountđối tượng và sau đó chuyển nó vào một AccountLogiclớp và yêu cầu lớp đó thay đổi Accountđối tượng. Đó là mô hình được gọi là thiếu máu và không thể hiện OO rất tốt. Thay vào đó, của bạnAccountlớp nên có hành vi, chẳng hạn như Account.Close()hoặc Account.UpdateEmail(), và những hành vi đó sẽ chỉ ảnh hưởng đến trường hợp đó của tài khoản.

Bây giờ, CÁCH các hành vi này được xử lý có thể (và trong rất nhiều trường hợp) nên được tải xuống cho các phụ thuộc được biểu thị bằng các tóm tắt (ví dụ: giao diện). Account.UpdateEmail, ví dụ, có thể muốn cập nhật cơ sở dữ liệu hoặc tệp hoặc gửi tin nhắn đến xe buýt dịch vụ, v.v. Và điều đó có thể thay đổi trong tương lai. Vì vậy, Accountlớp của bạn có thể có một sự phụ thuộc vào, ví dụ, một IEmailUpdate, có thể là một trong nhiều giao diện được thực hiện bởi một AccountRepositoryđối tượng. Bạn sẽ không muốn chuyển toàn bộ IAccountRepositorygiao diện cho Accountđối tượng bởi vì nó có thể sẽ làm quá nhiều, chẳng hạn như tìm kiếm và tìm các tài khoản (bất kỳ) khác, mà bạn có thể không muốn Accountđối tượng có quyền truy cập, nhưng mặc dù AccountRepositorycó thể thực hiện cả hai IAccountRepositoryIEmailUpdategiao diện,AccountĐối tượng sẽ chỉ có quyền truy cập vào các phần nhỏ mà nó cần. Điều này giúp bạn duy trì Nguyên tắc phân chia giao diện .

Trên thực tế, như những người khác đã đề cập, nếu bạn đang đối phó với sự bùng nổ của các lớp, rất có thể bạn đang sử dụng nguyên tắc RẮN (và, bằng cách mở rộng, OO) sai cách. RẮN sẽ giúp bạn đơn giản hóa mã của bạn, không làm phức tạp nó. Nhưng cần có thời gian để thực sự hiểu những thứ như SRP có nghĩa là gì. Tuy nhiên, điều quan trọng hơn là cách thức hoạt động của RẮN sẽ phụ thuộc rất nhiều vào miền của bạn và bối cảnh bị ràng buộc (một thuật ngữ DDD khác). Không có viên đạn bạc hoặc một kích cỡ phù hợp với tất cả.

Một điều nữa mà tôi muốn nhấn mạnh với những người tôi làm việc cùng: một lần nữa, một đối tượng OOP nên có hành vi và trên thực tế, được xác định bởi hành vi của nó, chứ không phải dữ liệu của nó. Nếu đối tượng của bạn không có gì ngoài thuộc tính và trường, nó vẫn có hành vi, mặc dù có thể không phải là hành vi bạn dự định. Một thuộc tính có thể ghi công khai / có thể giải quyết mà không có logic tập hợp nào khác ngụ ý rằng hành vi cho lớp chứa của nó là bất kỳ ai ở bất kỳ lý do gì và bất cứ lúc nào cũng được phép sửa đổi giá trị của tài sản đó mà không cần bất kỳ logic hoặc xác thực kinh doanh cần thiết nào ở giữa. Đó thường không phải là hành vi mà mọi người dự định, nhưng nếu bạn có một mô hình thiếu máu, đó thường là hành vi mà các lớp của bạn đang thông báo cho bất cứ ai sử dụng chúng.


2

Vì vậy, đó là tổng cộng 15 lớp (không bao gồm POCO và giàn giáo) để thực hiện tiết kiệm khá đơn giản.

Điều đó thật điên rồ .... nhưng những lớp học này nghe giống như những gì tôi tự viết. Vì vậy, hãy nhìn vào chúng. Bây giờ chúng ta hãy bỏ qua các giao diện và kiểm tra.

  • BasePathProvider- IMHO bất kỳ dự án không tầm thường làm việc với các tập tin cần nó. Vì vậy, tôi cho rằng, đã có một điều như vậy và bạn có thể sử dụng nó như là.
  • UniqueFilenameProvider - Chắc chắn, bạn đã có nó, phải không?
  • NewGuidProvider - Trường hợp tương tự, trừ khi bạn chỉ nhìn chằm chằm để sử dụng GUID.
  • FileExtensionCombiner - Trường hợp tương tự.
  • PatientFileWriter - Tôi đoán, đây là lớp chính cho nhiệm vụ hiện tại.

Đối với tôi, nó có vẻ tốt: Bạn cần phải viết một lớp mới cần bốn lớp trợ giúp. Tất cả bốn lớp người trợ giúp nghe có vẻ có thể tái sử dụng, vì vậy tôi cá là họ đã ở đâu đó trong cơ sở mã của bạn. Mặt khác, đó là một điều xui xẻo (bạn có thực sự là người trong nhóm của bạn viết tệp và sử dụng GUID ???) hoặc một số vấn đề khác.


Liên quan đến các lớp kiểm tra, chắc chắn, khi bạn tạo một lớp mới hoặc cập nhật nó, nó sẽ được kiểm tra. Vì vậy, viết năm lớp có nghĩa là viết năm lớp kiểm tra, quá. Nhưng điều này không làm cho thiết kế trở nên phức tạp hơn:

  • Bạn sẽ không bao giờ sử dụng các lớp kiểm tra ở nơi khác vì chúng sẽ được thực hiện tự động và đó là tất cả.
  • Bạn muốn nhìn lại chúng một lần nữa, trừ khi bạn cập nhật các lớp đang kiểm tra hoặc trừ khi bạn sử dụng chúng làm tài liệu (lý tưởng nhất là các bài kiểm tra cho thấy rõ cách thức một lớp được sử dụng).

Liên quan đến các giao diện, chúng chỉ cần thiết khi khung DI hoặc khung thử nghiệm của bạn không thể xử lý các lớp. Bạn có thể xem chúng như một khoản phí cho các công cụ không hoàn hảo. Hoặc bạn có thể xem chúng như một sự trừu tượng hữu ích cho phép bạn quên rằng có những điều phức tạp hơn - việc đọc nguồn của một giao diện mất ít thời gian hơn nhiều so với việc đọc nguồn thực hiện của nó.


Tôi rất biết ơn về quan điểm này. Trong trường hợp cụ thể này, tôi đã viết chức năng vào một microservice khá mới. Thật không may, ngay cả trong cơ sở mã chính của chúng tôi, trong khi chúng tôi có một số cách sử dụng ở trên, không ai trong số đó thực sự là một cách có thể tái sử dụng từ xa. Mọi thứ cần được tái sử dụng đều kết thúc trong một lớp tĩnh hoặc chỉ là sao chép và dán xung quanh mã. Tôi nghĩ rằng tôi vẫn còn đi được một chút, nhưng tôi đồng ý rằng không phải mọi thứ cần phải được mổ xẻ và tách rời hoàn toàn.
JD Davis

@JDDavis Tôi đã cố gắng viết một cái gì đó khác với các câu trả lời khác (mà tôi hầu hết đồng ý với). Bất cứ khi nào bạn sao chép và dán một cái gì đó, bạn sẽ ngăn chặn việc sử dụng lại thay vì khái quát hóa một thứ gì đó mà bạn tạo ra một đoạn mã không thể tái sử dụng khác, điều này sẽ buộc bạn phải sao chép và dán thêm một ngày. IMHO đó là tội lỗi lớn thứ hai, chỉ sau những quy tắc mù quáng. Bạn cần tìm ra điểm ngọt ngào của mình, trong đó các quy tắc sau giúp bạn làm việc hiệu quả hơn (đặc biệt là ghi lại những thay đổi trong tương lai) và đôi khi phá vỡ chúng một chút giúp đỡ trong trường hợp khi nỗ lực không phù hợp. Tất cả đều tương đối.
maaartinus

@JDDavis Và mọi thứ phụ thuộc vào chất lượng công cụ của bạn. Ví dụ: Có những người cho rằng DI là enterprisey và phức tạp, trong khi tôi cho rằng nó hầu hết là miễn phí . +++Liên quan đến việc phá vỡ các quy tắc: Có bốn lớp, tôi cần ở những nơi, nơi tôi chỉ có thể tiêm chúng sau khi tái cấu trúc chính làm cho mã trở nên xấu hơn (ít nhất là đối với mắt tôi), vì vậy tôi quyết định biến chúng thành singletons (một lập trình viên tốt hơn có thể tìm thấy một cách tốt hơn, nhưng tôi hài lòng với nó, số lượng những người độc thân này không thay đổi theo thời gian).
maaartinus

Câu trả lời này thể hiện khá nhiều những gì tôi đã nghĩ khi OP thêm ví dụ vào câu hỏi. @JDDavis Hãy để tôi thêm rằng bạn có thể lưu một số mã / lớp soạn sẵn bằng cách sử dụng các công cụ chức năng cho các trường hợp đơn giản. Ví dụ, một nhà cung cấp GUI - thay vì giới thiệu một giao diện mới một lớp mới cho việc này, tại sao không sử dụng Func<Guid>cho điều này và tiêm một phương thức ẩn danh như ()=>Guid.NewGuid()vào hàm tạo? Và không cần phải kiểm tra chức năng khung .Net này, đây là điều Microsoft đã làm cho bạn. Tổng cộng, điều này sẽ giúp bạn tiết kiệm 4 lớp.
Doc Brown

... Và bạn nên kiểm tra xem các trường hợp khác mà bạn trình bày có thể được đơn giản hóa theo cùng một cách không (có thể không phải tất cả chúng).
Doc Brown

2

Tùy thuộc vào sự trừu tượng, tạo các lớp đơn trách nhiệm và viết bài kiểm tra đơn vị không phải là khoa học chính xác. Hoàn toàn bình thường khi xoay quá xa theo một hướng khi học, đi đến cực điểm, và sau đó tìm ra một chuẩn mực có ý nghĩa. Có vẻ như con lắc của bạn đã vung quá xa, và thậm chí có thể bị mắc kẹt.

Đây là nơi tôi nghi ngờ điều này sẽ xảy ra.

Các bài kiểm tra đơn vị đã cực kỳ khó bán cho nhóm vì tất cả họ đều tin rằng họ lãng phí thời gian và họ có thể xử lý kiểm tra mã của họ nhanh hơn nhiều so với từng phần riêng lẻ. Sử dụng các bài kiểm tra đơn vị như một sự chứng thực cho RẮN hầu hết là vô ích và chủ yếu trở thành một trò đùa vào thời điểm này.

Một trong những lợi ích đến từ hầu hết các nguyên tắc RẮN (chắc chắn không phải là lợi ích duy nhất) là nó giúp việc kiểm tra đơn vị viết mã của chúng tôi dễ dàng hơn. Nếu một lớp phụ thuộc vào một trừu tượng, chúng ta có thể chế nhạo các trừu tượng. Trừu tượng được tách riêng dễ dàng hơn để giả. Nếu một lớp thực hiện một điều, nó có khả năng có độ phức tạp thấp hơn, điều đó có nghĩa là dễ dàng hơn để biết và kiểm tra tất cả các đường dẫn có thể của nó.

Nếu nhóm của bạn không viết bài kiểm tra đơn vị, hai điều liên quan sẽ xảy ra:

Đầu tiên, họ đang làm rất nhiều việc để tạo ra tất cả các giao diện và lớp này mà không nhận ra lợi ích đầy đủ. Phải mất một ít thời gian và thực hành để xem cách viết bài kiểm tra đơn vị làm cho cuộc sống của chúng ta dễ dàng hơn. Có nhiều lý do tại sao những người học viết bài kiểm tra đơn vị dính vào nó, nhưng bạn phải kiên trì đủ lâu để tự mình khám phá chúng. Nếu nhóm của bạn không cố gắng thì họ sẽ cảm thấy như phần còn lại của công việc làm thêm họ đang làm là vô ích.

Ví dụ, điều gì xảy ra khi họ cần cấu trúc lại? Nếu họ có một trăm lớp nhỏ nhưng không có bài kiểm tra nào để cho họ biết liệu những thay đổi của họ có hoạt động hay không, những lớp và giao diện bổ sung đó sẽ có vẻ như là một gánh nặng, không phải là một sự cải tiến.

Thứ hai, viết bài kiểm tra đơn vị có thể giúp bạn hiểu mức độ trừu tượng mà mã của bạn thực sự cần. Như tôi đã nói, nó không phải là một khoa học. Chúng tôi bắt đầu tồi tệ, đi khắp nơi và trở nên tốt hơn. Các bài kiểm tra đơn vị có một cách đặc biệt để bổ sung RẮN. Làm thế nào để bạn biết khi nào bạn cần thêm một sự trừu tượng hoặc phá vỡ một cái gì đó ngoài? Nói cách khác, làm thế nào để bạn biết khi nào bạn "RẮN đủ?" Thường thì câu trả lời là khi bạn không thể kiểm tra thứ gì đó.

Có thể mã của bạn sẽ có thể kiểm tra được mà không cần tạo nhiều trừu tượng và lớp nhỏ. Nhưng nếu bạn không viết bài kiểm tra, làm thế nào bạn có thể nói? Chúng ta đi bao xa? Chúng ta có thể bị ám ảnh với việc phá vỡ mọi thứ ngày càng nhỏ hơn. Đó là một cái hố thỏ. Khả năng viết bài kiểm tra cho mã của chúng tôi giúp chúng tôi biết khi nào chúng tôi hoàn thành mục đích của mình để chúng tôi có thể ngừng ám ảnh, tiếp tục và vui vẻ viết thêm mã.

Các bài kiểm tra đơn vị không phải là viên đạn bạc giải quyết mọi thứ, nhưng chúng là một viên đạn thực sự tuyệt vời giúp cuộc sống của các nhà phát triển tốt hơn. Chúng tôi không hoàn hảo, và cũng không phải là thử nghiệm của chúng tôi. Nhưng xét nghiệm cho chúng tôi sự tự tin. Chúng tôi hy vọng mã của chúng tôi là đúng và chúng tôi ngạc nhiên khi nó sai, không phải cách khác. Chúng tôi không hoàn hảo và cũng không phải là thử nghiệm của chúng tôi. Nhưng khi mã của chúng tôi được kiểm tra, chúng tôi có sự tự tin. Chúng tôi ít có khả năng cắn móng tay khi mã của chúng tôi được triển khai và tự hỏi điều gì sẽ phá vỡ lần này và liệu đó có phải là lỗi của chúng tôi không.

Trên hết, một khi chúng ta hiểu rõ, việc viết các bài kiểm tra đơn vị làm cho việc phát triển mã nhanh hơn, không chậm hơn. Chúng tôi dành ít thời gian hơn để xem lại mã cũ hoặc gỡ lỗi để tìm các vấn đề giống như kim trong đống cỏ khô.

Lỗi giảm, chúng tôi làm được nhiều hơn và chúng tôi thay thế sự lo lắng bằng sự tự tin. Nó không phải là mốt hay dầu rắn. Đó là sự thật. Nhiều nhà phát triển sẽ chứng thực điều này. Nếu nhóm của bạn chưa có kinh nghiệm này, họ cần phải vượt qua giai đoạn học tập đó và vượt qua cái bướu. Hãy cho nó một cơ hội, nhận ra rằng họ sẽ không nhận được kết quả ngay lập tức. Nhưng khi nó xảy ra, họ sẽ vui vì họ đã làm và họ sẽ không bao giờ nhìn lại. (Hoặc họ sẽ trở thành những kẻ bị cô lập và viết những bài đăng trên blog giận dữ về cách kiểm tra đơn vị và hầu hết kiến ​​thức lập trình tích lũy khác là một sự lãng phí thời gian.)

Kể từ khi thực hiện chuyển đổi, một trong những phàn nàn lớn nhất từ ​​các nhà phát triển là họ không thể xem xét ngang hàng và duyệt qua hàng chục và hàng chục tệp mà trước đây mọi tác vụ chỉ yêu cầu nhà phát triển chạm vào 5-10 tệp.

Đánh giá ngang hàng dễ dàng hơn rất nhiều khi tất cả các bài kiểm tra đơn vị vượt qua và một phần lớn của đánh giá đó chỉ là đảm bảo rằng các bài kiểm tra có ý nghĩa.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.