Sử dụng thư viện của bên thứ ba - luôn sử dụng trình bao bọc?


78

Hầu hết các dự án tôi tham gia đều sử dụng một số thành phần nguồn mở. Theo nguyên tắc chung, có phải luôn luôn là một ý tưởng tốt để tránh ràng buộc tất cả các thành phần của mã với các thư viện của bên thứ ba và thay vào đó thông qua một trình bao bọc đóng gói để tránh sự thay đổi?

Ví dụ, hầu hết các dự án PHP của chúng tôi trực tiếp sử dụng log4php làm khung ghi nhật ký, tức là chúng khởi tạo thông qua \ Logger :: getLogger (), chúng sử dụng các phương thức -> thông tin () hoặc -> cảnh báo (), v.v. tuy nhiên, một khung đăng nhập giả định có thể xuất hiện tốt hơn theo một cách nào đó. Vì thế, tất cả các dự án kết hợp chặt chẽ với chữ ký của phương thức log4php sẽ phải thay đổi, ở hàng chục nơi, để phù hợp với chữ ký mới. Điều này rõ ràng sẽ có tác động rộng lớn đến codebase và bất kỳ thay đổi nào cũng là một vấn đề tiềm năng.

Đối với các cơ sở mã mới trong tương lai từ loại kịch bản này, tôi thường xem xét (và đôi khi triển khai) một lớp trình bao bọc để đóng gói chức năng ghi nhật ký và làm cho nó dễ dàng hơn, mặc dù không thể đánh lừa, để thay đổi cách thức ghi nhật ký hoạt động trong tương lai với thay đổi tối thiểu ; mã gọi trình bao bọc, trình bao bọc chuyển cuộc gọi đến khung đăng nhập du jour .

Hãy nhớ rằng có nhiều ví dụ phức tạp hơn với các thư viện khác, tôi có quá kỹ thuật không hay đây có phải là một biện pháp phòng ngừa khôn ngoan trong hầu hết các trường hợp?

EDIT: Cân nhắc nhiều hơn - sử dụng phép thử và kiểm tra phụ thuộc đôi thực tế đòi hỏi chúng tôi phải loại bỏ hầu hết các API ("Tôi muốn kiểm tra mã của mình thực thi và cập nhật trạng thái của nó, nhưng không viết bình luận nhật ký / truy cập cơ sở dữ liệu thực"). Đây không phải là một người quyết định?


3
log4XYZ là một thương hiệu mạnh như vậy. API của nó sẽ thay đổi không sớm hơn khi API cho danh sách được liên kết sẽ. Cả hai đều là một vấn đề lâu dài được giải quyết.
Công việc

1
Bản sao chính xác của câu hỏi SO này: stackoverflow.com/questions/1916030/ Kẻ
Michael Borgwardt

1
Nếu bạn chỉ sử dụng nó trong nội bộ, việc bạn có bao bọc hay không chỉ là sự đánh đổi giữa công việc đã biết bây giờ và công việc có thể sau này. Một cuộc gọi phán xét. Nhưng một điều mà những người phản hồi khác dường như đã bỏ qua khi nói về việc đó là phụ thuộc API hay phụ thuộc triển khai . Nói cách khác, bạn có đang rò rỉ các lớp từ API của bên thứ ba này thông qua API công khai của riêng bạn và để lộ nó cho người dùng không? Trong trường hợp này, việc chuyển sang một thư viện khác không còn là vấn đề đơn giản nữa, vấn đề là bây giờ không thể thực hiện được nếu không phá vỡ API của chính bạn. Thật tồi tệ!
Elias Vasylenko

1
Để tham khảo thêm: Mẫu này được gọi là hành tây-kiến trúc nơi cơ sở hạ tầng bên ngoài (bạn gọi nó là lib bên ngoài) được ẩn đằng sau một giao diện
k3b

Câu trả lời:


42

Nếu bạn chỉ sử dụng một tập hợp con nhỏ của API bên thứ ba, sẽ rất hợp lý khi viết một trình bao bọc - điều này sẽ giúp đóng gói và ẩn thông tin, đảm bảo bạn không để lộ API có thể rất lớn cho mã của riêng bạn. Nó cũng có thể giúp đảm bảo rằng bất kỳ chức năng nào bạn không muốn sử dụng đều bị "ẩn".

Một lý do tốt khác cho trình bao bọc là nếu bạn muốn thay đổi thư viện của bên thứ ba. Nếu đây là một phần của cơ sở hạ tầng mà bạn biết bạn sẽ không thay đổi, đừng viết trình bao bọc cho nó.


Điểm tốt, nhưng chúng tôi được dạy rằng mã kết hợp chặt chẽ là xấu, vì nhiều lý do được hiểu rõ (khó kiểm tra hơn, khó hơn để cấu trúc lại, v.v.). Một cách diễn đạt khác của câu hỏi là "nếu khớp nối không tốt, tại sao việc kết hợp với API lại ổn?".
lotoffreetime

