Inyección de dependencia: cómo venderlo [cerrado]

95

Que se sepa que soy un gran admirador de la inyección de dependencia (DI) y las pruebas automatizadas. Podría hablar todo el día al respecto.

Antecedentes

Recientemente, nuestro equipo acaba de recibir este gran proyecto que se construirá desde cero. Es una aplicación estratégica con requisitos comerciales complejos. Por supuesto, quería que fuera agradable y limpio, lo que para mí significaba: mantenible y comprobable. Entonces quería usar DI.

Resistencia

El problema estaba en nuestro equipo, DI es tabú. Se ha mencionado varias veces, pero los dioses no lo aprueban. Pero eso no me desanimó.

Mi movimiento

Esto puede sonar extraño, pero las bibliotecas de terceros generalmente no están aprobadas por nuestro equipo de arquitectos (piense: "no hablarás de Unity , Ninject , NHibernate , Moq o NUnit , para que no te corte el dedo"). Entonces, en lugar de usar un contenedor DI establecido, escribí un contenedor extremadamente simple. Básicamente conectó todas sus dependencias al inicio, inyecta cualquier dependencia (constructor / propiedad) y eliminó los objetos desechables al final de la solicitud web. Era extremadamente liviano e hizo lo que necesitábamos. Y luego les pedí que lo revisaran.

La respuesta

Bueno, para abreviar. Me encontré con una fuerte resistencia. El argumento principal fue: "No necesitamos agregar esta capa de complejidad a un proyecto ya complejo". Además, "No es como si conectaremos diferentes implementaciones de componentes". Y "Queremos que sea simple, si es posible, simplemente coloque todo en un solo ensamblaje. DI es una complejidad innecesaria sin beneficio".

Finalmente mi pregunta

¿Cómo manejarías mi situación? No soy bueno para presentar mis ideas, y me gustaría saber cómo la gente presentaría sus argumentos.

Por supuesto, supongo que, como yo, prefieres usar DI. Si no está de acuerdo, explique por qué para que pueda ver el otro lado de la moneda. Sería realmente interesante ver el punto de vista de alguien que no está de acuerdo.

Actualizar

Gracias por las respuestas de todos. Realmente pone las cosas en perspectiva. Es lo suficientemente bueno como para tener otro par de ojos que te den retroalimentación, ¡quince es realmente increíble! Estas son respuestas realmente geniales y me ayudaron a ver el problema desde diferentes lados, pero solo puedo elegir una respuesta, por lo que elegiré la que haya sido votada mejor. Gracias a todos por tomarse el tiempo para responder.

He decidido que probablemente no sea el mejor momento para implementar DI, y no estamos preparados para ello. En cambio, concentraré mis esfuerzos en hacer que el diseño sea comprobable e intentaré presentar pruebas unitarias automatizadas. Soy consciente de que escribir pruebas es una sobrecarga adicional y si alguna vez se decide que la sobrecarga adicional no vale la pena, personalmente aún lo vería como una situación ganadora, ya que el diseño aún es comprobable. Y si alguna vez la prueba o DI es una opción en el futuro, el diseño puede manejarlo fácilmente.

Mel
fuente
64
Parece
24
Usar DI es (al menos en Java, C #, ...) una consecuencia casi natural de usar pruebas unitarias. ¿Su empresa utiliza pruebas unitarias?
sebastiangeiger
27
O simplemente no centrarse en DI. DI es diferente de "mantenible y comprobable". Cuando hay diferentes opiniones sobre un proyecto, debe componer y, a veces, aceptar que no decide. Yo, por ejemplo, he visto desastres amplificados por la inversión de controles, DI y múltiples capas de marcos que hacen cosas complejas que deberían ser simples (no puedo discutir en su caso).
Denys Séguret
34
"... Podría hablar todo el día al respecto". Parece que debe dejar de obsesionarse con algo que se abusa con mayor frecuencia debido a la adherencia dogmática a algún documento técnico del día.
21
Por lo que parece, sus compañeros de trabajo tienen argumentos objetivos que hablan en contra del uso de contenedores DI: “no necesitamos agregar esta capa de complejidad a un proyecto que ya es complejo”, ¿acaso tienen razón? ¿ De hecho se beneficia de la complejidad añadida? Si es así, eso debería ser posible discutir. Afirman que "DI es una complejidad innecesaria sin beneficio". Si puede mostrar un beneficio objetivo, entonces debería ganarlo con bastante facilidad, ¿no? Las pruebas se mencionan con bastante frecuencia en los otros comentarios; pero las pruebas no necesitan fundamentalmente DI, a menos que necesite simulacros (lo cual no es un hecho).
Konrad Rudolph

Respuestas:

62

Tomando un par de argumentos en contra:

"Queremos que sea simple, si es posible, simplemente coloque todo en un solo ensamblaje. DI es una complejidad innecesaria sin beneficio".

"No es como si conectaremos diferentes implementaciones de componentes".

Lo que quiere es que el sistema sea comprobable. Para que sea fácilmente comprobable, debe buscar burlarse de las diversas capas del proyecto (base de datos, comunicaciones, etc.) y, en este caso , conectará diferentes implementaciones de componentes.

Venda DI en los beneficios de prueba que le brinda. Si el proyecto es complejo, necesitará pruebas unitarias buenas y sólidas.

Otro beneficio es que, a medida que codifica para interfaces, si encuentra una mejor implementación (más rápida, con menos memoria, lo que sea) de uno de sus componentes, el uso de DI hace que sea mucho más fácil cambiar la implementación anterior por la nuevo.

Lo que digo aquí es que debe abordar los beneficios que brinda la DI en lugar de abogar por la DI por el bien de la DI. Al hacer que las personas estén de acuerdo con la declaración:

"Necesitamos X, Y y Z"

Entonces cambias el problema. Debe asegurarse de que DI es la respuesta a esta declaración. Al hacerlo, sus compañeros de trabajo serán dueños de la solución en lugar de sentir que se les ha impuesto.

ChrisF
fuente
+1. Breve y al grano. Desafortunadamente, a partir de los argumentos presentados por los compañeros de trabajo de Mel, tendrá dificultades para convencerlos de que vale la pena intentarlo, lo que Spoike dijo en su respuesta, es más un problema de personas que un problema técnico.
Patryk Ćwiek
1
@ Trustme-I'maDoctor - Oh, estoy de acuerdo en que es un problema de las personas, pero con este enfoque está mostrando los beneficios en lugar de discutir por DI por su propio bien.
ChrisF
Es cierto y le deseo suerte, su vida como desarrollador será más fácil con la capacidad de prueba adecuada, etc. :)
Patryk Ćwiek
44
+1 Creo que tu último párrafo debería ser el primero. Eso es central para el problema. Si se sugiriera poder hacer Unit Test y convertir todo el sistema a un modelo DI, también le diría que no.
Andrew T Finnell
+1 muy útil de hecho. No diría que la DI es una necesidad absoluta para las pruebas unitarias, pero hace que las cosas sean mucho más fáciles.
Mel
56

