Có phải việc sử dụng ngôn ngữ mới trong các nhà xây dựng luôn xấu?


37

Tôi đã đọc được rằng sử dụng "mới" trong một hàm tạo (đối với bất kỳ đối tượng nào khác ngoài các đối tượng giá trị đơn giản) là một thực tiễn tồi vì nó làm cho việc kiểm tra đơn vị là không thể (vì khi đó những cộng tác viên đó cũng cần phải được tạo ra và không thể bị chế giễu). Vì tôi không thực sự có kinh nghiệm trong thử nghiệm đơn vị, tôi đang cố gắng thu thập một số quy tắc mà tôi sẽ học trước tiên. Ngoài ra, đây có phải là một quy tắc thường hợp lệ, bất kể ngôn ngữ được sử dụng?


9
Nó không làm cho thử nghiệm không thể. Nó làm cho việc duy trì và kiểm tra mã của bạn khó hơn, mặc dù. Có đọc mới là keo chẳng hạn.
David Arno

38
"Luôn luôn" luôn không chính xác. :) Có những thực tiễn tốt nhất, nhưng ngoại lệ rất nhiều.
Paul

63
Ngôn ngữ này là gì? newcó nghĩa là những thứ khác nhau trong các ngôn ngữ khác nhau.
user2357112 hỗ trợ Monica

13
Câu hỏi này phụ thuộc một chút vào ngôn ngữ, phải không? Không phải tất cả các ngôn ngữ thậm chí có một newtừ khóa. Những ngôn ngữ bạn đang hỏi về?
Bryan Oakley

7
Quy tắc ngớ ngẩn. Không sử dụng tiêm phụ thuộc khi bạn nên có rất ít liên quan đến từ khóa "mới". Thay vì nói rằng "sử dụng mới là một vấn đề nếu bạn sử dụng nó để phá vỡ nghịch đảo phụ thuộc", chỉ cần nói "không phá vỡ nghịch đảo phụ thuộc".
Matt Timmermans

Câu trả lời:


36

Luôn có những trường hợp ngoại lệ và tôi có vấn đề với 'luôn luôn' trong tiêu đề, nhưng vâng, hướng dẫn này thường có hiệu lực và cũng được áp dụng bên ngoài nhà xây dựng.

Sử dụng mới trong một hàm tạo vi phạm D trong RẮN (nguyên tắc đảo ngược phụ thuộc). Nó làm cho mã của bạn khó kiểm tra vì kiểm thử đơn vị là tất cả về cách ly; thật khó để cô lập lớp nếu nó có tài liệu tham khảo cụ thể.

Nó không chỉ là về thử nghiệm đơn vị mặc dù. Điều gì xảy ra nếu tôi muốn trỏ một kho lưu trữ đến hai cơ sở dữ liệu khác nhau cùng một lúc? Khả năng vượt qua trong ngữ cảnh của riêng tôi cho phép tôi khởi tạo hai kho lưu trữ khác nhau chỉ đến các vị trí khác nhau.

Không sử dụng mới trong hàm tạo giúp mã của bạn linh hoạt hơn. Điều này cũng áp dụng cho các ngôn ngữ có thể sử dụng các cấu trúc khác ngoài newkhởi tạo đối tượng.

Tuy nhiên, rõ ràng, bạn cần sử dụng phán đoán tốt. Có nhiều lúc nó tốt để sử dụng new, hoặc nơi nào tốt hơn là không, nhưng bạn sẽ không có hậu quả tiêu cực. Tại một số điểm ở đâu đó, newphải được gọi. Chỉ cần rất cẩn thận về việc gọi newbên trong một lớp mà rất nhiều lớp khác phụ thuộc vào.

Làm một cái gì đó như khởi tạo một bộ sưu tập riêng trống trong hàm tạo của bạn là tốt, và tiêm nó sẽ là vô lý.

Một lớp càng có nhiều tài liệu tham khảo, bạn càng không nên gọi newtừ bên trong nó cẩn thận hơn .


