Utilisation de bibliothèques tierces - utilisez-vous toujours un wrapper?

78

La plupart des projets auxquels je participe utilisent plusieurs composants open source. En règle générale, est-ce une bonne idée de toujours éviter de lier tous les composants du code aux bibliothèques tierces et de passer plutôt par un wrapper encapsulant pour éviter les inconvénients du changement?

Par exemple, la plupart de nos projets PHP utilisent directement log4php en tant que framework de journalisation, c'est-à-dire qu'ils instancient via \ Logger :: getLogger (), qu'ils utilisent les méthodes -> info () ou -> warn (), etc. Cependant, un cadre de journalisation hypothétique peut apparaître, ce qui est préférable d'une certaine manière. Dans l'état actuel des choses, tous les projets étroitement liés à la méthode log4php devraient être modifiés, à des dizaines d'endroits, pour s'adapter aux nouvelles signatures. Ceci aurait évidemment un impact important sur la base de code et tout changement est un problème potentiel.

Afin de garantir la pérennité des nouvelles bases de code de ce type de scénario, je considère souvent (et implémente parfois) une classe wrapper pour encapsuler la fonctionnalité de journalisation et permettre plus facilement, bien que non infaillible, de modifier le fonctionnement futur de la journalisation. ; le code appelle le wrapper, le wrapper passe l'appel au framework de journalisation du jour .

Sachant qu'il y a des exemples plus compliqués avec d'autres bibliothèques, est-ce que je fais trop d'ingénierie ou est-ce une sage précaution dans la plupart des cas?

EDIT: Plus de considérations - l’utilisation de l’injection de dépendance et des doublons de test nécessite pratiquement de résumer la plupart des API ("Je veux vérifier que mon code s’exécute et met à jour son état, mais pas écrire un commentaire de journal / accéder à une vraie base de données"). N'est-ce pas un décideur?

beaucoupoffreetime
la source
3
log4XYZ est une marque si forte. Son API ne changera pas plus tôt que lorsque l'API d'une liste chaînée le sera. Les deux sont un problème résolu depuis longtemps maintenant.
Job le
1
Copie exacte de cette question SO: stackoverflow.com/questions/1916030/…
Michael Borgwardt le
1
Si vous l'utilisez uniquement en interne, que vous boucliez ou non, c'est un compromis entre le travail connu maintenant et le travail possible plus tard. Un appel de jugement. Mais les autres intervenants semblent avoir négligé de parler du fait qu'il s'agisse d'une dépendance à l' API ou à la mise en œuvre . En d'autres termes, divulguez-vous les classes de cette API tierce via votre propre API publique et exposez-vous les utilisateurs? Dans ce cas, il n'est plus simple de passer à une autre bibliothèque, le problème est que c'est désormais impossible, sans casser votre propre API. C'est très mauvais!
Elias Vasylenko
1
Pour plus de références: Ce modèle s'appelle onion-architecture, où une infrastructure externe (que vous appelez une bibliothèque externe) est cachée derrière une interface
k3b

Réponses:

42

Si vous utilisez uniquement un petit sous-ensemble de l'API tierce, il est judicieux d'écrire un wrapper. Cela facilite l'encapsulation et le masquage des informations, vous évitant d'exposer une API potentiellement énorme à votre propre code. Cela peut également vous aider à vous assurer que toute fonctionnalité que vous ne voulez pas utiliser est "cachée".

Une autre bonne raison pour un wrapper est si vous envisagez de changer la bibliothèque tierce. S'il s'agit d'un élément d'infrastructure dont vous savez qu'il ne changera pas, n'écrivez pas de wrapper pour cela.

