Usando bibliotecas de terceiros - sempre use um wrapper?

78

A maioria dos projetos em que estou envolvido usa vários componentes de código aberto. Como princípio geral, é sempre bom evitar vincular todos os componentes do código às bibliotecas de terceiros e, em vez disso, passar por um invólucro de encapsulamento para evitar a dor da mudança?

Como exemplo, a maioria dos nossos projetos PHP usa log4php diretamente como uma estrutura de registro, ou seja, eles instanciam via \ Logger :: getLogger (), usam métodos -> info () ou -> warn () etc. No futuro, no entanto, uma estrutura de registro hipotético pode aparecer, melhor de alguma forma. Tal como está, todos os projetos que se aproximam das assinaturas do método log4php precisariam ser alterados, em dezenas de lugares, para caber nas novas assinaturas. Obviamente, isso teria um grande impacto na base de código e qualquer alteração é um problema em potencial.

Para proteger novas bases de código desse tipo de cenário à prova do futuro, geralmente considero (e algumas vezes implemento) uma classe de wrapper para encapsular a funcionalidade de log e facilitar, embora não seja infalível, alterar a maneira como o log funciona no futuro com alterações mínimas ; o código chama o wrapper, o wrapper passa a chamada para o logging framework du jour .

Tendo em mente que existem exemplos mais complicados com outras bibliotecas, estou com excesso de engenharia ou essa é uma precaução sábia na maioria dos casos?

EDIT: Mais considerações - o uso de injeção e teste de dependência praticamente exige que abstraiamos a maioria das APIs de qualquer maneira ("Quero verificar se meu código executa e atualiza seu estado, mas não escreve um comentário de log / acessa um banco de dados real"). Isso não é uma decisão?

lotes fora do tempo
fonte
3
log4XYZ é uma marca comercial tão forte. Sua API mudará não antes do que a API de uma lista vinculada. Ambos são um longo problema resolvido agora.
Job
1
Cópia exata desta pergunta SO: stackoverflow.com/questions/1916030/...
Michael Borgwardt
1
Se você estiver usando apenas internamente, se você encerra ou não, é apenas uma troca entre o trabalho conhecido agora e o possível depois. Uma chamada de julgamento. Mas algo que outros respondedores parecem ter esquecido de falar é se é uma dependência de API ou de implementação . Em outras palavras, você está vazando classes dessa API de terceiros por meio de sua própria API pública e expondo-a aos usuários? Nesse caso, não é mais uma simples questão de trabalho árduo mudar para uma biblioteca diferente, o problema é que agora é impossível sem quebrar sua própria API. Isso é muito ruim!
Elias Vasylenko
1
Para referência futura: Este padrão é chamado de cebola arquitetura onde a infraestrutura externa (você chamá-lo lib externo) está escondido atrás de uma interface
k3b

Respostas:

42

Se você usar apenas um pequeno subconjunto da API de terceiros, faz sentido escrever um wrapper - isso ajuda no encapsulamento e na ocultação de informações, garantindo que você não exponha uma API possivelmente enorme ao seu próprio código. Também pode ajudar a garantir que qualquer funcionalidade que você não queira usar esteja "oculta".

Outro bom motivo para um wrapper é se você espera alterar a biblioteca de terceiros. Se essa é uma parte da infraestrutura que você sabe que não será alterada, não escreva um wrapper para ela.

