Bao nhiêu là quá nhiều phụ thuộc tiêm?


38

Tôi làm việc trong một dự án sử dụng Tiêm phụ thuộc (Mùa xuân) cho tất cả mọi thứ theo nghĩa đen của một lớp. Chúng tôi đang ở thời điểm mà tệp cấu hình Spring đã tăng lên khoảng 4000 dòng. Cách đây không lâu, tôi đã xem một trong những cuộc nói chuyện của chú Bob trên YouTube (thật không may, tôi không thể tìm thấy liên kết), trong đó ông ấy khuyên chỉ nên tiêm một vài phụ thuộc trung tâm (ví dụ: nhà máy, cơ sở dữ liệu, vào) từ đó họ sẽ được phân phối.

Ưu điểm của phương pháp này là tách rời khung công tác DI khỏi phần lớn ứng dụng và nó cũng làm cho trình cấu hình Spring vì các nhà máy sẽ chứa nhiều hơn những gì trong cấu hình trước đó. Ngược lại, điều này sẽ dẫn đến việc lan truyền logic sáng tạo giữa nhiều lớp nhà máy và việc kiểm tra có thể trở nên khó khăn hơn.

Vì vậy, câu hỏi của tôi thực sự là những lợi thế hoặc bất lợi khác mà bạn nhìn thấy trong một hoặc cách tiếp cận khác. Có thực hành tốt nhất? Cảm ơn rất nhiều cho câu trả lời của bạn!


3
Có tệp cấu hình 4000 dòng không phải là lỗi của việc tiêm phụ thuộc ... có rất nhiều cách để khắc phục: mô đun hóa tệp thành nhiều tệp nhỏ hơn, chuyển sang tiêm dựa trên chú thích, sử dụng tệp JavaConfig thay vì xml. Về cơ bản, vấn đề bạn đang gặp phải là thất bại trong việc quản lý mức độ phức tạp và quy mô của nhu cầu tiêm phụ thuộc ứng dụng của bạn.
Có lẽ là

1
@Maybe_Factor TL; DR chia dự án thành các thành phần nhỏ hơn.
Walfrat

Câu trả lời:


44

Như mọi khi, Nó phụ thuộc ™. Câu trả lời phụ thuộc vào vấn đề người ta đang cố gắng giải quyết. Trong câu trả lời này, tôi sẽ cố gắng giải quyết một số lực thúc đẩy phổ biến:

Ưu tiên cơ sở mã nhỏ hơn

Nếu bạn có 4.000 dòng mã cấu hình Spring, tôi cho rằng cơ sở mã có hàng ngàn lớp.

Đây không phải là vấn đề mà bạn có thể giải quyết sau thực tế, nhưng theo nguyên tắc thông thường, tôi có xu hướng thích các ứng dụng nhỏ hơn, với các cơ sở mã nhỏ hơn. Ví dụ, nếu bạn tham gia vào Thiết kế hướng tên miền , bạn có thể tạo một cơ sở mã cho mỗi bối cảnh bị ràng buộc.

Tôi dựa trên lời khuyên này về kinh nghiệm hạn chế của mình, vì tôi đã viết mã ngành nghề kinh doanh dựa trên web cho phần lớn sự nghiệp của mình. Tôi có thể tưởng tượng rằng nếu bạn đang phát triển một ứng dụng máy tính để bàn, hoặc một hệ thống nhúng, hoặc hệ thống nhúng khác, thì mọi thứ sẽ khó tách rời hơn.

Mặc dù tôi nhận ra rằng lời khuyên đầu tiên này là dễ thực tế nhất, tôi cũng tin rằng đó là điều quan trọng nhất, và đó là lý do tại sao tôi đưa nó vào. Độ phức tạp của mã thay đổi phi tuyến tính (có thể theo cấp số nhân) với kích thước của cơ sở mã.

Ưu đãi Pure DI

Trong khi tôi vẫn nhận ra rằng câu hỏi này đưa ra một tình huống hiện có, tôi khuyên dùng Pure DI . Không sử dụng DI Container, nhưng nếu có, ít nhất hãy sử dụng nó để triển khai thành phần dựa trên quy ước .

Tôi không có bất kỳ trải nghiệm thực tế nào với Spring, nhưng tôi giả sử rằng bằng tệp cấu hình , một tệp XML được ngụ ý.

Cấu hình các phụ thuộc bằng XML là điều tồi tệ nhất của cả hai thế giới. Đầu tiên, bạn mất an toàn kiểu thời gian biên dịch, nhưng bạn không đạt được gì. Một tệp cấu hình XML có thể dễ dàng lớn như mã mà nó cố gắng thay thế.

So với vấn đề mà nó cố gắng giải quyết, các tệp cấu hình tiêm phụ thuộc chiếm sai vị trí trên đồng hồ phức tạp cấu hình .

