A Injeção de Dependência do Pobre Homem é uma boa maneira de introduzir testabilidade em um aplicativo herdado?

14

No ano passado, criei um novo sistema usando Injeção de Dependência e um contêiner de COI. Isso me ensinou muito sobre DI!

No entanto, mesmo depois de aprender os conceitos e os padrões adequados, considero um desafio desacoplar o código e introduzir um contêiner IOC em um aplicativo herdado. O aplicativo é grande o suficiente para o ponto em que uma implementação verdadeira seria esmagadora. Mesmo que o valor tenha sido entendido e o tempo concedido. Quem concedeu tempo para algo assim?

O objetivo do curso é trazer testes de unidade para a lógica de negócios!
Lógica comercial interligada com chamadas de banco de dados que impedem testes.

Li os artigos e compreendo os perigos da Injeção de Dependência do Pobre Homem, conforme descrito neste artigo da Los Techies . Eu entendo que isso realmente não dissocia nada.
Entendo que isso pode envolver muita refatoração em todo o sistema, pois as implementações exigem novas dependências. Eu não consideraria usá-lo em um novo projeto com qualquer quantidade de tamanho.

Pergunta: Tudo bem usar o DI da Poor Man para introduzir testabilidade em um aplicativo herdado e começar a bola rolar?

Além disso, o uso do DI de Poor Man como uma abordagem básica da injeção de dependência verdadeira é uma maneira valiosa de educar sobre a necessidade e os benefícios do princípio?

Você pode refatorar um método que possui uma dependência de chamada de banco de dados e um resumo que chama atrás de uma interface? Simplesmente ter essa abstração tornaria esse método testável, pois uma implementação simulada poderia ser passada através de uma sobrecarga de construtor.

Mais adiante, uma vez que o esforço ganhe adeptos, o projeto poderá ser atualizado para implementar um contêiner do COI e os construtores estarão lá fora, captando as abstrações.

Airn5475
fonte
2
Apenas uma nota. I consider it a challenge to decouple code and introduce an IOC container into a legacy applicationclaro que é. É chamado de dívida técnica. É por isso que, antes de qualquer grande reforma, são preferíveis pequenos e contínuos refatores. Reduza as principais falhas de projeto e mudar para a IoC seria menos desafiador.
LAIV

Respostas:

25

A crítica sobre a Injeção do Pobre Homem no NerdDinner tem menos a ver com o uso ou não de um DI Container do que com a configuração correta de suas aulas.

No artigo, eles afirmam que

public class SearchController : Controller {

    IDinnerRepository dinnerRepository;

    public SearchController() : this(new DinnerRepository()) { }

    public SearchController(IDinnerRepository repository) {
        dinnerRepository = repository;
    }
}

está incorreto porque, embora o primeiro construtor forneça um mecanismo de fallback conveniente para a construção da classe, ele também cria uma dependência fortemente vinculada a DinnerRepository.

O remédio correto, obviamente, não é, como sugere o Los Techies, adicionar um contêiner de DI, mas remover o construtor ofensivo.

public class SearchController : Controller 
{
    IDinnerRepository dinnerRepository;

    public SearchController(IDinnerRepository repository) {
        dinnerRepository = repository;
    }
}

A classe restante agora tem suas dependências devidamente invertidas. Agora você está livre para injetar essas dependências da maneira que desejar.

Robert Harvey
fonte
Obrigado por seus pensamentos! Entendo o "construtor ofensivo" e como devemos evitá-lo. Entendo que ter essa dependência não dissocia nada, mas permite testes de unidade. Estou no início da introdução da DI e da realização dos testes de unidade o mais rápido possível. Estou tentando evitar a complicação de um recipiente ou fábrica de DI / COI tão cedo no jogo. Como mencionado, mais tarde, quando o suporte à DI aumentar, poderíamos implementar um Container e remover esses "construtores ofensivos".
precisa saber é o seguinte
O construtor padrão não é necessário para testes de unidade. Você pode entregar qualquer dependência que desejar para a classe sem ela, incluindo um objeto simulado.
Robert Harvey
2
@ Airn5475 tenha cuidado para não exagerar na abstração. Esses testes devem testar comportamentos significativos - testar se um XServicepassa um parâmetro para XRepositorye retorna o que é devolvido não é muito útil para ninguém e também não oferece muito em termos de extensibilidade. Escreva seus testes de unidade para testar um comportamento significativo e garantir que seus componentes não sejam tão estreitos que você esteja transferindo toda a sua lógica para a raiz da composição.
Ant P
Eu não entendo como a inclusão do construtor infrator ajudaria nos testes de unidade, certamente faz o oposto? Remova o construtor ofensivo para que o DI seja implementado corretamente e passe uma dependência simulada ao instanciar os objetos.
Rob
@ Rob: Não. É apenas uma maneira conveniente para o construtor padrão levantar um objeto válido.
Robert Harvey
16

Você faz uma suposição falsa aqui sobre o que é "DI do pobre homem".

Criar uma classe que tenha um construtor de "atalho" que ainda crie acoplamento não é um DI de pobre.

Não usar um contêiner e criar manualmente todas as injeções e mapeamentos é o DI do pobre homem.

O termo "DI do pobre homem" parece uma coisa ruim a se fazer. Por esse motivo, o termo "DI puro" é incentivado atualmente , pois soa mais positivo e na verdade descreve com mais precisão o processo.