Oded
fonte
Bons pontos, mas somos ensinados que códigos intimamente acoplados são ruins, por muitas razões bem compreendidas (mais difíceis de testar, mais difíceis de refatorar etc.). Uma redação alternativa da pergunta é "se o acoplamento é ruim, por que é correto associar uma API?".
lotsoffreetime
7
@lotsoffreetime Você não pode evitar um acoplamento a uma API. Portanto, é melhor associar à sua própria API. Dessa forma, você pode alterar a biblioteca e geralmente não precisa alterar a API fornecida pelo wrapper.
George Marian
@ george-marian Se não posso evitar o uso de uma determinada API, certamente posso minimizar os pontos de contato. A questão é: eu deveria estar tentando fazer isso o tempo todo ou isso está exagerando?
lotsoffreetime
2
@lotsoffreetime Essa é uma pergunta difícil de responder. Eu expandi minha resposta para esse fim. (Basicamente, é baixo para um monte de ifs.)
George Marian
2
@lotsoffreetime: se você tiver muito tempo livre, poderá fazê-lo. Mas eu recomendaria não escrever um wrapper de API, exceto sob esta condição: 1) a API original é de nível muito baixo; portanto, você cria uma API de nível superior para se adequar melhor ao seu projeto específico ou 2) você tem um plano no no futuro próximo para alternar entre bibliotecas, você está usando a biblioteca atual apenas como um trampolim enquanto procura por uma melhor.
Lie Ryan
28

Sem saber quais são os grandes novos recursos que esse alegado futuro logger aprimorado terá, como você escreveria o wrapper? A opção mais lógica é fazer com que seu wrapper instancie algum tipo de classe de logger e tenha métodos como ->info()ou ->warn(). Em outras palavras, essencialmente idêntico à sua API atual.

Em vez de código à prova de futuro que talvez eu nunca precise alterar, ou que possa exigir uma reescrita inevitável de qualquer maneira, prefiro código à prova de "passado". Ou seja, nas raras ocasiões em que altero significativamente um componente, é quando escrevo um wrapper para torná-lo compatível com o código anterior. No entanto, qualquer novo código usa a nova API, e refato o código antigo para usá-lo sempre que fizer uma alteração no mesmo arquivo, de qualquer maneira, ou conforme a programação permitir. Depois de alguns meses, posso remover o invólucro e a mudança foi gradual e robusta.

Em outras palavras, os wrappers realmente só fazem sentido quando você já conhece todas as APIs que precisa quebrar. Bons exemplos são se o seu aplicativo precisar atualmente suportar diversos drivers de banco de dados, sistemas operacionais ou versões PHP.

Karl Bielefeldt
fonte
"... wrappers realmente só fazem sentido quando você já conhece todas as APIs que precisa quebrar". Isso seria verdade se eu estivesse correspondendo à API no wrapper; talvez eu devesse usar o termo "encapsulamento" mais fortemente do que wrapper. Eu estaria abstraindo essas chamadas de API para "registrar este texto de alguma forma" em vez de "chamar foo :: log () com este parâmetro".
precisa saber é o seguinte
"Sem saber quais são os novos recursos excelentes que esse alegado futuro logger aprimorado terá, como você escreveria o wrapper?" O @ kevin-cline abaixo menciona um futuro criador de logs com melhor desempenho, em vez de um recurso mais recente. Nesse caso, nenhuma nova API para quebrar, apenas um método de fábrica diferente.
lotsoffreetime
27

Ao agrupar uma biblioteca de terceiros, você adiciona uma camada adicional de abstração sobre ela. Isso tem algumas vantagens:

  • Sua base de código se torna mais flexível a alterações

    Se você precisar substituir a biblioteca por outra, basta alterar sua implementação no wrapper - em um só lugar . Você pode alterar a implementação do wrapper e não precisa mudar nada sobre qualquer outra coisa; em outras palavras, você tem um sistema fracamente acoplado. Caso contrário, você teria que passar por toda a sua base de código e fazer modificações em todos os lugares - o que obviamente não é o que você deseja.

  • Você pode definir a API do wrapper independentemente da API da biblioteca

    Bibliotecas diferentes podem ter APIs muito diferentes e, ao mesmo tempo, nenhuma delas pode ser exatamente o que você precisa. E se alguma biblioteca precisar que um token seja passado junto com todas as chamadas? Você pode transmitir o token em seu aplicativo sempre que precisar usar a biblioteca ou pode protegê-lo em algum lugar mais central, mas, em qualquer caso, você precisa do token. Sua classe de wrapper simplifica tudo isso novamente - porque você pode manter o token dentro da sua classe de wrapper, nunca o expõe a nenhum componente do aplicativo e abstrai completamente a necessidade. Uma enorme vantagem se você já usou uma biblioteca que não enfatiza o bom design da API.

  • O teste de unidade é muito mais simples

    Os testes de unidade devem testar apenas uma coisa. Se você deseja testar uma unidade, precisa zombar de suas dependências. Isso se torna ainda mais importante se essa classe fizer chamadas de rede ou acessar algum outro recurso fora do seu software. Ao agrupar a biblioteca de terceiros, é fácil zombar dessas chamadas e retornar dados de teste ou o que o teste de unidade exigir. Se você não tem essa camada de abstração, fica muito mais difícil fazer isso - e na maioria das vezes isso resulta em muito código feio.

  • Você cria um sistema fracamente acoplado

    Alterações no seu wrapper não afetam outras partes do seu software - pelo menos desde que você não altere o comportamento do seu wrapper. Ao introduzir uma camada de abstração como esse wrapper, você pode simplificar as chamadas para a biblioteca e remover quase completamente a dependência do seu aplicativo nessa biblioteca. Seu software usará apenas o wrapper e não fará diferença a maneira como o wrapper é implementado ou como ele faz o que faz.


