15

Todo,¿Los métodos de extensión ocultan las dependencias?

Quería obtener algunas ideas al respecto. Últimamente me estoy volviendo cada vez más suscriptor de los principios "puristas" de DI/IOC al diseñar/desarrollar. Parte de esto (una gran parte) implica asegurarse de que haya poco acoplamiento entre mis clases, y que sus dependencias se resuelvan a través del constructor (sin duda hay otras formas de manejar esto, pero se entiende 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 asegurar que las cuerdas se insertan en las tablas de bases de datos se truncan 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); 
    } 
} 

A continuación, puedo llamar a mi cadena desde dentro de otra clase, así:

string myString = "myValue.TruncateThisPartPlease."; 
myString.TruncateToSize(8); 

una traducción de esta justa sin necesidad de utilizar un método de extensión sería:

string myString = "myValue.TruncateThisPartPlease."; 
StaticStringUtil.TruncateToSize(myString, 8); 

Cualquier clase que utilice cualquiera de los ejemplos anteriores no se pudo probar independientemente de la clase que contiene el método TruncateToSize (TypeMock a un lado). Si yo no estuviera usando un método de extensión, y yo no quiero crear una dependencia estática, que 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 podría probarse sin dependencia de la clase real del método TruncateToSize (se podría burlar fácilmente).

Desde mi punto de vista, los primeros dos ejemplos se basan en dependencias estáticas (uno explícito, uno oculto), mientras que el segundo invierte la dependencia y proporciona un acoplamiento reducido y una mejor capacidad de comprobación.

Entonces, ¿el uso de métodos de extensión está en conflicto con los principios de DI/IOC? Si es un suscriptor de la metodología IOC, ¿evita utilizar métodos de extensión?

+0

¡Su método de extensión no verifica la entrada nula! – canon

+0

@antisanity: AFAIK es lógicamente imposible que el parámetro de instancia de un método de extensión (* input * en este caso) sea nulo. –

+3

Sí, Phil ... puede ser nulo. – canon

Respuesta

15

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 deberían usarse para realizar una tarea que simplemente sería incómoda sintácticamente sin ellos. Su TruncateToLength es un buen ejemplo.

La prueba TruncateToLength no implicaría burlarse de ella, simplemente implicaría la creación de algunas cadenas y la prueba de que el método realmente devolvió el valor correcto.

Por otro lado, si tiene código en su capa de datos contenida 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.

Normalmente solo uso métodos de extensión para proporcionar azúcar sintáctico para operaciones pequeñas y simples.

18

Creo que está bien, porque no es como TruncateToSize es un componente reemplazable de manera realista. Es un método que solo necesitará hacer una sola cosa.

No necesita poder simular todo - solo servicios que interrumpen las pruebas unitarias (acceso a archivos, etc.) o los que desea probar en términos de dependencias genuinas. Si lo estuvieras usando para realizar la autenticación o algo así, sería una cuestión 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 burlar 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. Prueba la unidad al diablo TruncateToSize y no te preocupes por eso.

+0

La misma idea que mi publicación, de acuerdo – LorenVS

+0

Por lo tanto, no estoy en desacuerdo con su respuesta, es muy pragmático. Definitivamente puede tomar IOC (o cualquier teoría de diseño) demasiado lejos. En aras de la argumentación: suponiendo que está comprometido con el COI, no tendría ningún problema con el segundo ejemplo de implementación (la dependencia explícita de una clase estática)? Nunca he leído un artículo de un defensor incondicional de IOC que dijo que las dependencias estáticas son aceptables. –

+0

¿Qué pasa si hay dos algoritmos para TruncateToSize? Uno que intercambia velocidad por memoria y otro que intercambia memoria por velocidad. ¿No sería mejor en ese caso si tuviera una interfaz? Y dado que no puede predecir la necesidad de estos, ¿no debería simplemente hacer que sea una interfaz en primer lugar? –

0

Es un dolor porque son difíciles de burlar. Yo suelo usar una de estas estrategias

  1. Yep, el desguace de la extensión es un PITA para burlarse cabo
  2. Uso de la extensión y sólo prueba que hizo lo correcto. es decir, pasar datos al truncado y comprobar que se truncaron
  3. Si no es algo trivial, y TENGO que burlarlo, haré que mi clase de extensión tenga un colocador para el servicio que utiliza, y lo configuré en la prueba código.

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 es un poco de miedo porque alguien podría establecer el servicio cuando no debería, pero estoy un poco arrogante a veces, y si era realmente importante, lo que podía hacer algo inteligente con las banderas #if TEST o el patrón ServiceLocator para evitar que el setter se use en producción.

0

Métodos de extensión, clases parciales y objetos dinámicos. Realmente me gustan, sin embargo debes andar con cuidado, hay monstruos aquí.

Me gustaría ver los lenguajes dinámicos y ver cómo lidian con este tipo de problemas en el día a día, es realmente esclarecedor. Especialmente cuando no tienen nada que les impida hacer cosas estúpidas aparte de un buen diseño y disciplina. Todo es dinámico en tiempo de ejecución, lo único que los detiene es que la computadora arroje un error importante de tiempo de ejecución. "Duck Typing" es lo más loco que he visto en mi vida, el buen código se debe al buen diseño del programa, respeto por los demás integrantes de tu equipo y la confianza de que cada miembro, aunque tiene la capacidad de hacer cosas alocadas, decide no hacerlo porque es bueno el diseño conduce a mejores resultados.

En cuanto a su 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 manera funcional? Tiendo a usarlos como lo harías en un estilo de programación funcional, ingresa la entrada, los resultados salen sin magia en el medio, solo clases de framework directo que sabes que los chicos de MS han diseñado y probado: P en el que puedes confiar en.

Si está haciendo cosas pesadas utilizando métodos de extensión, volvería a ver el diseño de su programa, revise sus diseños CRC, modelos de clase, casos de uso, DFD, diagramas de acción o lo que quiera usar y descubra dónde en este diseño, planeó poner esto en un método de extensión en lugar de una clase adecuada.

Al final del día, solo puede probar contra el diseño de su sistema y no codificar fuera de su alcance.Si vas a usar clases de extensión, mi consejo sería mirar los modelos de composición de objetos en su lugar y usar la herencia solo cuando haya una muy buena razón.

Objeto La composición siempre gana conmigo ya que producen código sólido. Puede enchufarlos, sacarlos y hacer lo que quiera con ellos. Eso sí, todo depende de si usas interfaces o no como parte de tu diseño. Además, si utiliza clases de Composición, el árbol de jerarquía de clases se aplana en clases discretas y hay menos lugares donde su método de extensión será recogido a través de clases heredadas.

Si debe utilizar una clase que actúa sobre otra clase como es el caso con los métodos de extensión, mire primero el patrón de visitante y decida si es una mejor ruta.

Cuestiones relacionadas