Injeção de dependência: como vendê-lo [fechado]

95

Saiba que eu sou um grande fã de injeção de dependência (DI) e testes automatizados. Eu poderia falar o dia todo sobre isso.

fundo

Recentemente, nossa equipe acabou de adquirir esse grande projeto que deve ser construído do zero. É uma aplicação estratégica com requisitos de negócios complexos. É claro que eu queria que fosse agradável e limpo, o que para mim significava: manutenível e testável. Então, eu queria usar DI.

Resistência

O problema estava em nossa equipe, DI é um tabu. Foi mencionado algumas vezes, mas os deuses não aprovam. Mas isso não me desanima.

My Move

Isso pode parecer estranho, mas as bibliotecas de terceiros geralmente não são aprovadas pela nossa equipe de arquitetos (pense: "você não deve falar de Unity , Ninject , NHibernate , Moq ou NUnit , para que eu não corte o seu dedo"). Então, em vez de usar um contêiner DI estabelecido, escrevi um contêiner extremamente simples. Basicamente, ligou todas as suas dependências na inicialização, injeta todas as dependências (construtor / propriedade) e descarta qualquer objeto descartável no final da solicitação da Web. Era extremamente leve e fazia o que precisávamos. E então eu pedi para eles revisarem.

A resposta

Bem, para abreviar. Fui recebido com forte resistência. O principal argumento foi: "Não precisamos adicionar essa camada de complexidade a um projeto já complexo". Além disso, "não é como se estivéssemos conectando diferentes implementações de componentes". E "Queremos mantê-lo simples, se possível, apenas coloque tudo em uma montagem. DI é uma complexidade desnecessária, sem nenhum benefício".

Finalmente, minha pergunta

Como você lidaria com a minha situação? Não sou bom em apresentar minhas idéias e gostaria de saber como as pessoas apresentariam seus argumentos.

Claro, estou assumindo que, como eu, você prefere usar DI. Se você não concorda, diga o porquê para que eu possa ver o outro lado da moeda. Seria realmente interessante ver o ponto de vista de alguém que discorda.

Atualizar

Obrigado pelas respostas de todos. Ele realmente coloca as coisas em perspectiva. É bom o suficiente ter outro par de olhos para dar feedback, quinze é realmente incrível! Essas são realmente ótimas respostas e me ajudaram a ver a questão de lados diferentes, mas só posso escolher uma resposta, então vou escolher a que mais votou. Obrigado a todos por reservar um tempo para responder.

Decidi que provavelmente não é o melhor momento para implementar a DI, e não estamos prontos para isso. Em vez disso, concentrarei meus esforços em tornar o design testável e tentarei apresentar testes de unidade automatizados. Estou ciente de que escrever testes é uma sobrecarga adicional e, se alguma vez for decidido que a sobrecarga adicional não vale a pena, pessoalmente ainda a consideraria uma situação vitoriosa, pois o design ainda pode ser testado. E se alguma vez testar ou DI for uma escolha no futuro, o design poderá lidar com isso com facilidade.

Mel
fonte
64
Parece que você precisa para se deslocar para uma empresa diferente ...
Sardathrion
24
O uso de DI é (pelo menos em Java, C #, ...) quase uma consequência natural do uso de testes de unidade. Sua empresa usa testes de unidade?
sebastiangeiger
27
Ou simplesmente não se concentra no DI. DI é diferente de "manutenível e testável". Quando existem opiniões diferentes sobre um projeto, você deve compor e, às vezes, aceitar que não decide. Eu, por exemplo, vi desastres amplificados por inversão de controles, DI e várias camadas de estruturas, criando coisas complexas que deveriam ser simples (não posso argumentar no seu caso).
Denys Séguret
34
"... eu poderia falar o dia todo sobre isso." Parece que você precisa parar de ficar obcecado com algo que é abusado com mais frequência do que por causa da adesão dogmática a algum white paper do dia.
21
Pelo que parece, seus colegas de trabalho têm argumentos objetivos que se opõem ao uso de contêineres DI: “não precisamos adicionar essa camada de complexidade a um projeto já complexo” - eles talvez estejam certos? Você realmente se beneficia com a complexidade adicional? Nesse caso, isso deve ser possível argumentar. Eles afirmam que “o DI é uma complexidade desnecessária, sem benefício”. Se você pode mostrar um benefício objetivo, deve ganhar isso com bastante facilidade, não? O teste é mencionado com bastante frequência nos outros comentários; mas o teste não precisa fundamentalmente de DI, a menos que você precise de zombarias (o que não é um dado).
Konrad Rudolph

Respostas:

62

Tomando alguns dos argumentos contrários:

"Queremos simplificá-lo, se possível, reunir tudo em um único conjunto. DI é uma complexidade desnecessária, sem nenhum benefício".

"não é como se estivéssemos conectando diferentes implementações de componentes".

O que você deseja é que o sistema seja testável. Para ser facilmente testável, você precisa zombar das várias camadas do projeto (banco de dados, comunicações etc.) e, nesse caso , estará conectando diferentes implementações de componentes.

Venda DI sobre os benefícios de teste que ele oferece. Se o projeto for complexo, você precisará de bons e sólidos testes de unidade.

Outro benefício é que, ao codificar para interfaces, se você obtiver uma implementação melhor (mais rápida, com menos memória, qualquer que seja) de um de seus componentes, o uso do DI torna muito mais fácil trocar a implementação antiga pelo Novo.

O que estou dizendo aqui é que você precisa abordar os benefícios que o DI traz, em vez de argumentar por ele em prol do DI. Ao fazer as pessoas concordarem com a afirmação:

"Precisamos de X, Y e Z"

Você então muda o problema. Você precisa ter certeza de que DI é a resposta para esta afirmação. Ao fazer isso, os colegas de trabalho serão os donos da solução, em vez de sentirem que ela foi imposta a eles.

ChrisF
fonte
+1. Breve e direto ao ponto. Infelizmente, a partir dos argumentos apresentados pelos colegas de trabalho de Mel, ele terá dificuldade em convencê-los de que vale a pena tentar - o que Spoike disse em sua resposta, mais um problema do que uma questão técnica.
Patryk Ćwiek
1
@ Trustme-I'maDoctor - Oh, eu concordo que é um problema das pessoas, mas com essa abordagem você está mostrando os benefícios, em vez de defender a DI por si mesma.
ChrisF
Verdadeira e desejo-lhe sorte, sua vida como um desenvolvedor será mais fácil com a capacidade de teste adequada etc. :)
Patryk Ćwiek
4
+1 Acho que seu último parágrafo deve ser o primeiro. Isso é central para o problema. Se fosse possível fazer o teste de unidade e for sugerido a conversão de todo o sistema para um modelo de DI, eu também diria não.
Andrew T Finnell
+1 muito útil mesmo. Eu não diria que o DI é uma necessidade absoluta para testes de unidade, mas facilita muito as coisas.
Mel
56