Exemplo Prático

Sejamos honestos. As pessoas podem discutir sobre as vantagens e desvantagens de algo assim por horas - e é por isso que prefiro apenas mostrar um exemplo.

Digamos que você tenha algum tipo de aplicativo Android e precise baixar imagens. Existem várias bibliotecas por aí que facilitam o carregamento e o armazenamento de imagens em cache, por exemplo, o Picasso ou o Universal Image Loader .

Agora podemos definir uma interface que vamos usar para agrupar qualquer biblioteca que acabarmos usando:

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

Essa é a interface que agora podemos usar em todo o aplicativo sempre que precisarmos carregar uma imagem. Podemos criar uma implementação dessa interface e usar injeção de dependência para injetar uma instância dessa implementação em todos os lugares em que usamos o ImageService.

Digamos que inicialmente decidimos usar o Picasso. Agora podemos escrever uma implementação para a ImageServicequal usa o Picasso internamente:

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

Bem direto, se você me perguntar. O empacotador em torno das bibliotecas não precisa ser complicado para ser útil. A interface e a implementação têm menos de 25 linhas de código combinadas; portanto, não houve nenhum esforço para criar isso, mas já ganhamos algo fazendo isso. Veja o Contextcampo na implementação? A estrutura de injeção de dependência de sua escolha já cuidará de injetar essa dependência antes de usarmos o nosso ImageService, seu aplicativo agora não precisa se preocupar com a forma como as imagens são baixadas e com as dependências que a biblioteca possa ter. Tudo o que seu aplicativo vê é um ImageServicee, quando precisa de uma imagem, chama load()com um URL - simples e direto.

No entanto, o benefício real ocorre quando começamos a mudar as coisas. Imagine que agora precisamos substituir o Picasso pelo Universal Image Loader porque o Picasso não suporta alguns recursos que precisamos absolutamente no momento. Agora temos que vasculhar nossa base de código e substituir tediosamente todas as chamadas para o Picasso e depois lidar com dezenas de erros de compilação porque esquecemos de algumas chamadas do Picasso? Não. Tudo o que precisamos fazer é criar uma nova implementação ImageServicee dizer à nossa estrutura de injeção de dependência para usar essa implementação a partir de agora:

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

Como você pode ver, a implementação pode ser muito diferente, mas isso não importa. Não tivemos que alterar uma única linha de código em nenhum outro lugar do nosso aplicativo. Usamos uma biblioteca completamente diferente, que pode ter recursos completamente diferentes ou pode ser usada de maneira muito diferente, mas nosso aplicativo simplesmente não se importa. O mesmo de antes do resto do nosso aplicativo, apenas vê a ImageServiceinterface com seu load()método e, no entanto, esse método é implementado não importa mais.

Pelo menos para mim tudo isso já parece muito bom, mas espere! Ainda tem mais. Imagine que você está escrevendo testes de unidade para uma classe em que está trabalhando e essa classe usa o ImageService. É claro que você não pode permitir que seus testes de unidade façam chamadas de rede para algum recurso localizado em outro servidor, mas como você está usando agora, ImageServicevocê pode facilmente load()retornar uma estática Bitmapusada para os testes de unidade implementando uma simulação ImageService:

public class MockImageService implements ImageService {

    private final Bitmap mMockBitmap;

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

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

Para resumir, agrupando bibliotecas de terceiros, sua base de código se torna mais flexível a alterações, em geral mais simples, mais fácil de testar e você reduz o acoplamento de diferentes componentes em seu software - tudo que se torna cada vez mais importante quanto mais tempo você mantém um software.

Xaver Kapeller
fonte
1
Isso também se aplica a uma API instável. Nosso código não muda em 1000 lugares apenas porque a biblioteca subjacente mudou. Resposta muito boa.
precisa saber é o seguinte
Resposta muito concisa e clara. Eu trabalho front-end na web. A quantidade de mudanças nesse cenário é insana. O fato de as pessoas "pensarem" que não haverá mudanças não significa que não haverá mudanças. Eu vi menções de YAGNI. Gostaria de adicionar um novo acrônimo, YDKYAGNI, você não sabe que não precisará dele. Especialmente com implementações relacionadas à Web. Como regra, eu sempre agrupo bibliotecas que apenas expõem uma API pequena (como select2). Bibliotecas maiores afetam sua arquitetura e agrupá-las significa que você espera que sua arquitetura mude, pode, mas é menos provável que isso aconteça.
ByBye
Sua resposta foi super útil e a apresentação do conceito com um exemplo tornou o conceito ainda mais claro.
Anil Gorthy 02/02
24

Penso que o agrupamento de bibliotecas de terceiros hoje, caso algo melhor aconteça amanhã, é uma violação muito desnecessária do YAGNI. Se você estiver chamando repetidamente o código de terceiros de uma maneira peculiar ao seu aplicativo, refatorará (deverá) essas chamadas em uma classe de agrupamento para eliminar a repetição. Caso contrário, você estará usando totalmente a API da biblioteca e qualquer wrapper se parecerá com a própria biblioteca.

Agora, suponha que uma nova biblioteca apareça com desempenho superior ou o que seja. No primeiro caso, você acabou de reescrever o wrapper para a nova API. Sem problemas.

No segundo caso, você cria um wrapper adaptando a interface antiga para conduzir a nova biblioteca. Um pouco mais de trabalho, mas não há problema, e não há mais trabalho do que você teria feito se tivesse escrito o wrapper anteriormente.

Kevin Cline
fonte
4
Eu não acho que YAGNI necessariamente se aplique a esta situação. Não se trata de construir funcionalidade, caso você precise dela no futuro. Trata-se de criar flexibilidade na arquitetura. Se essa flexibilidade é desnecessária, sim, o YAGNI se aplica. No entanto, essa determinação tende a ser feita em algum momento no futuro, quando a alteração provavelmente será dolorosa.
George Marian
7
@ George Marian: o problema é 95% do tempo, você nunca precisará da flexibilidade de mudar. Se você precisar mudar para uma nova biblioteca futura com desempenho superior, deve ser bastante trivial procurar / substituir chamadas ou gravar um wrapper quando necessário. Por outro lado, se sua nova biblioteca vem com funcionalidades diferentes, o wrapper agora se torna um obstáculo, pois agora você tem dois problemas: portar código antigo para explorar os novos recursos e manter o wrapper.
Lie Ryan
3
@lotsoffreetime: O objetivo do "bom design" é minimizar o custo total do aplicativo ao longo de sua vida útil. Adicionar camadas de indireção para mudanças futuras imaginadas é um seguro muito caro. Eu nunca vi alguém perceber alguma economia com essa abordagem. Ele apenas cria um trabalho trivial para programadores cujo tempo seria muito melhor gasto em requisitos específicos do cliente. Na maioria das vezes, se você estiver escrevendo um código que não é específico para o seu cliente, estará perdendo tempo e dinheiro.
kevin Cline
1
@ George: se essas mudanças são dolorosas, acho que é um cheiro de processo. Em Java, eu criava novas classes com os mesmos nomes das classes antigas, mas em um pacote diferente, alterava todas as ocorrências do nome do pacote antigo e executava novamente os testes automatizados.
kevin Cline
1
@kevin Isso é mais trabalhoso e, portanto, apresenta mais riscos do que simplesmente atualizar o wrapper e executar os testes.
George Marian
9

O motivo básico para escrever um wrapper em torno de uma biblioteca de terceiros é para que você possa trocar essa biblioteca de terceiros sem alterar o código que a utiliza. Você não pode evitar o acoplamento a algo, então o argumento é que é melhor associar uma API que você escreveu.

Se vale a pena o esforço é uma história diferente. Esse debate provavelmente continuará por muito tempo.

Para pequenos projetos, onde a probabilidade de que tal mudança seja necessária é baixa, é provavelmente um esforço desnecessário. Para projetos maiores, essa flexibilidade pode muito mais do que o esforço extra para agrupar a biblioteca. No entanto, é difícil saber se esse é o caso de antemão.

Outra maneira de ver é o princípio básico de abstrair o que provavelmente mudará. Portanto, se a biblioteca de terceiros estiver bem estabelecida e provavelmente não será alterada, pode ser bom não quebrá-la. No entanto, se a biblioteca de terceiros for relativamente nova, há uma chance maior de que ela precise ser substituída. Dito isto, o desenvolvimento de bibliotecas estabelecidas foi abandonado várias vezes. Portanto, essa não é uma pergunta fácil de responder.

George Marian
fonte
No caso de testes de unidade em que a capacidade de injetar uma simulação da API serve para minimizar a unidade em teste, o "potencial de mudança" não é um fator. Dito isto, esta ainda é a minha resposta favorita, pois está mais próxima de como eu penso. O que o tio Bob diria? :)
lotesoffreetime
Além disso, pequenos projetos (sem equipe, especificações básicas, etc.) têm suas próprias regras nas quais você pode violar boas práticas como essa e se safar, até certo ponto. Mas essa é uma pergunta diferente ...
lotsoffreetime
1