Por lo que está escribiendo, no DI en sí es tabú, sino el uso de contenedores DI, que es una cosa diferente. Supongo que no tendrás problemas con tu equipo cuando solo escribas componentes independientes que obtengan otros componentes que deben pasar, por ejemplo, por el constructor. Simplemente no lo llames "DI".

Puede pensar que dichos componentes no usan un contenedor DI es tedioso, y tiene razón, pero dé a su equipo el tiempo para descubrirlo por sí mismos.

Doc Brown
fuente
10
+1 una solución pragmática a un punto muerto político / dogmático
k3b
32
Vale la pena considerar cablear todos sus objetos manualmente de todos modos, e introducir un contenedor DI solo cuando eso empiece a ponerse difícil. Si hace esto, no verán los contenedores DI como una complejidad añadida, lo verán como algo simple.
Phil
2
El único problema es que el patrón de pasar dependencias débilmente acopladas a dependientes es DI. IoC, o el uso de un "contenedor" para resolver dependencias débilmente acopladas, es DI automatizado .
KeithS
51

Como me lo pidió, me pondré del lado de su equipo contra DI.

El caso contra la inyección de dependencia

Hay una regla que llamo "Conservación de la Complejidad" que es análoga a la regla en física llamada "Conservación de la Energía". Establece que ninguna cantidad de ingeniería puede reducir la complejidad total de una solución óptima a un problema, todo lo que puede hacer es cambiar esa complejidad. Los productos de Apple no son menos complejos que los de Microsoft, simplemente trasladan la complejidad del espacio del usuario al espacio de ingeniería. (Sin embargo, es una analogía defectuosa. Si bien no se puede crear energía, los ingenieros tienen la mala costumbre de crear complejidad a cada paso. De hecho, muchos programadores parecen disfrutar agregando complejidad y usando magia, que por definición es una complejidad que el programador no comprende completamente. Este exceso de complejidad se puede reducir.) Por lo general, lo que parece reducir la complejidad es simplemente trasladar la complejidad a otro lugar, generalmente a una biblioteca.

Del mismo modo, DI no reduce la complejidad de vincular objetos de cliente con objetos de servidor. Lo que hace es sacarlo de Java y moverlo a XML. Eso es bueno porque centraliza todo en uno (o algunos) archivos XML donde es fácil ver todo lo que está sucediendo y donde es posible cambiar las cosas sin volver a compilar el código. Por otro lado, eso es malo porque los archivos XML no son Java y, por lo tanto, no están disponibles para las herramientas Java. No puede depurarlos en el depurador, no puede utilizar el análisis estático para rastrear a través de su jerarquía de llamadas, y así sucesivamente. En particular, los errores en el marco DI se vuelven extremadamente difíciles de detectar, identificar y corregir.

Por lo tanto, es mucho más difícil demostrar que un programa es correcto cuando se utiliza DI. Mucha gente argumenta que los programas que usan DI son más fáciles de probar , pero las pruebas de programa nunca pueden probar la ausencia de errores . (Tenga en cuenta que el documento vinculado tiene alrededor de 40 años y también tiene algunas referencias arcanas y cita algunas prácticas desactualizadas, pero muchas de las declaraciones básicas aún son ciertas. En particular, que P <= p ^ n, donde P es la probabilidad de que el sistema esté libre de errores, p es la probabilidad de que un componente del sistema esté libre de errores yn es el número de componentes. Por supuesto, esta ecuación está demasiado simplificada, pero apunta a una verdad importante). El bloqueo de NASDAQ durante la IPO de Facebook es un ejemplo reciente, dondeAfirmaron que habían pasado 1,000 horas probando el código y aún no descubrieron los errores que causaron el accidente. 1,000 horas es media persona-año, por lo que no es como si estuvieran escatimando en las pruebas.

Es cierto que casi nadie se compromete (y mucho menos completa) la tarea de probar formalmente que cualquier código es correcto, pero incluso los intentos débiles de prueba superan a la mayoría de los regímenes de prueba para encontrar errores. Intentos de prueba, por ejemplo , L4.verified , descubren invariablemente una gran cantidad de errores que las pruebas no detectaron y probablemente nunca descubrirían a menos que el probador ya conociera la naturaleza exacta del error. Cualquier cosa que haga que el código sea más difícil de verificar mediante inspección puede verse como una reducción de la posibilidad de que se detecten errores , incluso si aumenta la capacidad de prueba.

