5

? En mi proyecto actual, nos marcamos algunos objetivos para las métricas de código "Índice de mantenimiento" y "Complejidad ciclométrica". El Índice de Mantenibilidad debe ser de 60 o más y la Complejidad Ciclometica de 25 o menos. Sabemos que el Índice de Mantenibilidad de 60 y más es bastante alto.¿Podría abstraer sus consultas LINQ en los métodos de extensión

También utilizamos una gran cantidad de linq para filtrar/agrupar/seleccionar entidades. Descubrí que estas consultas de linq no tienen una puntuación tan alta en el Índice de Mantenibilidad. Resumiendo estas consultas en métodos de extensión me está dando un Índice de Mantenibilidad más alto, que es bueno. Pero en la mayoría de los casos, los métodos de extensión ya no son genéricos porque los utilizo con mis Tipos en lugar de los genéricos.

Por ejemplo la siguiente LINQ consulta vs método de extensión:

consulta LINQ

List.Where(m => m.BeginTime >= selectionFrom && m.EndTime <= selectionTo) 

método de extensión:

public static IEnumerable<MyType> FilterBy(this IEnumerable<MyType> source, DateTime selectionFrom, DateTime selectionTo) 
{ 
    return (IEnumerable<MyType>)source.Where(m => m.BeginTime >= selectionFrom && m.EndTime <= selectionTo); 
} 

List.FilterBy(selectionFrom, selectionTo); 

El método de extensión me da un índice de mantenibilidad mejora de 6 puntos, y da una buena sintaxis fluida. Por otro lado, tengo que agregar una clase estática, no es genérica.

¿Alguna idea sobre qué enfoque tendría su favor? ¿O quizás tenga diferentes ideas sobre cómo refactorizar las consultas de linq para mejorar el Índice de Mantenibilidad?

+3

Las métricas son simplemente una medida, pero nunca un objetivo. –

+0

Soy un poco escéptico con respecto a cualquier Índice de Mantenibilidad que sea una caja negra inexplicable. Al menos con la complejidad ciclomática, es posible establecer lo que realmente se está midiendo. ¿Sabe si se explica en algún lugar más allá de la (no) explicación [aquí] (http://msdn.microsoft.com/en-us/library/bb385914.aspx)? – AakashM

Respuesta

5

No debe agregar clases por métricas. Cualquier métrica tiene como objetivo hacer que su código sea mejor pero seguir las reglas a ciegas, incluso las mejores reglas, pueden de hecho dañar su código.

No creo que sea una buena idea mantener ciertos índices de Mantenibilidad y Complejidad. Creo que son útiles para evaluar el código anterior, es decir, cuando heredó un proyecto y necesita estimar su complejidad. Sin embargo, es absurdo extraer un método porque no ha puntuado lo suficiente.

Solo refactorice si dicha refactorización agrega valor al código. Tal valor es un complejo humana métrica inexpresable en número, y la estimación es exactamente lo que la experiencia de programación es sobre-encontrar el equilibrio entre la optimización vs legibilidad vs API limpia vs código fresca vs código simple vs envío rápido vs generalización vs especificación, etc.

Ésta es la única métrica que debe seguir, pero no siempre es la métrica todo el mundo está de acuerdo en ...

En cuanto a su ejemplo, si la misma consulta LINQ se utiliza una y otra vez, tiene todo el sentido de crear una carpeta EnumerableExtensions en Extensions y extráigala allí. Sin embargo, si se usa una o dos veces, o está sujeto a cambios, la consulta detallada es mucho mejor.

También no entiendo por qué dices que no son genéricos con connotaciones algo negativas. ¡No necesitas genéricos en todas partes! De hecho, al escribir métodos de extensión, debe considerar los tipos más específicos que puede elegir para no contaminar el conjunto de métodos de otras clases. Si usted quiere que su ayudante para trabajar sólo con IEnumerable<MyType>, no hay absolutamente ninguna vergüenza en la declaración de un método de extensión exactamente para this IEnumerable<MyType>. Por cierto, hay fundición redundante en su ejemplo. Desaste de eso.

Y no se olvide, las herramientas son estúpidos. Nosotros también, humanos.

4

Mi consejo para usted sería ... no sea esclavo de sus métricas! Se generan en máquina y solo están destinados a ser utilizados como guía. Nunca van a ser un reemplazo para un programador experimentado con experiencia.

¿Cuál crees que es correcto para tu aplicación?

+0

Tal vez mi pregunta haga que parezca que estamos esclavizados a las métricas, que no es el caso. Lo estamos utilizando como una guía, pero también nos fijamos un objetivo alto, para asegurarnos de que todos piensen sobre cómo hacer que sus métodos sean lo más limpios posible. Tengo algunas ideas sobre qué usar, pero me gustaría ver lo que otros piensan al respecto. – ChristiaanV

1

Nº: en este caso yo ignoro la complejidad ciclomática - lo que originalmente tenía era mejor.

preguntarse lo que es más explicativo. Este:

List.Where(m => m.BeginTime >= selectionFrom && m.EndTime <= selectionTo) 

o esto:

List.FilterBy(selectionFrom, selectionTo); 

La primera expresa claramente lo que quiere, mientras que el segundo no lo hace. La única forma de saber qué significa "FilterBy" es ir al código fuente y observar su implementación.

Resumir fragmentos de consulta en métodos de extensión tiene sentido en escenarios más complejos, donde no es fácil juzgar de un vistazo qué está haciendo el fragmento de consulta.

+0

Estoy de acuerdo con su posición; solo para observar que según la pregunta, este cambio aparentemente mejora el * Índice de Mantenibilidad *, en lugar de la complejidad ciclomática. – AakashM

2

mi parte, de acuerdo con la estrategia de método de extensión. Lo he usado sin problemas en un puñado de aplicaciones del mundo real.

Para mí, no solo se trata de las métricas, sino también de la reutilización del código allí. Consulte los siguientes pseudo-ejemplos:

var x = _repository.Customers().WhichAreGoldCustomers(); 

var y = _repository.Customers().WhichAreBehindInPayments(); 

Tener esos dos métodos de extensión logra su objetivo para las métricas, y también proporciona "un lugar para la definición de lo que es ser un cliente de oro." No tiene diferentes consultas que se crean en diferentes lugares por diferentes desarrolladores cuando necesitan trabajar con "clientes de oro".

Además, son componibles:

var z = _repository.Customers().WhichAreGoldCustomers().WhichAreBehindInPayments(); 

mi humilde opinión este es un enfoque ganador.

El único problema al que nos hemos enfrentado es que hay un error ReSharper que a veces el Intellisense para los métodos de extensión se vuelve loco. Escribe ".Whic" y le permite elegir el método de extensión que desea, pero cuando "tab" en él, pone algo completamente diferente en el código, no el método de extensión que seleccionó. Consideré cambiar de ReSharper por esto, pero ...nah :)

