Korzystanie z bibliotek firm trzecich - zawsze używasz opakowania?

78

Większość projektów, w które jestem zaangażowany, wykorzystuje kilka komponentów typu open source. Zgodnie z ogólną zasadą, czy dobrym pomysłem jest zawsze unikanie wiązania wszystkich składników kodu do bibliotek stron trzecich, a zamiast tego przechodzenie przez enkapsulujące opakowanie, aby uniknąć bólu związanego ze zmianami?

Na przykład większość naszych projektów PHP bezpośrednio używa log4php jako struktury rejestrowania, tzn. Są one tworzone za pomocą \ Logger :: getLogger (), używają -> info () lub -> warn () itp. W przyszłości może się jednak pojawić hipotetyczna struktura rejestrowania, która jest w pewien sposób lepsza. Na obecnym etapie wszystkie projekty ściśle powiązane z podpisami metody log4php musiałyby się zmieniać w kilkudziesięciu miejscach, aby dopasować je do nowych podpisów. Miałoby to oczywiście duży wpływ na bazę kodów, a każda zmiana jest potencjalnym problemem.

Aby przygotować przyszłe nowe bazy kodu z tego rodzaju scenariusza, często rozważam (a czasem implementuję) klasę opakowania, która zawiera funkcje rejestrowania i ułatwia, choć nie jest niezawodny, zmianę sposobu, w jaki rejestrowanie działa w przyszłości przy minimalnej zmianie ; kod wywołuje opakowanie, opakowanie przekazuje wywołanie do środowiska rejestrowania du Jour .

Pamiętając o tym, że istnieją inne bardziej skomplikowane przykłady z innymi bibliotekami, czy jestem nadmiernie inżynieryjny, czy jest to rozsądne środki ostrożności w większości przypadków?

EDYCJA: Więcej uwag - użycie wstrzykiwania zależności i dublowania testów praktycznie wymaga, aby i tak wyodrębnić większość interfejsów API („Chcę sprawdzić, czy mój kod wykonuje i aktualizuje jego stan, ale nie pisać komentarza do dziennika / uzyskać dostęp do prawdziwej bazy danych”). Czy to nie decyduje?

dużo czasu wolnego
źródło
3
log4XYZ to tak silny znak towarowy. Jego interfejs API zmieni się nie wcześniej niż wtedy, gdy zmieni się interfejs API dla listy połączonej. Oba są od dawna rozwiązanym problemem.
Job
1
Dokładna kopia tego pytania SO: stackoverflow.com/questions/1916030/…
Michael Borgwardt
1
Jeśli używasz go tylko wewnętrznie, niezależnie od tego, czy chcesz go owinąć, czy nie, jest to tylko kompromis między znaną pracą teraz a możliwą pracą później. Wezwanie sądu. Ale wydaje się, że inni respondenci zaniedbali rozmawiać o tym, czy jest to zależność API czy zależność implementacyjna . Innymi słowy, czy przeciekasz klasy z tego interfejsu API innej firmy za pośrednictwem własnego publicznego interfejsu API i udostępniasz go użytkownikom? W tym przypadku nie jest to prosta sprawa ciężkiej pracy, aby przenieść się do innej biblioteki, problemem jest to, że obecnie niemożliwe bez zerwania własnego API. To jest bardzo złe!
Elias Vasylenko
1
Więcej informacji: wzorzec ten nazywany jest architekturą cebulową, w której zewnętrzna infrastruktura (nazywana zewnętrzną biblioteką lib) jest ukryta za interfejsem
k3b

Odpowiedzi:

42

Jeśli używasz tylko niewielkiego podzbioru interfejsu API innej firmy, warto napisać opakowanie - pomaga to w enkapsulacji i ukrywaniu informacji, dzięki czemu nie narażasz potencjalnie dużego API na własny kod. Może także pomóc upewnić się, że każda funkcja, której nie chcesz używać, jest „ukryta”.

Innym dobrym powodem dla opakowania jest to, że spodziewasz się zmienić bibliotekę strony trzeciej. Jeśli jest to infrastruktura, o której wiesz, że się nie zmienisz, nie pisz dla niej opakowania.

