Verwenden Sie Bibliotheken von Drittanbietern - verwenden Sie immer einen Wrapper?


78

Die meisten Projekte, an denen ich beteiligt bin, verwenden mehrere Open-Source-Komponenten. Ist es grundsätzlich eine gute Idee, immer zu vermeiden, dass alle Komponenten des Codes an die Bibliotheken von Drittanbietern gebunden werden, und stattdessen einen Kapselungswrapper zu verwenden, um den Schmerz der Veränderung zu vermeiden?

Zum Beispiel verwenden die meisten unserer PHP-Projekte log4php direkt als Logging-Framework, dh sie instanziieren über \ Logger :: getLogger (), sie verwenden -> info () oder -> warn () -Methoden usw. In Zukunft Möglicherweise wird jedoch ein hypothetisches Protokollierungsframework angezeigt, das in gewisser Weise besser ist. So wie es aussieht, müssten alle Projekte, die eng mit den Signaturen der log4php-Methode gekoppelt sind, an Dutzenden Stellen geändert werden, um den neuen Signaturen zu entsprechen. Dies hätte natürlich weitreichende Auswirkungen auf die Codebasis, und jede Änderung ist ein potenzielles Problem.

Um neue Codebasen aus dieser Art von Szenario zukunftssicher zu machen, wird häufig eine Wrapper-Klasse in Betracht gezogen (und manchmal implementiert), um die Protokollierungsfunktionalität zu kapseln und die zukünftige Funktionsweise der Protokollierung mit minimalen Änderungen zu vereinfachen, obwohl dies nicht kinderleicht ist ; Der Code ruft den Wrapper auf, der Wrapper leitet den Aufruf an das Protokollierungsframework du jour weiter .

In Anbetracht der Tatsache, dass es kompliziertere Beispiele für andere Bibliotheken gibt, bin ich überentwickelt oder ist dies in den meisten Fällen eine kluge Vorsichtsmaßnahme?

BEARBEITEN: Weitere Überlegungen - für die Verwendung von Abhängigkeitsinjektion und Test-Double müssen die meisten APIs sowieso abstrahiert werden ("Ich möchte überprüfen, ob mein Code ausgeführt wird und seinen Status aktualisiert, aber keinen Protokollkommentar schreiben / auf eine echte Datenbank zugreifen"). Ist das nicht eine Entscheidung?


3
log4XYZ ist solch ein starkes Markenzeichen. Die API ändert sich nicht früher als zu dem Zeitpunkt, an dem die API für eine verknüpfte Liste geändert wird. Beides ist mittlerweile ein längst gelöstes Problem.
Job

1
Genaues Duplikat dieser SO-Frage: stackoverflow.com/questions/1916030/…
Michael Borgwardt

1
Wenn Sie es nur intern verwenden, ist es nur ein Kompromiss zwischen bekannter Arbeit jetzt und möglicher Arbeit später, ob Sie abschließen oder nicht. Ein Urteilsspruch. Aber etwas, worüber andere Responder offenbar nicht gesprochen haben, ist, ob es sich um eine API- Abhängigkeit oder eine Implementierungsabhängigkeit handelt . Mit anderen Worten, leiten Sie Klassen von dieser Drittanbieter-API über Ihre eigene öffentliche API ab und machen sie für Benutzer verfügbar? In diesem Fall ist es keine leichte Aufgabe mehr, in eine andere Bibliothek zu wechseln. Das Problem ist, dass dies jetzt nicht mehr möglich ist, ohne die eigene API zu beschädigen. Das ist sehr schlecht!
Elias Vasylenko

1
Zur weiteren Bezugnahme: Dieses Muster wird Zwiebelarchitektur genannt, bei der die externe Infrastruktur (Sie nennen es externe Bibliothek) hinter einer Schnittstelle verborgen ist
k3b

Antworten:


42

Wenn Sie nur eine kleine Teilmenge der API eines Drittanbieters verwenden, ist es sinnvoll, einen Wrapper zu schreiben. Dies hilft bei der Kapselung und beim Verbergen von Informationen und stellt sicher, dass Sie Ihrem eigenen Code keine möglicherweise große API aussetzen. Dies kann auch dazu beitragen, sicherzustellen, dass alle Funktionen, die Sie nicht verwenden möchten, "ausgeblendet" sind.

Ein weiterer guter Grund für einen Wrapper ist, wenn Sie damit rechnen , die Bibliothek eines Drittanbieters zu ändern. Wenn dies eine Infrastruktur ist, von der Sie wissen, dass Sie sie nicht ändern werden, schreiben Sie keinen Wrapper dafür.