7
@lotsoffreetime Bạn không thể tránh một số khớp nối với API. Do đó, tốt hơn là kết hợp với API của riêng bạn. Bằng cách đó, bạn có thể thay đổi thư viện và thường không cần thay đổi API do trình bao bọc cung cấp.
George Marian

@ george-marian Nếu tôi không thể tránh sử dụng API cụ thể, tôi chắc chắn có thể giảm thiểu các điểm chạm. Câu hỏi là, tôi có nên cố gắng làm điều này mọi lúc hay đó là việc làm quá sức?
lotoffreetime

2
@lotsoffreetime Đó là một câu hỏi khó trả lời. Tôi đã mở rộng câu trả lời của mình cho kết thúc đó. (Về cơ bản, nó giảm xuống rất nhiều ifs.)
George Marian

2
@lotsoffreetime: nếu bạn có nhiều thời gian rảnh thì bạn cũng có thể làm được. Nhưng tôi khuyên bạn không nên viết trình bao bọc API, ngoại trừ trong điều kiện này: 1) API ban đầu ở mức rất thấp, vì vậy bạn viết API cấp cao hơn để phù hợp với dự án cụ thể của bạn cần tốt hơn hoặc 2) bạn có kế hoạch trong tương lai gần để chuyển đổi thư viện, bạn chỉ sử dụng thư viện hiện tại như một bước đệm trong khi tìm kiếm thư viện tốt hơn.
Lie Ryan

28

Không biết những tính năng mới siêu tuyệt vời mà logger được cải tiến trong tương lai này sẽ có, bạn sẽ viết trình bao bọc như thế nào? Sự lựa chọn hợp lý nhất là để trình bao bọc của bạn khởi tạo một số loại logger và có các phương thức như ->info()hoặc ->warn(). Nói cách khác, về cơ bản giống hệt với API hiện tại của bạn.

Thay vì mã bằng chứng trong tương lai mà tôi có thể không bao giờ cần thay đổi, hoặc có thể yêu cầu viết lại không thể tránh khỏi bằng mọi cách, tôi thích mã "bằng chứng quá khứ". Đó là, trong những trường hợp hiếm hoi khi tôi thay đổi đáng kể một thành phần, đó là khi tôi viết một trình bao bọc để làm cho nó tương thích với mã quá khứ. Tuy nhiên, bất kỳ mã mới nào cũng sử dụng API mới và tôi cấu trúc lại mã cũ để sử dụng bất cứ khi nào tôi thực hiện thay đổi trong cùng một tệp hoặc theo lịch trình cho phép. Sau một vài tháng, tôi có thể loại bỏ trình bao bọc và sự thay đổi đã dần dần và mạnh mẽ.

Nói cách khác, trình bao bọc thực sự chỉ có ý nghĩa khi bạn đã biết tất cả các API bạn cần bọc. Ví dụ điển hình là nếu ứng dụng của bạn hiện cần hỗ trợ nhiều trình điều khiển cơ sở dữ liệu, hệ điều hành hoặc phiên bản PHP khác nhau.


"... trình bao bọc thực sự chỉ có ý nghĩa khi bạn đã biết tất cả các API bạn cần bọc." Điều này sẽ đúng nếu tôi khớp API trong trình bao bọc; có lẽ tôi nên sử dụng thuật ngữ "đóng gói" mạnh hơn trình bao bọc. Tôi sẽ trừu tượng hóa các lệnh gọi API này để "ghi lại văn bản này bằng cách nào đó" thay vì "gọi foo :: log () với tham số này".
lotoffreetime

"Không biết những tính năng mới siêu tuyệt vời mà logger được cải tiến trong tương lai này sẽ có, bạn sẽ viết trình bao bọc như thế nào?" @ kevin-cline dưới đây đề cập đến một logger tương lai với hiệu suất tốt hơn, thay vì một tính năng mới hơn. Trong trường hợp này, không có API mới để bọc, chỉ là một phương thức xuất xưởng khác.
lotoffreetime

27