Pelo que você está escrevendo, não o DI em si é um tabu, mas o uso de contêineres de DI, o que é uma coisa diferente. Acho que você não terá problemas com sua equipe ao escrever componentes independentes que recebem outros componentes necessários, por exemplo, pelo construtor. Só não chame isso de "DI".

Você pode pensar que esses componentes não usam um contêiner de DI é entediante e você está certo, mas dê tempo à sua equipe para descobrir isso por si mesma.

Doc Brown
fonte
10
+1 uma solução pragmática para um impasse político / dogmática
k3b
32
Vale a pena considerar conectar todos os seus objetos manualmente de qualquer maneira e introduzir um contêiner de DI somente quando ele começar a ficar peludo. Se você fizer isso, eles não verão os contêineres DI como uma complexidade adicional - eles verão uma simplicidade adicional.
23412 Phil
2
O único problema é que o padrão de passagem de dependências fracamente acopladas para dependentes é DI. IoC, ou o uso de um "contêiner" para resolver dependências fracamente acopladas, é DI automatizado .
24512 KeithS
51

Desde que você pediu, eu ficarei do lado do seu time contra o DI.

O caso contra injeção de dependência

Existe uma regra que eu chamo de "Conservação da Complexidade" que é análoga à regra da física chamada "Conservação de Energia". Ele afirma que nenhuma quantidade de engenharia pode reduzir a complexidade total de uma solução ideal para um problema; tudo o que pode fazer é mudar essa complexidade. Os produtos da Apple não são menos complexos que os da Microsoft, eles simplesmente movem a complexidade do espaço do usuário para o espaço da engenharia. (Porém, é uma analogia falha. Embora você não possa criar energia, os engenheiros têm o hábito desagradável de criar complexidade a cada passo. De fato, muitos programadores parecem gostar de adicionar complexidade e usar magia, que é por definição a complexidade que o programador não entende completamente. Esse excesso de complexidade pode ser reduzido.) Geralmente, o que parece reduzir a complexidade é simplesmente mudar a complexidade para outro lugar, geralmente para uma biblioteca.

Da mesma forma, o DI não reduz a complexidade de vincular objetos do cliente a objetos do servidor. O que ele faz é retirá-lo do Java e movê-lo para XML. Isso é bom, pois centraliza tudo em um (ou alguns) arquivos XML, onde é fácil ver tudo o que está acontecendo e onde é possível mudar as coisas sem recompilar o código. Por outro lado, isso é ruim porque os arquivos XML não são Java e, portanto, não estão disponíveis para ferramentas Java. Você não pode depurá-los no depurador, não pode usar a análise estática para rastrear a hierarquia de chamadas e assim por diante. Em particular, os erros na estrutura de DI se tornam extremamente difíceis de detectar, identificar e corrigir.

Portanto, é muito mais difícil provar que um programa está correto ao usar o DI. Muitas pessoas argumentam que os programas que usam DI são mais fáceis de testar , mas o teste de programa nunca pode provar a ausência de bugs . (Observe que o artigo vinculado tem cerca de 40 anos e, portanto, tem algumas referências misteriosas e cita algumas práticas desatualizadas, mas muitas das afirmações básicas ainda são verdadeiras. Em particular, P <= p ^ n, em que P é a probabilidade de que o sistema esteja livre de erros, p é a probabilidade de um componente do sistema estar livre de erros e n é o número de componentes. Obviamente, essa equação é simplificada demais, mas aponta para uma verdade importante.) A falha da NASDAQ durante o IPO do Facebook é um exemplo recente, em queeles alegaram ter passado 1.000 horas testando o código e ainda não descobriram os bugs que causaram o acidente. 1.000 horas são meia pessoa-ano, então não é como se estivessem economizando nos testes.

É verdade que quase ninguém se compromete (muito menos conclui) a tarefa de provar formalmente que qualquer código está correto, mas mesmo tentativas fracas de prova vencem a maioria dos regimes de teste para encontrar bugs. Fortes tentativas de prova, por exemplo , L4.verified , invariavelmente descobrem uma grande quantidade de bugs que os testes não descobriram e provavelmente nunca descobririam, a menos que o testador já soubesse a natureza exata do bug. Qualquer coisa que dificulte a verificação do código por inspeção pode ser vista como uma redução da chance de erros serem detectados , mesmo se aumentar a capacidade de testar.