Oded
la source
Bons points, mais on nous apprend qu'un code étroitement couplé est mauvais, pour de nombreuses raisons bien comprises (plus difficile à tester, plus difficile à refactoriser, etc.). Une autre formulation de la question est la suivante: "si le couplage est mauvais, pourquoi le couplage à une API est-il correct?".
lotsoffreetime
7
@lotsoffreetime Vous ne pouvez pas éviter certains couplages avec une API. Par conséquent, il est préférable de vous connecter à votre propre API. De cette façon, vous pouvez changer la bibliothèque sans généralement avoir besoin de changer l'API fournie par le wrapper.
George Marian
@ george-marian Si je ne peux pas éviter d' utiliser une API donnée, je peux certainement minimiser les points de contact. La question est la suivante: devrais-je essayer de le faire tout le temps ou est-ce exagéré?
lotsoffreetime
2
@lotsoffreetime C'est une question difficile à répondre. J'ai développé ma réponse à cette fin. (En gros, il y a beaucoup de si.)
George Marian
2
@lotsoffreetime: si vous avez beaucoup de temps libre, vous pouvez faire l'une ou l'autre. Mais je vous déconseille d’écrire un wrapper d’API, sauf dans les cas suivants: 1) l’API d’origine est de très faible niveau; vous devez donc écrire une API de niveau supérieur pour mieux répondre à vos besoins spécifiques en Dans un avenir proche pour changer de bibliothèque, vous utilisez la bibliothèque actuelle uniquement comme un tremplin tout en recherchant une meilleure.
Lie Ryan
28

Sans savoir quelles super-nouvelles fonctionnalités ce futur supposé enregistreur amélioré aura, comment écririez-vous le wrapper? Le choix le plus logique est que votre wrapper instancie une sorte de classe de consignateur et utilise des méthodes telles que ->info()ou ->warn(). En d'autres termes, essentiellement identique à votre API actuelle.

Plutôt que du code à l'épreuve du temps que je n'aurai peut-être jamais besoin de changer, ou qui peut nécessiter de toute façon une réécriture inévitable, je préfère le code "à l'épreuve des années". C'est, dans les rares occasions où je ne change considérablement d' un composant, qui est quand j'écris un emballage pour le rendre compatible avec le code passé. Cependant, tout nouveau code utilise la nouvelle API et je refacture l'ancien code pour l'utiliser chaque fois que je modifie le même fichier, ou lorsque le calendrier le permet. Après quelques mois, je peux retirer l'emballage et le changement a été progressif et robuste.

En d'autres termes, les wrappers n'ont vraiment de sens que si vous connaissez déjà toutes les API que vous devez wrapper. De bons exemples sont si votre application doit actuellement prendre en charge de nombreux pilotes de base de données, systèmes d'exploitation ou versions de PHP.

Karl Bielefeldt
la source
"... les wrappers n'ont vraiment de sens que lorsque vous connaissez déjà toutes les API dont vous avez besoin pour envelopper." Ce serait vrai si je correspondais à l'API dans le wrapper; peut-être devrais-je utiliser le terme «encapsulation» plus fortement que wrapper. Je résumerais ces appels d'API pour "enregistrer ce texte d'une manière ou d'une autre" plutôt que "d'appeler foo :: log () avec ce paramètre".
lotsoffreetime
"Sans savoir quelles super-nouvelles fonctionnalités auront cet supposé futur enregistreur, comment écririez-vous le wrapper?" @ kevin-cline ci-dessous mentionne un futur enregistreur avec de meilleures performances, plutôt qu'une fonctionnalité plus récente. Dans ce cas, pas de nouvelle API à boucler, juste une méthode d'usine différente.
lotsoffreetime
27