Bằng cách gói một thư viện bên thứ ba, bạn thêm một lớp trừu tượng bổ sung lên trên nó. Điều này có một vài lợi thế:

  • Cơ sở mã của bạn trở nên linh hoạt hơn với các thay đổi

    Nếu bạn cần thay thế thư viện bằng một thư viện khác, bạn chỉ cần thay đổi triển khai trong trình bao bọc của mình - ở một nơi . Bạn có thể thay đổi việc triển khai trình bao bọc và không phải thay đổi điều gì khác, nói cách khác, bạn có một hệ thống kết nối lỏng lẻo. Nếu không, bạn sẽ phải trải qua toàn bộ cơ sở mã của mình và sửa đổi ở mọi nơi - đó rõ ràng không phải là điều bạn muốn.

  • Bạn có thể xác định API của trình bao bọc độc lập với API của thư viện

    Các thư viện khác nhau có thể có các API khác nhau rất lớn và đồng thời không ai trong số chúng có thể chính xác là những gì bạn cần. Điều gì xảy ra nếu một số thư viện cần mã thông báo được chuyển cùng với mỗi cuộc gọi? Bạn có thể chuyển mã thông báo xung quanh trong ứng dụng của mình bất cứ nơi nào bạn cần sử dụng thư viện hoặc bạn có thể bảo vệ nó ở một nơi nào đó tập trung hơn, nhưng trong mọi trường hợp bạn cần mã thông báo. Lớp trình bao bọc của bạn làm cho toàn bộ điều này trở lại đơn giản - bởi vì bạn chỉ có thể giữ mã thông báo bên trong lớp trình bao bọc của mình, không bao giờ để lộ nó cho bất kỳ thành phần nào trong ứng dụng của bạn và hoàn toàn không cần đến nó. Một lợi thế rất lớn nếu bạn từng sử dụng một thư viện không nhấn mạnh vào thiết kế API tốt.

  • Kiểm thử đơn vị là cách đơn giản hơn

    Kiểm tra đơn vị chỉ nên kiểm tra một điều. Nếu bạn muốn kiểm tra đơn vị một lớp, bạn phải chế giễu các phụ thuộc của nó. Điều này càng trở nên quan trọng hơn nếu lớp đó thực hiện các cuộc gọi mạng hoặc truy cập một số tài nguyên khác bên ngoài phần mềm của bạn. Bằng cách gói thư viện của bên thứ ba, thật dễ dàng để giả lập các cuộc gọi đó và trả lại dữ liệu kiểm tra hoặc bất cứ điều gì mà bài kiểm tra đơn vị yêu cầu. Nếu bạn không có một lớp trừu tượng như vậy, việc này sẽ trở nên khó khăn hơn nhiều - và hầu hết thời gian điều này dẫn đến rất nhiều mã xấu.

  • Bạn tạo ra một hệ thống kết nối lỏng lẻo

    Các thay đổi đối với trình bao bọc của bạn không có tác dụng đối với các phần khác trong phần mềm của bạn - ít nhất là miễn là bạn không thay đổi hành vi của trình bao bọc của mình. Bằng cách giới thiệu một lớp trừu tượng như trình bao bọc này, bạn có thể đơn giản hóa các cuộc gọi đến thư viện và gần như loại bỏ hoàn toàn sự phụ thuộc của ứng dụng của bạn vào thư viện đó. Phần mềm của bạn sẽ chỉ sử dụng trình bao bọc và nó sẽ không tạo ra sự khác biệt về cách trình bao bọc được thực hiện hoặc cách thức thực hiện.


Ví dụ thực tế

Hãy Trung thực. Mọi người có thể tranh luận về những lợi thế và bất lợi của một cái gì đó như thế này trong nhiều giờ - đó là lý do tại sao tôi thay vì chỉ cho bạn một ví dụ.

Giả sử bạn có một số loại ứng dụng Android và bạn cần tải xuống hình ảnh. Có một loạt các thư viện ngoài kia làm cho việc tải và lưu trữ hình ảnh trở nên dễ dàng, ví dụ như Picasso hoặc Trình tải hình ảnh phổ quát .

Bây giờ chúng ta có thể định nghĩa một giao diện mà chúng ta sẽ sử dụng để bao bọc bất kỳ thư viện nào chúng ta kết thúc bằng cách sử dụng:

public interface ImageService {
    Bitmap load(String url);
}

Đây là giao diện bây giờ chúng ta có thể sử dụng trong toàn bộ ứng dụng bất cứ khi nào chúng ta cần tải hình ảnh. Chúng ta có thể tạo một triển khai giao diện này và sử dụng phép nội xạ phụ thuộc để thêm một thể hiện của triển khai đó ở mọi nơi chúng ta sử dụng ImageService.

Giả sử ban đầu chúng tôi quyết định sử dụng Picasso. Bây giờ chúng ta có thể viết một triển khai ImageServicesử dụng Picasso trong nội bộ:

public class PicassoImageService implements ImageService {

    private final Context mContext;

    public PicassoImageService(Context context) {
        mContext = context;
    }

    @Override
    public Bitmap load(String url) {
        return Picasso.with(mContext).load(url).get();
    }
}

Khá thẳng về phía trước nếu bạn hỏi tôi. Wrapper quanh các thư viện không cần phải phức tạp để có ích. Giao diện và cách triển khai có ít hơn 25 dòng mã kết hợp, do đó hầu như không có bất kỳ nỗ lực nào để tạo ra điều này, nhưng chúng tôi đã đạt được điều gì đó bằng cách làm điều này. Xem Contextlĩnh vực trong việc thực hiện? Khung tiêm phụ thuộc mà bạn chọn sẽ quan tâm đến việc tiêm phụ thuộc đó trước khi chúng tôi sử dụng ImageService, ứng dụng của bạn bây giờ không phải quan tâm đến cách hình ảnh được tải xuống và bất kỳ phụ thuộc nào mà thư viện có thể có. Tất cả các ứng dụng của bạn nhìn thấy là một ImageServicevà khi nó cần một hình ảnh, nó gọi load()bằng một url - đơn giản và dễ hiểu.