+0

Es cierto, eso es lo que también me gusta de él. – ChristiaanV

0

He utilizado esta técnica en lugares, por ejemplo, una clase Payment tiene una clase correspondiente PaymentLinqExtensions que proporciona extensiones específicas de dominio para pagos.

En el ejemplo que da elegiría un nombre de método más descriptivo. También está la cuestión de si el rango es inclusivo o exclusivo, de lo contrario se ve bien.

Si tiene varios objetos en su sistema por el cual el concepto de tener una fecha es común luego considerar una interfaz, tal vez IHaveADate (o algo mejor :-)

public static IQueryable<T> WithinDateRange(this IQueryable<T> source, DateTime from, DateTime to) where T:IHaveADate 

(IQueryable es interesante. Pongo No creo que IEnumerable pueda lanzarlo, lo cual es una pena. Si está trabajando con consultas de bases de datos, entonces puede permitir que su lógica aparezca en el SQL final que se envía al servidor, lo cual es bueno. LINQ que su código no se ejecuta cuando espera que lo sea)

Si los rangos de fechas son un concepto importante en su aplicación y debe ser coherente sobre si el rango comienza a la medianoche al final de "Fecha de finalización" o la medianoche al comienzo del mismo, entonces una clase DateRange puede ser útil. Entonces

public static IQueryable<T> WithinDateRange(this IQueryable<T> source, DateRange range) where T:IHaveADate 

Se podría también, en caso que lo desee, proporcionar

public static IEnumerable<T> WithinDateRange(this IEnumerable<T> source, DateRange range, Func<DateTime,T> getDate) 

pero esto a mí se siente más que ver con DateRange. No sé cuánto se usaría, aunque su situación puede variar. Descubrí que ser demasiado genérico puede dificultar la comprensión de las cosas y LINQ puede ser difícil de depurar.

var filtered = myThingCollection.WithinDateRange(myDateRange, x => x.Date) 
Cuestiones relacionadas