Gute Punkte, aber uns wird beigebracht, dass eng gekoppelter Code aus vielen bekannten Gründen schlecht ist (schwieriger zu testen, schwieriger zu überarbeiten usw.). Ein alternativer Wortlaut der Frage lautet "Wenn die Kopplung schlecht ist, warum ist es in Ordnung, eine Kopplung mit einer API vorzunehmen?".
vieleFreizeit

7
@lotsoffreetime Sie können eine Kopplung mit einer API nicht vermeiden. Daher ist es besser, eine Kopplung mit Ihrer eigenen API vorzunehmen. Auf diese Weise können Sie die Bibliothek ändern und müssen im Allgemeinen die vom Wrapper bereitgestellte API nicht ändern.
George Marian

@ george-marian Wenn ich die Verwendung einer bestimmten API nicht vermeiden kann, kann ich die Berührungspunkte auf jeden Fall minimieren. Die Frage ist, sollte ich versuchen, dies die ganze Zeit zu tun, oder ist das übertrieben?
vieleFreizeit

2
@lotsoffreetime Das ist eine schwer zu beantwortende Frage. Ich habe meine Antwort zu diesem Zweck erweitert. (Grundsätzlich liegt es an vielen Wenns.)
George Marian

2
@lotsoffreetime: Wenn Sie viel Freizeit haben, können Sie beides tun. Ich würde jedoch davon abraten, einen API-Wrapper zu schreiben, außer unter dieser Bedingung: 1) Die ursprüngliche API ist sehr niedrig, sodass Sie eine API höherer Ebene schreiben, die besser zu Ihrem spezifischen Projektbedarf passt, oder 2) Sie haben einen Plan in der Um in naher Zukunft die Bibliothek zu wechseln, verwenden Sie die aktuelle Bibliothek nur als Sprungbrett, während Sie nach einer besseren suchen.
Lie Ryan

28

Wie würden Sie den Wrapper schreiben, ohne zu wissen, welche großartigen neuen Funktionen dieser angeblich in Zukunft verbesserte Logger haben wird? Die logischste Wahl ist, dass Ihr Wrapper eine Art Logger-Klasse instanziiert und über Methoden wie ->info()oder verfügt ->warn(). Mit anderen Worten, im Wesentlichen identisch mit Ihrer aktuellen API.

Anstatt zukunftssicheren Code, den ich möglicherweise nie ändern muss oder der ohnehin ein unvermeidbares Umschreiben erfordert, ziehe ich es vor, Code "past-proof" zu machen. Das heißt, bei den seltenen Gelegenheiten , bei denen ich deutlich eine Komponente ändern, das ist , wenn ich einen Wrapper schreiben , um sie mit früherem Code kompatibel zu machen. Jeder neue Code verwendet jedoch die neue API, und ich überarbeite den alten Code, um ihn zu verwenden, wenn ich ohnehin eine Änderung in derselben Datei vornehme oder wenn es der Zeitplan zulässt. Nach ein paar Monaten kann ich den Wrapper entfernen, und die Änderung war schrittweise und robust.

Anders ausgedrückt: Wrapper sind nur dann sinnvoll, wenn Sie bereits alle APIs kennen, die Sie zum Wrappen benötigen. Gute Beispiele sind, wenn Ihre Anwendung derzeit viele verschiedene Datenbanktreiber, Betriebssysteme oder PHP-Versionen unterstützen muss.


"... Wrapper sind nur dann sinnvoll, wenn Sie bereits alle APIs kennen, die Sie zum Wrappen benötigen." Dies wäre der Fall, wenn ich die API im Wrapper abgleichen würde. Vielleicht sollte ich den Begriff "Kapselung" stärker verwenden als Wrapper. Ich würde diese API-Aufrufe abstrahieren, um "diesen Text irgendwie zu protokollieren" anstatt "foo :: log () mit diesem Parameter aufzurufen".
vieleFreizeit

"Wie würden Sie den Wrapper schreiben, ohne zu wissen, welche großartigen neuen Funktionen dieser angeblich in Zukunft verbesserte Logger haben wird?" @ kevin-cline unten erwähnt einen zukünftigen Logger mit besserer Leistung anstatt einer neueren Funktion. In diesem Fall muss keine neue API gewickelt werden, nur eine andere Factory-Methode.
vieleFreizeit

27