Oded
źródło
Dobre punkty, ale uczono nas, że ściśle powiązany kod jest zły z wielu dobrze rozumianych powodów (trudniejszy do przetestowania, trudniejszy do refaktoryzacji itp.). Alternatywne sformułowanie pytania brzmi: „jeśli sprzęganie jest złe, dlaczego można łączyć się z interfejsem API?”.
lotsoffreetime
7
@lotsoffreetime Nie można uniknąć sprzężenia z interfejsem API. Dlatego lepiej jest połączyć się z własnym interfejsem API. W ten sposób możesz zmienić bibliotekę i ogólnie nie musisz zmieniać interfejsu API dostarczanego przez opakowanie.
George Marian,
@ george-marian Jeśli nie mogę uniknąć korzystania z danego interfejsu API, z pewnością mogę zminimalizować punkty kontaktowe. Pytanie brzmi: czy powinienem próbować to robić przez cały czas, czy to przesadza?
lotsoffreetime
2
@lotsoffreetime To trudne pytanie, na które należy odpowiedzieć. Rozszerzyłem swoją odpowiedź na ten cel. (Zasadniczo sprowadza się to do wielu ifs.)
George Marian,
2
@lotsoffreetime: jeśli masz dużo wolnego czasu, możesz zrobić jedno z nich. Ale odradzam pisanie opakowania API, z wyjątkiem następujących warunków: 1) oryginalny interfejs API ma bardzo niski poziom, więc piszesz interfejs API wyższego poziomu, aby lepiej pasował do konkretnego projektu, lub 2) masz plan w w najbliższej przyszłości, aby zmienić biblioteki, używasz bieżącej biblioteki tylko jako odskocznię, szukając lepszej.
Lie Ryan,
28

Nie wiedząc, jakie super wspaniałe nowe funkcje będzie miał ten rzekomo ulepszony rejestrator, jak napisałbyś opakowanie? Najbardziej logicznym wyborem jest, aby otoki utworzyły jakąś klasę logger i miały metody takie jak ->info()lub ->warn(). Innymi słowy, zasadniczo identyczny z obecnym API.

Zamiast kodu na przyszłość, którego nigdy nie będę musiał zmieniać lub który może wymagać nieuniknionego przepisania, wolę kod „na przeszłość”. Oznacza to, że w rzadkich przypadkach, kiedy zmieniają znacząco składnik, to kiedy piszę otoki aby był on zgodny z ostatniego kodu. Jednak każdy nowy kod korzysta z nowego interfejsu API i zmieniam stary kod, aby użyć go za każdym razem, gdy i tak dokonuję zmiany w tym samym pliku lub na ile pozwala na to harmonogram. Po kilku miesiącach mogę usunąć opakowanie, a zmiana jest stopniowa i solidna.

Innymi słowy, opakowania naprawdę mają sens tylko wtedy, gdy znasz już wszystkie interfejsy API, które musisz zawinąć. Dobrym przykładem jest sytuacja, w której aplikacja musi obsługiwać wiele różnych sterowników baz danych, systemów operacyjnych lub wersji PHP.

Karl Bielefeldt
źródło
„... opakowania naprawdę mają sens tylko wtedy, gdy znasz już wszystkie interfejsy API, które musisz zawinąć”. Byłoby to prawdą, gdybym dopasował API w opakowaniu; być może powinienem używać terminu „kapsułkowanie” mocniej niż opakowanie. Abstrahowałem od tych wywołań API, aby „jakoś zalogować ten tekst” zamiast „wywołać foo :: log () z tym parametrem”.
lotsoffreetime
„Nie wiedząc, jakie super wspaniałe nowe funkcje będzie miał ten rzekomo ulepszony rejestrator, jak byś napisał opakowanie?” @ Kevin-Cline poniżej wspomina o przyszłym loggerze o lepszej wydajności, a nie o nowszej funkcji. W tym przypadku nie ma nowego API do zawinięcia, po prostu inna metoda fabryczna.
lotsoffreetime
27