Además, no se requiere DI para las pruebas . Raramente lo uso para ese propósito, ya que prefiero probar el sistema contra el software real en el sistema en lugar de alguna versión de prueba alternativa. Todo lo que cambio en la prueba se almacena (o bien podría) almacenarse en archivos de propiedades que dirigen el programa a los recursos en el entorno de prueba en lugar de la producción. Prefiero pasar el tiempo configurando un servidor de base de datos de prueba con una copia de la base de datos de producción que pasar el tiempo codificando una base de datos simulada, porque de esta manera podré rastrear problemas con los datos de producción (los problemas de codificación de caracteres son solo un ejemplo ) y errores de integración del sistema, así como errores en el resto del código.

La inyección de dependencia tampoco es necesaria para la inversión de dependencia . Puede utilizar fácilmente métodos de fábrica estáticos para implementar la Inversión de dependencias. Así es, de hecho, cómo se hizo en la década de 1990.

En verdad, aunque he estado usando DI durante una década, las únicas ventajas que ofrece que me parecen convincentes son la capacidad de configurar bibliotecas de terceros para usar otras bibliotecas de terceros (por ejemplo, configurar Spring para usar Hibernate y ambas para usar Log4J ) y la habilitación de IoC a través de proxies. Si no está utilizando bibliotecas de terceros y no está utilizando IoC, entonces no tiene mucho sentido y, de hecho, agrega complejidad injustificada.

Old Pro
fuente
1
No estoy de acuerdo, es posible reducir la complejidad. Lo sé porque lo hice. Por supuesto, algunos problemas / requisitos fuerzan la complejidad, pero por encima de eso, la mayor parte se puede eliminar con esfuerzo.
Ricky Clarkson
10
@Ricky, es una sutil distinción. Como dije, los ingenieros pueden agregar complejidad, y esa complejidad se puede eliminar, por lo que puede haber reducido la complejidad de la implementación, pero por definición no puede reducir la complejidad de la solución óptima a un problema. Con mayor frecuencia, lo que parece reducir la complejidad es simplemente cambiarlo a otro lugar (generalmente a una biblioteca). En cualquier caso, es secundario a mi punto principal, que es que DI no reduce la complejidad.
Old Pro
2
@Lee, configurar DI en el código es aún menos útil. Uno de los beneficios más ampliamente aceptados de DI es la capacidad de cambiar la implementación del servicio sin recompilar el código, y si configura DI en el código, entonces lo pierde.
Old Pro
77
@OldPro: la configuración en código tiene el beneficio de la seguridad de tipo, y la mayoría de las dependencias son estáticas y no tienen ningún beneficio de ser definidas externamente.
Lee
3
@Lee Si las dependencias son estáticas, ¿por qué no están codificadas en primer lugar, sin el desvío a través de DI?
Konrad Rudolph
25

Lamento decir que el problema que tiene, dado lo que han dicho sus compañeros de trabajo en su pregunta, es de naturaleza política más que técnica. Hay dos mantras que debes tener en cuenta:

  • "Siempre es un problema de personas"
  • "El cambio da miedo"

Claro, puede sacar muchos argumentos en contra con razones técnicas y beneficios sólidos, pero eso lo pondrá en una mala situación con el resto de la compañía. Ya tienen una postura de que no la quieren (porque se sienten cómodos con cómo funcionan las cosas ahora). Por lo tanto, incluso podría dañar su relación con sus colegas si sigue siendo persistente al respecto. Confía en mí en esto porque me ha sucedido y finalmente me despidieron hace varios años (joven e ingenuo que era); Esta es una situación en la que no quieres meterte.

La cuestión es que debe alcanzar cierto nivel de confianza antes de que las personas lleguen a cambiar su forma actual de trabajar. A menos que esté en una posición en la que tenga mucha confianza o pueda decidir este tipo de cosas, como tener el papel de arquitecto o líder tecnológico, es muy poco lo que puede hacer para cambiar a sus colegas. Tomará mucho tiempo con persuasión y esfuerzo (como tomar pequeños pasos de mejora) para ganar confianza y lograr que la cultura de su empresa cambie. Estamos hablando de un intervalo de tiempo de muchos meses o años.

Si encuentra que la cultura actual de la empresa no está funcionando con usted, entonces al menos sabe otra cosa que preguntar en su próxima entrevista de trabajo. ;-)

3 revoluciones
fuente
23

No uses DI. Olvídalo.

Cree una implementación del patrón de fábrica GoF que "cree" o "encuentre" sus dependencias particulares y las pase a su objeto y déjelo así, pero no lo llame DI y no cree un marco complejo complejo. Mantenlo simple y relevante. Asegúrese de que las dependencias y sus clases sean tales que sea posible cambiar a DI; pero mantén la fábrica como maestra y mantén un alcance extremadamente limitado.

Con el tiempo, puede esperar que crezca e incluso un día la transición a DI. Deje que las pistas lleguen a la conclusión en su propio tiempo y no empuje. Incluso si nunca llega a eso, al menos su código tiene algunas de las ventajas de DI, por ejemplo, código comprobable por unidad.

jasonk
fuente
3
+1 por crecer junto con el proyecto y "no lo llames DI"
Kevin McCormick
55
Estoy de acuerdo, pero al final, es DI. Simplemente no está usando un contenedor DI; está pasando la carga a la persona que llama en lugar de centralizarla en un solo lugar. Sin embargo, sería inteligente llamarlo "Dependencias de externalización" en lugar de DI.
Juan Mendes
55
A menudo se me ocurre que para entender realmente un concepto de torre de marfil como DI, debe haber pasado por el mismo proceso que los evangelistas para darse cuenta de que había un problema que resolver en primer lugar. Los autores a menudo olvidan esto. Siga con el lenguaje de patrones de bajo nivel y la necesidad de un contenedor puede (o no) surgir como consecuencia.
Tom W
@JuanMendes desde su respuesta, estoy pensando que externalizar dependencias es específicamente a lo que se oponen porque tampoco les gustan las bibliotecas de terceros y quieren todo en un solo ensamblaje (¿monolito?). Si eso es cierto, llamarlo "Dependencias de externalización" no es mejor que llamar a DI desde su punto de vista.
Jonathan Neufeld
22