Além disso, o DI não é necessário para o teste . Eu raramente o uso para esse fim, pois prefiro testar o sistema contra o software real do sistema do que em alguma versão de teste alternativa. Tudo o que mudo no teste é (ou pode ser) armazenado em arquivos de propriedades que direcionam o programa para recursos no ambiente de teste e não para produção. Eu prefiro gastar tempo configurando um servidor de banco de dados de teste com uma cópia do banco de dados de produção do que gastando tempo codificando um banco de dados simulado, porque dessa forma poderei rastrear problemas com os dados de produção (problemas de codificação de caracteres são apenas um exemplo ) e erros de integração do sistema, bem como erros no restante do código.

A injeção de dependência também não é necessária para a inversão de dependência . Você pode usar facilmente métodos estáticos de fábrica para implementar a Inversão de Dependências. De fato, foi assim que ocorreu nos anos 90.

Na verdade, embora eu esteja usando o DI há uma década, as únicas vantagens que eu acho persuasivas são a capacidade de configurar bibliotecas de terceiros para usar outras bibliotecas de terceiros (por exemplo, configurar o Spring para usar o Hibernate e ambos para usar o Log4J ) e a habilitação da IoC por meio de proxies. Se você não está usando bibliotecas de terceiros e não utiliza IoC, não há muito sentido nisso e, de fato, adiciona complexidade injustificada.

Old Pro
fonte
1
Eu discordo, é possível reduzir a complexidade. Eu sei porque eu fiz isso. Certamente, alguns problemas / requisitos forçam a complexidade, mas acima disso a maioria pode ser removida com esforço.
Ricky Clarkson
10
@ Ricky, é uma distinção sutil. Como eu disse, os engenheiros podem adicionar complexidade e essa complexidade pode ser removida; portanto, você pode ter reduzido a complexidade da implementação, mas, por definição, não pode reduzir a complexidade da solução ideal para um problema. Na maioria das vezes, o que parece reduzir a complexidade é apenas transferi-lo para outro lugar (geralmente para uma biblioteca). De qualquer forma, é secundário ao meu ponto principal, que é que o DI não reduz a complexidade.
Old Pro
2
@ Lee, configurar DI no código é ainda menos útil. Um dos benefícios mais aceitos do DI é a capacidade de alterar a implementação do serviço sem recompilar o código e, se você configurar o DI no código, perderá isso.
Old Pro
7
@OldPro - A configuração no código tem o benefício da segurança de tipo, e a maioria das dependências é estática e não tem o benefício de ser definida externamente.
23412 Lee
3
@ Lee Se as dependências são estáticas, por que não são codificadas em primeiro lugar, sem o desvio via DI?
Konrad Rudolph
25

Lamento dizer que o problema que você tem, considerando o que seus colegas de trabalho disseram em sua pergunta, é de natureza política e não técnica. Existem dois mantras que você deve ter em mente:

  • "É sempre um problema de pessoas"
  • "A mudança é assustadora"

Claro, você pode apresentar muitos argumentos contrários com sólidas razões e benefícios técnicos, mas isso o colocará em uma situação ruim com o restante da empresa. Eles já têm uma postura de que não querem isso (porque estão confortáveis ​​com a forma como as coisas estão funcionando agora). Portanto, pode até prejudicar seu relacionamento com seus colegas se você continuar sendo persistente. Confie em mim porque isso aconteceu comigo e acabou me despedindo há vários anos (jovem e ingênuo que eu era); esta é uma situação em que você não deseja se envolver.

A questão é que você precisa atingir algum nível de confiança antes que as pessoas cheguem ao ponto de mudar sua maneira atual de trabalhar. A menos que você esteja em uma posição em que tenha muita confiança ou possa decidir esse tipo de coisa, como ter o papel de arquiteto ou líder técnico, há muito pouco que você pode fazer para mudar seus colegas. Levará muito tempo com persuasão e esforço (como em pequenas etapas de aprimoramento) para ganhar confiança e fazer com que a cultura da sua empresa mude. Estamos falando de um período de tempo de muitos meses ou anos.

Se você acha que a cultura atual da empresa não está funcionando com você, pelo menos você sabe outra coisa a perguntar na sua próxima entrevista de emprego. ;-)

Spoike
fonte
23

Não use DI. Esqueça.

Crie uma implementação do padrão de fábrica GoF que "crie" ou "encontre" suas dependências específicas e as passe para seu objeto e deixe assim, mas não chame DI e não crie uma estrutura generalizada complexa. Mantenha-o simples e relevante. Certifique-se de que as dependências e suas classes sejam de tal ordem que seja possível mudar para DI; mas mantenha a fábrica como mestre e mantenha-a extremamente limitada em escopo.

Com o tempo, você pode esperar que ele cresça e até um dia faça a transição para o DI. Deixe os leads chegarem à conclusão em seu próprio tempo e não empurre. Mesmo que você nunca chegue a isso, pelo menos o seu código tem algumas vantagens do DI, por exemplo, código testável por unidade.

jasonk
fonte
3
+1 para crescer junto com o projeto e "não chamá-lo DI"
Kevin McCormick
5
Eu concordo, mas no final, é DI. Apenas não está usando um contêiner de DI; está passando o fardo para o chamador em vez de centralizá-lo em um só lugar. No entanto, seria inteligente chamá-lo de "Externalização de dependências" em vez de DI.
Juan Mendes
5
Muitas vezes me ocorreu que, para realmente criar um conceito de torre de marfim como o DI, você deve ter passado pelo mesmo processo que os evangelistas para perceber que havia um problema a ser resolvido em primeiro lugar. Os autores costumam esquecer isso. Atenha-se à linguagem de padrões de baixo nível e a necessidade de um contêiner pode (ou não) surgir como conseqüência.
Tom W
@JuanMendes, pela resposta deles, estou pensando que externalizar dependências é especificamente o que elas se opõem, porque também não gostam de bibliotecas de terceiros e querem tudo em um único assembly (monólito?). Se isso é verdade, chamá-lo de "Externalização de dependências" não é melhor do que chamar DI de seu ponto de vista.
27715 Jonathan Jönfeld as
22