Além do que o @Oded já disse, eu gostaria de adicionar esta resposta com o objetivo especial de registrar.


Eu sempre tenho uma interface para log, mas nunca tive que substituir uma log4fooestrutura ainda.

Leva apenas meia hora para fornecer a interface e gravar o wrapper, então acho que você não perde muito tempo se isso for desnecessário.

É um caso especial de YAGNI. Embora eu não precise, não leva muito tempo e me sinto mais seguro com isso. Se realmente chegar o dia de trocar o logger, ficarei feliz por ter investido meia hora, pois isso me poupará mais de um dia trocando chamadas em um projeto do mundo real. E nunca escrevi ou vi um teste de unidade para registro (além dos testes para a implementação do criador de logs), portanto, espere defeitos sem o wrapper.

Falcão
fonte
Não espero alterar o log4foo, mas é amplamente conhecido e serve como exemplo. Também é interessante como as duas respostas até agora são complementares - "nem sempre envolvem"; "embrulhe apenas no caso".
lotsoffreetime
@ Falcon: você embrulha tudo? ORM, interface de log, classes de idioma principal? Afinal, nunca se sabe quando um HashMap melhor pode ser necessário.
Kevin cline
1

Estou lidando com esse problema exato em um projeto em que estou trabalhando atualmente. Mas, no meu caso, a biblioteca é para gráficos e, portanto, sou capaz de restringir seu uso a um pequeno número de classes que lidam com gráficos, em vez de espalhá-lo por todo o projeto. Portanto, é muito fácil alternar as APIs mais tarde, se necessário; no caso de um madeireiro, o assunto se torna muito mais complicado.

Assim, eu diria que a decisão tem muito a ver com o que exatamente a biblioteca de terceiros está fazendo e com quanto sofrimento estaria associado à sua alteração. Se alterar todas as chamadas de API seria fácil, independentemente, provavelmente não vale a pena fazer. Se, no entanto, mudar a biblioteca mais tarde for realmente difícil, provavelmente eu a envolverei agora.


