Como reduzir o esforço manual para agrupar bibliotecas de terceiros com um modelo de objeto maior?


16

Como o autor desta pergunta de 2012 e de 2013 , eu tenho uma biblioteca de terceiros que preciso agrupar para testar corretamente meu aplicativo. A resposta principal indica:

Você sempre deseja agrupar tipos e métodos de terceiros atrás de uma interface. Isso pode ser entediante e doloroso. Às vezes, você pode escrever um gerador de código ou usar uma ferramenta para fazer isso.

No meu caso, a biblioteca é para um modelo de objeto e, consequentemente, possui um número maior de classes e métodos que precisariam ser agrupados para que essa estratégia fosse bem-sucedida. Além de apenas "tedioso e doloroso", isso se torna uma barreira difícil para os testes.

Nos 4 anos desde essa pergunta, estou ciente de que as estruturas de isolamento percorreram um longo caminho. Minha pergunta é: existe agora uma maneira mais simples de obter o efeito de agrupamento completo de bibliotecas de terceiros? Como posso aliviar a dor desse processo e reduzir o esforço manual?


Minha pergunta não é uma duplicata das perguntas às quais vinculei inicialmente, já que a minha pergunta é sobre reduzir o esforço manual de empacotamento. Essas outras perguntas apenas perguntam se a embalagem faz sentido, e não como o esforço pode ser pequeno.


De qual linguagem de programação e de que tipo de biblioteca você está falando?
Doc Brown

@DocBrown C # e uma biblioteca de manipulação de PDF.
Tom Wright

2
Comecei um post na meta para encontrar algum suporte para reabrir sua pergunta.
Doc Brown

Obrigado @DocBrown - Espero que haja algumas perspectivas interessantes por aí.
Tom Wright

1
Quando não conseguirmos respostas melhores nas próximas 48 horas, colocarei uma recompensa nisso.
Doc Brown

Respostas:


4

Supondo que você não esteja procurando uma estrutura de zombaria, porque é ultra-onipresente e fácil de encontrar , há algumas coisas que vale a pena observar com antecedência:

  1. "Nunca" existe algo que você deve "sempre" fazer.
    Nem sempre é melhor agrupar uma biblioteca de terceiros. Se seu aplicativo é intrinsecamente dependente de uma biblioteca, ou se é literalmente construído em torno de uma ou duas bibliotecas principais, não perca seu tempo finalizando. Se as bibliotecas mudarem, seu aplicativo precisará mudar de qualquer maneira.
  2. Não há problema em usar testes de integração.
    Isso é especialmente verdadeiro em relação aos limites que são estáveis, intrínsecos ao seu aplicativo ou que não podem ser facilmente ridicularizados. Se essas condições forem atendidas, o acondicionamento e a zombaria serão complicados e tediosos. Nesse caso, eu evitaria os dois: não envolva nem zombe; basta escrever testes de integração. (Se o teste automatizado é uma meta.)
  3. Ferramentas e estrutura não podem eliminar a complexidade lógica.
    Em princípio, uma ferramenta só pode reduzir o padrão. Porém, não há algoritmo automatizado para pegar uma interface complexa e simplificá-la - muito menos pegar a interface X e adaptá-la às suas necessidades. (Somente você conhece esse algoritmo!) Portanto, embora haja indubitavelmente ferramentas que podem gerar invólucros finos, sugiro que elas ainda não sejam onipresentes porque, no final, você ainda precisa apenas codificar de forma inteligente e, portanto, manualmente, contra a interface, mesmo que oculta atrás de um invólucro.

Dito isto, existem táticas que você pode usar em vários idiomas para evitar se referir diretamente a uma classe. E, em alguns casos, você pode "fingir" uma interface ou um invólucro fino que não existe realmente. Em C #, por exemplo, eu usaria uma das duas rotas:

  1. Use uma fábrica e digitação implícita .

Você pode evitar o esforço de agrupar completamente uma classe complexa com este pequeno combo:

// "factory"
class PdfDocumentFactory {
  public static ExternalPDFLibraryDocument Build() {
    return new ExternalPDFLibraryDocument();
  }
}

// code that uses the factory.
class CoreBusinessEntity {
  public void DoImportantThings() {
    var doc = PdfDocumentFactory.Build();

    // ... i have no idea what your lib does, so, I'm making stuff but.
    // but, you can do whatever you want here without explicitly
    // referring to the library's actual types.
    doc.addHeader("Wee");
    doc.getAllText().makeBiggerBy(4).makeBold().makeItalic();
    return doc.exportBinaryStreamOrSomething();
  }
}