12
Tôi thực sự không thấy bất kỳ giá trị trong quy tắc này. Có các quy tắc và hướng dẫn về cách tránh khớp nối chặt chẽ trong mã. Quy tắc này dường như xử lý cùng một vấn đề chính xác, ngoại trừ việc nó cực kỳ hạn chế trong khi không cung cấp cho chúng tôi bất kỳ giá trị bổ sung nào. Vậy quan điểm là gì? Tại sao việc tạo một đối tượng tiêu đề giả cho việc triển khai LinkedList của tôi thay vì xử lý tất cả các trường hợp đặc biệt xuất phát từ việc có head = nullbất kỳ cách nào cải thiện mã? Tại sao việc để các bộ sưu tập được bao gồm là null và tạo chúng theo yêu cầu sẽ tốt hơn là làm như vậy trong hàm tạo?
Voo

22
Bạn đang thiếu điểm. Có, một mô-đun kiên trì hoàn toàn là một mô-đun cấp cao hơn không nên phụ thuộc vào. Nhưng "không bao giờ sử dụng mới" không tuân theo "một số thứ nên được tiêm và không được ghép trực tiếp". Nếu bạn lấy bản sao Phát triển phần mềm Agile đáng tin cậy của mình, bạn sẽ thấy Martin thường nói về các mô-đun chứ không phải các lớp đơn lẻ: "Các mô-đun cấp cao xử lý các chính sách cấp cao của ứng dụng. Các chính sách này thường quan tâm rất ít đến các chi tiết triển khai họ. "
Voo

11
Vì vậy, vấn đề là bạn nên tránh sự phụ thuộc giữa các mô-đun - đặc biệt là giữa các mô-đun có mức độ trừu tượng khác nhau. Nhưng một mô-đun có thể nhiều hơn một lớp và đơn giản là không có điểm nào tạo giao diện giữa mã được ghép chặt chẽ của cùng một lớp trừu tượng. Trong một ứng dụng được thiết kế tốt tuân theo các nguyên tắc RẮN, không phải mọi lớp đơn lẻ nên thực hiện một giao diện và không phải mọi nhà xây dựng nên luôn luôn chỉ lấy các giao diện làm tham số.
Voo

18
Tôi đánh giá thấp bởi vì đó là rất nhiều súp buzzword và không thảo luận nhiều về những cân nhắc thực tế. Có một cái gì đó về một repo cho hai DB, nhưng thành thực mà nói, đó không phải là một ví dụ thực tế.
jpmc26

5
@ jpmc26, BJoBnh Nếu không đi vào nếu tôi bỏ qua câu trả lời này hay không, điểm được thể hiện rất rõ ràng. Nếu bạn nghĩ "hiệu trưởng đảo ngược phụ thuộc", "tham chiếu cụ thể" hoặc "khởi tạo đối tượng" chỉ là từ thông dụng, thì thực sự bạn không biết ý nghĩa của chúng. Đó không phải là lỗi của câu trả lời.
R. Schmitz

50

Mặc dù tôi ủng hộ việc sử dụng hàm tạo để chỉ khởi tạo thể hiện mới thay vì tạo nhiều đối tượng khác, các đối tượng trợ giúp vẫn ổn và bạn phải sử dụng phán đoán của mình xem có thứ gì đó có phải là trợ giúp nội bộ hay không.

Nếu lớp đại diện cho một bộ sưu tập, thì nó có thể có một mảng trình trợ giúp nội bộ hoặc danh sách hoặc hàm băm. Nó sẽ sử dụng newđể tạo ra những người trợ giúp này và nó sẽ được coi là khá bình thường. Lớp học không cung cấp tiêm để sử dụng các trợ giúp nội bộ khác nhau và không có lý do. Trong trường hợp này, bạn muốn kiểm tra các phương thức công khai của đối tượng, có thể đi đến tích lũy, loại bỏ và thay thế các phần tử trong bộ sưu tập.


Theo một cách hiểu nào đó, cấu trúc lớp của ngôn ngữ lập trình là một cơ chế để tạo ra sự trừu tượng hóa ở mức cao hơn và chúng ta tạo ra sự trừu tượng hóa đó để thu hẹp khoảng cách giữa miền vấn đề và nguyên thủy ngôn ngữ lập trình. Tuy nhiên, cơ chế lớp chỉ là một công cụ; nó thay đổi theo ngôn ngữ lập trình và một số trừu tượng miền, trong một số ngôn ngữ, chỉ cần yêu cầu nhiều đối tượng ở cấp độ ngôn ngữ lập trình.