Các trường hợp cho tiêm phụ thuộc hạt thô

Tôi có thể làm một trường hợp cho tiêm phụ thuộc hạt thô. Tôi cũng có thể tạo ra một trường hợp cho tiêm phụ thuộc hạt mịn (xem phần tiếp theo).

Nếu bạn chỉ tiêm một vài phụ thuộc 'trung tâm', thì hầu hết các lớp có thể trông như thế này:

public class Foo
{
    private readonly Bar bar;

    public Foo()
    {
        this.bar = new Bar();
    }

    // Members go here...
}

Điều này vẫn phù hợp với thành phần đối tượng ưu tiên của Mẫu thiết kế so với kế thừa lớp , bởi vì Foosáng tác Bar. Từ góc độ bảo trì, điều này vẫn có thể được coi là có thể duy trì được, bởi vì nếu bạn cần thay đổi thành phần, bạn chỉ cần chỉnh sửa mã nguồn cho Foo.

Điều này hầu như không thể duy trì ít hơn so với tiêm phụ thuộc. Trên thực tế, tôi muốn nói rằng việc chỉnh sửa trực tiếp lớp sử dụng sẽ dễ dàng hơn Bar, thay vì phải tuân theo quy tắc cố định bằng cách tiêm phụ thuộc.

Trong ấn bản đầu tiên của cuốn sách về Dependency Injection , tôi đã phân biệt giữa các phụ thuộc dễ bay hơi và ổn định.

Phụ thuộc dễ bay hơi là những phụ thuộc mà bạn nên xem xét tiêm. Chúng bao gồm

  • Các phụ thuộc phải được cấu hình lại sau khi biên dịch
  • Sự phụ thuộc được phát triển song song bởi một nhóm khác
  • Phụ thuộc với hành vi không xác định hoặc hành vi có tác dụng phụ

Mặt khác, các phụ thuộc ổn định là các phụ thuộc hoạt động theo cách được xác định rõ. Theo một nghĩa nào đó, bạn có thể lập luận rằng sự khác biệt này làm cho trường hợp tiêm phụ thuộc hạt thô, mặc dù tôi phải thừa nhận rằng tôi đã không hoàn toàn nhận ra điều đó khi tôi viết cuốn sách.

Tuy nhiên, từ góc độ thử nghiệm, điều này làm cho thử nghiệm đơn vị khó hơn. Bạn không còn có thể kiểm tra đơn vị Foođộc lập với Bar. Như JB Rainsberger giải thích , các bài kiểm tra tích hợp phải chịu một sự bùng nổ phức tạp. Theo nghĩa đen, bạn sẽ phải viết hàng chục ngàn trường hợp kiểm tra nếu bạn muốn bao quát tất cả các đường dẫn thông qua tích hợp 4-5 lớp.

Đối số thường là, nhiệm vụ của bạn không phải là lập trình một lớp. Nhiệm vụ của bạn là phát triển một hệ thống giải quyết một số vấn đề cụ thể. Đây là động lực đằng sau sự phát triển dựa trên hành vi (BDD).

Một quan điểm khác về điều này được trình bày bởi DHH, người tuyên bố rằng TDD dẫn đến thiệt hại thiết kế do thử nghiệm gây ra . Ông cũng ủng hộ thử nghiệm tích hợp hạt thô.

Nếu bạn có quan điểm này về phát triển phần mềm, thì tiêm phụ thuộc hạt thô có ý nghĩa.

Các trường hợp cho tiêm phụ thuộc hạt mịn

Mặt khác, tiêm phụ thuộc hạt mịn, có thể được mô tả là tiêm tất cả mọi thứ!

Mối quan tâm chính của tôi liên quan đến việc tiêm phụ thuộc hạt thô là những lời chỉ trích được thể hiện bởi JB Rainsberger. Bạn không thể bao gồm tất cả các đường dẫn mã bằng các kiểm tra tích hợp, bởi vì bạn cần viết đúng hàng ngàn hoặc hàng chục nghìn trường hợp kiểm thử để bao quát tất cả các đường dẫn mã.

Những người đề xuất của BDD sẽ phản bác lại lập luận rằng bạn không cần phải bao gồm tất cả các đường dẫn mã bằng các bài kiểm tra. Bạn chỉ cần bao gồm những người sản xuất giá trị kinh doanh.

Tuy nhiên, theo kinh nghiệm của tôi, tất cả các đường dẫn mã 'kỳ lạ' cũng sẽ thực thi trong triển khai khối lượng lớn và nếu không được kiểm tra, nhiều trong số đó sẽ có lỗi và gây ra ngoại lệ trong thời gian chạy (thường là ngoại lệ tham chiếu null).

Điều này đã khiến tôi ủng hộ việc tiêm phụ thuộc hạt mịn, bởi vì nó cho phép tôi kiểm tra các bất biến của tất cả các đối tượng trong sự cô lập.