En encapsulant une bibliothèque tierce, vous ajoutez une couche supplémentaire d’abstraction. Cela présente quelques avantages:

  • Votre base de code devient plus flexible aux changements

    Si vous devez remplacer la bibliothèque par une autre, il vous suffit de modifier votre implémentation dans votre wrapper - à un endroit . Vous pouvez modifier l'implémentation du wrapper sans rien changer d'autre, autrement dit, vous avez un système faiblement couplé. Sinon, vous devrez parcourir tout votre code et apporter des modifications partout - ce qui n'est évidemment pas ce que vous voulez.

  • Vous pouvez définir l'API du wrapper indépendamment de l'API de la bibliothèque.

    Différentes bibliothèques peuvent avoir des API très différentes et en même temps aucune d’entre elles ne correspond exactement à vos besoins. Que se passe-t-il si une bibliothèque a besoin d'un jeton à transmettre avec chaque appel? Vous pouvez faire passer le jeton dans votre application partout où vous avez besoin d'utiliser la bibliothèque ou la sécuriser quelque part de manière plus centralisée, mais dans tous les cas, vous avez besoin du jeton. Votre classe de wrapper simplifie à nouveau tout cela, car vous pouvez simplement conserver le jeton dans votre classe de wrapper, ne jamais l'exposer à aucun composant de votre application et en supprimer complètement la nécessité. Un énorme avantage si vous avez déjà utilisé une bibliothèque qui ne met pas l’accent sur une bonne conception d’API.

  • Le test unitaire est beaucoup plus simple

    Les tests unitaires ne devraient tester qu'une seule chose. Si vous voulez tester une classe, vous devez vous moquer de ses dépendances. Cela devient encore plus important si cette classe passe des appels réseau ou accède à une autre ressource en dehors de votre logiciel. En encapsulant la bibliothèque tierce, il est facile de simuler ces appels et de renvoyer des données de test ou tout autre élément requis par le test unitaire. Si vous n’avez pas une telle couche d’abstraction, il devient beaucoup plus difficile de le faire - et la plupart du temps, cela produit beaucoup de code laid.

  • Vous créez un système faiblement couplé

    Les modifications apportées à votre enveloppe n'ont aucun effet sur les autres parties de votre logiciel, du moins tant que vous ne modifiez pas le comportement de votre enveloppe. En introduisant une couche d'abstraction comme celle-ci, vous pouvez simplifier les appels vers la bibliothèque et supprimer presque complètement la dépendance de votre application à cette bibliothèque. Votre logiciel utilisera simplement l'encapsuleur et cela ne changera rien à la façon dont l'encapsuleur est implémenté ou comment il fait ce qu'il fait.


Exemple pratique

Soyons honnêtes. Les gens peuvent discuter pendant des heures des avantages et des inconvénients d’une chose de la sorte - c’est pourquoi je préfère vous montrer un exemple.

Disons que vous avez une sorte d'application Android et que vous devez télécharger des images. Il existe de nombreuses bibliothèques qui facilitent grandement le chargement et la mise en cache des images, par exemple Picasso ou Universal Image Loader .

Nous pouvons maintenant définir une interface que nous allons utiliser pour envelopper quelle que soit la bibliothèque utilisée:

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

C'est l'interface que nous pouvons maintenant utiliser dans l'application chaque fois que nous devons charger une image. Nous pouvons créer une implémentation de cette interface et utiliser l’injection de dépendance pour injecter une instance de cette implémentation partout où nous utilisons le ImageService.

Disons que nous avons initialement décidé d'utiliser Picasso. Nous pouvons maintenant écrire une implémentation pour ImageServicelaquelle Picasso utilise en interne:

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

Assez simple si vous me demandez. Wrapper autour des bibliothèques n'a pas besoin d'être compliqué pour être utile. L’interface et l’implémentation ont moins de 25 lignes de code combinées, ce qui a été un effort de création, mais nous gagnons déjà quelque chose en le faisant. Voir le Contextchamp dans la mise en œuvre? Le framework d'injection de dépendance de votre choix se chargera déjà d'injecter cette dépendance avant même que nous utilisions notre ImageService, votre application n'a plus à se soucier de la façon dont les images sont téléchargées ni des dépendances éventuelles de la bibliothèque. Tout ce que votre application voit est un ImageServiceet quand il a besoin d'une image, il appelle load()avec une URL - simple et direct.