Indem Sie eine Bibliothek eines Drittanbieters einschließen, fügen Sie eine zusätzliche Abstraktionsebene hinzu. Dies hat einige Vorteile:

  • Ihre Codebasis wird flexibler für Änderungen

    Wenn Sie die Bibliothek jemals durch eine andere ersetzen müssen, müssen Sie nur Ihre Implementierung in Ihrem Wrapper ändern - an einem Ort . Sie können die Implementierung des Wrappers ändern und müssen an nichts anderem etwas ändern, mit anderen Worten, Sie haben ein lose gekoppeltes System. Andernfalls müssten Sie Ihre gesamte Codebasis durchgehen und überall Änderungen vornehmen - was offensichtlich nicht das ist, was Sie wollen.

  • Sie können die API des Wrappers unabhängig von der API der Bibliothek definieren

    Verschiedene Bibliotheken können sehr unterschiedliche APIs haben, und gleichzeitig ist keine davon genau das, was Sie benötigen. Was ist, wenn in einer Bibliothek bei jedem Aufruf ein Token übergeben werden muss? Sie können das Token in Ihrer App überall dort weitergeben, wo Sie die Bibliothek verwenden möchten, oder Sie können es an einem zentraleren Ort speichern, aber auf jeden Fall benötigen Sie das Token. Mit Ihrer Wrapper-Klasse ist das wieder ganz einfach: Sie können das Token einfach in Ihrer Wrapper-Klasse belassen, es niemals einer Komponente in Ihrer App aussetzen und die Notwendigkeit dafür vollständig ausschließen. Ein großer Vorteil, wenn Sie jemals eine Bibliothek verwendet haben, die kein gutes API-Design hervorhebt.

  • Unit-Tests sind viel einfacher

    Unit-Tests sollten nur eines testen. Wenn Sie eine Klasse einem Komponententest unterziehen möchten, müssen Sie sich über ihre Abhängigkeiten lustig machen. Dies wird noch wichtiger, wenn diese Klasse Netzwerkanrufe durchführt oder auf eine andere Ressource außerhalb Ihrer Software zugreift. Indem Sie die Bibliothek eines Drittanbieters einbinden, können Sie diese Aufrufe einfach verspotten und Testdaten oder was auch immer für diesen Komponententest erforderlich ist, zurückgeben. Wenn Sie nicht über eine solche Abstraktionsebene verfügen, wird dies sehr viel schwieriger - und die meiste Zeit führt dies zu viel hässlichem Code.

  • Sie erstellen ein lose gekoppeltes System

    Änderungen an Ihrem Wrapper haben keine Auswirkungen auf andere Teile Ihrer Software - zumindest solange Sie das Verhalten Ihres Wrappers nicht ändern. Durch die Einführung einer Abstraktionsebene wie dieser Wrapper können Sie die Aufrufe der Bibliothek vereinfachen und die Abhängigkeit Ihrer App von dieser Bibliothek fast vollständig entfernen. Ihre Software verwendet nur den Wrapper und es macht keinen Unterschied, wie der Wrapper implementiert ist oder was er tut.


Praktisches Beispiel

Lass uns ehrlich sein. Die Leute können stundenlang über die Vor- und Nachteile von so etwas streiten - deshalb zeige ich Ihnen lieber nur ein Beispiel.

Angenommen, Sie haben eine Art Android-App und müssen Bilder herunterladen. Es gibt eine Reihe von Bibliotheken, die das Laden und Zwischenspeichern von Bildern zum Kinderspiel machen, zum Beispiel Picasso oder den Universal Image Loader .

Wir können jetzt eine Schnittstelle definieren, die wir verwenden, um die Bibliothek zu verpacken, die wir letztendlich verwenden:

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

Dies ist die Benutzeroberfläche, die wir jetzt in der gesamten App verwenden können, wenn wir ein Bild laden müssen. Wir können eine Implementierung dieser Schnittstelle erstellen und mithilfe der Abhängigkeitsinjektion eine Instanz dieser Implementierung überall dort einfügen, wo wir die verwenden ImageService.

Nehmen wir an, wir entscheiden uns zunächst für Picasso. Wir können jetzt eine Implementierung schreiben, für ImageServicedie Picasso intern verwendet wird:

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

Ziemlich direkt, wenn du mich fragst. Wrapper um Bibliotheken müssen nicht kompliziert sein, um nützlich zu sein. Die Schnittstelle und die Implementierung haben weniger als 25 kombinierte Codezeilen, so dass es kaum Aufwand gab, dies zu erstellen, aber wir gewinnen bereits etwas, indem wir dies tun. Siehe das ContextFeld in der Implementierung? Das Abhängigkeitsinjektionsframework Ihrer Wahl wird sich bereits darum kümmern, diese Abhängigkeit zu injizieren, bevor wir unsere verwenden ImageService. Ihre App muss sich jetzt nicht mehr darum kümmern, wie die Bilder heruntergeladen werden und welche Abhängigkeiten diese Bibliothek möglicherweise hat. Alles, was Ihre App sieht, ist ein ImageServiceund wenn sie ein Bild benötigt, ruft sie es load()mit einer URL auf - einfach und unkompliziert.