Tóm lại, bạn phải sử dụng một số phán đoán cho dù việc trừu tượng hóa chỉ cần một hoặc nhiều đối tượng trợ giúp bên trong, trong khi vẫn được người gọi xem là một sự trừu tượng hóa duy nhất, hoặc, liệu các đối tượng khác có được tiếp xúc tốt hơn với người gọi để tạo ra không kiểm soát các phụ thuộc, sẽ được đề xuất, ví dụ, khi người gọi nhìn thấy các đối tượng khác này trong việc sử dụng lớp.


4
+1. Điều này là chính xác. Bạn phải xác định hành vi dự định của lớp là gì và điều đó xác định chi tiết triển khai nào cần được tiết lộ và điều nào không. Có một loại trò chơi trực giác tinh tế mà bạn phải chơi để xác định "trách nhiệm" của lớp là gì.
jpmc26

27

Không phải tất cả các cộng tác viên đều đủ thú vị để kiểm tra đơn vị riêng biệt, bạn có thể (gián tiếp) kiểm tra họ thông qua lớp lưu trữ / khởi tạo. Điều này có thể không phù hợp với ý tưởng của một số người cần kiểm tra từng lớp, từng phương thức công cộng, v.v. đặc biệt là khi thực hiện kiểm tra sau. Khi sử dụng TDD, bạn có thể cấu trúc lại 'cộng tác viên' này trích xuất một lớp trong đó nó đã được kiểm tra đầy đủ từ quy trình thử nghiệm đầu tiên của bạn.


14
Not all collaborators are interesting enough to unit-test separatelykết thúc câu chuyện :-), Trường hợp này là có thể và không ai dám đề cập. @Joppe Tôi khuyến khích bạn xây dựng câu trả lời một chút. Ví dụ, bạn có thể thêm một số ví dụ về các lớp chỉ là chi tiết triển khai (không phù hợp để thay thế) và làm thế nào chúng có thể được trích xuất sau nếu chúng tôi thấy cần thiết.
Laiv

Các mô hình miền @Laiv thường cụ thể, không trừu tượng, bạn không đi tiêm các đối tượng lồng nhau ở đó. Đối tượng giá trị đơn giản / đối tượng phức tạp có mặt khác không có logic cũng là một ứng cử viên.
Joppe

4
+1, hoàn toàn. Nếu một ngôn ngữ được thiết lập để bạn phải gọi new File()để làm bất cứ điều gì liên quan đến tập tin, sẽ không có ý nghĩa gì trong việc cấm cuộc gọi đó. Bạn sẽ làm gì, viết các bài kiểm tra hồi quy chống lại Filemô-đun của stdlib ? Không có khả năng. Mặt khác, kêu gọi newmột lớp học tự phát minh là đáng ngờ hơn.
Kilian Foth

7
@KilianFoth: Chúc may mắn đơn vị kiểm tra bất cứ điều gì trực tiếp gọi new File().
Phoshi

1
Đó là một phỏng đoán . Chúng ta có thể tìm thấy những trường hợp không có ý nghĩa, những trường hợp không hữu ích và những trường hợp không có ý nghĩa và nó hữu ích. Đó là vấn đề về nhu cầu và sở thích.
Laiv

13

Vì tôi không thực sự có kinh nghiệm trong thử nghiệm đơn vị, tôi đang cố gắng thu thập một số quy tắc mà tôi sẽ học trước tiên.

Hãy cẩn thận học "quy tắc" cho các vấn đề bạn chưa bao giờ gặp phải. Nếu bạn đi qua một số "quy tắc" hay "thực hành tốt nhất", tôi sẽ đề nghị tìm một ví dụ đồ chơi đơn giản về nơi quy định này là "giả" được sử dụng, và cố gắng giải quyết vấn đề mà bản thân , bỏ qua những gì các "quy tắc" cho biết.

Trong trường hợp này, bạn có thể cố gắng đưa ra 2 hoặc 3 lớp đơn giản và một số hành vi họ nên thực hiện. Thực hiện các lớp theo bất cứ cách nào cảm thấy tự nhiên và viết một bài kiểm tra đơn vị cho mỗi hành vi. Lập danh sách bất kỳ vấn đề nào bạn gặp phải, ví dụ nếu bạn bắt đầu với những thứ hoạt động theo một cách, sau đó phải quay lại và thay đổi nó sau; nếu bạn bối rối về cách mọi thứ được cho là phù hợp với nhau; nếu bạn cảm thấy khó chịu khi viết bản soạn sẵn; v.v.