Tuy nhiên, lợi ích thực sự đến khi chúng ta bắt đầu thay đổi mọi thứ. Hãy tưởng tượng bây giờ chúng ta cần thay thế Picasso bằng Trình tải hình ảnh phổ quát vì Picasso không hỗ trợ một số tính năng mà chúng tôi hoàn toàn cần ngay bây giờ. Bây giờ chúng ta có phải lướt qua cơ sở mã của mình và thay thế một cách tẻ nhạt tất cả các cuộc gọi đến Picasso và sau đó xử lý hàng tá lỗi biên dịch vì chúng ta quên một vài cuộc gọi Picasso không? Không. Tất cả những gì chúng ta cần làm là tạo ra một triển khai mới ImageServicevà nói với khung tiêm phụ thuộc của chúng ta để sử dụng triển khai này kể từ bây giờ:

public class UniversalImageLoaderImageService implements ImageService {

    private final ImageLoader mImageLoader;

    public UniversalImageLoaderImageService(Context context) {

        DisplayImageOptions defaultOptions = new DisplayImageOptions.Builder()
                .cacheInMemory(true)
                .cacheOnDisk(true)
                .build();

        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
                .defaultDisplayImageOptions(defaultOptions)
                .build();

        mImageLoader = ImageLoader.getInstance();
        mImageLoader.init(config);
    }

    @Override
    public Bitmap load(String url) {
        return mImageLoader.loadImageSync(url);
    }
}

Như bạn có thể thấy việc thực hiện có thể rất khác nhau, nhưng nó không quan trọng. Chúng tôi đã không phải thay đổi một dòng mã ở bất kỳ nơi nào khác trong ứng dụng của mình. Chúng tôi sử dụng một thư viện hoàn toàn khác nhau có thể có các tính năng hoàn toàn khác nhau hoặc có thể được sử dụng rất khác nhau nhưng ứng dụng của chúng tôi không quan tâm. Giống như trước khi phần còn lại của ứng dụng của chúng tôi chỉ nhìn thấy ImageServicegiao diện với load()phương thức của nó và tuy nhiên phương thức này được triển khai không còn quan trọng nữa.

Ít nhất với tôi điều này tất cả đã nghe khá hay rồi, nhưng chờ đã! Vẫn còn nhiều nữa. Hãy tưởng tượng bạn đang viết bài kiểm tra đơn vị cho một lớp bạn đang làm việc và lớp này sử dụng ImageService. Tất nhiên, bạn không thể để các kiểm tra đơn vị của mình thực hiện các cuộc gọi mạng đến một số tài nguyên trên một số máy chủ khác nhưng vì hiện tại bạn đang sử dụng nên ImageServicebạn có thể dễ dàng load()trả lại một tĩnh Bitmapđược sử dụng cho các kiểm tra đơn vị bằng cách thực hiện một thử nghiệm giả ImageService:

public class MockImageService implements ImageService {

    private final Bitmap mMockBitmap;

    public MockImageService(Bitmap mockBitmap) {
        mMockBitmap = mockBitmap;
    }

    @Override
    public Bitmap load(String url) {
        return mMockBitmap;
    }
}

Để tóm tắt bằng cách gói các thư viện của bên thứ ba, cơ sở mã của bạn trở nên linh hoạt hơn với các thay đổi, nói chung đơn giản hơn, dễ kiểm tra hơn và bạn giảm việc ghép các thành phần khác nhau trong phần mềm của mình - tất cả những điều quan trọng hơn là bạn duy trì phần mềm càng lâu.


1
Điều này cũng đúng với API không ổn định. Mã của chúng tôi không thay đổi ở 1000 địa điểm chỉ vì thư viện bên dưới đã thay đổi. Câu trả lời rất hay.
RubberDuck

Câu trả lời rất súc tích và rõ ràng. Tôi làm công việc front-end trên web. Số lượng thay đổi trong cảnh quan đó là điên rồ. Việc mọi người "nghĩ" họ sẽ không có thay đổi không có nghĩa là sẽ không có bất kỳ thay đổi nào. Tôi thấy đề cập đến YAGNI. Tôi muốn thêm một từ viết tắt mới, YDKYAGNI, Bạn không biết bạn sẽ không cần nó. Đặc biệt với các triển khai liên quan đến web. Theo quy tắc, tôi luôn bao bọc các thư viện chỉ hiển thị một API nhỏ (như select2). Các thư viện lớn hơn ảnh hưởng đến kiến ​​trúc của bạn và gói chúng có nghĩa là bạn đang mong đợi kiến ​​trúc của mình thay đổi, điều đó có thể, nhưng nó ít có khả năng làm điều đó.
Byebye