Owijając bibliotekę strony trzeciej, dodajesz na niej dodatkową warstwę abstrakcji. Ma to kilka zalet:

  • Baza kodu staje się bardziej elastyczna na zmiany

    Jeśli kiedykolwiek zajdzie potrzeba zastąpienia biblioteki inną biblioteką, wystarczy zmienić implementację w opakowaniu - w jednym miejscu . Możesz zmienić implementację opakowania i nie musisz niczego zmieniać, innymi słowy, masz luźno powiązany system. W przeciwnym razie musiałbyś przejrzeć całą bazę kodu i dokonać modyfikacji wszędzie - co oczywiście nie jest tym, czego chcesz.

  • Interfejs API opakowania można zdefiniować niezależnie od interfejsu API biblioteki

    Różne biblioteki mogą mieć bardzo różne interfejsy API, a jednocześnie żadna z nich może nie być dokładnie tym, czego potrzebujesz. Co się stanie, jeśli jakaś biblioteka potrzebuje tokena do przekazania wraz z każdym połączeniem? Możesz przekazać token w swojej aplikacji, gdziekolwiek chcesz użyć biblioteki, lub możesz go zabezpieczyć gdzieś bardziej centralnie, ale w każdym razie potrzebujesz tokena. Twoja klasa opakowań ponownie upraszcza to wszystko - ponieważ możesz po prostu trzymać token wewnątrz klasy opakowania, nigdy nie wystawiając go na działanie żadnego komponentu w aplikacji i całkowicie odciąć od tego potrzebę. Ogromna zaleta, jeśli kiedykolwiek korzystałeś z biblioteki, która nie podkreśla dobrego projektu API.

  • Testowanie jednostkowe jest znacznie prostsze

    Testy jednostkowe powinny testować tylko jedną rzecz. Jeśli chcesz przetestować jednostkę w klasie, musisz wyśmiewać jej zależności. Staje się to jeszcze ważniejsze, jeśli klasa ta wykonuje połączenia sieciowe lub uzyskuje dostęp do innych zasobów poza twoim oprogramowaniem. Otaczając bibliotekę innej firmy, można łatwo wyśmiewać te połączenia i zwracać dane testowe lub cokolwiek, czego wymaga test jednostkowy. Jeśli nie masz takiej warstwy abstrakcji, staje się to znacznie trudniejsze - i przez większość czasu powoduje to dużo brzydkiego kodu.

  • Tworzysz luźno powiązany system

    Zmiany w opakowaniu nie mają wpływu na inne części oprogramowania - przynajmniej o ile nie zmienisz zachowania opakowania. Wprowadzając warstwę abstrakcji, taką jak to opakowanie, możesz uprościć połączenia z biblioteką i prawie całkowicie usunąć zależność aplikacji od tej biblioteki. Twoje oprogramowanie będzie po prostu używać opakowania i nie będzie miało znaczenia, w jaki sposób opakowanie jest wdrażane ani w jaki sposób robi to, co robi.


Praktyczny przykład

Bądźmy szczerzy. Ludzie mogą dyskutować o zaletach i wadach czegoś takiego przez wiele godzin - dlatego raczej raczej pokazuję wam przykład.

Załóżmy, że masz aplikację na Androida i musisz pobrać obrazy. Istnieje wiele bibliotek, dzięki którym ładowanie i buforowanie obrazów jest dziecinnie proste, na przykład Picasso lub Universal Image Loader .

Możemy teraz zdefiniować interfejs, którego będziemy używać do zawijania dowolnej biblioteki, w której ostatecznie wykorzystamy:

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

Jest to interfejs, którego możemy teraz używać w aplikacji za każdym razem, gdy potrzebujemy załadować obraz. Możemy stworzyć implementację tego interfejsu i użyć wstrzykiwania zależności, aby wstrzyknąć instancję tej implementacji wszędzie tam, gdzie używamy ImageService.

Załóżmy, że początkowo zdecydowaliśmy się na użycie Picassa. Możemy teraz napisać implementację, dla ImageServicektórej Picasso korzysta wewnętrznie:

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();
    }
}

Całkiem prosto, jeśli mnie zapytasz. Otaczanie bibliotek nie musi być skomplikowane, aby było przydatne. Interfejs i implementacja mają mniej niż 25 połączonych linii kodu, więc stworzenie tego nie było prawie żadnym wysiłkiem, ale już coś zyskujemy dzięki temu. Zobacz Contextpole we wdrożeniu? Wybrana przez Ciebie struktura wstrzykiwania zależności już zadba o wstrzyknięcie tej zależności, zanim jeszcze skorzystamy z naszej ImageService, twoja aplikacja nie musi teraz przejmować się sposobem pobierania obrazów i innymi zależnościami, jakie może mieć biblioteka. Twoja aplikacja widzi tylko to, ImageServicea kiedy potrzebuje obrazu, wywołuje go load()za pomocą adresu URL - prosty i bezpośredni.