Além disso, outras respostas cobriram muito bem a questão principal, então só quero focar nessa última adição, sobre injeção de dependência e objetos simulados. Depende, é claro, de como exatamente sua estrutura de criação de log funciona, mas na maioria dos casos não é necessário um wrapper (embora provavelmente se beneficie de um). Basta tornar a API do seu objeto simulado exatamente a mesma da biblioteca de terceiros e, em seguida, você pode facilmente trocar o objeto simulado para teste.

O principal fator aqui é se a biblioteca de terceiros é implementada ou não por meio de injeção de dependência (ou um localizador de serviço ou algum desses padrões fracamente acoplados). Se as funções da biblioteca forem acessadas por meio de métodos singleton ou estáticos ou algo assim, você precisará agrupá-lo em um objeto com o qual possa trabalhar na injeção de dependência.

jhocking
fonte
1

Estou fortemente no campo de encerramento e não consigo substituir a biblioteca de terceiros com a maior prioridade (embora isso seja um bônus). Minha lógica principal que favorece a embalagem é simples

Bibliotecas de terceiros não foram projetadas para nossas necessidades específicas.

E isso se manifesta, normalmente, na forma de um monte de duplicação de código, como desenvolvedores escrevendo 8 linhas de código apenas para criar QButtone estilizá-lo da maneira que deve procurar o aplicativo, apenas para o designer querer não apenas a aparência mas também a funcionalidade dos botões para mudar completamente para todo o software, o que acaba exigindo voltar e reescrever milhares de linhas de código, ou descobrir que a modernização de um pipeline de renderização requer uma reescrita épica, porque a base de código borrifou ad-hoc fixo de baixo nível. canalize o código OpenGL em todo o lugar, em vez de centralizar o design do renderizador em tempo real e deixar o uso do OGL estritamente para sua implementação.

Esses projetos não são adaptados às nossas necessidades específicas. Eles tendem a oferecer um superconjunto enorme do que é realmente necessário (e o que não faz parte de um design é tão importante, se não mais, do que é), e suas interfaces não são projetadas para atender especificamente às nossas necessidades em um "nível superior". thought = one request "de uma maneira que nos priva de todo controle central do design se os usarmos diretamente. Se os desenvolvedores acabam escrevendo um código de nível muito inferior ao necessário para expressar o que precisam, às vezes podem envolvê-lo de maneira ad-hoc que faz com que você acabe com dezenas de documentos escritos às pressas e de forma grosseira. invólucros projetados e documentados em vez de um bem projetado e bem documentado.

É claro que eu aplicaria fortes exceções a bibliotecas nas quais os wrappers são quase traduções individuais do que as APIs de terceiros têm a oferecer. Nesse caso, pode não ser necessário procurar um design de nível superior que expresse mais diretamente os requisitos de negócios e de design (pode ser o caso de algo que se assemelha mais a uma biblioteca de "utilitários"). Mas se houver um design muito mais personalizado disponível, que expresse mais diretamente nossas necessidades, estou fortemente no campo de empacotamento, assim como sou fortemente a favor de usar uma função de nível superior e reutilizá-la sobre o código de montagem embutido por todo o lugar.

Estranhamente, colidi com os desenvolvedores de maneiras que pareciam tão desconfiadas e pessimistas quanto à nossa capacidade de projetar, digamos, uma função para criar um botão e devolvê-lo, que eles preferem escrever 8 linhas de código de nível inferior focadas em microscópicos. detalhes da criação do botão (que acabou precisando mudar repetidamente no futuro) sobre o design e o uso dessa função. Eu nem vejo o propósito de tentarmos projetar algo em primeiro lugar, se não podemos confiar em nós mesmos para projetar esses tipos de embalagens de maneira razoável.

Em outras palavras, vejo as bibliotecas de terceiros como formas de economizar potencialmente um enorme tempo na implementação, e não como substitutos do design de sistemas.

Dragon Energy
fonte
0

Minha ideia sobre bibliotecas de terceiros:

Recentemente, houve uma discussão na comunidade iOS sobre prós e contras (OK, principalmente contras) do uso de dependências de terceiros. Muitos argumentos que vi foram bastante genéricos - agrupando todas as bibliotecas de terceiros em uma única cesta. Como na maioria das coisas, porém, não é assim tão simples. Então, vamos tentar focar em um único caso

Devemos evitar o uso de bibliotecas de interface do usuário de terceiros?

Razões para considerar bibliotecas de terceiros:

Parece haver duas razões principais que os desenvolvedores consideram usar uma biblioteca de terceiros:

  1. Falta de habilidade ou conhecimento. Digamos que você esteja trabalhando em um aplicativo de compartilhamento de fotos. Você não começa rolando sua própria criptografia.
  2. Falta de tempo ou interesse para construir algo. A menos que você tenha uma quantidade ilimitada de tempo (que nenhum ser humano ainda tem), você deve priorizar.

A maioria das bibliotecas de interface do usuário ( nem todas! ) Tendem a se enquadrar na segunda categoria. Esse material não é ciência de foguetes, mas leva tempo para construí-lo corretamente.

Se é uma função principal do negócio - faça você mesmo, não importa qual seja.

Existem praticamente dois tipos de controles / visualizações:

  1. Genérico, permitindo que você os use em muitos contextos diferentes, nem mesmo pensados ​​por seus criadores, por exemplo, UICollectionViewde UIKit.
  2. Específico, projetado para um único caso de uso, por exemplo UIPickerView. A maioria das bibliotecas de terceiros tende a pertencer à segunda categoria. Além disso, eles geralmente são extraídos de uma base de código existente para a qual foram otimizados.

Suposições iniciais desconhecidas

Muitos desenvolvedores fazem revisões de código de seu código interno, mas podem estar considerando a qualidade do código fonte de terceiros. Vale a pena gastar um pouco de tempo apenas navegando no código de uma biblioteca. Você pode se surpreender ao ver algumas bandeiras vermelhas, por exemplo, swizzling usado onde não é necessário.

Muitas vezes, aprender a idéia é mais benéfico do que obter o próprio código resultante.

Você não pode esconder

Devido à maneira como o UIKit foi projetado, você provavelmente não poderá ocultar a biblioteca de UI de terceiros, por exemplo, atrás de um adaptador. Uma biblioteca entrelaçará com o código da interface do usuário, tornando-se um fato de seu projeto.

Custo futuro

O UIKit muda a cada versão do iOS. As coisas vão quebrar. Sua dependência de terceiros não será tão livre de manutenção quanto o esperado.

Conclusão:

Da minha experiência pessoal, a maioria dos usos de código de interface do usuário de terceiros se resume a trocar flexibilidade menor por algum ganho de tempo.

Usamos código pronto para enviar nosso release atual mais rapidamente. Mais cedo ou mais tarde, porém, chegamos aos limites da biblioteca e ficamos diante de uma decisão difícil: o que fazer a seguir?

Mukesh
fonte
0

Usar a biblioteca diretamente é mais amigável para a equipe de desenvolvedores. Quando um novo desenvolvedor ingressa, ele pode ter uma experiência completa com todas as estruturas usadas, mas não poderá contribuir produtivamente antes de aprender sua API desenvolvida em casa. Quando um desenvolvedor mais jovem tenta progredir em seu grupo, ele é forçado a aprender que sua API específica não está presente em nenhum outro lugar, em vez de adquirir competência genérica mais útil. Se alguém conhece recursos ou possibilidades úteis da API original, talvez não seja possível acessar a camada escrita por alguém que não a conhecia. Se alguém conseguir uma tarefa de programação enquanto procura um emprego, talvez não consiga demonstrar as coisas básicas que ele usou muitas vezes, apenas porque todas essas vezes ele estava acessando a funcionalidade necessária através do seu wrapper.

Eu acho que essas questões podem ser mais importantes do que a possibilidade remota de usar uma biblioteca completamente diferente posteriormente. O único caso em que eu usaria um wrapper é quando a migração para outra implementação é definitivamente planejada ou a API empacotada não está congelada o suficiente e continua mudando.

h22
fonte