Câu trả lời của bạn là rất hữu ích và trình bày khái niệm với một ví dụ làm cho khái niệm này thậm chí còn rõ ràng hơn.
Anil Gorthy

24

Tôi nghĩ rằng việc bao bọc các thư viện của bên thứ ba ngày hôm nay trong trường hợp có thứ gì đó tốt hơn xuất hiện vào ngày mai là vi phạm YAGNI rất lãng phí. Nếu bạn liên tục gọi mã của bên thứ ba theo cách đặc biệt với ứng dụng của mình, bạn sẽ (nên) cấu trúc lại các cuộc gọi đó thành một lớp gói để loại bỏ sự lặp lại. Mặt khác, bạn hoàn toàn sử dụng API thư viện và mọi trình bao bọc sẽ trông giống như chính thư viện.

Bây giờ giả sử một thư viện mới xuất hiện với hiệu suất vượt trội hoặc bất cứ điều gì. Trong trường hợp đầu tiên, bạn chỉ cần viết lại trình bao bọc cho API mới. Không vấn đề gì.

Trong trường hợp thứ hai, bạn tạo một trình bao bọc điều chỉnh giao diện cũ để lái thư viện mới. Một công việc nhỏ hơn, nhưng không có vấn đề gì, và không có nhiều công việc hơn bạn sẽ làm nếu bạn đã viết trình bao bọc trước đó.


4
Tôi không nghĩ rằng YAGNI nhất thiết phải áp dụng trong tình huống này. Đây không phải là về xây dựng chức năng trong trường hợp bạn có thể cần nó trong tương lai. Đó là về xây dựng tính linh hoạt vào kiến ​​trúc. Nếu sự linh hoạt đó là không cần thiết, thì, vâng, YAGNI áp dụng. Tuy nhiên, quyết tâm đó có xu hướng được thực hiện đôi khi trong tương lai khi thực hiện thay đổi có thể sẽ đau đớn.
George Marian

7
@George Marian: vấn đề là 95% thời gian, bạn sẽ không bao giờ cần sự linh hoạt để thay đổi. Nếu bạn cần chuyển sang một thư viện mới trong tương lai có hiệu suất vượt trội, thì việc tìm kiếm / thay thế các cuộc gọi hoặc viết một trình bao bọc khi bạn cần nó là khá đơn giản. Mặt khác, nếu thư viện mới của bạn có các chức năng khác nhau, trình bao bọc bây giờ trở thành một trở ngại vì bây giờ bạn có hai vấn đề: chuyển mã cũ để khai thác các tính năng mới và duy trì trình bao bọc.
Nói dối Ryan

3
@lotsoffreetime: Mục đích của "thiết kế tốt" là để giảm thiểu tổng chi phí của ứng dụng trong suốt vòng đời của nó. Thêm các lớp không xác định cho những thay đổi trong tương lai là bảo hiểm rất tốn kém. Tôi chưa bao giờ thấy ai nhận ra bất kỳ khoản tiết kiệm nào từ phương pháp đó. Nó chỉ tạo ra công việc tầm thường cho các lập trình viên có thời gian tốt hơn dành cho các yêu cầu cụ thể của khách hàng. Hầu hết thời gian, nếu bạn đang viết mã không dành riêng cho khách hàng của mình, bạn đang lãng phí thời gian và tiền bạc.
kevin cline

1
@George: nếu những thay đổi này là đau đớn, tôi nghĩ đó là một quá trình mùi. Trong Java, tôi sẽ tạo các lớp mới có cùng tên với các lớp cũ, nhưng trong một gói khác, thay đổi tất cả các lần xuất hiện của tên gói cũ và chạy lại các kiểm tra tự động.
kevin cline

1
@kevin Đó là công việc nhiều hơn, và do đó mang nhiều rủi ro hơn, chỉ đơn giản là cập nhật trình bao bọc và chạy thử nghiệm.
George Marian

9

Lý do cơ bản để viết một trình bao bọc xung quanh thư viện của bên thứ ba là để bạn có thể trao đổi thư viện của bên thứ ba mà không thay đổi mã sử dụng nó. Bạn không thể tránh ghép nối với một cái gì đó, vì vậy, tranh luận rằng tốt hơn là kết hợp với một API bạn đã viết.

Cho dù điều này là giá trị nỗ lực là một câu chuyện khác nhau. Cuộc tranh luận đó có thể sẽ tiếp tục trong một thời gian dài.

Đối với các dự án nhỏ, trong đó khả năng thay đổi như vậy là cần thiết là thấp, có lẽ đó là nỗ lực không cần thiết. Đối với các dự án lớn hơn, tính linh hoạt đó rất có thể vượt xa nỗ lực thêm để bọc thư viện. Tuy nhiên, thật khó để biết liệu đó có phải là trường hợp trước hay không.