Jednak prawdziwa korzyść przychodzi, gdy zaczynamy coś zmieniać. Wyobraź sobie, że musimy teraz zastąpić Picasso uniwersalnym programem ładującym obrazy, ponieważ Picasso nie obsługuje niektórych funkcji, których absolutnie potrzebujemy w tej chwili. Czy musimy teraz przeczesywać naszą bazę kodów i żmudnie zastępować wszystkie wywołania Picassa, a następnie radzić sobie z dziesiątkami błędów kompilacji, ponieważ zapomnieliśmy o kilku wywołaniach Picassa? Nie. Wszystko, co musimy zrobić, to stworzyć nową implementację ImageServicei poinformować naszą strukturę wstrzykiwania zależności, aby od tej pory korzystała z tej implementacji:

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);
    }
}

Jak widać implementacja może być bardzo różna, ale to nie ma znaczenia. Nie musieliśmy zmieniać ani jednego wiersza kodu nigdzie indziej w naszej aplikacji. Używamy zupełnie innej biblioteki, która może mieć zupełnie inne funkcje lub może być używana zupełnie inaczej, ale nasza aplikacja po prostu nie ma znaczenia. Tak jak wcześniej reszta naszej aplikacji widzi ImageServiceinterfejs ze swoją load()metodą, ale ta metoda jest zaimplementowana, nie ma już znaczenia.

Przynajmniej dla mnie to wszystko już brzmi całkiem ładnie, ale poczekaj! Jest jeszcze więcej. Wyobraź sobie, że piszesz testy jednostkowe dla klasy, nad którą pracujesz, a ta klasa używa ImageService. Oczywiście nie możesz pozwolić, aby testy jednostkowe nawiązywały połączenia sieciowe z niektórymi zasobami znajdującymi się na innym serwerze, ale ponieważ teraz używasz tej opcji ImageService, możesz łatwo pozwolić na load()zwrócenie wartości statycznej Bitmapużywanej do testów jednostkowych poprzez wdrożenie fałszywego ImageService:

public class MockImageService implements ImageService {