Ưu tiên lập trình chức năng

Trong khi tôi nghiêng về tiêm phụ thuộc hạt mịn, tôi đã chuyển trọng tâm của mình sang lập trình chức năng, trong số các lý do khác vì về bản chất nó có thể kiểm chứng được .

Bạn càng di chuyển về phía mã RẮN, nó càng trở nên có chức năng hơn . Sớm hay muộn, bạn cũng có thể đi sâu. Kiến trúc chức năng là kiến ​​trúc Cổng và Bộ điều hợptiêm phụ thuộc cũng là một nỗ lực và Cổng và Bộ điều hợp . Tuy nhiên, sự khác biệt là một ngôn ngữ như Haskell thi hành kiến ​​trúc đó thông qua hệ thống loại của nó.

Ưu tiên chương trình chức năng gõ tĩnh

Tại thời điểm này, về cơ bản, tôi đã từ bỏ việc lập trình hướng đối tượng (OOP), mặc dù nhiều vấn đề của OOP thực chất được kết hợp với các ngôn ngữ chính như Java và C # nhiều hơn chính khái niệm này.

Vấn đề với các ngôn ngữ OOP chính là gần như không thể tránh được vấn đề nổ tổ hợp, chưa được kiểm chứng, dẫn đến các ngoại lệ trong thời gian chạy. Mặt khác, các ngôn ngữ được nhập tĩnh như Haskell và F #, cho phép bạn mã hóa nhiều điểm quyết định trong hệ thống loại. Điều này có nghĩa là thay vì phải viết hàng ngàn bài kiểm tra, trình biên dịch sẽ chỉ cho bạn biết liệu bạn đã xử lý tất cả các đường dẫn mã có thể chưa (ở một mức độ nào đó, đó không phải là viên đạn bạc).

Ngoài ra, tiêm phụ thuộc không phải là chức năng . Lập trình chức năng thực sự phải từ chối toàn bộ khái niệm phụ thuộc . Kết quả là mã đơn giản hơn.

Tóm lược

Nếu bị buộc phải làm việc với C #, tôi thích tiêm phụ thuộc chi tiết hơn vì nó cho phép tôi bao phủ toàn bộ cơ sở mã với số lượng các trường hợp thử nghiệm có thể quản lý được.

Cuối cùng, động lực của tôi là phản hồi nhanh chóng. Tuy nhiên, kiểm tra đơn vị không phải là cách duy nhất để có được phản hồi .


1
Tôi thực sự thích câu trả lời này và đặc biệt là câu nói này được sử dụng trong ngữ cảnh của Spring: "Định cấu hình các phụ thuộc bằng XML là điều tồi tệ nhất của cả hai thế giới. Đầu tiên, bạn mất an toàn kiểu thời gian biên dịch, nhưng bạn không đạt được gì cả. Cấu hình XML tập tin có thể dễ dàng lớn như mã mà nó cố gắng thay thế. "
Thomas Carleway

@ThomasCarleway không phải là điểm cấu hình XML mà bạn không phải chạm vào mã (thậm chí biên dịch) để thay đổi nó? Tôi chưa bao giờ (hoặc hầu như) sử dụng nó vì hai điều Mark đã đề cập, nhưng bạn có được một cái gì đó đáp lại.
El Mac

1

Các lớp thiết lập DI lớn là một vấn đề. Nhưng hãy nghĩ về mã nó thay thế.

Bạn cần phải khởi tạo tất cả các dịch vụ và không có gì. Mã để làm như vậy sẽ ở app.main()điểm bắt đầu và được chèn thủ công, hoặc được ghép chặt chẽ như this.myService = new MyService();trong các lớp.

Giảm kích thước của lớp thiết lập của bạn bằng cách chia nó thành nhiều lớp thiết lập và gọi chúng từ điểm bắt đầu chương trình. I E.

main()
{
   var c = new diContainer();
   var service1 = diSetupClass.SetupService1(c);
   var service2 = diSetupClass.SetupService2(c, service1); //if service1 is required by service2
   //etc

   //main logic
}

Bạn không cần tham chiếu dịch vụ1 hoặc 2 ngoại trừ chuyển chúng vào các phương thức thiết lập ci khác.

Điều này tốt hơn là cố gắng lấy chúng từ container vì nó thực thi lệnh gọi các thiết lập.


1
Đối với những người muốn tìm hiểu thêm về khái niệm này (xây dựng cây lớp của bạn tại điểm bắt đầu chương trình), thuật ngữ tốt nhất để tìm kiếm là "gốc thành phần"
e_i_pi

@Ewan cibiến đó là gì?
superjos

lớp thiết lập ci của bạn với các phương thức tĩnh
Ewan

thúc giục nên di. tôi cứ nghĩ 'container'
Ewan
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.