Một cách khác để xem xét nó là nguyên tắc cơ bản của trừu tượng hóa những gì có khả năng thay đổi. Vì vậy, nếu thư viện của bên thứ ba được thiết lập tốt và không có khả năng thay đổi, có thể không bao bọc nó. Tuy nhiên, nếu thư viện của bên thứ ba tương đối mới, có nhiều khả năng nó sẽ cần phải được thay thế. Điều đó nói rằng, sự phát triển của các thư viện thành lập đã bị bỏ rơi rất nhiều lần. Vì vậy, đây không phải là một câu hỏi dễ trả lời.


Trong trường hợp thử nghiệm đơn vị trong đó việc có thể đưa ra một giả API phục vụ để giảm thiểu đơn vị được thử nghiệm, "tiềm năng thay đổi" không phải là một yếu tố. Có nói rằng, đây vẫn là câu trả lời yêu thích của tôi vì nó gần nhất với cách tôi nghĩ. Chú Bob sẽ nói gì? :)
lotoffreetime

Ngoài ra, các dự án nhỏ (không có nhóm, thông số kỹ thuật cơ bản, v.v.) có các quy tắc riêng trong đó bạn có thể vi phạm thông lệ tốt như thế này và thoát khỏi nó, ở một mức độ nào đó. Nhưng đó là một câu hỏi khác nhau ...
lotsoffreetime

1

Ngoài những gì @Oded đã nói, tôi chỉ muốn thêm câu trả lời này cho mục đích đặc biệt là đăng nhập.


Tôi luôn có một giao diện để đăng nhập nhưng tôi chưa bao giờ phải thay thế một log4fookhung công tác nào.

Chỉ mất nửa giờ để cung cấp giao diện và viết trình bao bọc, vì vậy tôi đoán bạn không lãng phí quá nhiều thời gian nếu nó không cần thiết.

Đó là một trường hợp đặc biệt của YAGNI. Mặc dù tôi không cần nhưng nó không mất nhiều thời gian và tôi cảm thấy an toàn hơn với nó. Nếu ngày trao đổi logger thực sự đến, tôi sẽ vui vì tôi đã đầu tư nửa giờ vì nó sẽ giúp tôi tiết kiệm hơn một ngày trong các cuộc gọi trong một dự án thế giới thực. Và tôi chưa bao giờ viết hoặc xem một bài kiểm tra đơn vị để ghi nhật ký (ngoài các bài kiểm tra cho chính việc thực hiện logger), vì vậy, mong đợi các lỗi mà không có trình bao bọc.


Tôi không mong đợi thay đổi log4foo, nhưng nó được biết đến rộng rãi và là một ví dụ. Điều thú vị là hai câu trả lời cho đến nay là bổ sung - "không phải lúc nào cũng bao bọc"; "Chỉ bọc trong trường hợp".
lotoffreetime

@Falcon: bạn có bọc mọi thứ không? ORM, giao diện đăng nhập, các lớp ngôn ngữ cốt lõi? Rốt cuộc, người ta không bao giờ có thể biết khi nào cần một HashMap tốt hơn.
kevin cline

1

Tôi đang giải quyết vấn đề chính xác này trong một dự án mà tôi hiện đang làm. Nhưng trong trường hợp của tôi, thư viện dành cho đồ họa và do đó tôi có thể hạn chế sử dụng nó ở một số lượng nhỏ các lớp liên quan đến đồ họa, so với việc rắc nó trong toàn bộ dự án. Do đó, việc chuyển đổi API sau này khá dễ dàng nếu tôi cần; trong trường hợp của một logger, vấn đề trở nên phức tạp hơn rất nhiều.

Do đó, tôi muốn nói rằng quyết định này có liên quan nhiều đến việc chính xác thư viện của bên thứ 3 đang làm gì và có bao nhiêu nỗi đau sẽ liên quan đến việc thay đổi nó. Nếu thay đổi tất cả các lệnh gọi API sẽ dễ dàng bất kể thì có lẽ không đáng để thực hiện. Tuy nhiên, nếu việc thay đổi thư viện sau này sẽ thực sự khó khăn thì có lẽ tôi sẽ bọc nó ngay bây giờ.


Ngoài ra, các câu trả lời khác đã bao quát rất tốt câu hỏi chính vì vậy tôi chỉ muốn tập trung vào phần bổ sung cuối cùng đó, về các đối tượng tiêm phụ thuộc và giả. Tất nhiên nó phụ thuộc vào cách chính xác khung ghi nhật ký của bạn hoạt động, nhưng trong hầu hết các trường hợp, điều đó không yêu cầu trình bao bọc (mặc dù nó có thể sẽ được hưởng lợi từ một). Chỉ cần tạo API cho đối tượng giả của bạn giống hệt như thư viện của bên thứ 3 và sau đó bạn có thể dễ dàng trao đổi trong đối tượng giả để thử nghiệm.