Der eigentliche Vorteil ergibt sich jedoch, wenn wir anfangen, Dinge zu ändern. Stellen Sie sich vor, wir müssen Picasso jetzt durch Universal Image Loader ersetzen, da Picasso einige Funktionen, die wir momentan unbedingt benötigen, nicht unterstützt. Müssen wir jetzt unsere Codebasis durchkämmen und mühsam alle Aufrufe von Picasso ersetzen und uns dann mit Dutzenden von Kompilierungsfehlern befassen, weil wir einige Picasso-Aufrufe vergessen haben? Nein. Wir müssen lediglich eine neue Implementierung von erstellen ImageServiceund unserem Abhängigkeitsinjektionsframework mitteilen, dass diese Implementierung von nun an verwendet werden soll:

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

Wie Sie sehen, kann die Implementierung sehr unterschiedlich sein, aber es spielt keine Rolle. Wir mussten an keiner anderen Stelle in unserer App eine einzige Codezeile ändern. Wir verwenden eine völlig andere Bibliothek, die möglicherweise völlig andere Funktionen aufweist oder sehr unterschiedlich verwendet wird, aber unsere App kümmert sich einfach nicht darum. Wie zuvor sieht der Rest unserer App nur das ImageServiceInterface mit seiner load()Methode und diese Methode ist jedoch nicht mehr von Bedeutung.

Zumindest für mich klingt das alles schon ganz nett, aber warte! Es gibt noch mehr. Stellen Sie sich vor, Sie schreiben Komponententests für eine Klasse, an der Sie arbeiten, und diese Klasse verwendet die ImageService. Natürlich können Sie nicht zulassen, dass Ihre Komponententests Netzwerkanrufe an eine Ressource auf einem anderen Server senden, aber da Sie jetzt die verwenden ImageService, können Sie load()eine Bitmapfür die Komponententests verwendete statische Aufladung einfach zurückgeben lassen , indem Sie eine verspottete Anweisung implementieren ImageService:

public class MockImageService implements ImageService {