Se você puder evitar o armazenamento desses objetos como membros, por meio de uma abordagem mais "funcional" ou armazenando-os em um dicionário (ou qualquer outro ), essa abordagem terá o benefício de verificar o tipo em tempo de compilação sem que suas principais entidades de negócios precisem saber exatamente com que classe eles estão trabalhando.

Tudo o que é necessário é que, em tempo de compilação, a classe retornada por sua fábrica realmente possua os métodos que seu objeto de negócios está usando.

  1. Use digitação dinâmica .

Isso ocorre da mesma maneira que o uso de digitação implícita , mas envolve outra desvantagem: você perde verificações do tipo de compilação e ganha a capacidade de adicionar anonimamente dependências externas como membros da classe e injetar suas dependências.

class CoreBusinessEntity {
  dynamic Doc;

  public void InjectDoc(dynamic Doc) {
    Doc = doc;
  }

  public void DoImortantThings() {
    Doc.addHeader("Wee");
    Doc.getAllText().makeBiggerBy(4).makeBold().makeItalic();
    return Doc.exportBinaryStreamOrSomething();
  }
}

Com essas duas táticas, quando chega a hora de zombar ExternalPDFLibraryDocument, como afirmei anteriormente, você tem algum trabalho a fazer - mas é um trabalho que você precisaria fazer de qualquer maneira . E, com essa construção, você evitou definir tediosamente centenas de pequenas e finas classes de embalagens. Você simplesmente usou a biblioteca sem olhar diretamente para ela - na maior parte.

Com tudo isso dito, há três grandes razões pelas quais eu consideraria explicitamente encerrar uma biblioteca de terceiros - nenhuma das quais sugeriria o uso de uma ferramenta ou estrutura:

  1. A biblioteca específica não é intrínseca ao aplicativo.
  2. Seria muito caro trocar sem empacotá-lo.
  3. Eu não gosto da própria API.

Se eu não tiver alguma preocupação de nível nessas três áreas, você não fará nenhum esforço significativo para concluí-lo. E, se você tiver alguma preocupação nas três áreas, um invólucro fino gerado automaticamente não ajudará.

Se você decidiu encerrar uma biblioteca, o uso mais eficiente e eficaz do seu tempo é criar seu aplicativo na interface desejada ; não contra uma API existente.

Em outras palavras, preste atenção ao conselho clássico: adie todas as decisões que puder. Crie o "núcleo" do seu aplicativo primeiro. Codifique contra interfaces que eventualmente farão o que você deseja, que acabará sendo cumprido por "coisas periféricas" que ainda não existem. Preencha as lacunas conforme necessário.

Esse esforço pode não parecer economizado em tempo; mas se achar que precisa de um invólucro, essa é a maneira mais eficiente de fazê-lo com segurança.

Pense desta maneira.

Você precisa codificar nessa biblioteca em algum canto escuro do seu código - mesmo que esteja finalizado. Se você zomba da biblioteca durante o teste, há um esforço manual inevitável por lá - mesmo que seja finalizado. Mas isso não significa que você precise reconhecer diretamente essa biblioteca pelo nome em toda a maior parte do seu aplicativo.

TLDR

Se vale a pena agrupar a biblioteca, use táticas para evitar referências diretas e difundidas à sua biblioteca de terceiros, mas não use atalhos para gerar wrappers finos. Construa sua lógica de negócios primeiro, seja deliberado sobre suas interfaces e eleve seus adaptadores organicamente, conforme necessário.

E, se for o caso, não tenha medo de testes de integração. Eles são um pouco mais confusos, mas ainda oferecem evidências de código funcional e ainda podem ser feitos com facilidade para manter as regressões afastadas.


2
Este é outro post que não responde à pergunta - que explicitamente não era "quando encerrar", mas "como reduzir o esforço manual".
Doc Brown

1
Desculpe, mas acho que você perdeu o ponto principal da pergunta. Não vejo como suas sugestões podem reduzir qualquer esforço manual no acondicionamento. Suponha que a lib em jogo possua um modelo de objeto complexo como API, com algumas dezenas de classes e centenas de métodos. Sua API é boa como é para uso padrão, mas como envolvê-la para testes de unidade com menos esforço / esforço?
Doc Brown

1
TLDR; se você quiser que os pontos de bônus, diga-nos algo que ainda não sabe ;-)
Doc Brown