Cependant, le véritable avantage vient lorsque nous commençons à changer les choses. Imaginez que nous devions maintenant remplacer Picasso par Universal Image Loader, car Picasso ne prend pas en charge certaines fonctionnalités dont nous avons absolument besoin. Devons-nous maintenant passer au crible notre base de code, et remplacer fastidieusement tous les appels de Picasso, puis traiter des dizaines d’erreurs de compilation, car nous avons oublié quelques appels à Picasso? Non. Tout ce que nous avons à faire est de créer une nouvelle implémentation ImageServiceet d'indiquer à notre infrastructure d'injection de dépendance d'utiliser cette implémentation à partir de maintenant:

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

Comme vous pouvez le constater, la mise en œuvre peut être très différente, mais cela n’a aucune importance. Nous n'avons pas eu à modifier une seule ligne de code ailleurs dans notre application. Nous utilisons une bibliothèque complètement différente qui peut avoir des fonctionnalités complètement différentes ou peut être utilisée de manière très différente, mais notre application ne s'en soucie tout simplement pas. Comme auparavant, le reste de notre application ne voit que l' ImageServiceinterface avec sa load()méthode. Cependant, cette méthode n'est pas utilisée.

Au moins pour moi tout cela sonne déjà déjà bien, mais attendez! Il y a encore plus. Imaginez que vous écrivez des tests unitaires pour une classe sur laquelle vous travaillez et que cette classe utilise le ImageService. Bien sûr, vous ne pouvez pas laisser vos tests unitaires faire des appels réseau vers une ressource située sur un autre serveur, mais puisque vous utilisez maintenant le, ImageServicevous pouvez facilement laisser load()renvoyer une statique Bitmaputilisée pour les tests unitaires en implémentant un modèle fictif ImageService:

public class MockImageService implements ImageService {