    private final Bitmap mMockBitmap;

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

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

Podsumowując, opakowując biblioteki stron trzecich, baza kodu staje się bardziej elastyczna w stosunku do zmian, ogólnie prostsza, łatwiejsza do testowania i zmniejszasz sprzężenie różnych komponentów w oprogramowaniu - wszystkie rzeczy, które stają się coraz ważniejsze, im dłużej utrzymujesz oprogramowanie.

Xaver Kapeller
źródło
1
Dotyczy to również niestabilnego API. Nasz kod nie zmienia się w 1000 miejscach tylko dlatego, że zmieniła się podstawowa biblioteka. Bardzo miła odpowiedź.
RubberDuck
Bardzo zwięzła i jasna odpowiedź. Zajmuję się front-endem w sieci. Ilość zmian w tym krajobrazie jest szalona. Fakt, że ludzie „myślą”, że nie będzie żadnych zmian, nie oznacza, że ​​nie będzie żadnych zmian. Widziałem wzmianki o YAGNI. Chciałbym dodać nowy akronim YDKYAGNI. Nie wiesz, że go nie potrzebujesz. Zwłaszcza w przypadku implementacji internetowych. Z reguły zawsze zawijam biblioteki, które ujawniają tylko małe API (jak select2). Większe biblioteki wpływają na twoją architekturę, a zawijanie ich oznacza, że ​​spodziewasz się, że twoja architektura się zmieni, może, ale rzadziej to zrobi.
Pa pa
Twoja odpowiedź była bardzo pomocna, a przedstawienie koncepcji na przykładzie uczyniło ją jeszcze bardziej przejrzystą.
Anil Gorthy,
24

Myślę, że owijanie bibliotek stron trzecich na wypadek, gdyby jutro pojawi się coś lepszego, jest bardzo marnotrawnym naruszeniem YAGNI. Jeśli wielokrotnie wywołujesz kod innej firmy w sposób charakterystyczny dla Twojej aplikacji, powinieneś (należy) przekierować te wywołania do klasy zawijającej, aby wyeliminować powtarzanie. W przeciwnym razie w pełni używasz interfejsu API biblioteki, a każde opakowanie wyglądałoby tak jak sama biblioteka.

Załóżmy teraz, że pojawi się nowa biblioteka z lepszą wydajnością lub czymkolwiek. W pierwszym przypadku po prostu przepisujesz opakowanie dla nowego interfejsu API. Nie ma problemu.

W drugim przypadku tworzysz opakowanie dostosowujące stary interfejs do sterowania nową biblioteką. Trochę więcej pracy, ale nie ma problemu i nie więcej pracy niż zrobiłbyś, gdybyś napisał opakowanie wcześniej.

Kevin Cline
źródło
4
Nie sądzę, aby YAGNI koniecznie miało zastosowanie w tej sytuacji. Nie chodzi o budowanie funkcjonalności na wypadek, gdybyś jej potrzebował w przyszłości. Chodzi o budowanie elastyczności w architekturze. Jeśli ta elastyczność jest niepotrzebna, wówczas tak, obowiązuje YAGNI. Jednak taka determinacja jest zwykle podejmowana w przyszłości, kiedy dokonywanie zmian prawdopodobnie będzie bolesne.
George Marian,
7
@George Marian: problem występuje w 95% przypadków, nigdy nie będziesz potrzebować elastyczności, aby zmienić. Jeśli chcesz przejść do nowej biblioteki o lepszej wydajności, wyszukiwanie / zamiana wywołań lub pisanie opakowania w razie potrzeby powinno być dość trywialne. Z drugiej strony, jeśli twoja nowa biblioteka ma różne funkcje, opakowanie staje się przeszkodą, ponieważ masz teraz dwa problemy: przeniesienie starego kodu w celu wykorzystania nowych funkcji i utrzymanie opakowania.
Lie Ryan,
3
@lotsoffreetime: Celem „dobrego projektu” jest zminimalizowanie całkowitego kosztu aplikacji w całym okresie jej użytkowania. Dodanie warstw pośrednich dla wyobrażonych przyszłych zmian jest bardzo drogim ubezpieczeniem. Nigdy nie widziałem, aby ktokolwiek zrealizował jakiekolwiek oszczędności dzięki takiemu podejściu. To po prostu tworzy banalną pracę dla programistów, których czas lepiej byłoby poświęcić na specyficzne wymagania klienta. Przez większość czasu, jeśli piszesz kod, który nie jest specyficzny dla twojego klienta, marnujesz czas i pieniądze.
kevin cline,
1
@George: jeśli te zmiany są bolesne, myślę, że to zapach procesu. W Javie tworzyłem nowe klasy o tych samych nazwach co stare klasy, ale w innym pakiecie, zmieniałem wszystkie wystąpienia starej nazwy pakietu i ponownie uruchamiałem testy automatyczne.
kevin cline,
1
@kevin To więcej pracy, a zatem niesie większe ryzyko, niż po prostu aktualizacja opakowania i uruchomienie testów.
George Marian,
9

Podstawowym powodem napisania opakowania wokół biblioteki innej firmy jest to, że możesz wymieniać bibliotekę innej firmy bez zmiany kodu, który jej używa. Nie można uniknąć sprzężenia z czymś, więc argumentuje, że lepiej jest połączyć się z napisanym przez Ciebie interfejsem API.

To, czy jest to warte wysiłku, to inna historia. Ta debata prawdopodobnie potrwa jeszcze długo.

W przypadku małych projektów, w których prawdopodobieństwo, że taka zmiana będzie konieczna, jest niewielkie, jest to prawdopodobnie niepotrzebny wysiłek. W przypadku większych projektów ta elastyczność może znacznie przewyższyć dodatkowy wysiłek związany z opakowaniem biblioteki. Trudno jednak wcześniej ustalić, czy tak jest.

Innym sposobem spojrzenia na to jest podstawowa zasada abstrakcji tego, co może się zmienić. Tak więc, jeśli biblioteka innej firmy jest dobrze ugruntowana i jest mało prawdopodobne, że zostanie zmieniona, może być nieopakowanie jej. Jeśli jednak biblioteka innej firmy jest stosunkowo nowa, istnieje większe prawdopodobieństwo, że będzie trzeba ją wymienić. To powiedziawszy, rozwój ustalonych bibliotek był porzucany wiele razy. Odpowiedź na to pytanie nie jest łatwa.

George Marian
źródło
W przypadku testów jednostkowych, w których możliwość wstrzyknięcia fałszywego interfejsu API służy zminimalizowaniu testowanej jednostki, „zmiana potencjału” nie jest czynnikiem. To powiedziawszy, to wciąż moja ulubiona odpowiedź, ponieważ jest najbliższa temu, co myślę. Co powiedziałby wujek Bob? :)
lotsoffreetime
Również małe projekty (brak zespołu, podstawowa specyfikacja itp.) Mają swoje własne zasady, w których możesz naruszać dobre praktyki, takie jak ten, i do pewnego stopnia sobie z tym radzić. Ale to jest inne pytanie ...
Lotsoffreetime
1