    private final Bitmap mMockBitmap;

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

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

Zusammenfassend lässt sich festhalten, dass Ihre Codebasis durch das Einschließen von Bibliotheken von Drittanbietern flexibler für Änderungen wird, insgesamt einfacher und einfacher zu testen ist und Sie die Kopplung verschiedener Komponenten in Ihrer Software verringern - alles Dinge, die immer wichtiger werden, je länger Sie eine Software warten.


1
Dies gilt auch für eine instabile API. Unser Code ändert sich nicht an 1000 Stellen, nur weil sich die zugrunde liegende Bibliothek geändert hat. Sehr nette Antwort.
RubberDuck

Sehr kurze und klare Antwort. Ich mache Front-End-Arbeiten im Web. Das Ausmaß der Veränderungen in dieser Landschaft ist verrückt. Die Tatsache, dass die Leute glauben, dass es keine Änderungen geben wird, bedeutet nicht, dass es keine Änderungen geben wird. Ich habe die Erwähnungen von YAGNI gesehen. Ich möchte ein neues Akronym hinzufügen, YDKYAGNI. Sie wissen nicht, dass Sie es nicht brauchen werden. Besonders bei webbezogenen Implementierungen. In der Regel verpacke ich immer Bibliotheken, die nur eine kleine API (wie select2) verfügbar machen. Größere Bibliotheken wirken sich auf Ihre Architektur aus, und das Umschließen bedeutet, dass Sie davon ausgehen, dass sich Ihre Architektur ändert. Dies ist jedoch weniger wahrscheinlich.
Byebye

Ihre Antwort war super hilfreich und das Präsentieren des Konzepts mit einem Beispiel machte das Konzept noch klarer.
Anil Gorthy

24

Ich denke, dass es eine sehr verschwenderische Verletzung von YAGNI ist, heute Bibliotheken von Drittanbietern einzupacken, falls morgen etwas Besseres herauskommt. Wenn Sie wiederholt Code von Drittanbietern in einer für Ihre Anwendung spezifischen Weise aufrufen, werden Sie diese Aufrufe in eine Wrapping-Klasse umstrukturieren (sollten), um die Wiederholung zu vermeiden. Andernfalls verwenden Sie die Bibliotheks-API vollständig und jeder Wrapper würde genau wie die Bibliothek selbst aussehen.

Angenommen, jetzt wird eine neue Bibliothek mit überragender Leistung oder was auch immer angezeigt. Im ersten Fall schreiben Sie einfach den Wrapper für die neue API neu. Kein Problem.

Im zweiten Fall erstellen Sie einen Wrapper, der die alte Schnittstelle an das Laufwerk der neuen Bibliothek anpasst. Ein bisschen mehr Arbeit, aber kein Problem, und nicht mehr Arbeit, als Sie getan hätten, wenn Sie den Wrapper früher geschrieben hätten.


4
Ich denke nicht, dass YAGNI in dieser Situation unbedingt zutrifft. Es geht nicht darum, Funktionen einzubauen, falls Sie sie in Zukunft benötigen. Es geht darum, Flexibilität in die Architektur einzubauen. Wenn diese Flexibilität nicht erforderlich ist, gilt YAGNI. Diese Entscheidung wird jedoch in der Regel irgendwann in der Zukunft getroffen, wenn die Änderung vorgenommen wird, was wahrscheinlich schmerzhaft sein wird.
George Marian

7
@ George Marian: Das Problem ist 95% der Zeit, Sie werden nie die Flexibilität brauchen, um sich zu ändern. Wenn Sie zu einer zukünftigen neuen Bibliothek mit überlegener Leistung wechseln müssen, sollte es ziemlich einfach sein, Anrufe zu suchen / ersetzen oder einen Wrapper zu schreiben, wenn Sie ihn benötigen. Wenn Ihre neue Bibliothek jedoch andere Funktionen bietet, wird der Wrapper zu einem Hindernis, da Sie jetzt zwei Probleme haben: den alten Code zu portieren, um die neuen Funktionen auszunutzen, und den Wrapper zu warten.
Lie Ryan

3
@lotsoffreetime: Der Zweck von "gutem Design" besteht darin, die Gesamtkosten der Anwendung über ihre gesamte Lebensdauer zu minimieren. Das Hinzufügen von Indirektionsebenen für vorgestellte zukünftige Änderungen ist eine sehr teure Versicherung. Ich habe noch nie jemanden gesehen, der mit diesem Ansatz Einsparungen erzielt hat. Für Programmierer, deren Zeit viel besser für kundenspezifische Anforderungen aufgewendet werden würde, ist dies nur eine triviale Arbeit. In den meisten Fällen verschwenden Sie Zeit und Geld, wenn Sie Code schreiben, der nicht für Ihren Kunden spezifisch ist.
Kevin Cline

1
@ George: Wenn diese Veränderungen schmerzhaft sind, denke ich, dass das ein Prozessgeruch ist. In Java würde ich neue Klassen mit denselben Namen wie die alten Klassen erstellen, aber in einem anderen Paket alle Vorkommen des alten Paketnamens ändern und die automatisierten Tests erneut ausführen.
Kevin Cline

1
@kevin Das ist mehr Arbeit und birgt somit mehr Risiken als nur das Aktualisieren des Wrappers und das Ausführen der Tests.
George Marian

9

Der Hauptgrund für das Schreiben eines Wrappers um eine Drittanbieter-Bibliothek besteht darin, dass Sie diese Drittanbieter-Bibliothek austauschen können, ohne den Code zu ändern, der sie verwendet. Sie können es nicht vermeiden, an etwas zu koppeln. Daher wird argumentiert, dass es besser ist, eine von Ihnen geschriebene API zu koppeln.

Ob sich die Mühe lohnt, ist eine andere Geschichte. Diese Debatte wird wahrscheinlich noch lange dauern.

Bei kleinen Projekten, bei denen die Wahrscheinlichkeit, dass eine solche Änderung erforderlich ist, gering ist, ist dies wahrscheinlich unnötiger Aufwand. Bei größeren Projekten überwiegt diese Flexibilität möglicherweise den zusätzlichen Aufwand für das Einpacken der Bibliothek. Es ist jedoch schwierig, vorher zu wissen, ob dies der Fall ist.

Eine andere Sichtweise ist das Grundprinzip der Abstraktion dessen, was sich wahrscheinlich ändern wird. Wenn die Bibliothek eines Drittanbieters gut etabliert ist und wahrscheinlich nicht geändert wird, ist es möglicherweise in Ordnung, sie nicht zu verpacken. Wenn die Bibliothek eines Drittanbieters jedoch relativ neu ist, besteht eine größere Wahrscheinlichkeit, dass sie ersetzt werden muss. Die Entwicklung etablierter Bibliotheken wurde jedoch schon oft aufgegeben. Diese Frage ist also nicht einfach zu beantworten.


Im Falle von Unit-Tests, bei denen die Möglichkeit besteht, einen Schein der API zu injizieren, um die zu testende Unit zu minimieren, spielt "Änderungspotential" keine Rolle. Trotzdem ist dies immer noch meine Lieblingsantwort, da sie meiner Meinung nach am nächsten kommt. Was würde Onkel Bob sagen? :)
vieleFreizeit