He visto proyectos que llevaron la DI al extremo para probar unidades y simular objetos. Tanto es así que la configuración DI se convirtió en el lenguaje de programación en sí mismo con tanta configuración necesaria para que un sistema se ejecute como el código real.

¿El sistema está sufriendo en este momento debido a la falta de API internas apropiadas que no se pueden probar? ¿Espera que cambiar el sistema a un modelo DI le permita realizar mejores pruebas unitarias? No necesita un contenedor para probar el código de la unidad. El uso de un modelo DI le permitiría realizar pruebas unitarias más fácilmente con los objetos Mock, pero no es obligatorio.

No necesita cambiar todo el sistema para que sea compatible con DI a la vez. Tampoco deberías escribir arbitrariamente pruebas unitarias. Refactorice el código a medida que lo encuentre en sus tickets de funciones y errores. Luego, asegúrese de que el código que ha tocado sea fácilmente comprobable.

Piensa en CodeRot como una enfermedad. No quieres comenzar a piratear arbitrariamente las cosas. Tienes un paciente, ves un punto dolorido, por lo que comienzas a arreglar el lugar y las cosas a su alrededor. Una vez que esa área está limpia, pasa a la siguiente. Tarde o temprano habrás tocado la mayoría del código y no requirió este cambio arquitectónico "masivo".

No me gusta que las pruebas DI y Mock sean mutuamente excluyentes. Y después de ver un proyecto que se fue, por falta de una palabra mejor, loco usando DI, estaría cansado también sin ver el beneficio.

Hay algunos lugares que tiene sentido usar DI:

  • La capa de acceso a datos para intercambiar estrategias por datos de historias
  • Capa de autenticación y autorización.
  • Temas para la interfaz de usuario
  • Servicios
  • Puntos de extensión del complemento que puede utilizar su propio sistema o un tercero.

Exigir a cada desarrollador que sepa dónde está el archivo de configuración DI (me doy cuenta de que los contenedores se pueden instanciar a través del código, pero ¿por qué haría eso?) Cuando quieren realizar un RegEx es frustrante. Ah, veo que hay IRegExCompiler, DefaultRegExCompiler y SuperAwesomeRegExCompiler. Sin embargo, en ningún lugar del código puedo hacer un análisis para ver dónde se usa Default y SuperAwesome.

Mi propia personalidad reaccionaría mejor si alguien me mostrara algo mejor, y luego tratar de convencerme con sus palabras. Hay algo que decir sobre alguien que dedicaría tiempo y esfuerzo para mejorar el sistema. No creo que muchos desarrolladores se preocupen lo suficiente como para que el producto sea realmente mejor. Si pudiera recurrir a ellos con cambios reales en el sistema y mostrarles cómo lo hizo mejor y más fácil para usted, eso vale más que cualquier investigación en línea.

En resumen, la mejor manera de realizar un cambio en un sistema es hacer que el código que está tocando actualmente sea mejor con cada pasada. Tarde o temprano podrás transformar el sistema en algo mejor.

Andrew Finnell
fuente
"Las pruebas DI y simulacros son mutuamente excluyentes" Eso es falso, tampoco son mutuamente inclusivas. Simplemente trabajan bien juntos. También enumera los lugares donde DI es bueno, la capa de servicio, el dao y la interfaz de usuario, eso es casi en todas partes, ¿no?
NimChimpsky
@Andrew puntos válidos. Por supuesto, no quisiera que fuera demasiado lejos. Lo que más quería era la prueba automatizada para las clases de negocios, ya que tenemos que implementar reglas comerciales muy complejas. Y me doy cuenta de que todavía puedo probarlos sin contenedor, pero personalmente, tiene sentido usar un contenedor. Realmente hice una pequeña muestra que mostraba el concepto. Pero ni siquiera fue mirado. Supongo que mi error fue que no creé una prueba que mostrara lo fácil que es la prueba / burla.
Mel
¡Mucho acuerdo! Realmente odio los archivos de configuración DI y el efecto que tiene en el seguimiento del flujo del programa. Todo se convierte en "acción espeluznante a distancia" sin conexiones aparentes entre las piezas. Cuando se hace más fácil leer el código en un depurador que en los archivos fuente, algo está realmente mal.
Zan Lynx
19

DI no requiere recipientes invasivos de inversión de control o estructuras de burla. Puede desacoplar el código y permitir DI a través de un diseño simple. Puede realizar pruebas unitarias a través de las capacidades integradas de Visual Studio.

Para ser justos, sus compañeros de trabajo tienen un punto. Usar esas bibliotecas mal (y dado que no están familiarizadas con ellas, habrá problemas de aprendizaje) causará problemas. Al final, el código importa. No arruines tu producto para tus pruebas.

Solo recuerde que el compromiso es necesario en estos escenarios.

Telastyn
fuente
2
Estoy de acuerdo en que DI no requiere contenedores de IoC. aunque no lo llamaría invasivo (al menos esta implementación) ya que el código fue diseñado para ser independiente del contenedor (principalmente inyecciones de constructor) y todas las cosas DI ocurren en un solo lugar. Por lo tanto, todo funciona sin un contenedor DI: pongo constructores sin parámetros que "inyectan" las implementaciones concretas al otro constructor con dependencias. De esa manera todavía puedo burlarme fácilmente. Así que sí, estoy de acuerdo, la DI se logra realmente por diseño.
Mel
+1 Por compromiso. Si nadie quiere comprometerse, todos terminan sufriendo.
Tjaart
14

No creo que pueda vender DI más, porque esta es una decisión dogmática (o, como @Spoike lo llamó más cortésmente, político ).

En esta situación, discutir con las mejores prácticas ampliamente aceptadas como los principios de diseño SÓLIDO ya no es posible.