Eu já vi projetos que levaram o DI ao extremo para testar unidades e zombar de objetos. Tanto que a configuração do DI se tornou a linguagem de programação em si, com a mesma configuração necessária para que o sistema funcionasse como o código real.

O sistema está sofrendo agora devido à falta de APIs internas apropriadas que não podem ser testadas? Você espera que a mudança do sistema para um modelo DI permita um teste de unidade melhor? Você não precisa de um contêiner para testar seu código de unidade. O uso de um modelo de DI permitiria um teste de unidade mais fácil com objetos Mock, mas não é necessário.

Você não precisa mudar todo o sistema para ser compatível com DI de uma só vez. Você também não deve escrever arbitrariamente testes de unidade. Refatore o código conforme você o encontra em seus recursos e trabalhe com tickets de trabalho. Depois, verifique se o código que você tocou é facilmente testável.

Pense no CodeRot como uma doença. Você não quer começar a cortar arbitrariamente as coisas. Você tem um paciente, vê um ponto dolorido e começa a consertá-lo e as coisas ao seu redor. Depois que a área estiver limpa, você passa para a próxima. Mais cedo ou mais tarde, você terá tocado a maioria do código e não exigiu essa mudança arquitetônica "maciça".

Não gosto que os testes DI e Mock sejam mutuamente exclusivos. E depois de ver um projeto que foi, por falta de uma palavra melhor, louco usando DI, eu ficaria cansado também sem ver o benefício.

Existem alguns lugares que fazem sentido usar o DI:

  • A camada de acesso a dados para trocar estratégias de dados de criação de histórias
  • Camada de autenticação e autorização.
  • Temas para a interface do usuário
  • Serviços
  • Pontos de extensão de plug-in que seu próprio sistema ou terceiros podem usar.

Exigir que cada desenvolvedor saiba onde está o arquivo de configuração da DI (percebo que os contêineres podem ser instanciados por meio do código, mas por que você faria isso.) Quando eles desejam executar um RegEx é frustrante. Ah, eu vejo que há IRegExCompiler, DefaultRegExCompiler e SuperAwesomeRegExCompiler. No entanto, em nenhum lugar do código posso fazer uma análise para ver onde o padrão é usado e o SuperAwesome é usado.

Minha própria personalidade reagiria melhor se alguém me mostrasse algo melhor, depois tentasse me convencer com suas palavras. Há algo a ser dito sobre alguém que colocaria tempo e esforço para melhorar o sistema. Não acho que muitos desenvolvedores se importem o suficiente para tornar o produto realmente melhor. Se você pudesse abordá-los com mudanças reais no sistema e mostrar como isso o tornou melhor e mais fácil para você, vale mais do que qualquer pesquisa on-line.

Em resumo, a melhor maneira de efetuar alterações em um sistema é lentamente tornar o código que você está tocando melhor a cada passagem. Mais cedo ou mais tarde, você poderá transformar o sistema em algo melhor.

Andrew Finnell
fonte
"Os testes DI e Mock são mutuamente exclusivos" Isso é falso, eles também não são mutuamente inclusivos. Eles apenas trabalham bem juntos. Você também lista os locais onde o DI é bom, camada de serviço, dao e interface do usuário - em quase todos os lugares, não é?
NimChimpsky
@Andrew pontos válidos. Claro, eu não gostaria que isso fosse longe demais. Eu queria o teste automatizado para as classes de negócios, pois precisamos implementar regras de negócios muito complexas. E percebo que ainda posso testá-los sem contêiner, mas pessoalmente, faz sentido usar um contêiner. Na verdade, fiz uma pequena amostra que mostrou o conceito. Mas nem foi olhado. Acho que meu erro foi não criar um teste que mostrasse como é fácil testar / zombar.
Mel
Tanto acordo! Eu realmente odeio arquivos de configuração de DI e o efeito que isso tem no rastreamento do fluxo do programa. A coisa toda se transforma em "ação assustadora à distância", sem conexões aparentes entre as peças. Quando fica mais fácil ler o código em um depurador do que nos arquivos de origem, algo está realmente errado.
Zan Lynx
19

O DI não requer contêineres invasivos de Inversão de Controle ou estruturas de simulação. Você pode desacoplar o código e permitir o DI por um design simples. Você pode fazer testes de unidade usando os recursos internos do Visual Studio.

Para ser justo, seus colegas de trabalho têm razão. Usar mal essas bibliotecas (e como elas não estão familiarizadas com elas, haverá dificuldades de aprendizado) causará problemas. No final, o código é importante. Não estrague seu produto para seus testes.

Lembre-se de que o compromisso é necessário nesses cenários.

Telastyn
fonte
2
Eu concordo que o DI não exija contêineres IoC. embora eu não o chamasse invasivo (pelo menos essa implementação), já que o código foi projetado para ser independente de contêiner (principalmente injeções de construtor) e todo o material de DI acontece em um só lugar. Portanto, ainda funciona tudo sem um contêiner de DI - eu coloquei construtores sem parâmetros que "injetam" as implementações concretas no outro construtor com dependências. Dessa forma, ainda posso zombar facilmente. Então, sim, eu concordo, o DI é realmente alcançado pelo design.
Mel
+1 por compromisso. Se ninguém quer se comprometer, todo mundo acaba sofrendo.
Tjaart 25/05
14

Eu não acho que você possa vender DI mais, porque essa é uma decisão dogmática (ou, como @Spoike chamou mais educadamente, política ).

Nesta situação, discutir com as melhores práticas amplamente aceitas, como os princípios de design do SOLID, não é mais possível.

Alguém que tem mais poder político do que você tomou uma decisão (talvez errada) de não usar DI.