    private final Bitmap mMockBitmap;

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

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

Pour résumer en encapsulant des bibliothèques tierces, votre base de code devient plus souple aux changements, plus simple, plus facile à tester et vous réduisez le couplage des différents composants de votre logiciel - autant de choses qui deviennent de plus en plus importantes plus la maintenance d'un logiciel est longue.

Xaver Kapeller
la source
1
Cela s'applique également à une API instable. Notre code ne change pas sur 1 000 emplacements simplement parce que la bibliothèque sous-jacente a été modifiée. Très belle réponse.
RubberDuck
Réponse très concise et claire. Je fais du travail frontal sur le Web. La quantité de changements dans ce paysage est insensée. Le fait que les gens «pensent» qu’ils ne changeront pas ne signifie pas qu’il n’y aura pas de changement. J'ai vu des mentions de YAGNI. Je voudrais ajouter un nouvel acronyme, YDKYAGNI, vous ne savez pas que vous n’en aurez pas besoin. Surtout avec les implémentations liées au Web. En règle générale, j'emballe toujours les bibliothèques qui n'exposent qu'une petite API (comme select2). Des bibliothèques plus volumineuses affectent votre architecture et, en les enveloppant, vous vous attendez à ce que votre architecture change, cela est peut-être le cas, mais cela est moins probable.
revoir
Votre réponse a été extrêmement utile et la présentation du concept avec un exemple l'a rendu encore plus clair.
Anil Gorthy
24

Je pense qu'emballer des bibliothèques tierces aujourd'hui au cas où quelque chose de mieux se présenterait demain est une violation très fastidieuse de YAGNI. Si vous appelez à plusieurs reprises du code tiers d'une manière particulière à votre application, vous devrez (devriez) refactoriser ces appels dans une classe d'habillage pour éliminer la répétition. Sinon, vous utilisez pleinement l'API de la bibliothèque et tout wrapper ressemblerait à la bibliothèque elle-même.

Supposons maintenant qu'une nouvelle bibliothèque apparaisse avec une performance supérieure ou autre. Dans le premier cas, il vous suffit de réécrire l'encapsuleur pour la nouvelle API. Aucun problème.

Dans le second cas, vous créez un wrapper adaptant l'ancienne interface au lecteur de la nouvelle bibliothèque. Un peu plus de travail, mais pas de problème, et pas plus de travail que si vous aviez écrit le wrapper plus tôt.

Kevin Cline
la source
4
Je ne pense pas que YAGNI s'applique nécessairement dans cette situation. Il ne s'agit pas d'intégrer des fonctionnalités au cas où vous en auriez besoin ultérieurement. Il s'agit d'introduire de la flexibilité dans l'architecture. Si cette flexibilité est inutile, alors, oui, YAGNI s'applique. Cependant, cette décision a tendance à être prise à un moment ultérieur, lorsque le changement sera probablement douloureux.
George Marian
7
@ George Marian: le problème est dans 95% des cas, vous n'aurez jamais besoin de la souplesse nécessaire pour changer. Si vous devez basculer vers une nouvelle bibliothèque future offrant des performances supérieures, il devrait être relativement simple de rechercher / remplacer des appels ou d'écrire un wrapper lorsque vous en avez besoin. D'un autre côté, si votre nouvelle bibliothèque est dotée de fonctionnalités différentes, le wrapper devient un obstacle car vous rencontrez maintenant deux problèmes: le portage de l'ancien code pour exploiter les nouvelles fonctionnalités et la maintenance du wrapper.
Lie Ryan
3
@lotsoffreetime: Le but d'une "bonne conception" est de minimiser le coût total de l'application au cours de sa vie. Ajouter des couches d'indirection pour des modifications futures imaginaires est une assurance très coûteuse. Je n'ai jamais vu personne réaliser des économies grâce à cette approche. Cela crée simplement un travail trivial pour les programmeurs dont le temps serait mieux dépensé pour répondre aux besoins spécifiques du client. La plupart du temps, si vous écrivez du code qui n'est pas spécifique à votre client, vous perdez du temps et de l'argent.
kevin cline
1
@ George: si ces changements sont douloureux, je pense que c'est une odeur de processus. En Java, je créerais de nouvelles classes avec les mêmes noms que les anciennes classes, mais dans un package différent, modifierions toutes les occurrences de l'ancien nom de package et réexécuter les tests automatisés.
kevin cline
1
@kevin Cela représente plus de travail et comporte donc plus de risques que de simplement mettre à jour le wrapper et d'exécuter les tests.
George Marian
9

La raison fondamentale pour écrire un wrapper autour d'une bibliothèque tierce est que vous pouvez échanger cette bibliothèque tierce sans changer le code qui l'utilise. Vous ne pouvez pas éviter le couplage à quelque chose. L'argument va donc selon lequel il est préférable de coupler à une API que vous avez écrite.

Que cela en vaille la peine est une autre histoire. Ce débat continuera probablement pendant longtemps.

Pour les petits projets, où la probabilité qu'un tel changement soit nécessaire est faible, il s'agit probablement d'un effort inutile. Pour les projets de grande envergure, cette flexibilité peut bien compenser les efforts supplémentaires déployés pour emballer la bibliothèque. Cependant, il est difficile de savoir si tel est le cas auparavant.

Une autre façon de voir les choses est le principe de base qui consiste à résumer ce qui est susceptible de changer. Donc, si la bibliothèque tierce est bien établie et qu’elle ne risque pas d’être modifiée, il peut être judicieux de ne pas l’emballer. Cependant, si la bibliothèque tierce est relativement nouvelle, il y a plus de chance qu'elle doive être remplacée. Cela dit, le développement de bibliothèques établies a été abandonné à maintes reprises. Donc, ce n’est pas une question facile à répondre.

George Marian
la source
Dans le cas de tests unitaires où le fait de pouvoir injecter une maquette de l'API sert à minimiser l'unité testée, le "potentiel de changement" n'est pas un facteur. Cela dit, c'est toujours ma réponse préférée, car elle correspond le mieux à mes idées. Que dirait Oncle Bob? :)
lotsoffreetime
De plus, les petits projets (pas d’équipe, spécifications de base, etc.) ont leurs propres règles selon lesquelles vous pouvez enfreindre de bonnes pratiques comme celle-ci et vous en sortir, dans une certaine mesure. Mais c’est une question différente ...
beaucoupoffreetime
1