Yếu tố chính ở đây là liệu thư viện của bên thứ 3 có được triển khai thông qua việc tiêm phụ thuộc hay không (hoặc một bộ định vị dịch vụ hoặc một số mẫu được ghép lỏng lẻo như vậy). Nếu các hàm thư viện được truy cập thông qua một phương thức đơn hoặc tĩnh hoặc một cái gì đó thì bạn sẽ cần phải bọc nó trong một đối tượng mà bạn có thể làm việc với phép nội xạ phụ thuộc.


1

Tôi mạnh mẽ trong trại gói và không thể thay thế thư viện bên thứ ba với ưu tiên lớn nhất (mặc dù đó là phần thưởng). Lý do chính của tôi rằng ủng hộ gói là đơn giản

Thư viện bên thứ ba không được thiết kế cho nhu cầu cụ thể của chúng tôi.

Và điều này biểu hiện, thông thường, dưới dạng một khối sao chép mã, giống như các nhà phát triển viết 8 dòng mã chỉ để tạo QButtonvà tạo kiểu cho nó theo cách tìm ứng dụng, chỉ dành cho nhà thiết kế không chỉ muốn nhìn. mà còn là chức năng của các nút để thay đổi hoàn toàn cho toàn bộ phần mềm, đòi hỏi phải quay lại và viết lại hàng ngàn dòng mã, hoặc thấy rằng hiện đại hóa một đường dẫn kết xuất đòi hỏi phải viết lại sử thi vì codebase đã cố định ad-hoc cấp thấp đường ống mã OpenGL ở khắp mọi nơi thay vì tập trung vào thiết kế trình kết xuất thời gian thực và không sử dụng OGL một cách nghiêm ngặt để thực hiện.

Những thiết kế này không phù hợp với nhu cầu thiết kế cụ thể của chúng tôi. Họ có xu hướng cung cấp một lượng lớn các thứ thực sự cần thiết (và những gì không phải là một phần của thiết kế cũng quan trọng, nếu không hơn là những gì), và giao diện của chúng không được thiết kế để phục vụ cụ thể các nhu cầu của chúng tôi ở cấp độ cao think = one request "cách loại bỏ tất cả các điều khiển thiết kế trung tâm nếu chúng ta sử dụng chúng trực tiếp. Nếu các nhà phát triển cuối cùng viết mã cấp thấp hơn nhiều so với yêu cầu để thể hiện những gì họ cần, đôi khi họ có thể tự gói nó theo những cách đặc biệt khiến bạn kết thúc với hàng tá chữ viết vội vàng và thô lỗ- được bao bọc và thiết kế tài liệu thay vì một trình bao bọc được thiết kế tốt, tài liệu tốt.

Tất nhiên, tôi sẽ áp dụng các ngoại lệ mạnh mẽ cho các thư viện nơi các trình bao bọc gần như là bản dịch một-một về những gì API của bên thứ ba cung cấp. Trong trường hợp đó, có thể không có thiết kế cấp cao nào được tìm kiếm mà thể hiện trực tiếp hơn các yêu cầu kinh doanh và thiết kế (có thể là trường hợp giống với thư viện "tiện ích" hơn). Nhưng nếu có sẵn một thiết kế phù hợp hơn, thể hiện trực tiếp hơn nhu cầu của chúng tôi, thì tôi mạnh mẽ trong trại gói, giống như tôi rất thích sử dụng chức năng cấp cao hơn và tái sử dụng nó qua mã lắp ráp nội tuyến khắp nơi.

Thật kỳ lạ, tôi đã đụng độ với các nhà phát triển theo cách mà họ có vẻ không tin và quá bi quan về khả năng thiết kế của chúng tôi, giả sử, một chức năng tạo nút và trả lại rằng họ muốn viết 8 dòng mã cấp thấp hơn tập trung vào kính hiển vi chi tiết về việc tạo nút (cuối cùng cần phải thay đổi nhiều lần trong tương lai) về thiết kế và sử dụng chức năng đã nói. Tôi thậm chí không thấy mục đích của chúng tôi là cố gắng thiết kế bất cứ thứ gì ở nơi đầu tiên nếu chúng tôi không thể tin tưởng bản thân mình để thiết kế các loại bao bọc này một cách hợp lý.

Nói cách khác, tôi thấy các thư viện của bên thứ ba là cách để có khả năng tiết kiệm thời gian rất lớn trong việc triển khai, chứ không phải thay thế cho việc thiết kế hệ thống.


0

Ý tưởng của tôi về Thư viện của bên thứ ba:

Gần đây đã có một số cuộc thảo luận trong cộng đồng iOS về ưu và nhược điểm (OK, chủ yếu là nhược điểm) của việc sử dụng phụ thuộc của bên thứ ba. Nhiều đối số tôi thấy khá chung chung - nhóm tất cả các thư viện của bên thứ ba vào một giỏ. Tuy nhiên, với hầu hết mọi thứ, nó không đơn giản. Vì vậy, hãy cố gắng tập trung vào một trường hợp duy nhất