Sau đó thử giải quyết vấn đề tương tự bằng cách làm theo "quy tắc". Một lần nữa, lập danh sách các vấn đề bạn gặp phải. So sánh các danh sách và suy nghĩ về tình huống nào có thể tốt hơn khi tuân theo quy tắc và tình huống nào có thể không.


Đối với câu hỏi thực tế của bạn, tôi có xu hướng ủng hộ cách tiếp cận cổng và bộ điều hợp , trong đó chúng tôi phân biệt giữa "logic cốt lõi" và "dịch vụ" (điều này tương tự như phân biệt giữa các chức năng thuần túy và quy trình hiệu quả).

Logic cốt lõi là tất cả về việc tính toán những thứ "bên trong" ứng dụng, dựa trên miền vấn đề. Nó có thể chứa các lớp học như User, Document, Order, Invoice, vv Nó của tốt để có lớp lõi gọi newcho lớp lõi khác, vì họ đang chi tiết thực hiện "nội bộ". Ví dụ: tạo một Ordercũng có thể tạo InvoiceDocumentchi tiết những gì đã được đặt hàng. Không cần phải chế giễu những điều này trong các bài kiểm tra, bởi vì đây là những điều thực tế chúng tôi muốn kiểm tra!

Các cổng và bộ điều hợp là cách logic cốt lõi tương tác với thế giới bên ngoài. Đây là nơi mà mọi thứ như Database, ConfigFile, EmailSendervv sống. Đây là những điều khiến việc kiểm tra trở nên khó khăn, vì vậy, nên tạo ra những thứ bên ngoài logic cốt lõi và chuyển chúng vào khi cần thiết (bằng cách tiêm phụ thuộc hoặc làm đối số phương thức, v.v.).

Bằng cách này, logic cốt lõi (là phần dành riêng cho ứng dụng, nơi logic kinh doanh quan trọng sống và chịu sự khuấy động nhất) có thể được kiểm tra một mình, mà không cần phải quan tâm đến cơ sở dữ liệu, tệp, email, v.v. Chúng ta chỉ có thể chuyển vào một số giá trị mẫu và kiểm tra xem chúng ta có nhận được các giá trị đầu ra đúng không.

Các cổng và bộ điều hợp có thể được kiểm tra riêng, sử dụng giả cho cơ sở dữ liệu, hệ thống tệp, v.v. mà không cần phải quan tâm đến logic nghiệp vụ. Chúng tôi chỉ có thể chuyển vào một số giá trị mẫu và đảm bảo rằng chúng đang được lưu trữ / đọc / gửi / v.v. thích hợp.


6

Cho phép tôi trả lời câu hỏi, thu thập những gì tôi cho là điểm chính ở đây. Tôi sẽ trích dẫn một số người dùng cho ngắn gọn.

Luôn có các trường hợp ngoại lệ, nhưng vâng, quy tắc này thường hợp lệ và cũng được áp dụng bên ngoài hàm tạo.

Sử dụng mới trong một hàm tạo vi phạm D trong RẮN (hiệu trưởng đảo ngược phụ thuộc). Nó làm cho mã của bạn khó kiểm tra vì kiểm thử đơn vị là tất cả về cách ly; thật khó để cô lập lớp nếu nó có tài liệu tham khảo cụ thể.

-TheCatWhisperer-

Có, sử dụng các nhà newxây dựng bên trong thường dẫn đến các lỗi thiết kế (ví dụ như khớp nối chặt chẽ) làm cho thiết kế của chúng tôi cứng nhắc. Khó để kiểm tra có, nhưng không phải là không thể. Tài sản đang chơi ở đây là khả năng phục hồi (khả năng chịu đựng những thay đổi) 1 .

Tuy nhiên, trích dẫn trên không phải lúc nào cũng đúng. Trong một số trường hợp, có thể có các lớp có nghĩa là được liên kết chặt chẽ . David Arno đã bình luận một cặp vợ chồng.

Tất nhiên có các trường hợp ngoại lệ trong đó lớp là một đối tượng giá trị bất biến , một chi tiết triển khai , v.v ... Nơi mà chúng được cho là được liên kết chặt chẽ .

-David Arno-

Chính xác. Một số lớp (ví dụ lớp bên trong) có thể chỉ là chi tiết triển khai của lớp chính. Chúng có nghĩa là được thử nghiệm cùng với lớp chính và chúng không nhất thiết phải thay thế hoặc mở rộng.