En plus de ce que @Oded a déjà dit, j'aimerais ajouter cette réponse uniquement dans le but de se connecter.


J'ai toujours une interface pour la journalisation mais je n'ai jamais encore eu à remplacer un log4fooframework.

Cela ne prend qu'une demi-heure pour fournir l'interface et écrire le wrapper, donc je suppose que vous ne perdez pas trop de temps si cela s'avère inutile.

C'est un cas particulier de YAGNI. Bien que je n'en ai pas besoin, cela ne prend pas beaucoup de temps et je me sens plus en sécurité avec cela. Si le jour de l'échange de l'enregistreur arrive vraiment, je serai heureux d'avoir investi une demi-heure, car cela me fera économiser plus d'une journée d'échange d'appels dans un projet réel. Et je n'ai jamais écrit ni vu de test unitaire pour la journalisation (mis à part les tests pour l'implémentation de l'enregistreur lui-même), attendez-vous à des défauts sans wrapper.

Faucon
la source
Je ne m'attends pas à changer log4foo, mais il est largement connu et sert d'exemple. Il est également intéressant de voir comment les deux réponses jusqu’à présent sont complémentaires: «ne pas emballer toujours»; "envelopper juste au cas où".
lotsoffreetime
@ Falcon: est-ce que vous emballez tout? ORM, interface de journalisation, cours de langue de base? Après tout, on ne sait jamais quand un meilleur HashMap sera nécessaire.
Kevin Cline
1

Je traite de cette question précise sur un projet sur lequel je travaille actuellement. Mais dans mon cas, la bibliothèque est dédiée aux graphiques et je suis donc en mesure de limiter son utilisation à un petit nombre de classes qui traitent des graphiques, au lieu de l’éparpiller tout au long du projet. Ainsi, il est assez facile de changer d'API plus tard si j'en ai besoin. dans le cas d'un bûcheron, la question devient beaucoup plus compliquée.

Ainsi, je dirais que la décision a beaucoup à voir avec ce que fait exactement la bibliothèque tierce partie et combien il serait difficile de la changer. Si changer tous les appels d'API serait facile malgré tout, alors ça ne vaut probablement pas la peine de le faire. Si toutefois changer la bibliothèque plus tard serait vraiment difficile, je le referais probablement maintenant.


Au-delà de cela, d’autres réponses ont très bien répondu à la question principale, je souhaite donc me concentrer sur le dernier ajout, à savoir l’injection de dépendance et les objets fictifs. Cela dépend bien sûr de la manière dont fonctionne exactement votre structure de journalisation, mais dans la plupart des cas, cela ne nécessiterait pas d'encapsuleur (même s'il en bénéficierait probablement). Assurez-vous que l'API de votre objet fictif soit identique à celui de la bibliothèque tierce et vous pourrez ensuite facilement permuter l'objet fictif à des fins de test.

Dans ce cas, le principal facteur est de savoir si la bibliothèque tierce est implémentée même via une injection de dépendance (ou un localisateur de service ou un tel modèle faiblement couplé). Si les fonctions de la bibliothèque sont accessibles via un singleton, des méthodes statiques ou quelque chose de ce type, vous devrez envelopper le tout dans un objet que vous pourrez utiliser lors de l'injection de dépendances.

jhocking
la source
1

Je suis fermement dans le camp de l'emballage et non pas dans le fait de pouvoir remplacer la bibliothèque tierce par la plus grande priorité (bien que ce soit un bonus). Mon principal argument en faveur de l'emballage est simple

Les bibliothèques tierces ne sont pas conçues pour nos besoins spécifiques.