1
@DocBrown Não perdi o ponto. Mas acho que você perdeu o ponto da minha resposta. Investir o esforço certo economiza muito trabalho. Existem implicações nos testes - mas isso é apenas um efeito colateral. Usar uma ferramenta para gerar automaticamente um invólucro fino em torno de uma biblioteca ainda permite criar sua biblioteca principal em torno da API de alguém e criar zombarias - o que é muito esforço que não pode ser evitado se você insistir em deixar a biblioteca de fora. dos seus testes.
svidgen

1
@DocBrown Isso é um absurdo. "Essa classe de problemas tem uma classe de ferramentas ou abordagens?" Sim. Claro que sim. Codificação defensiva e não ficar dogmático sobre testes de unidade ... como minha resposta diz. Se você fez ter um wrapper fino automagicamente gerado, o valor que iria fornecer !? ... Não permitiria injetar dependências para teste, você ainda precisa fazer isso manualmente. E não permitiria que você trocasse a biblioteca, porque você ainda está codificando na API da biblioteca ... agora é apenas indireta.
svidgen

9

Não teste de unidade esse código. Escreva testes de integração. Em alguns casos, o teste de unidade, a zombaria, é tedioso e doloroso. Abandone os testes de unidade e escreva testes de integração que realmente fazem chamadas de fornecedor.

Observe que esses testes devem ser executados após a implantação como uma atividade automatizada pós-implantação. Eles não são executados como parte de testes de unidade ou como parte do processo de construção.

Às vezes, um teste de integração é mais adequado do que um teste de unidade em certas partes do seu aplicativo. Os aros pelos quais se deve passar para tornar o código "testável" às vezes podem ser prejudiciais.


2
A primeira coisa que pensei ao ler sua resposta foi "irreal para PDF, pois comparar os arquivos em nível binário não indica o que mudou ou se a alteração é problemática". Então eu encontrei esta postagem mais antiga do SO , indica que realmente pode funcionar (então +1).
Doc Brown

@DocBrown a ferramenta no link SO não compara os PDFs em nível binário. Ele compara a estrutura e o conteúdo das páginas. Estrutura interna em PDF: safaribooksonline.com/library/view/pdf-explained/9781449321581/…
linuxunil

1
@linuxunil: sim, eu sei, como escrevi, primeiro pensei ... mas depois encontrei a solução mencionada acima.
Doc Brown

oh .. entendo .. talvez o 'realmente funcione' na final da sua resposta me confunda.
Linuxunil

1

Pelo que entendi, esta discussão se concentra em oportunidades para a automação da criação de wrapper, em vez de agrupar as diretrizes de idéia e implementação. Vou tentar abstrair da idéia, pois já existe muita coisa aqui.

Vejo que estamos brincando com as tecnologias .NET; portanto, temos poderosos recursos de reflexão em nossas mãos. Você pode considerar:

  1. Ferramenta como o .NET Wrapper Class Generator . Não usei essa ferramenta e sei que ela opera em uma pilha de tecnologia herdada, mas talvez para o seu caso ela se encaixe. Obviamente, a qualidade do código, o suporte à inversão de dependência e à segregação de interfaces devem ser investigados separadamente. Talvez haja outras ferramentas como essa, mas não pesquisei muito.
  2. Escreva sua própria ferramenta que procurará na montagem referenciada métodos / interfaces públicos e faz o mapeamento e a geração de código. Contribuição para a comunidade seria mais que bem-vinda!
  3. Se o .NET não for um caso ... talvez veja aqui .

Acredito que essa automação possa constituir um ponto base, mas não uma solução final. A refatoração manual de código será necessária ... ou, na pior das hipóteses, uma reformulação conforme eu concordo completamente com o que a svidgen escreveu:

Crie seu aplicativo na interface que você deseja; não contra uma API de terceiros.


Ok, pelo menos uma ideia de como o problema pode ser tratado. Mas não com base na própria experiência para este caso de uso, presumo?
Doc Brown

A questão é baseada na minha própria experiência em relação ao domínio de engenharia de software (wrapper, reflexão, di)! Em relação às ferramentas - não usei isso como indicado no asnwer.
tom3k

0

Siga estas diretrizes ao criar bibliotecas de wrapper:

  • exponha apenas um pequeno subconjunto da biblioteca de terceiros que você precisa agora (e expanda sob demanda)
  • mantenha o invólucro o mais fino possível (nenhuma lógica pertence a ele).

1
Apenas me pergunto por que você recebeu 3 votos positivos para um post que perde o objetivo da pergunta.
Doc Brown
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.