Por outro lado, você se opôs a essa decisão implementando seu próprio contêiner porque era proibido usar um contêiner estabelecido. Essa política de fatos consumados que você tentou pode ter piorado a situação.

Análise

Na minha opinião, o DI sem desenvolvimento orientado a testes (TDD) e testes de unidade não possui benefícios significativos que superem os custos extras iniciais.

Esta questão é realmente sobre

  • nós não queremos DI ?

ou isso é mais sobre

  • tdd / unittesting é um desperdício de recursos ?

[Atualizar]

Se você vir seu projeto com base nos erros clássicos na exibição de gerenciamento de projetos , verá

#12: Politics placed over substance.
#21: Inadequate design. 
#22: Shortchanged quality assurance.
#27: Code-like-hell programming.

enquanto os outros veem

#3: Uncontrolled problem employees. 
#30: Developer gold-plating. 
#32: Research-oriented development. 
#34: Overestimated savings from new tools or methods. 
k3b
fonte
No momento, não estamos fazendo nenhum teste de unidade em nossa equipe. Suspeito que sua última declaração esteja no local. Eu trouxe à tona o teste de unidade algumas vezes, mas ninguém mostrou nenhum interesse real por ele e sempre houve razões pelas quais não podemos adotá-lo.
Mel
10

Se você tiver que "vender" um princípio para sua equipe, provavelmente já deve estar a vários quilômetros do caminho errado.

Ninguém quer fazer um trabalho extra; portanto, se o que você está tentando vendê-los parecer um trabalho extra, você não os convencerá com a Invocação repetida do argumento superior. Eles precisam saber que você está mostrando a eles uma maneira de reduzir a carga de trabalho, e não aumentá-la.

A DI é vista como complexidade extra agora, com a promessa de uma potencial complexidade reduzida posteriormente , que tem valor, mas não tanto para alguém que está tentando reduzir sua carga de trabalho inicial. E, em muitos casos, o DI é apenas mais uma camada do YAGNI . E o custo dessa complexidade não é zero, é realmente muito alto para uma equipe que não está acostumada a esse tipo de padrão. E isso é dólares reais sendo jogados em um balde que pode nunca render nenhum benefício.

Coloque no lugar A
sua melhor aposta é usar o DI, onde é extremamente útil, e não apenas onde é comum. Por exemplo, muitos aplicativos usam o DI para selecionar e configurar bancos de dados. E se o seu aplicativo for enviado com uma camada de banco de dados, é um local tentador para colocá-lo. Mas se o seu aplicativo for fornecido com um banco de dados invisível ao usuário interno que, de outra forma, é totalmente integrado ao código, as chances de precisar substituir o banco de dados no tempo de execução se aproximam de zero. E vender a DI dizendo: "olha, nós podemos facilmente fazer isso que nunca, nunca, jamais queremos fazer", provavelmente colocará você na lista "esse cara tem más idéias" com seu chefe.

Mas digamos que você tenha uma saída de depuração que normalmente é liberada por IFDEF no lançamento. E sempre que um usuário tem um problema, sua equipe de suporte precisa enviar a ele uma compilação de depuração, para que eles possam obter a saída do log. Você pode usar o DI aqui para permitir que o cliente alterne entre drivers de log - LogConsolepara desenvolvedores, LogNullclientes e LogNetworkclientes com problemas. Uma construção unificada, tempo de execução configurável.

Então você nem precisa chamá-lo de DI. Em vez disso, é chamado de "ideia realmente boa de Mel" e agora você está na lista de boas idéias, em vez da má. As pessoas verão como o padrão funciona e começarão a usá-lo onde fizer sentido em seu código.

Quando você vende adequadamente uma ideia, as pessoas não sabem que você as convenceu de nada.

tylerl
fonte
8

Obviamente, a Inversão de controle (IoC) tem um grande número de benefícios: simplifica o código, adiciona um pouco de mágica que está realizando as tarefas típicas, etc.

Contudo:

  1. Adiciona muitas dependências. O aplicativo hello world simples escrito com o Spring pode pesar uma dúzia de MB, e eu vi o Spring adicionado apenas para evitar algumas linhas de construtor e setter.

  2. Ele adiciona a mágica mencionada acima , que é difícil de entender para quem está de fora (por exemplo, desenvolvedores de GUI que precisam fazer algumas alterações na camada de negócios). Veja o excelente artigo Mentes inovadoras não pensam da mesma forma - New York Times , que descreve como esse efeito é subestimado pelos especialistas .

  3. Isso dificulta o rastreamento do uso de classes. IDEs como o Eclipse têm grandes habilidades para rastrear usos de determinadas classes ou invocações de métodos. Mas esse rastreamento é perdido quando há injeção e reflexão de dependência invocadas.

  4. O uso de IoC normalmente não termina com dependências de injeção, termina com inúmeros proxies, e você obtém rastreamento de pilha contando centenas de linhas, 90% das quais são proxies e invoca via reflexão. Isso complica bastante a análise de rastreamento de pilha.

Sendo um grande fã de Spring e IoC, recentemente tive que repensar as perguntas acima. Alguns de meus colegas são desenvolvedores da Web, considerados dentro do mundo Java, governados por Python e PHP, e o óbvio para as coisas javaístas é algo óbvio para eles. Alguns de meus colegas estão fazendo truques (obviamente não documentados), o que dificulta a localização do erro. É claro que o uso indevido de IoC torna as coisas mais leves para eles;)

Meu conselho, se forçar o uso de IoC em seu trabalho, você será responsabilizado por qualquer uso indevido que alguém possa fazer;)

Łukasz Lech
fonte
+1 como em todas as ferramentas poderosas, é facilmente mal utilizado e você DEVE entender quando NÃO usá-lo.
23412 Phil
4