Alguien que tiene más poder político que usted ha tomado una decisión (tal vez incorrecta) de no usar DI.

Por otro lado, usted se ha opuesto a esta decisión al implementar su propio contenedor porque estaba prohibido usar un contenedor establecido. Esta política de hechos consumados que probaste podría haber empeorado la situación.

Análisis

En mi opinión, la DI sin desarrollo impulsado por pruebas (TDD) y pruebas unitarias no tiene un beneficio significativo que supere los costos adicionales iniciales.

¿Es este problema realmente sobre

  • no queremos DI ?

o se trata más de

  • tdd / unittesting es un desperdicio de recursos ?

[Actualizar]

Si ve su proyecto desde los errores clásicos en la vista de gestión de proyectos , verá

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

mientras los demás ven

#3: Uncontrolled problem employees. 
#30: Developer gold-plating. 
#32: Research-oriented development. 
#34: Overestimated savings from new tools or methods. 
k3b
fuente
Actualmente no estamos haciendo ninguna prueba de unidad en nuestro equipo. Sospecho que su última declaración es acertada. He mencionado las pruebas unitarias varias veces, pero nadie mostró ningún interés real en él y siempre hubo razones por las que no podemos adoptarlo.
Mel
10

Si se ve obligado a "vender" un principio a su equipo, entonces probablemente ya haya recorrido varias millas por el camino equivocado.

Nadie quiere hacer un trabajo extra, por lo que si lo que estás tratando de vender les parece un trabajo extra, entonces no vas a convencerlos con Invocación repetida de argumento superior. Necesitan saber que les está mostrando una forma de reducir su carga de trabajo, no aumentarla.

La DI a menudo se ve como una complejidad adicional ahora con la promesa de una posible complejidad reducida más adelante , lo que tiene valor, pero no tanto para alguien que está tratando de reducir su carga de trabajo inicial. Y en muchos casos, DI es solo otra capa de YAGNI . Y el costo de esta complejidad no es cero, en realidad es bastante alto para un equipo que no está acostumbrado a este tipo de patrón. Y se trata de dólares reales que se invierten en un balde que tal vez nunca produzca ningún beneficio.

Póngalo en su lugar.
Su mejor opción es usar DI donde es muy útil, en lugar de solo donde es común. Por ejemplo, muchas aplicaciones usan DI para seleccionar y configurar bases de datos. Y si su aplicación se envía con una capa de base de datos, entonces ese es un lugar tentador para ponerla. Pero si su aplicación se envía con una base de datos invisible para el usuario integrada que de otro modo está estrechamente integrada en el código, entonces las posibilidades de tener que reemplazar la base de datos en tiempo de ejecución se acercan a cero. Y vender DI al decir "mira, podemos hacer fácilmente esto que nunca, nunca, nunca querremos hacer", es probable que te lleve a la lista de "este tipo tiene malas ideas" con tu jefe.

Pero supongamos que tiene una salida de depuración que normalmente se libera IFDEF en el lanzamiento. Y cada vez que un usuario tiene un problema, su equipo de soporte debe enviarle una compilación de depuración para que pueda obtener la salida del registro. Puede usar DI aquí para permitir que el cliente cambie entre controladores de registro: LogConsolepara desarrolladores, LogNullpara clientes y LogNetworkpara clientes con problemas. Una compilación unificada, tiempo de ejecución configurable.

Entonces ni siquiera tiene que llamarlo DI, sino que se llama "la idea realmente buena de Mel" y ahora está en la lista de buenas ideas en lugar de la mala. La gente verá qué tan bien funciona el patrón y comenzará a usarlo donde tenga sentido en su código.

Cuando vendes adecuadamente una idea, la gente no sabrá que los convenciste de nada.

tylerl
fuente
8

Por supuesto, la Inversión de control (IoC) tiene una gran cantidad de beneficios: simplifica el código, agrega algo de magia que está haciendo las tareas típicas, etc.

Sin embargo:

  1. Agrega muchas dependencias. La aplicación simple hello world escrita con Spring puede pesar una docena de MB, y he visto que Spring agregó solo para evitar algunas líneas de constructor y setters.

  2. Agrega la magia mencionada anteriormente , que es difícil de entender para los extraños (por ejemplo, los desarrolladores de GUI que necesitan hacer algunos cambios en la capa empresarial). Vea el gran artículo Las mentes innovadoras no piensen igual - New York Times , que describe cómo los expertos subestiman este efecto .

  3. Se endurece para rastrear el uso de clases. IDEs como Eclipse tiene grandes habilidades para rastrear usos de clases dadas o invocaciones de métodos. Pero este rastro se pierde cuando se invoca la inyección de dependencia y la reflexión.

  4. El uso de IoC normalmente no termina con la inyección de dependencias, termina con innumerables proxies, y obtienes el seguimiento de la pila contando cientos de líneas, el 90% de las cuales son proxies e invocaciones mediante reflexión. Complica en gran medida el análisis de seguimiento de pila.

Entonces, como soy un gran admirador de Spring y IoC, recientemente tuve que repensar las preguntas anteriores. Algunos de mis colegas son desarrolladores web tomados dentro del mundo Java del mundo web gobernado por Python y PHP y lo obvio para las cosas javaistas es algo obvio para ellos. Algunos de mis otros colegas están haciendo trucos (por supuesto, indocumentados) que hacen que el error sea muy difícil de encontrar. Por supuesto, el mal uso de IoC les hace las cosas más ligeras;)

Mi consejo, si forzará el uso de IoC en su trabajo, se le hará responsable de cualquier mal uso que alguien más haga;)

Łukasz Lech
fuente
+1 como con todas las herramientas poderosas, se usa mal fácilmente y DEBE entender cuándo NO usarlo.
Phil
4

Creo que ChrisF está en algo correcto. No venda DI en sí mismo. Pero venda la idea sobre la unidad que prueba el código.