Não apenas é absolutamente bom usar o DI de homem pobre / puro para introduzir o DI em um aplicativo existente, mas também é uma maneira válida de usar o DI em muitos aplicativos novos. E, como você diz, todos devem usar o DI puro em pelo menos um projeto para realmente entender como o DI funciona, antes de assumir a responsabilidade pela "mágica" de um contêiner de IoC.

David Arno
fonte
8
DI "Pobre homem" ou "Puro", o que você preferir, também atenua muitos dos problemas com contêineres de IoC. Eu costumava usar contêineres IoC para tudo, mas quanto mais tempo passa, mais prefiro evitá-los.
Ant P
7
@ AntP, estou com você: tenho DI 100% puro hoje em dia e evito ativamente os contêineres. Mas eu (nós) somos uma minoria nesse ponto, até onde eu sei.
David Arno
2
Parece tão triste - à medida que me afasto do campo "mágico" de contêineres de IoC, zombando de bibliotecas e assim por diante, e me vejo abraçando OOP, encapsulamento, duplos de testes feitos à mão e verificação de estado, parece que a tendência para a magia ainda está no ar acima. +1 de qualquer maneira.
Ant P
3
@ AntP & David: É bom saber que não estou sozinha no campo 'Pure DI'. Os contêineres DI foram criados para solucionar deficiências em idiomas. Eles agregam valor nesse sentido. Mas, em minha opinião, a resposta real é consertar os idiomas (ou mudar para outros idiomas) e não criar fragmentos sobre eles.
precisa saber é o seguinte
1

Mudanças de Paragidm em equipes legadas / bases de código são extremamente arriscadas:

Sempre que você sugere "melhorias" no código legado e há programadores "legados" na equipe, você está apenas dizendo a todos que "eles fizeram de errado" e você se torna O Inimigo pelo resto do seu tempo na empresa.

Usar um DI Framework como um martelo para esmagar todos os pregos tornará o código legado pior do que melhor em todos os casos. Também é extremamente arriscado pessoalmente.

Mesmo nos casos mais limitados, apenas para que ele possa ser usado em casos de teste, esses códigos de código "não padrão" e "estrangeiro" serão apenas marcados @Ignorequando forem quebrados ou, pior ainda, reclamados constantemente pelo programadores legados com mais influência no gerenciamento e são reescritos "corretamente" com todo esse "desperdício de tempo em testes de unidade", atribuído exclusivamente a você.

A introdução de uma estrutura de DI, ou mesmo o conceito de "Pure DI" em um aplicativo de linha de negócios, muito menos uma enorme base de códigos herdada sem a administração, a equipe e, principalmente, o patrocínio do desenvolvedor líder serão o ponto de partida. para você social e politicamente na equipe / empresa. Fazer coisas assim é extremamente arriscado e pode ser um suicídio político da pior maneira.

Injeção de Dependência é uma solução que procura um problema.

Qualquer linguagem com construtores por definição usa injeção de dependência por convenção, se você sabe o que está fazendo e entende como usar adequadamente um construtor, esse é apenas um bom design.

A injeção de dependência é útil apenas em pequenas doses para uma gama muito estreita de coisas:

  • Coisas que mudam muito ou têm muitas implementações alternativas vinculadas estaticamente.

    • Os drivers JDBC são um exemplo perfeito.
    • Clientes HTTP que podem variar de plataforma para plataforma.
    • Sistemas de log que variam de acordo com a plataforma.
  • Sistemas de plug-in que possuem plug-ins configuráveis ​​que podem ser definidos no código de configuração em sua estrutura e descobertos automaticamente na inicialização e carregados / recarregados dinamicamente enquanto o programa está em execução.


fonte
10
O aplicativo matador para DI é o teste de unidade. Como você apontou, a maioria dos softwares não precisa de muita DI por si só. Mas os testes também são um cliente desse código, portanto, devemos projetar para testabilidade. Em particular, isso significa colocar costuras em nosso projeto, onde os testes podem observar o comportamento. Isso não requer uma estrutura de DI sofisticada, mas exige que as dependências relevantes sejam explicitadas e substituíveis.
amon
4
Se nós, devs, soubéssemos se avançaríamos o que poderia mudar, isso seria metade da batalha. Eu estive em desenvolvimentos longos, onde muitas coisas supostamente "fixas" mudaram. Prova de futuro aqui e ali usando DI não é uma coisa ruim. Usá-lo em todos os lugares o tempo todo cheira a programação de cultos de carga.
Robbie Dee
complicar demais o teste significa que eles ficarão obsoletos e marcados como ignorados, em vez de atualizados no futuro. A DI no código legado é uma economia falsa. Tudo o que eles precisam fazer é torná-lo o "bandido" por colocar todo esse "código não-padrão e excessivamente complexo que ninguém entende" na base de código. Você foi avisado.
4
@JarrodRoberson, esse é realmente um ambiente tóxico. Um dos principais sinais de um bom desenvolvedor é que eles olham para o código que escreveram no passado e pensam: "ugh, eu realmente escrevi isso? Isso é tão errado!" Isso porque eles cresceram como desenvolvedores e aprenderam coisas novas. Alguém que olha o código que escreveu há cinco anos e não vê nada de errado com ele não aprendeu nada nesses cinco anos. Portanto, se contar às pessoas que elas fizeram de errado no passado faz de você um inimigo, então fuja dessa empresa rapidamente, pois eles são um time sem saída que o impedirá e o reduzirá ao nível ruim.
David Arno
1
Não tenho certeza se concordo com o material do bandido, mas para mim o principal argumento é que você não precisa de DI para fazer testes de unidade. A implementação da DI para tornar o código mais testável é apenas corrigir o problema.
Ant P