Oprócz tego, co powiedział już @Oded , chciałbym tylko dodać tę odpowiedź do specjalnego celu logowania.


Zawsze mam interfejs do logowania, ale nigdy nie musiałem zastępować log4fooframeworka.

Dostarczenie interfejsu i napisanie opakowania zajmuje tylko pół godziny, więc myślę, że nie tracisz zbyt wiele czasu, jeśli okaże się to niepotrzebne.

To szczególny przypadek YAGNI. Chociaż nie potrzebuję go, nie zajmuje to dużo czasu i czuję się z tym bezpieczniej. Jeśli dzień wymiany rejestratora naprawdę nadejdzie, cieszę się, że zainwestowałem pół godziny, ponieważ pozwoli mi to zaoszczędzić więcej niż dzień na wymianie połączeń w projekcie z prawdziwego świata. Nigdy nie pisałem ani nie widziałem testu jednostkowego do rejestrowania (oprócz testów samej implementacji rejestratora), więc oczekuj defektów bez opakowania.

Sokół
źródło
Nie oczekuję zmiany log4foo, ale jest on powszechnie znany i służy jako przykład. Interesujące jest również to, jak dotychczas obie odpowiedzi się uzupełniają - „nie zawsze zawijaj”; „zawiń na wszelki wypadek”.
LotOffreetime
@ Falcon: czy pakujesz wszystko? ORM, interfejs dziennika, podstawowe klasy językowe? W końcu nigdy nie wiadomo, kiedy może być potrzebna lepsza HashMap.
kevin cline
1

Dokładnie rozwiązuję ten problem w projekcie, nad którym obecnie pracuję. Ale w moim przypadku biblioteka służy do grafiki i dlatego jestem w stanie ograniczyć jej użycie do niewielkiej liczby klas, które zajmują się grafiką, w przeciwieństwie do rozsypywania jej w całym projekcie. Dlatego w razie potrzeby można łatwo zmieniać interfejsy API; w przypadku rejestratora sprawa staje się znacznie bardziej skomplikowana.

Dlatego powiedziałbym, że decyzja ma wiele wspólnego z tym, co dokładnie robi biblioteka innej firmy i ile bólu wiązałoby się z jej zmianą. Jeśli zmiana wszystkich wywołań interfejsu API byłaby łatwa, niezależnie od tego, prawdopodobnie nie warto tego robić. Jeśli jednak późniejsza zmiana biblioteki byłaby naprawdę trudna, prawdopodobnie teraz ją zapakuję.


Poza tym inne odpowiedzi bardzo dobrze ujęły główne pytanie, więc chcę skupić się na ostatnim dodatku, na wstrzykiwaniu zależności i próbnych obiektach. Zależy to oczywiście od tego, jak dokładnie działa środowisko rejestrowania, ale w większości przypadków nie wymagałoby to opakowania (chociaż prawdopodobnie skorzysta z niego). Po prostu ułóż interfejs API dla fałszywego obiektu dokładnie tak samo, jak biblioteki innej firmy, a następnie możesz łatwo zamienić sztuczny obiekt do testowania.

Głównym czynnikiem tutaj jest to, czy biblioteka innej firmy jest nawet implementowana poprzez wstrzykiwanie zależności (lub lokalizator usług lub jakiś taki luźno powiązany wzór). Jeśli dostęp do funkcji biblioteki uzyskuje się za pomocą metod singletonowych lub statycznych lub w inny sposób, należy to zawinąć w obiekt, z którym można pracować przy wstrzykiwaniu zależności.

jhocking
źródło
1

Jestem zdecydowanie w obozie pakowania i nie jestem w stanie zastąpić biblioteki strony trzeciej największym priorytetem (choć jest to premia). Moje główne uzasadnienie sprzyjające pakowaniu jest proste