Un enfoque para obtener un contenedor DI en la arquitectura podría ser dejar que las pruebas conduzcan lentamente la implementación a algo que se adapte bien a esa arquitectura. Por ejemplo, supongamos que está trabajando en un controlador en una aplicación ASP.NET MVC. Digamos que escribes la clase así:

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

    public UserController(IUserRepository userRepository) {
        ...
    }

    ...
}

Eso le permite escribir pruebas unitarias desacopladas de la implementación real del repositorio de usuarios. Pero la clase aún funciona sin un contenedor DI para crearlo para usted. El constructor sin parámetros lo creará con el repositorio predeterminado.

Si sus compañeros de trabajo pueden ver que esto lleva a pruebas mucho más limpias, tal vez puedan darse cuenta de que es una mejor manera. Tal vez puedas hacer que empiecen a codificar así.

Una vez que se hayan dado cuenta de que este es un mejor diseño que el UserController que conoce una implementación específica de UserRepository, puede dar el siguiente paso y mostrarles que puede tener un componente, el contenedor DI, que puede proporcionar automáticamente las instancias que necesita.

Pete
fuente
¡Excelente! Actualmente estoy haciendo esto porque realmente quiero la prueba de la unidad automatizada, incluso si no puedo obtener DI. Olvidé mencionar que las pruebas unitarias tampoco se realizan en nuestro equipo :( así que si voy a ser el primero.
Mel
2
En ese caso, diría que ese es el verdadero problema aquí. Encuentra la raíz de la resistencia a las pruebas unitarias y ataca eso. Deje que DI mienta por ahora. La prueba es más importante en mi humilde opinión.
Christian Horsdal
@ChristianHorsdal Estoy de acuerdo, quería que se acoplara libremente para permitir burlas / pruebas fáciles.
Mel
Esa idea es realmente genial ... Gran venta para probar
bunglestink
2

Quizás lo mejor que puede hacer es dar un paso atrás y preguntarse ¿por qué quiere introducir contenedores DI? Si es para mejorar la capacidad de prueba, entonces su primera tarea es lograr que el equipo compre las pruebas unitarias. Personalmente, me preocuparía mucho más la falta de pruebas unitarias que la falta de un contenedor DI.

Armado con un conjunto completo o conjuntos de pruebas unitarias, puede establecer con confianza la evolución de su diseño con el tiempo. Sin ellos, se enfrenta a una lucha cada vez más tensa para mantener su diseño al día con los requisitos cambiantes, una lucha que eventualmente muchos equipos pierden.

Por lo tanto, mi consejo sería probar primero la unidad de campeón y refactorizar, luego, a su vez, introducir DI y finalmente contenedores DI.

kimj100
fuente
2

Estoy escuchando algunos grandes no-nos de su equipo aquí:

  • "No hay bibliotecas de terceros" - AKA "No inventado aquí" o "NIH". El temor es que, al implementar una biblioteca de terceros y hacer que la base de código dependa de ella, si la biblioteca tiene un error, un agujero de seguridad, o incluso una limitación que necesita superar, se vuelve dependiente de este tercero. parte para arreglar su biblioteca para que su código funcione. Mientras tanto, debido a que es "su" programa que falló espectacularmente, o fue pirateado, o no hace lo que le pidieron, aún tiene la responsabilidad (y los desarrolladores de terceros dirán que su herramienta es "tal cual" ", Úselo bajo su propio riesgo).

    El contraargumento es que el código que resuelve el problema ya existe en la biblioteca de terceros. ¿Estás construyendo tus computadoras desde cero? ¿Escribiste Visual Studio? ¿Estás evitando las bibliotecas integradas en .NET? No. Usted tiene un nivel de confianza en Intel / AMD y Microsoft, y encuentran errores todo el tiempo (y no siempre los corrigen rápidamente). Agregar una biblioteca de terceros le permite obtener rápidamente una base de código mantenible que funcione, sin tener que reinventar la rueda. Y el ejército de abogados de Microsoft se reirá en tu cara cuando les digas que un error en las bibliotecas de criptografía .NET los hace responsables del incidente de piratería que sufrió tu cliente al usar tu programa .NET. Si bien Microsoft ciertamente saltará para solucionar los agujeros de seguridad de "hora cero" en cualquiera de sus productos,

  • "Queremos poner todo en un solo ensamblaje", en realidad, no. En .NET al menos, si realiza un cambio en una línea de código, se debe reconstruir todo el ensamblado que contiene esa línea de código. El buen diseño del código se basa en múltiples ensamblajes que puede reconstruir de la manera más independiente posible (o al menos solo tiene que reconstruir dependencias, no dependientes). Si REALMENTE desean lanzar un EXE que sea solo el EXE (que tiene valor en ciertas circunstancias), hay una aplicación para eso; ILMerge.

  • "DI es tabú" - ¿WTF? DI es una práctica recomendada aceptada en cualquier lenguaje O / O con una construcción de código de "interfaz". ¿Cómo se adhiere su equipo a las metodologías de diseño GRASP o SOLID si no acoplan las dependencias entre objetos? Si no se molestan, entonces están perpetuando la fábrica de espaguetis que hace que el producto sea el complicado pantano que es. Ahora, DI aumenta la complejidad al aumentar el recuento de objetos, y si bien hace que la sustitución de una implementación diferente sea más fácil, hace que cambiar la interfaz sea más difícil (al agregar una capa adicional de objeto de código que tiene que cambiar).

  • "No necesitamos agregar esta capa de complejidad a un proyecto ya complejo": pueden tener un punto. Una cosa crucial a considerar en cualquier desarrollo de código es YAGNI; "No lo vas a necesitar". Un diseño que está SÓLIDAMENTE diseñado desde el principio es un diseño hecho SÓLIDO "por fe"; no sabe si necesitará la facilidad de cambio que proporcionan los diseños SOLIDOS, pero tiene una corazonada. Si bien puede tener razón, también puede estar muy equivocado; un diseño muy SÓLIDO podría terminar viéndose como "código de lasaña" o "código de ravioles", teniendo tantas capas o trozos pequeños que hacer que los cambios aparentemente triviales se vuelvan difíciles porque tienes que cavar a través de tantas capas y / o trazar una pila de llamadas tan complicada.

    Apoyo una regla de tres golpes: haz que funcione, que sea limpio, que sea SÓLIDO. Cuando escribe una línea de código por primera vez, solo tiene que funcionar, y no obtiene puntos por diseños de torre de marfil. Suponga que es algo único y haga lo que tiene que hacer. La segunda vez que mira ese código, probablemente esté buscando expandirlo o reutilizarlo, y no es el único que pensó que sería. En este punto, hazlo más elegante y comprensible refactorizándolo; simplifique la lógica enrevesada, extraiga código repetido en bucles y / o llamadas a métodos, agregue algunos comentarios aquí y allá para cualquier error que tenga que dejar en su lugar, etc. La tercera vez que mire ese código probablemente sea un gran problema. Ahora debe cumplir con las reglas SÓLIDAS; inyecte dependencias en lugar de construirlas, extraiga clases para mantener métodos que sean menos coherentes con la "carne" del código,

  • "No es como si estuviéramos conectando diferentes implementaciones" - Usted, señor, tiene un equipo que no cree en las pruebas unitarias en sus manos. Creo que es imposible escribir una prueba unitaria, que se ejecuta de forma aislada y no tiene efectos secundarios, sin poder "burlarse" de los objetos que tienen esos efectos secundarios. Cualquier programa no trivial tiene efectos secundarios; si su programa lee o escribe en una base de datos, abre o guarda archivos, se comunica a través de una red o socket periférico, inicia otro programa, dibuja ventanas, salidas a la consola, o usa memoria o recursos fuera del "sandbox" de su proceso, tiene Un efecto secundario. Si no hace ninguna de estas cosas, ¿qué hace y por qué debería comprarlo?

    Para ser justos, las pruebas unitarias no son la única forma de demostrar que un programa funciona; puede escribir pruebas de integración, codificar algoritmos probados matemáticamente, etc. Sin embargo, las pruebas unitarias muestran, muy rápidamente, que dadas las entradas A, B y C, este código genera la X esperada (o no), y por lo tanto el código funciona correctamente (o no) en un caso dado. Los conjuntos de pruebas unitarias pueden demostrar con un alto grado de confianza que el código funciona como se espera en todos los casos, y también proporcionan algo que una prueba matemática no puede; una demostración de que el programa TODAVÍA funciona de la manera prevista. Una prueba matemática del algoritmo no prueba que se implementó correctamente, ni tampoco prueba que los cambios realizados se implementaron correctamente y no lo rompieron.

En resumen, si este equipo no ve ningún valor en lo que DI haría, entonces no lo apoyarán, y todos (al menos todos los muchachos mayores) tienen que comprar algo para un cambio real. a suceder Están poniendo mucho énfasis en YAGNI. Estás poniendo mucho énfasis en las pruebas SOLIDAS y automatizadas. En algún lugar en el medio se encuentra la verdad.

KeithS
fuente
1

Tenga cuidado al agregar características adicionales (como DI) a un proyecto cuando no esté aprobado. Si tiene un plan de proyecto con límites de tiempo, podría caer en la trampa de no tener tiempo suficiente para terminar las funciones aprobadas.

Incluso si no usa DI ahora, participe en las pruebas para saber exactamente cuáles son las expectativas para las pruebas en su empresa. Habrá otro proyecto y podrá contrastar los problemas con las pruebas del proyecto actual vs. DI.

Si no usa DI, puede ser creativo y hacer que su código DI- "esté listo". Use un constructor sin parámetros para llamar a otros constructores:

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

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

Creo que una de sus mayores preocupaciones es, de hecho, todo lo contrario: DI no está haciendo complejo el proyecto. En la mayoría de los casos está reduciendo mucha complejidad.

Solo piense sin DI, ¿qué va a obtener el objeto / componente dependiente que necesita? Normalmente es mediante la búsqueda desde un repositorio, o obteniendo un singleton, o instanciando usted mismo.

El primero 2 implica un código adicional para localizar el componente dependiente, en el mundo DI, no hizo nada para realizar la búsqueda. La complejidad de sus componentes se reduce de hecho.

Si se está creando una instancia en algunos casos que no es apropiado, incluso está creando más complejidad. Necesita saber cómo crear el objeto correctamente, necesita saber qué valores inicializar el objeto a un estado viable, todo esto crea una complejidad adicional para su código.

Entonces, DI no está haciendo complejo el proyecto, lo está haciendo más simple, al simplificar los componentes.

Otro gran argumento es que no va a conectar una implementación diferente. Sí, es cierto que en el código de producción, no es común tener muchas implementaciones diferentes en el código de producción real. Sin embargo, hay un lugar importante que necesita una implementación diferente: Mock / Stub / Test Doubles en Unit Tests. Para que DI funcione, en la mayoría de los casos se basa en un modelo de desarrollo basado en interfaz, a su vez hace posible la burla / tropezado en pruebas unitarias. Además de reemplazar la implementación, el "diseño orientado a la interfaz" también hace un diseño más limpio y mejor. Por supuesto, aún puede diseñar con interfaz sin DI. Sin embargo, las pruebas unitarias y la DI lo empujan a adoptar tales prácticas.

A menos que sus "dioses" en la compañía piensen que: "código más simple", "pruebas unitarias", "modularización", "responsabilidad única", etc., son algo en contra de ellos, no debería haber ninguna razón para que rechacen el uso de DI.

Adrian Shum
fuente
Eso es bueno en teoría, pero en la práctica agregar un contenedor DI ES una complejidad adicional. La segunda parte de su argumento muestra por qué vale la pena el trabajo, pero sigue siendo trabajo.
schlingel
De hecho, de mis experiencias pasadas, DI está REDUCIENDO en lugar de una adición a la complejidad. Sí, tiene algo extra agregado al sistema, lo que contribuye a la complejidad. Sin embargo, con DI, la complejidad de otros componentes en el sistema se reduce considerablemente. Al final, la complejidad se reduce de hecho. Para la segunda parte, a menos que no estén haciendo pruebas unitarias, NO son trabajos adicionales. Sin DI, sus pruebas unitarias en su mayoría serán difíciles de escribir y mantener. Con DI, el esfuerzo se reduce considerablemente. Entonces, de hecho es REDUCIR el trabajo (a menos que no lo sean y no confíen en las pruebas unitarias)
Adrian Shum
Debo enfatizar una cosa: DI en mi respuesta no significa ningún marco DI existente. Es solo la metodología en el diseño / desarrollo de componentes en su aplicación. Puede beneficiarse mucho si su código está controlado por la interfaz, sus componentes esperan que se inyecten las dependencias en lugar de buscarlas / crearlas. Con dicho diseño, incluso si está escribiendo algún "cargador" específico de la aplicación que realiza el cableado de los componentes (sin propósito general DI fw), aún se beneficia de lo que mencioné: reducción de la complejidad y reducción del trabajo en las pruebas unitarias
Adrian Shum
0

"Queremos que sea simple, si es posible, simplemente coloque todo en un solo ensamblaje. DI es una complejidad innecesaria sin beneficio".

El beneficio es que promueve el diseño de interfaces y permite una burla fácil. Lo que en consecuencia facilita las pruebas unitarias. Las pruebas unitarias aseguran un código confiable, modular y fácil de mantener. Si su jefe aún mantiene que es una mala idea, bueno, no hay nada que pueda hacer: es la mejor práctica aceptada en la industria contra la que está discutiendo.

Pídales que intenten escribir una prueba unitaria con un marco DI y una biblioteca burlona, ​​pronto aprenderán a amarla.

NimChimpsky
fuente
Literalmente doblar o triplicar el número de clases en su sistema ahora es lo que me parece útil. Esto es similar al debate de tratar de obtener una cobertura del 100% de las pruebas unitarias escribiendo una sola prueba por método o escribiendo pruebas unitarias significativas.
Andrew T Finnell
No entiendo, ¿crees que las pruebas unitarias son una buena idea? Si lo haces, ¿también te burlas de los objetos? Esto es más fácil con interfaces y DI. Pero eso parece ser contra lo que estás discutiendo.
NimChimpsky
Realmente es el problema del huevo y la gallina. Las interfaces / DI y las pruebas unitarias son tan relevantes entre sí que es difícil hacer una sin la otra. Pero sí creo que las pruebas unitarias son una vía mejor y más fácil para comenzar con una base de código que existe. Refactorizando gradualmente el viejo código kludgy en componentes cohesivos. Después de eso, podría argumentar que la creación de objetos debe hacerse con un marco DI en lugar de ir newa todas partes y escribir clases de fábrica, ya que tiene todos estos componentes en su lugar.
Spoike
0

¿Trabajas con estas personas en proyectos más pequeños o solo en uno grande? Es más fácil convencer a las personas de que prueben algo nuevo en un proyecto secundario / terciario que el equipo / las empresas. Las razones principales son que si falla, el costo de quitar / reemplazar es mucho menor, y es mucho menos trabajo llevarlo a todas partes en un proyecto pequeño. En uno grande, usted tiene un gran costo inicial para hacer el cambio en todas partes a la vez, o un período prolongado en el que el código es una mezcla del enfoque antiguo / nuevo que agrega fricción porque debe recordar de qué manera está diseñada cada clase trabajar.

Dan Neely
fuente
0

Aunque una de las cosas que promueven la DI es la prueba unitaria, pero creo que agrega una capa de complejidad en algunos casos.

  • Es mejor tener un automóvil difícil en las pruebas, pero fácil de reparar que un automóvil fácil en las pruebas y difícil en la reparación.

  • También me parece que el grupo que impulsa la DI solo presenta sus ideas al influir en que algunos enfoques de diseño existentes son malos.

  • si tenemos un método que realiza 5 funciones diferentes

    DI-People dice:

    • Sin separación de preocupaciones. [¿Cuál es el problema de eso? están tratando de hacer que se vea mal, lo que en lógica y programación es mucho mejor saber qué están haciendo de cerca para evitar conflictos y expectativas erróneas y ver el impacto del código fácilmente, porque no podemos hacer todo objetos no relacionados entre sí al 100% o al menos no podemos garantizar esto {a menos que ¿por qué las pruebas de regresión sean importantes?}]
    • Complejo [Quieren reducir la complejidad haciendo cinco clases adicionales cada una para manejar una función y una clase adicional (Contenedor) para crear la clase para la función necesaria ahora basada en la configuración (XML o Diccionario) además de la filosofía "para tener un valor predeterminado / o no "discusiones, ...] luego trasladaron la complejidad al contenedor y a la estructura, [a mí me parece como mover la complejidad de un lugar a dos lugares con todas las complejidades de los detalles del lenguaje para manejar esto. ]

Creo que es mejor crear nuestro propio patrón de diseño que se adapte a las necesidades del negocio en lugar de ser influenciado por DI.

a favor de DI, podría ayudar en algunas situaciones, por ejemplo, si tiene cientos de implementaciones para la misma interfaz depende de un selector y estas implementaciones son muy diferentes en lógica, en este caso iría con DI o DI técnicas similares. pero si tenemos pocas implementaciones para la misma interfaz, no necesitamos DI.

usuario114367
fuente