Hơn nữa, nếu giáo phái RẮN của chúng ta khiến chúng ta trích xuất các lớp này , chúng ta có thể vi phạm một nguyên tắc tốt khác. Cái gọi là Luật của Demeter . Mặt khác, tôi thấy nó thực sự quan trọng từ quan điểm thiết kế.

Vì vậy, câu trả lời có khả năng, như thường lệ, là phụ thuộc . Sử dụng newbên trong các nhà xây dựng có thể là một thực hành xấu. Nhưng không phải lúc nào cũng có hệ thống.

Vì vậy, chúng ta phải đánh giá xem các lớp có phải là chi tiết triển khai hay không (hầu hết các trường hợp chúng sẽ không) của lớp chính. Nếu có, hãy để họ một mình. Nếu không, hãy xem xét các kỹ thuật như Root Root hoặc Dependency Injection của IoC Container .


1: mục tiêu chính của RẮN là không làm cho mã của chúng tôi dễ kiểm tra hơn. Đó là để làm cho mã của chúng tôi khoan dung hơn với các thay đổi. Linh hoạt hơn và do đó, dễ kiểm tra hơn

Lưu ý: David Arno, TheWhisperCat, tôi hy vọng bạn không phiền tôi đã trích dẫn bạn.


3

Một ví dụ đơn giản, hãy xem xét mã giả sau đây

class Foo {
  private:
     class Bar {}
     class Baz inherits Bar {}
     Bar myBar
  public:
     Foo(bool useBaz) { if (useBaz) myBar = new Baz else myBar = new Bar; }
}

Vì đây newlà một chi tiết triển khai thuần túy Foo, và cả hai Foo::BarFoo::Bazlà một phần của Foo, khi thử nghiệm đơn vị Fookhông có điểm nào trong các phần chế nhạo của Foo. Bạn chỉ chế giễu các bộ phận bên ngoài Foo khi kiểm tra đơn vị Foo.


-3

Có, sử dụng 'mới' trong các lớp gốc ứng dụng của bạn là một mùi mã. Điều đó có nghĩa là bạn đang khóa lớp sử dụng một triển khai cụ thể và sẽ không thể thay thế lớp khác. Luôn luôn chọn cách tiêm phụ thuộc vào hàm tạo. Bằng cách đó, bạn không chỉ có thể dễ dàng tiêm các phụ thuộc bị chế giễu trong quá trình thử nghiệm mà còn giúp ứng dụng của bạn linh hoạt hơn nhiều bằng cách cho phép bạn nhanh chóng thay thế các triển khai khác nhau nếu cần.

EDIT: Dành cho người xuống - đây là một liên kết đến một cuốn sách phát triển phần mềm gắn cờ 'mới' như một mùi mã có thể có: https://books.google.com.vn/books?id=18SuDgAAQBAJ&lpg=PT169&dq=new%20keyword%20code%20smell&pg=PT169 # v = onepage & q = new% 20 từ khóa% 20code% 20smell & f = false


15
Yes, using 'new' in your non-root classes is a code smell. It means you are locking the class into using a specific implementation, and will not be able to substitute another.Tại sao điều này là một vấn đề? Không phải mọi phụ thuộc đơn lẻ trong cây phụ thuộc đều phải được mở để thay thế
Laiv

4
@Paul: Có một triển khai mặc định có nghĩa là bạn có một tham chiếu ràng buộc chặt chẽ với lớp cụ thể được chỉ định làm mặc định. Điều đó không làm cho nó được gọi là "mùi mã", mặc dù.
Robert Harvey

8
@EzoelaVacca: Tôi sẽ thận trọng khi sử dụng các từ "mùi mã" trong bất kỳ bối cảnh nào. Nó giống như nói "cộng hòa" hoặc "dân chủ;" những từ đó có nghĩa là gì? Ngay khi bạn đưa ra một cái gì đó một nhãn hiệu như vậy, bạn ngừng suy nghĩ về các vấn đề thực sự, và việc học tập dừng lại.
Robert Harvey

4
"Linh hoạt hơn" không tự động "tốt hơn".
whatsisname

4
@EzoelaVacca: Sử dụng newtừ khóa không phải là một thực hành xấu, và không bao giờ được. Đó là cách bạn sử dụng công cụ quan trọng. Bạn không sử dụng búa tạ, ví dụ, trong đó một cây búa bóng tròn sẽ đủ.
Robert Harvey
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.