Biblioteki stron trzecich nie są zaprojektowane dla naszych konkretnych potrzeb.

I objawia się to zwykle w postaci mnóstwa duplikacji kodu, jak na przykład programiści piszący 8 wierszy kodu tylko w celu stworzenia QButtoni nadania mu stylu, tak jak powinien wyglądać aplikacja, tylko dla projektanta, który chce nie tylko wyglądu ale także funkcjonalność przycisków, które można całkowicie zmienić w całym oprogramowaniu, co w końcu wymaga cofnięcia i przepisania tysięcy wierszy kodu, lub stwierdzenia, że ​​modernizacja potoku renderowania wymaga epickiego przepisania, ponieważ podstawa kodu obsypana ad-hoc niskiego poziomu naprawiona- potokuj kod OpenGL w dowolnym miejscu zamiast scentralizować projekt mechanizmu renderującego w czasie rzeczywistym i pozostawić użycie OGL wyłącznie do jego implementacji.

Te projekty nie są dostosowane do naszych konkretnych potrzeb projektowych. Oferują one ogromny nadzór nad tym, co jest rzeczywiście potrzebne (a to, co nie jest częścią projektu, jest tak ważne, jeśli nie większe niż to, co jest), a ich interfejsy nie są zaprojektowane tak, aby konkretnie obsługiwać nasze potrzeby w „wysokim” think = one request ”sposób, który pozbawia nas wszelkiej centralnej kontroli projektu, jeśli wykorzystamy je bezpośrednio. Jeśli programiści skończą pisać dużo kodu niższego poziomu, niż powinien być zobowiązany do wyrażenia tego, czego potrzebują, mogą czasem sami go zapakować w sposób ad-hoc, co sprawia, że ​​kończy się to dziesiątkami pochopnie napisanych i nieuprzejmie- zaprojektowane i udokumentowane opakowania zamiast jednego dobrze zaprojektowanego i dobrze udokumentowanego.

Oczywiście zastosowałbym wyraźne wyjątki od bibliotek, w których opakowania są prawie jeden do jednego tłumaczeniem tego, co oferują interfejsy API stron trzecich. W takim przypadku może nie być poszukiwanego projektu wyższego poziomu, który bardziej bezpośrednio wyraża wymagania biznesowe i projektowe (może tak być w przypadku czegoś bardziej przypominającego bibliotekę „narzędziową”). Ale jeśli istnieje o wiele bardziej dostosowany projekt, który bardziej bezpośrednio wyraża nasze potrzeby, to jestem zdecydowanie w obozie pakowania, podobnie jak zdecydowanie opowiadam się za użyciem funkcji wyższego poziomu i ponownego użycia jej zamiast wstawiania kodu asemblera wszędzie wokoło.

Dziwne, że starłem się z programistami w taki sposób, że wydawali się tak nieufni i tak pesymistyczni co do naszej zdolności zaprojektowania, powiedzmy, funkcji tworzenia przycisku i zwrócenia go, że wolą napisać 8 wierszy kodu niższego poziomu skoncentrowanego na mikroskopii szczegóły dotyczące tworzenia przycisków (które w przyszłości musiały się wielokrotnie zmieniać) nad projektowaniem i używaniem tej funkcji. Nawet nie widzę celu, w którym staramy się projektować cokolwiek, jeśli nie możemy ufać sobie, że zaprojektujemy tego rodzaju opakowania w rozsądny sposób.

Innymi słowy, postrzegam biblioteki stron trzecich jako potencjalne sposoby na zaoszczędzenie ogromnego czasu podczas wdrażania, a nie jako substytuty projektowania systemów.

Dragon Energy
źródło
0

Mój pomysł na biblioteki stron trzecich:

Nie było pewne dyskusje niedawno w społeczności iOS na temat zalet i wad (OK, głównie cons) korzystania z zależnościami osób trzecich. Wiele argumentów, które widziałem, było dość ogólnych - pogrupowanie wszystkich bibliotek stron trzecich w jeden koszyk. Jednak jak w przypadku większości rzeczy, nie jest to takie proste. Spróbujmy więc skupić się na jednym przypadku

Czy powinniśmy unikać korzystania z bibliotek interfejsu użytkownika innych firm?

