c# 3.0 - ¿Los métodos de extensión ocultan dependencias?
c#-3.0 inversion-of-control (4)
Creo que está bien, porque TruncateToSize
es un componente realmente reemplazable. Es un método que solo tendrá que hacer una sola cosa.
No necesita ser capaz de burlarse de todo , solo servicios que interrumpan las pruebas de unidad (acceso a archivos, etc.) o que desea probar en términos de dependencias genuinas. Si lo estuviera usando para realizar la autenticación o algo así, sería un asunto muy diferente ... pero solo haciendo una operación de cadena recta que no tiene absolutamente ninguna capacidad de configuración, diferentes opciones de implementación, etc. No tiene sentido ver eso como una dependencia. en el sentido normal
En otras palabras, si TruncateToSize
fuera un miembro genuino de String
, ¿lo pensaría dos veces antes de usarlo? ¿Intentas IInt32Adder
también la aritmética de enteros, introduciendo IInt32Adder
etc.? Por supuesto no. Esto es lo mismo, es solo que usted está suministrando la implementación. Haga una prueba de unidad al diablo de TruncateToSize
y no se preocupe por eso.
Todos,
Quería tener algunos pensamientos sobre esto. Últimamente me estoy convirtiendo cada vez más en un suscriptor de los principios "puristas" de DI / COI al diseñar / desarrollar. Parte de esto (una gran parte) implica asegurarse de que haya poca conexión entre mis clases y que sus dependencias se resuelvan a través del constructor (ciertamente hay otras formas de gestionar esto, pero entiendes la idea).
Mi premisa básica es que los métodos de extensión violan los principios de DI / IOC.
Creé el siguiente método de extensión que utilizo para asegurarme de que las cadenas insertadas en las tablas de la base de datos se trunquen al tamaño correcto:
public static class StringExtensions
{
public static string TruncateToSize(this string input, int maxLength)
{
int lengthToUse = maxLength;
if (input.Length < maxLength)
{
lengthToUse = input.Length;
}
return input.Substring(0, lengthToUse);
}
}
Entonces puedo llamar a mi cadena desde otra clase así:
string myString = "myValue.TruncateThisPartPlease.";
myString.TruncateToSize(8);
Una traducción justa de esto sin usar un método de extensión sería:
string myString = "myValue.TruncateThisPartPlease.";
StaticStringUtil.TruncateToSize(myString, 8);
Cualquier clase que use cualquiera de los ejemplos anteriores no se pudo probar independientemente de la clase que contiene el método TruncateToSize (aparte de TypeMock). Si no estuviera usando un método de extensión y no quisiera crear una dependencia estática, se vería más como:
string myString = "myValue.TruncateThisPartPlease.";
_stringUtil.TruncateToSize(myString, 8);
En el último ejemplo, la dependencia _stringUtil se resolvería a través del constructor y la clase se podría probar sin depender de la clase del método TruncateToSize real (se podría burlar fácilmente).
Desde mi perspectiva, los dos primeros ejemplos se basan en dependencias estáticas (una explícita, otra oculta), mientras que la segunda invierte la dependencia y proporciona un acoplamiento reducido y una mejor capacidad de prueba.
Entonces, ¿el uso de los métodos de extensión entra en conflicto con los principios DI / COI? Si está suscrito a la metodología IOC, ¿evita utilizar métodos de extensión?
Es un dolor porque son difíciles de burlar. Usualmente uso una de estas estrategias.
- Sí, desechar la extensión es un PITA para burlarse
- Usa la extensión y solo prueba que hizo lo correcto. es decir, pasar datos al truncado y verificar que se haya truncado
- Si no es algo trivial, y TENGO que burlarme, haré que mi clase de extensión tenga un configurador para el servicio que usa, y lo estableceré en el código de prueba.
es decir
static class TruncateExtensions{
public ITruncateService Service {private get;set;}
public string TruncateToSize(string s, int size)
{
return (Service ?? Service = new MyDefaultTranslationServiceImpl()). TruncateToSize(s, size);
}
}
Esto da un poco de miedo porque alguien podría configurar el servicio cuando no debería, pero a veces soy un poco arrogante, y si fuera realmente importante, podría hacer algo inteligente con las banderas #IF TEST, o el patrón ServiceLocator para evitar El setter se utiliza en la producción.
Métodos de extensión, clases parciales y objetos dinámicos. Realmente me gustan, sin embargo, debes andar con cuidado, aquí hay monstruos.
Me gustaría echar un vistazo a los lenguajes dinámicos y ver cómo se enfrentan a este tipo de problemas en el día a día, es realmente esclarecedor. Especialmente cuando no tienen nada que les impida hacer estupideces, aparte del buen diseño y la disciplina. Todo es dinámico en el tiempo de ejecución, lo único que los detiene es que la computadora emita un error de tiempo de ejecución importante. "Escribir pato" es la cosa más loca que he visto, el buen código se debe al buen diseño del programa, al respeto por los demás miembros de su equipo y a la confianza de que todos los miembros, aunque tienen la capacidad de hacer cosas extrañas, eligen no hacerlo porque es bueno. El diseño lleva a mejores resultados.
En cuanto al escenario de prueba con objetos simulados / ICO / DI, ¿realmente pondría algún trabajo pesado en un método de extensión o simplemente algunas cosas estáticas simples que operan de una manera funcional? Tiendo a usarlos como lo harías en un estilo de programación funcional, los aportes entran, los resultados salen sin magia en el medio, solo clases de frameworks directas que sabes que los chicos de MS han diseñado y probado: P en las que puedes confiar en.
Si está realizando tareas de trabajo pesado utilizando métodos de extensión, volvería a ver el diseño de su programa, verifique los diseños de CRC, los modelos de clase, los casos de uso, los DFD, los diagramas de acción o lo que quiera usar y descubra en qué lugar de este diseño planeado poner estas cosas en un método de extensión en lugar de una clase adecuada.
Al final del día, solo puede probar el diseño de su sistema y no codificar fuera de su alcance. Si va a usar clases de extensión, mi consejo sería mirar los modelos de Composición de Objetos y usar la herencia solo cuando haya una buena razón.
La composición de objetos siempre gana conmigo ya que producen código sólido. Puedes enchufarlos, sacarlos y hacer lo que quieras con ellos. Tenga en cuenta que todo depende de si utiliza Interfaces o no como parte de su diseño. Además, si usa clases de Composición, el árbol de jerarquía de clases se aplana en clases discretas y hay menos lugares donde se recogerá su método de extensión a través de clases heredadas.
Si debe usar una clase que actúa sobre otra clase, como es el caso con los métodos de extensión, primero observe el patrón de visitantes y decida si es una mejor ruta.
Veo de dónde viene, sin embargo, si está tratando de burlarse de la funcionalidad de un método de extensión, creo que los está utilizando incorrectamente. Los métodos de extensión se deben utilizar para realizar una tarea que simplemente sería inconveniente sintácticamente sin ellos. Su TruncateToLength es un buen ejemplo.
Probar TruncateToLength no implicaría burlarse, simplemente implicaría la creación de algunas cadenas y probar que el método realmente devolvió el valor adecuado.
Por otro lado, si tiene un código en su capa de datos contenido en los métodos de extensión que está accediendo a su almacén de datos, entonces sí, tiene un problema y las pruebas se convertirán en un problema.
Por lo general, solo uso métodos de extensión para proporcionar azúcar sintáctica para operaciones pequeñas y simples.