Chúng ta có nên tránh sử dụng các thư viện UI của bên thứ ba?

Lý do để xem xét các thư viện của bên thứ ba:

Dường như có hai lý do chính khiến các nhà phát triển cân nhắc sử dụng thư viện của bên thứ ba:

  1. Thiếu kỹ năng hoặc kiến ​​thức. Giả sử, bạn đang làm việc trên một ứng dụng chia sẻ ảnh. Bạn không bắt đầu bằng cách cuộn tiền điện tử của riêng bạn.
  2. Thiếu thời gian hoặc hứng thú để xây dựng một cái gì đó. Trừ khi bạn có một lượng thời gian không giới hạn (mà chưa có con người nào), bạn phải ưu tiên.

Hầu hết các thư viện UI ( không phải tất cả! ) Có xu hướng rơi vào loại thứ hai. Công cụ này không phải là khoa học tên lửa, nhưng cần có thời gian để xây dựng nó đúng.

Nếu đó là một chức năng kinh doanh cốt lõi - hãy tự làm, bất kể đó là gì.

Có khá nhiều loại điều khiển / lượt xem:

  1. Chung chung, cho phép bạn sử dụng chúng trong nhiều bối cảnh khác nhau mà thậm chí không nghĩ đến bởi người tạo ra chúng, ví dụ UICollectionViewtừ UIKit.
  2. Cụ thể, được thiết kế cho một trường hợp sử dụng duy nhất, ví dụ UIPickerView. Hầu hết các thư viện của bên thứ ba có xu hướng rơi vào loại thứ hai. Hơn nữa, chúng thường được trích xuất từ ​​một cơ sở mã hiện có mà chúng được tối ưu hóa.

Giả định sớm không biết

Nhiều nhà phát triển thực hiện đánh giá mã về mã nội bộ của họ nhưng có thể được coi là chất lượng của mã nguồn bên thứ ba. Thật đáng để dành một chút thời gian chỉ để duyệt mã của thư viện. Cuối cùng, bạn có thể ngạc nhiên khi thấy một số lá cờ đỏ, ví dụ như swizzling được sử dụng ở những nơi không cần thiết.

Thường học ý tưởng có lợi hơn là lấy mã kết quả.

Bạn không thể che giấu nó

Do cách thiết kế UIKit, rất có thể bạn sẽ không thể ẩn thư viện UI của bên thứ ba, ví dụ: đằng sau bộ điều hợp. Một thư viện sẽ đan xen với mã UI của bạn trở thành một thực tế của dự án của bạn.

Chi phí thời gian trong tương lai

UIKit thay đổi với mỗi bản phát hành iOS. Mọi thứ sẽ vỡ. Sự phụ thuộc của bên thứ ba của bạn sẽ không được bảo trì như bạn mong đợi.

Phần kết luận :

Từ kinh nghiệm cá nhân của tôi, hầu hết việc sử dụng mã UI của bên thứ ba sôi sục để trao đổi tính linh hoạt nhỏ hơn trong một thời gian.

Chúng tôi sử dụng mã làm sẵn để gửi bản phát hành hiện tại của chúng tôi nhanh hơn. Dù sớm hay muộn, chúng tôi đã đạt đến giới hạn của thư viện và đứng trước một quyết định khó khăn: phải làm gì tiếp theo?


0

Sử dụng thư viện trực tiếp thân thiện hơn cho nhóm phát triển. Khi một nhà phát triển mới tham gia, anh ta có thể có đầy đủ kinh nghiệm với tất cả các khung được sử dụng nhưng sẽ không thể đóng góp hiệu quả trước khi tìm hiểu API phát triển tại nhà của bạn. Khi một nhà phát triển trẻ cố gắng tiến bộ trong nhóm của bạn, anh ta sẽ bị buộc phải học API cụ thể của bạn không có ở bất kỳ nơi nào khác, thay vì có được năng lực chung hữu ích hơn. Nếu ai đó biết các tính năng hoặc khả năng hữu ích của API gốc, có thể không thể truy cập vào lớp được viết bởi một người không biết về chúng. Nếu ai đó sẽ nhận được một nhiệm vụ lập trình trong khi tìm kiếm một công việc, có thể không thể chứng minh những điều cơ bản anh ta đã sử dụng nhiều lần, chỉ vì tất cả những lần này anh ta đã truy cập các chức năng cần thiết thông qua trình bao bọc của bạn.

Tôi nghĩ những vấn đề này có thể quan trọng hơn khả năng sử dụng một thư viện hoàn toàn khác sau này. Trường hợp duy nhất tôi sẽ sử dụng trình bao bọc là khi di chuyển sang triển khai khác chắc chắn được lên kế hoạch hoặc API được bao bọc không đủ đóng băng và tiếp tục thay đổi.

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.