Powody, dla których warto rozważyć biblioteki stron trzecich:

Wydaje się, że istnieją dwa główne powody, dla których programiści rozważają użycie biblioteki innej firmy:

  1. Brak umiejętności lub wiedzy. Załóżmy, że pracujesz nad aplikacją do udostępniania zdjęć. Nie zaczynasz od rzucania własnym krypto.
  2. Brak czasu lub zainteresowania, aby coś zbudować. O ile nie masz nieograniczonej ilości czasu (której nie ma jeszcze żaden człowiek), musisz traktować priorytetowo.

Większość bibliotek interfejsu użytkownika ( nie wszystkie! ) Ma tendencję do zaliczania się do drugiej kategorii. To nie jest nauka o rakietach, ale zbudowanie jej wymaga czasu.

Jeśli jest to podstawowa funkcja biznesowa - zrób to sam, bez względu na to, co to jest.

Istnieją prawie dwa rodzaje kontroli / widoków:

  1. Generic, co pozwala na ich zastosowanie w wielu różnych kontekstach nie nawet o ich twórców, na przykład UICollectionViewz UIKit.
  2. Specjalny, zaprojektowany do pojedynczego przypadku użycia, np UIPickerView. Większość bibliotek stron trzecich zwykle zalicza się do drugiej kategorii. Co więcej, często są one pobierane z istniejącej bazy kodu, dla której zostały zoptymalizowane.

Nieznane wczesne założenia

Wielu programistów dokonuje przeglądu kodu wewnętrznego, ale może przyjmować jakość kodu źródłowego innej firmy za pewnik. Warto poświęcić trochę czasu na przeglądanie kodu biblioteki. Możesz być zaskoczony, widząc czerwone flagi, np. Zamiatanie używane tam, gdzie nie jest potrzebne.

Często nauka tego pomysłu jest bardziej korzystna niż samo uzyskanie wynikowego kodu.

Nie możesz tego ukryć

Ze względu na sposób zaprojektowania UIKit najprawdopodobniej nie będziesz w stanie ukryć biblioteki interfejsu użytkownika innej firmy, np. Za adapterem. Biblioteka przeplata się z twoim kodem interfejsu użytkownika, stając się de facto twoim projektem.

Koszt w przyszłości

UIKit zmienia się z każdą wersją iOS. Wszystko się zepsuje. Twoje uzależnienie od osób trzecich nie będzie tak bezobsługowe, jak możesz się spodziewać.

Wniosek:

Z mojego osobistego doświadczenia wynika, że ​​większość zastosowań kodu interfejsu użytkownika innej firmy sprowadza się do wymiany mniejszej elastyczności na pewien czas.

Używamy gotowego kodu, aby szybciej wysłać naszą aktualną wersję. Wcześniej czy później przekroczyliśmy granice biblioteki i stanęliśmy przed trudną decyzją: co dalej?

Mukesh
źródło
0

Bezpośrednie korzystanie z biblioteki jest bardziej przyjazne dla zespołu programistów. Kiedy dołącza nowy programista, może mieć pełne doświadczenie ze wszystkimi używanymi frameworkami, ale nie będzie w stanie wnieść produktywnego wkładu przed nauczeniem się własnego API. Gdy młodszy programista próbuje rozwijać się w grupie, będzie zmuszony nauczyć się konkretnego interfejsu API, którego nie ma nigdzie indziej, zamiast nabyć bardziej przydatnych ogólnych kompetencji. Jeśli ktoś zna przydatne funkcje lub możliwości oryginalnego API, może nie być w stanie sięgnąć ponad warstwę napisaną przez kogoś, kto nie był ich świadomy. Jeśli ktoś dostanie zadanie programowania podczas szukania pracy, może nie być w stanie zademonstrować podstawowych rzeczy, z których korzystał wiele razy, tylko dlatego, że przez cały ten czas uzyskiwał dostęp do potrzebnej funkcjonalności za pośrednictwem twojego opakowania.

Myślę, że te problemy mogą być ważniejsze niż raczej odległa możliwość późniejszego korzystania z zupełnie innej biblioteki. Jedynym przypadkiem, w którym użyłbym opakowania, jest to, że migracja do innej implementacji jest zdecydowanie zaplanowana lub opakowany interfejs API nie jest wystarczająco zamrożony i ciągle się zmienia.

h22
źródło