Außerdem haben kleine Projekte (kein Team, Grundausstattung usw.) ihre eigenen Regeln, in denen Sie gegen gute Praktiken wie diese verstoßen und bis zu einem gewissen Grad damit durchkommen können. Aber das ist eine andere Frage ...
oftmals

1

Zusätzlich zu dem, was @Oded bereits gesagt hat, möchte ich diese Antwort nur für den speziellen Zweck der Protokollierung hinzufügen.


Ich habe immer eine Schnittstelle für die Protokollierung, aber ich musste noch nie ein log4fooFramework ersetzen .

Es dauert nur eine halbe Stunde, um die Schnittstelle bereitzustellen und den Wrapper zu schreiben, sodass Sie vermutlich nicht zu viel Zeit verschwenden, wenn sich herausstellt, dass dies nicht erforderlich ist.

Es ist ein Sonderfall von YAGNI. Obwohl ich es nicht brauche, dauert es nicht lange und ich fühle mich sicherer damit. Wenn der Tag des Austauschs des Loggers wirklich kommt, bin ich froh, dass ich eine halbe Stunde investiert habe, weil ich dadurch mehr als einen Tag Zeit für den Austausch von Anrufen in einem realen Projekt habe. Und ich habe noch nie einen Komponententest für die Protokollierung geschrieben oder gesehen (abgesehen von Tests für die Protokollierungsimplementierung selbst). Erwarten Sie also Fehler ohne den Wrapper.


Ich erwarte nicht, log4foo zu ändern, aber es ist allgemein bekannt und dient als Beispiel. Interessant ist auch, wie sich die beiden bisherigen Antworten ergänzen - "nicht immer einpacken"; msgstr "Nur für den Fall einwickeln".
vieleFreizeit

@ Falcon: wickelst du alles ein? ORM, Protokollschnittstelle, Kernsprachenklassen? Man kann ja nie sagen, wann eine bessere HashMap benötigt wird.
Kevin Cline

1

Ich beschäftige mich mit genau diesem Problem in einem Projekt, an dem ich gerade arbeite. In meinem Fall ist die Bibliothek jedoch für Grafiken vorgesehen, und daher kann ich ihre Verwendung auf eine kleine Anzahl von Klassen beschränken, die sich mit Grafiken befassen, anstatt sie über das gesamte Projekt zu verteilen. So ist es ziemlich einfach, APIs später zu wechseln, wenn ich muss; Im Falle eines Loggers wird die Sache viel komplizierter.

Daher würde ich sagen, dass die Entscheidung viel damit zu tun hat, was genau die Bibliothek von Drittanbietern tut und wie viel Schmerz damit verbunden wäre, sie zu ändern. Wenn es einfach wäre, alle API-Aufrufe zu ändern, lohnt es sich wahrscheinlich nicht. Wenn es jedoch sehr schwierig wäre, die Bibliothek später zu ändern, würde ich sie wahrscheinlich jetzt einpacken.


Darüber hinaus haben andere Antworten die Hauptfrage sehr gut abgedeckt, sodass ich mich nur auf die letzte Ergänzung konzentrieren möchte, die sich mit Abhängigkeitsinjektionen und Scheinobjekten befasst. Es hängt natürlich davon ab, wie genau Ihr Protokollierungsframework funktioniert, aber in den meisten Fällen ist kein Wrapper erforderlich (obwohl es wahrscheinlich von einem profitieren wird). Stellen Sie einfach die API für Ihr Mock-Objekt genau so ein wie die Bibliothek eines Drittanbieters, und Sie können das Mock-Objekt zum Testen problemlos austauschen.

Der Hauptfaktor hierbei ist, ob die Bibliothek eines Drittanbieters sogar durch Abhängigkeitsinjektion (oder einen Dienstlokalisierer oder ein solches lose gekoppeltes Muster) implementiert wird oder nicht. Wenn auf die Bibliotheksfunktionen über ein Singleton oder statische Methoden oder etwas anderes zugegriffen wird, müssen Sie dies in ein Objekt einschließen, mit dem Sie in Abhängigkeitsinjektion arbeiten können.


1

Ich bin stark im Wrapping-Camp und nicht in der Lage, die Drittanbieter-Bibliothek mit der höchsten Priorität zu ersetzen (obwohl das ein Bonus ist). Mein Hauptgrund, der das Wickeln bevorzugt, ist einfach