Eu acho que ChrisF está em algo certo. Não venda DI por si só. Mas venda a ideia de testar o código da unidade.

Uma abordagem para inserir um contêiner de DI na arquitetura seria permitir que lentamente os testes levassem a implementação a algo que se encaixasse bem nessa arquitetura. Por exemplo, digamos que você esteja trabalhando em um controlador em um aplicativo ASP.NET MVC. Digamos que você escreva a classe assim:

public class UserController {
    public UserController() : this (new UserRepository()) {}

    public UserController(IUserRepository userRepository) {
        ...
    }

    ...
}

Isso permite que você escreva testes de unidade dissociados da implementação real do repositório do usuário. Mas a classe ainda funciona sem um contêiner de DI para criá-lo para você. O construtor sem parâmetros o criará com o repositório padrão.

Se vocês, colegas de trabalho, perceberem que isso leva a testes muito mais limpos, talvez eles possam perceber que é uma maneira melhor. Talvez você possa fazê-los começar a codificar assim.

Depois que eles perceberem que esse é um design melhor do que o UserController sabendo sobre uma implementação específica do UserRepository, você poderá dar o próximo passo e mostrar a eles que você pode ter um componente, o contêiner DI, que pode fornecer automaticamente as instâncias necessárias.

Pete
fonte
Ótimo! Atualmente, estou fazendo isso porque realmente quero o teste de unidade automatizado, mesmo que não consiga obter DI. Eu esqueci de mencionar que unidade de testes também não são feitas em nossa equipe :( por isso, se eu vou ser o primeiro.
Mel
2
Nesse caso, eu diria que esse é o verdadeiro problema aqui. Encontre a raiz da resistência ao teste de unidade e ataque-a. Deixe DI mentir por enquanto. O teste é mais importante IMHO.
Christian Horsdal
@ChristianHorsdal Eu concordo, queria que ele fosse fracamente acoplado para permitir zombarias / testes fáceis.
Mel
Essa idéia é muito legal ... Great sell para o teste
bunglestink
2

Talvez a melhor coisa a fazer seja recuar e se perguntar por que você deseja introduzir contêineres DI? Para melhorar a testabilidade, sua primeira tarefa é fazer com que a equipe compre testes de unidade. Pessoalmente, eu ficaria muito mais preocupado com a falta de testes de unidade do que com a falta de um contêiner de DI.

Armado com um conjunto abrangente ou conjuntos de testes de unidade, você pode confiar em evoluir seu projeto ao longo do tempo. Sem eles, você enfrenta uma luta cada vez mais difícil para manter seu design atualizado com os requisitos em evolução, uma luta que, eventualmente, muitas equipes perdem.

Portanto, meu conselho seria o primeiro campeão em testes e refatoração de unidades e, por sua vez, a introdução de recipientes DI e, finalmente, DI.

kimj100
fonte
2

Estou ouvindo alguns grandes problemas da sua equipe aqui:

  • "Nenhuma biblioteca de terceiros" - AKA "Not Invented Here" ou "NIH". O medo é que, ao implementar uma biblioteca de terceiros e, assim, tornar a base de código dependente, a biblioteca deva ter um bug ou uma falha de segurança ou mesmo apenas uma limitação que você precisa superar, você se torna dependente desse terceiro consertar sua biblioteca para que seu código funcione. Enquanto isso, como o programa "your" falhou espetacularmente, foi hackeado ou não faz o que eles pediram, você ainda assume a responsabilidade (e os desenvolvedores de terceiros dizem que a ferramenta é "como está" ", Use por sua conta e risco).

    O contra-argumento é que o código que resolve o problema já existe na biblioteca de terceiros. Você está construindo seus computadores a partir do zero? Você escreveu o Visual Studio? Você está evitando as bibliotecas internas do .NET? Não. Você tem um nível de confiança na Intel / AMD e Microsoft e eles encontram erros o tempo todo (e nem sempre os corrigem rapidamente). A adição de uma biblioteca de terceiros permite que você rapidamente trabalhe com uma base de código sustentável, sem precisar reinventar a roda. E o exército de advogados da Microsoft rirá na sua cara quando você lhes disser que um bug nas bibliotecas de criptografia .NET os responsabiliza pelo incidente de invasão que seu cliente sofreu ao usar seu programa .NET. Embora a Microsoft certamente salte para corrigir falhas de segurança "zero hora" em qualquer um de seus produtos,

  • "Queremos colocar tudo em uma montagem" - na verdade, você não. No .NET, pelo menos, se você alterar uma linha de código, todo o assembly que contém essa linha de código deve ser reconstruído. O bom design de código é baseado em vários assemblies que você pode reconstruir da maneira mais independente possível (ou pelo menos apenas reconstruir dependências, não dependentes). Se eles realmente querem lançar um EXE que seja apenas o EXE (que tem valor em determinadas circunstâncias), existe um aplicativo para isso; ILMerge.

  • "DI é tabu" - WTF? O DI é uma prática recomendada aceita em qualquer idioma de O / O com uma construção de código de "interface". Como sua equipe adere às metodologias de design GRASP ou SOLID, se não acoplam livremente as dependências entre os objetos? Se eles não se incomodam, estão perpetuando a fábrica de espaguete que torna o produto o pântano complicado. Agora, o DI aumenta a complexidade aumentando a contagem de objetos e, embora facilite a substituição de uma implementação diferente, torna mais difícil alterar a própria interface (adicionando uma camada extra de objeto de código que precisa ser alterado).

  • "Não precisamos adicionar essa camada de complexidade a um projeto já complexo" - eles podem ter razão. Uma coisa crucial a considerar em qualquer desenvolvimento de código é o YAGNI; "Você não precisa disso". Um design que foi desenvolvido SOLIDly desde o início é um design que é feito SOLID "na fé"; você não sabe se precisará da facilidade de alteração oferecida pelos projetos SOLID, mas tem um palpite. Embora você esteja certo, você também pode estar muito errado; um design muito SÓLIDO pode acabar sendo visto como "código de lasanha" ou "código ravioli", com tantas camadas ou pedaços pequenos que dificultam as alterações aparentemente triviais porque você precisa cavar várias camadas e / ou rastrear uma pilha de chamadas complicada.

    Eu apoio uma regra de três advertências: faça funcionar, limpe, faça SOLID. Quando você escreve uma linha de código pela primeira vez, ela apenas funciona e você não recebe pontos pelos projetos das torres de marfim. Suponha que seja único e faça o que você precisa fazer. Na segunda vez em que você analisa esse código, provavelmente está procurando expandi-lo ou reutilizá-lo, e não é o único que você pensou que seria. Nesse ponto, torne-o mais elegante e compreensível, refatorando-o; simplifique a lógica complicada, extraia o código repetido em loops e / ou chamadas de método, adicione alguns comentários aqui e ali para qualquer comentário que você precise deixar no lugar, etc. Agora você deve aderir às regras do SOLID; injetar dependências em vez de construí-las, extrair classes para manter métodos menos coesos com a "carne" do código,

  • "Não é como se estivéssemos implementando diferentes implementações" - senhor, você tem uma equipe que não acredita em testes de unidade em suas mãos. Eu afirmo que é impossível escrever um teste de unidade, que é executado isoladamente e não tem efeitos colaterais, sem poder "zombar" dos objetos que têm esses efeitos colaterais. Qualquer programa não trivial tem efeitos colaterais; se seu programa lê ou grava em um banco de dados, abre ou salva arquivos, se comunica em uma rede ou soquete periférico, lança outro programa, desenha janelas, produz saídas para o console ou usa memória ou recursos fora da "sandbox" de seu processo, ele possui um efeito colateral. Se isso não acontecer, o que FAZ, e por que devo comprá-lo?

    Para ser justo, o teste de unidade não é a única maneira de provar que um programa funciona; você pode escrever testes de integração, código para algoritmos matematicamente comprovados etc. No entanto, o teste de unidade mostra, muito rapidamente, que, dadas as entradas A, B e C, esse trecho de código gera o X esperado (ou não) e, portanto, que o código funciona corretamente (ou não) em um determinado caso. Conjuntos de testes de unidade podem demonstrar com um alto grau de confiança que o código funciona como esperado em todos os casos e também fornecem algo que uma prova matemática não pode; uma demonstração de que o programa AINDA funciona da maneira como foi planejado. Uma prova matemática do algoritmo não prova que foi implementado corretamente, nem prova que quaisquer alterações feitas foram implementadas corretamente e não o quebraram.

O que falta é que, se essa equipe não vê nenhum valor no que DI faria, eles não a apoiarão, e todo mundo (pelo menos todos os veteranos) terá que comprar algo para uma mudança real acontecer. Eles estão colocando muita ênfase no YAGNI. Você está colocando muita ênfase no SOLID e nos testes automatizados. Em algum lugar no meio está a verdade.

KeithS
fonte
1

Tenha cuidado ao adicionar recursos extras (como DI) a um projeto quando não for aprovado. Se você tiver um plano de projeto com prazos, poderá cair na armadilha de não ter tempo suficiente para concluir os recursos aprovados.

Mesmo que você não use o DI agora, envolva-se nos testes para saber exatamente quais são as expectativas para os testes na sua empresa. Haverá outro projeto e você poderá contrastar os problemas com os testes do projeto atual versus o DI.

Se você não usa o DI, pode ser criativo e tornar seu código DI - "pronto". Use um construtor sem parâmetros para chamar outros construtores:

MyClassConstructor()
{
    MyClassConstructor(new Dependency)
    Property = New Dependent Property
}

MyClassConstructor(Dependency)
{
    _Dependency = Dependency
}
JLH
fonte
0

Eu acho que uma das maiores preocupações deles é exatamente o oposto: o DI não está tornando o projeto complexo. Na maioria dos casos, está reduzindo muita complexidade.

Basta pensar sem o DI, o que você obterá no objeto / componente dependente de que precisa? Normalmente, é pesquisando em um repositório, obtendo um singleton ou instanciando-se.

O primeiro 2 envolve código extra na localização do componente dependente; no mundo do DI, você não fez nada para realizar a pesquisa. A complexidade de seus componentes é de fato reduzida.

Se você está se instanciando em alguns casos que não é apropriado, isso cria ainda mais complexidade. Você precisa saber como criar o objeto corretamente, precisa saber quais valores para inicializar o objeto em um estado viável, todos eles criam complexidade extra ao seu código.

Portanto, o DI não está tornando o projeto complexo, está simplificando, simplificando os componentes.

Outro grande argumento é que você não irá conectar uma implementação diferente. Sim, é verdade que no código de produção não é comum ter muitas implementações diferentes no código de produção real. No entanto, há um local importante que precisa de implementação diferente: Mock / Stub / Test Duplas em testes de unidade. Para fazer com que o DI funcione, na maioria dos casos, ele se baseia no modelo de desenvolvimento orientado por interface, por sua vez, torna possível a simulação / stubbing em testes de unidade. Além de substituir a implementação, o "design orientado a interface" também torna o design mais limpo e melhor. Claro que você ainda pode projetar com interface sem DI. No entanto, testes de unidade e DI estão pressionando você a adotar essas práticas.

A menos que seus "deuses" na empresa pensem que: "código mais simples", "testes de unidade", "modularização", "responsabilidade única" etc. são todos contra eles, não deve haver razão para rejeitar o uso do DI.

Adrian Shum
fonte
Isso é legal em teoria, mas, na prática, adicionar um contêiner de DI É complexidade adicional. A segunda parte do seu argumento mostra por que vale o trabalho, mas ainda funciona.
schlingel 23/05
De fato, de minhas experiências passadas, DI está REDUZINDO em vez de uma adição à complexidade. Sim, ele adicionou alguns extras ao sistema, o que contribui para a complexidade. No entanto, com o DI, a complexidade de outros componentes no sistema é bastante reduzida. No final, a complexidade é de fato reduzida. Para a segunda parte, a menos que eles não estejam fazendo nenhum teste de unidade, NÃO é um trabalho extra. Sem o DI, seus testes de unidade serão difíceis de escrever e manter. Com o DI, o esforço é bastante reduzido. Assim, é de fato REDUZIR do trabalho (a menos que eles não são e não confiar testes de unidade)
Adrian Shum
Devo enfatizar uma coisa: DI na minha resposta não significa nenhuma estrutura de DI existente. É apenas a metodologia no design / desenvolvimento de componentes no seu aplicativo. Você pode se beneficiar muito se seu código for orientado por interface, se seus componentes esperam que as dependências sejam injetadas em vez de pesquisadas / criadas. Com esse design, mesmo que você esteja escrevendo algum "carregador" específico para aplicação que faz a fiação de componentes (sem uso geral DI fw), você ainda está se beneficiando do que mencionei: redução da complexidade e trabalho em testes de unidade
Adrian Shum
0

"Queremos simplificá-lo, se possível, reunir tudo em um único conjunto. DI é uma complexidade desnecessária, sem nenhum benefício".

O benefício é o fato de promover o design de interfaces e facilitar a zombaria. O que consequentemente facilita o teste de unidade. O teste de unidade garante um código confiável, modular e de fácil manutenção. Se o seu chefe ainda mantém uma má ideia, não há nada que você possa fazer - é a melhor prática aceita do setor contra a qual eles estão discutindo.

Faça com que eles tentem escrever um teste de unidade com uma estrutura de DI e uma biblioteca de zombaria, em breve eles aprenderão a amá-lo.

NimChimpsky
fonte
Agora, literalmente, duplicar ou triplicar o número de classes no seu sistema é útil. Isso é semelhante ao debate de tentar obter 100% de cobertura do Teste de Unidade escrevendo um único teste por método ou escrevendo testes de unidade significativos.
Andrew T Finnell
Eu não entendo, você acha que o teste de unidade é uma boa ideia? Se sim, você também zomba de objetos também? Isso é mais fácil com interfaces e DI. Mas isso parece ser contra o que você está argumentando.
NimChimpsky
É realmente o problema do frango e do ovo. Interfaces / DI e testes de unidade são tão relevantes um para o outro que é difícil fazer um sem o outro. Mas acredito que o teste de unidade é um caminho melhor e mais fácil para começar com uma base de código existente. Refatorar gradualmente o código kludgy antigo em componentes coesos. Depois disso, você pode argumentar que a criação de objetos deve ser feita com uma estrutura DI, em vez de entrar em newtodos os lugares e escrever classes de fábrica, já que você possui todos esses componentes.
Spoike 23/05
0

Você trabalha com essas pessoas em projetos menores ou apenas em um grande? É mais fácil convencer as pessoas a tentar algo novo em um projeto secundário / terciário do que o pão e a manteiga da equipe / empresa. As principais razões são que, se falhar, o custo de remoção / substituição é muito menor e é muito menos trabalhoso obtê-lo em todos os lugares em um projeto pequeno. Em uma grande existente, você tem um grande custo inicial para fazer a alteração em todos os lugares ao mesmo tempo, ou um longo período em que o código é uma mistura da abordagem antiga / nova, adicionando atrito, porque você precisa se lembrar de que maneira cada classe é projetada trabalhar.

Dan Neely
fonte
0

Embora uma das coisas que promova o DI seja o teste de unidade, acredito que, em alguns casos, ele adicione uma camada de complexidade.

  • Melhor Ter um carro duro nos testes, mas fácil no reparo do que o carro fácil nos testes e difícil nos reparos.

  • Também me parece que o grupo que está pressionando pela DI está apresentando suas idéias apenas por influenciar que algumas abordagens de design existentes são ruins.

  • se tivermos um método executando 5 funções diferentes

    DI-People diz:

    • sem separação de preocupações. [qual é o problema disso? eles estão tentando fazer com que pareça ruim, o que, na lógica e na programação, é muito melhor saber o que o outro está fazendo de perto para evitar conflitos e expectativas erradas e ver o impacto do código mudar facilmente, porque não podemos fazer toda a sua diferença. objetos não relacionados 100% entre si ou, pelo menos, não podemos garantir isso {a menos que o teste de regressão seja importante?}]
    • Complexos [Eles desejam reduzir a complexidade criando mais cinco classes adicionais para lidar com uma função e uma classe adicional (Container) para criar a classe para a função necessária agora com base nas configurações (XML ou Dicionário), além da filosofia "para ter um padrão / ou não "discussões, [...] então eles mudaram a complexidade para o contêiner e a estrutura [para mim parece mover a complexidade de um lugar para dois lugares com todas as complexidades de especificidades de linguagem para lidar com isso. ]

Acredito que é melhor criar nosso próprio padrão de design que atenda às necessidades da empresa, em vez de ser influenciado pelo DI.

a favor do DI, isso pode ajudar em algumas situações, por exemplo, se você tiver centenas de implementações para a mesma interface, isso depende de um seletor e essas implementações são muito diferentes na lógica; nesse caso, eu usaria técnicas semelhantes ao DI ou ao DI. mas se tivermos poucas implementações para a mesma interface, não precisamos de DI.

user114367
fonte