Et cela se manifeste, généralement, sous la forme d'une charge de duplication de code, telle que les développeurs écrivant 8 lignes de code juste pour créer un QButtonet le styler comme il se doit pour l'application, mais uniquement pour le concepteur. mais aussi la fonctionnalité des boutons pour changer complètement pour tout le logiciel, ce qui finit par nécessiter de revenir en arrière et de réécrire des milliers de lignes de code, ou de constater que la modernisation d'un pipeline de rendu nécessite une réécriture épique, car la base de code saupoudrait de bas niveau fixe ad-hoc fixe. pipeline code OpenGL partout au lieu de centraliser une conception de rendu en temps réel et de laisser l'utilisation de OGL strictement pour sa mise en œuvre.

Ces conceptions ne sont pas adaptées à nos besoins de conception spécifiques. Ils ont tendance à offrir un vaste ensemble de ce qui est réellement nécessaire (et ce qui ne fait pas partie d’un projet est aussi important, sinon plus, que ce qui est en réalité), et leurs interfaces ne sont pas conçues pour répondre spécifiquement à nos besoins dans un environnement de haut niveau. pensée = une requête ", ce qui nous prive de tout contrôle de conception central si nous les utilisons directement. Si les développeurs finissent par écrire du code de niveau inférieur à ce qu'il devrait être nécessaire pour exprimer ce dont ils ont besoin, ils peuvent parfois le résumer eux-mêmes de manière ad-hoc, ce qui permet de se retrouver avec des dizaines de lettres écrites à la hâte et grossières. des emballages conçus et documentés au lieu d’un emballage bien conçu et bien documenté.

Bien sûr, j'appliquerais de fortes exceptions aux bibliothèques dont les wrappers sont des traductions presque uniques de ce que les API tierces ont à offrir. Dans ce cas, il pourrait ne pas être recherché de conception de niveau supérieur exprimant plus directement les exigences commerciales et de conception (tel pourrait être le cas pour quelque chose qui ressemble davantage à une bibliothèque "utilitaire"). Mais s'il existe une conception beaucoup plus personnalisée disponible qui exprime beaucoup plus directement nos besoins, alors je suis tout à fait dans le camp de l'habillage, tout comme je suis fortement en faveur de l'utilisation d'une fonction de niveau supérieur et de la réutilisation par-dessus le code d'assemblage en ligne. partout.

Bizarrement, je me suis heurté aux développeurs d'une manière qui leur semblait si méfiante et si pessimiste quant à notre capacité à concevoir, par exemple, une fonction permettant de créer un bouton et de le renvoyer, préférant écrire 8 lignes de code de niveau inférieur axé sur la microscopie. les détails de la création de boutons (qui devaient éventuellement être modifiés à l'avenir) lors de la conception et de l'utilisation de cette fonction. Je ne vois même pas pourquoi nous essayons de concevoir quoi que ce soit si nous ne pouvons pas nous fier à nous-mêmes pour concevoir ce type d’emballage de manière raisonnable.

En d'autres termes, je considère les bibliothèques tierces comme des moyens de gagner un temps considérable dans la mise en œuvre, et non comme des substituts à la conception de systèmes.

Énergie de dragon
la source
0

Mon idée sur les bibliothèques tierces:

La communauté iOS a récemment discuté des avantages et inconvénients (OK, principalement des inconvénients) de l'utilisation de dépendances tierces. De nombreux arguments que j'ai vus étaient plutôt génériques - regroupant toutes les bibliothèques tierces dans un même panier. Comme avec la plupart des choses, cependant, ce n'est pas si simple. Alors, essayons de nous concentrer sur un seul cas

Devrions-nous éviter d'utiliser des bibliothèques d'interface utilisateur tierces?

Raisons pour envisager des bibliothèques tierces:

Il semble y avoir deux raisons principales pour lesquelles les développeurs envisagent d’utiliser une bibliothèque tierce:

  1. Manque de compétences ou de connaissances. Disons que vous travaillez sur une application de partage de photos. Vous ne commencez pas par lancer votre propre crypto.
  2. Manque de temps ou d'intérêt pour construire quelque chose. Si vous ne disposez pas d'un temps illimité (qu'aucun humain n'a encore), vous devez définir des priorités.

La plupart des bibliothèques d'interface utilisateur ( pas toutes! ) Ont tendance à tomber dans la deuxième catégorie. Ce matériel n’est pas sournois, mais il faut du temps pour le construire correctement.

S'il s'agit d'une fonction essentielle, faites-le vous-même, peu importe ce que c'est.

Il existe à peu près deux types de contrôles / vues:

  1. Générique, ce qui vous permet de les utiliser dans de nombreux contextes différents, même par ceux de leurs créateurs, par exemple UICollectionViewdepuis UIKit.
  2. Spécifique, conçu pour un seul cas d'utilisation, par exemple UIPickerView. La plupart des bibliothèques tierces appartiennent généralement à la deuxième catégorie. De plus, ils sont souvent extraits d'une base de code existante pour laquelle ils ont été optimisés.

Hypothèses précoces inconnues

De nombreux développeurs vérifient le code interne de leur code, mais prennent peut-être pour acquis la qualité du code source tiers. Cela vaut la peine de passer un peu de temps à parcourir le code d'une bibliothèque. Vous pourriez finir par être surpris de voir des drapeaux rouges, par exemple utiliser du grésillement là où il n’est pas nécessaire.

Il est souvent plus bénéfique d’apprendre que l’idée est d’obtenir le code résultant lui-même.

Vous ne pouvez pas le cacher

En raison de la manière dont UIKit est conçu, vous ne pourrez probablement pas cacher la bibliothèque d'interface utilisateur tierce, par exemple derrière un adaptateur. Une bibliothèque entremêlera avec votre code d'interface utilisateur devenant de facto de votre projet.

Coût du temps futur

UIKit change avec chaque version iOS. Les choses vont casser. Votre dépendance vis-à-vis de tiers ne sera pas aussi sans entretien que prévu.

Conclusion :

D'après mon expérience personnelle, la plupart des utilisations du code d'interface utilisateur tiers se résument à l'échange d'une plus petite flexibilité contre un gain de temps.

Nous utilisons du code prêt à l'emploi pour expédier notre version actuelle plus rapidement. Tôt ou tard, cependant, nous atteignons les limites de la bibliothèque et nous nous trouvons devant une décision difficile: que faire ensuite?

Mukesh
la source
0

L'utilisation directe de la bibliothèque est plus conviviale pour l'équipe de développeurs. Lorsqu'un nouveau développeur rejoint le groupe, il peut être pleinement familiarisé avec tous les frameworks utilisés, sans pour autant être en mesure de contribuer de manière productive avant d'apprendre votre propre API. Lorsqu'un jeune développeur tente de progresser dans votre groupe, il est forcé d'apprendre que votre API spécifique n'est présente nulle part ailleurs, au lieu d'acquérir des compétences génériques plus utiles. Si quelqu'un connaît des fonctionnalités utiles ou des possibilités de l'API d'origine, il peut ne pas être en mesure d'atteindre la couche écrite par quelqu'un qui ne le savait pas. Si quelqu'un souhaite exécuter une tâche de programmation tout en cherchant un emploi, il risque de ne pas être en mesure de démontrer les éléments de base qu'il a utilisés plusieurs fois, simplement parce qu'il accédait à ces fonctionnalités par le biais de votre wrapper.

Je pense que ces problèmes peuvent être plus importants que la possibilité plutôt lointaine d'utiliser une bibliothèque complètement différente plus tard. Le seul cas où j'utiliserais un wrapper est lorsque la migration vers une autre implémentation est définitivement planifiée ou que l'API encapsulée n'est pas suffisamment gelée et continue à changer.

h22
la source