Bibliotheken von Drittanbietern sind nicht für unsere spezifischen Anforderungen ausgelegt.

Und dies manifestiert sich in der Regel in Form einer Schiffsladung von Code-Duplikaten, z. B. QButtonwenn Entwickler 8 Codezeilen schreiben, um einen Code so zu erstellen und zu formatieren, wie er für die Anwendung aussehen sollte, und nur, wenn der Designer nicht nur das Aussehen haben möchte aber auch die Funktionalität der Schaltflächen, die sich für die gesamte Software vollständig ändern, was dazu führt, dass Tausende von Codezeilen zurückgeschrieben und neu geschrieben werden müssen, oder dass die Modernisierung einer Rendering-Pipeline ein episches Umschreiben erfordert, da die Codebasis mit Ad-hoc-Fixes auf niedriger Ebene bestreut ist. Pipeline-OpenGL-Code wird überall verwendet, anstatt ein Echtzeit-Renderer-Design zu zentralisieren und die Verwendung von OGL für dessen Implementierung strikt zu überlassen.

Diese Designs sind nicht auf unsere spezifischen Designanforderungen zugeschnitten. Sie bieten in der Regel einen großen Teil dessen, was tatsächlich benötigt wird (und was nicht Teil eines Designs ist, ist genauso wichtig, wenn nicht mehr als das, was ist), und ihre Schnittstellen sind nicht speziell darauf ausgelegt, unsere Anforderungen auf hohem Niveau zu erfüllen thought = one request "Art und Weise, die uns jegliche zentrale Designkontrolle entzieht, wenn wir sie direkt verwenden. Wenn die Entwickler am Ende viel weniger Code schreiben, als sie benötigen, um auszudrücken, was sie brauchen, können sie ihn manchmal selbst ad-hoc verpacken, so dass Sie am Ende Dutzende von hastig geschriebenen und groben Codes haben. entworfene und dokumentierte Wrapper statt eines gut entworfenen, gut dokumentierten.

Natürlich würde ich Bibliotheken, bei denen es sich bei den Wrappern fast um Eins-zu-Eins-Übersetzungen der APIs von Drittanbietern handelt, strenge Ausnahmen zuweisen. In diesem Fall ist möglicherweise kein übergeordnetes Design zu suchen, das die geschäftlichen und gestalterischen Anforderungen direkter zum Ausdruck bringt (dies könnte der Fall sein, wenn etwas eher einer "Utility" -Bibliothek ähnelt). Aber wenn es ein viel maßgeschneiderteres Design gibt, das unsere Bedürfnisse viel direkter zum Ausdruck bringt, dann bin ich stark im Wrapping Camp, genauso wie ich es stark befürworte, eine übergeordnete Funktion zu verwenden und sie über inline Assembler-Code wiederzuverwenden überall.

Seltsamerweise habe ich mich mit Entwicklern auf eine Weise gestritten, bei der sie so misstrauisch und pessimistisch wirkten, wenn es darum ging, beispielsweise eine Funktion zum Erstellen und Zurückgeben von Schaltflächen zu entwerfen, dass sie lieber 8 Zeilen Code auf niedrigerer Ebene schreiben würden, der sich auf Mikroskopie konzentriert Details der Schaltflächenerstellung (die in Zukunft wiederholt geändert werden musste) über das Entwerfen und Verwenden dieser Funktion. Ich sehe nicht einmal den Grund, warum wir überhaupt versuchen, etwas zu entwerfen, wenn wir uns nicht darauf verlassen können, dass wir diese Art von Umhüllungen auf vernünftige Weise entwerfen.

Anders ausgedrückt, ich betrachte Bibliotheken von Drittanbietern als Möglichkeiten, um möglicherweise enorme Zeit bei der Implementierung zu sparen, und nicht als Ersatz für das Entwerfen von Systemen.


0

Meine Idee zu Bibliotheken von Drittanbietern:

In letzter Zeit gab es in der iOS-Community einige Diskussionen über Vor- und Nachteile (OK, meistens Nachteile) der Verwendung von Abhängigkeiten von Drittanbietern. Viele Argumente waren eher allgemein gehalten: Alle Bibliotheken von Drittanbietern in einem Korb zusammenfassen. Wie bei den meisten Dingen ist es jedoch nicht so einfach. Konzentrieren wir uns also auf einen einzelnen Fall

Sollten wir die Verwendung von UI-Bibliotheken von Drittanbietern vermeiden?

Gründe, Bibliotheken von Drittanbietern in Betracht zu ziehen:

Es gibt anscheinend zwei Hauptgründe, warum Entwickler die Verwendung einer Drittanbieter-Bibliothek in Betracht ziehen:

  1. Mangel an Fähigkeiten oder Kenntnissen. Angenommen, Sie arbeiten an einer App für die gemeinsame Nutzung von Fotos. Sie beginnen nicht damit, Ihre eigene Krypto zu rollen.
  2. Mangel an Zeit oder Interesse, etwas zu bauen. Sofern Sie keine unbegrenzte Zeit haben (die noch kein Mensch hat), müssen Sie Prioritäten setzen.

Die meisten UI-Bibliotheken ( nicht alle! ) Fallen in die zweite Kategorie. Dieses Zeug ist keine Raketenwissenschaft, aber es braucht Zeit, um es richtig zu bauen.

Wenn es eine Kerngeschäftsfunktion ist - machen Sie es selbst, egal was es ist.

Es gibt zwei Arten von Steuerelementen / Ansichten:

  1. Generisch, so dass Sie sie in vielen unterschiedlichen Kontexten verwenden , nicht einmal gedacht von ihren Schöpfern, zB UICollectionViewaus UIKit.
  2. Speziell für einen einzelnen Anwendungsfall entwickelt, z UIPickerView. Die meisten Bibliotheken von Drittanbietern fallen in die zweite Kategorie. Außerdem werden sie häufig aus einer vorhandenen Codebasis extrahiert, für die sie optimiert wurden.

Unbekannte frühe Annahmen

Viele Entwickler führen Codeüberprüfungen ihres internen Codes durch, halten jedoch die Qualität des Quellcodes von Drittanbietern möglicherweise für selbstverständlich. Es lohnt sich, ein bisschen Zeit damit zu verbringen, nur den Code einer Bibliothek zu durchsuchen. Es kann sein, dass Sie überrascht sind, einige rote Flaggen zu sehen, z. B. wenn das Swizzeln dort eingesetzt wird, wo es nicht benötigt wird.

Oft ist es vorteilhafter, die Idee zu erlernen, als den resultierenden Code selbst zu erhalten.

Du kannst es nicht verstecken

Aufgrund des Designs von UIKit können Sie die UI-Bibliothek eines Drittanbieters wahrscheinlich nicht ausblenden, z. B. hinter einem Adapter. Eine Bibliothek wird mit Ihrem UI-Code verflochten und wird de facto zu Ihrem Projekt.

Zukünftige Zeitkosten

UIKit ändert sich mit jeder iOS-Version. Die Dinge werden brechen. Ihre Abhängigkeit von Drittanbietern ist nicht so wartungsfrei, wie Sie es vielleicht erwarten.

Fazit :

Nach meiner persönlichen Erfahrung läuft die Verwendung von UI-Code von Drittanbietern meist darauf hinaus, die geringere Flexibilität gegen einen gewissen Zeitgewinn auszutauschen.

Wir verwenden vorgefertigten Code, um unsere aktuelle Version schneller zu versenden. Früher oder später stoßen wir jedoch an die Grenzen der Bibliothek und stehen vor einer schwierigen Entscheidung: Was tun?


0

Die direkte Nutzung der Bibliothek ist für das Entwicklerteam angenehmer. Wenn sich ein neuer Entwickler anmeldet, ist er möglicherweise mit allen verwendeten Frameworks vollständig vertraut, kann jedoch keinen produktiven Beitrag leisten, bevor er die von ihm selbst entwickelte API erlernt hat. Wenn ein jüngerer Entwickler versucht, in Ihrer Gruppe voranzukommen, muss er lernen, dass Ihre spezifische API nirgendwo anders vorhanden ist, anstatt sich eine nützlichere allgemeine Kompetenz anzueignen. Wenn jemand nützliche Funktionen oder Möglichkeiten der ursprünglichen API kennt, ist er möglicherweise nicht in der Lage, über die Ebene zu gelangen, die von jemandem geschrieben wurde, der sich dessen nicht bewusst war. Wenn jemand auf der Suche nach einem Job eine Programmieraufgabe erhält, kann er möglicherweise nicht die grundlegenden Dinge demonstrieren, die er oft verwendet hat, nur weil er all diese Male über Ihren Wrapper auf die erforderlichen Funktionen zugegriffen hat.

Ich denke, dass diese Probleme wichtiger sind als die Möglichkeit, später eine komplett andere Bibliothek zu verwenden. Der einzige Fall, in dem ich einen Wrapper verwenden würde, ist, wenn die Migration auf eine andere Implementierung definitiv geplant ist oder die umschlossene API nicht ausreichend eingefroren ist und sich ständig ändert.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.