¿Qué es más fácil de entender, una gran declaración booleana (bastante compleja) o la misma declaración desglosada en métodos predicados (mucho código adicional para leer)?
Opción 1, la gran expresión booleana:
private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
return propVal.PropertyId == context.Definition.Id
&& !repo.ParentId.HasValue || repo.ParentId == propVal.ParentId
&& ((propVal.SecondaryFilter.HasValue && context.SecondaryFilter.HasValue && propVal.SecondaryFilter.Value == context.SecondaryFilter) || (!context.SecondaryFilter.HasValue && !propVal.SecondaryFilter.HasValue));
}
Opción 2, las condiciones desglosadas en métodos predicados:
private static bool ContextMatchesProp(CurrentSearchContext context, TValToMatch propVal)
{
return MatchesDefinitionId(context, propVal)
&& MatchesParentId(propVal)
&& (MatchedSecondaryFilter(context, propVal) || HasNoSecondaryFilter(context, propVal));
}
private static bool HasNoSecondaryFilter(CurrentSearchContext context, TValToMatch propVal)
{
return (!context.No.HasValue && !propVal.SecondaryFilter.HasValue);
}
private static bool MatchedSecondaryFilter(CurrentSearchContext context, TValToMatch propVal)
{
return (propVal.SecondaryFilter.HasValue && context.No.HasValue && propVal.SecondaryFilter.Value == context.No);
}
private bool MatchesParentId(TValToMatch propVal)
{
return (!repo.ParentId.HasValue || repo.ParentId == propVal.ParentId);
}
private static bool MatchesDefinitionId(CurrentSearchContext context, TValToMatch propVal)
{
return propVal.PropertyId == context.Definition.Id;
}
Prefiero el segundo enfoque, porque veo los nombres de los métodos como comentarios, pero entiendo que es problemático porque tienes que leer todos los métodos para entender lo que hace el código, por lo que abstrae la intención del código.
c#
readability
willem
fuente
fuente
if
declaración en ninguno de los bloques de código. Su pregunta es sobre expresiones booleanas .Respuestas:
El último enfoque. No solo es más fácil de entender, sino que también es más fácil escribir, probar, refactorizar y ampliar. Cada condición requerida se puede desacoplar y manejar de manera segura a su manera.
No es problemático si los métodos se nombran correctamente. De hecho, sería más fácil de entender ya que el nombre del método describiría la intención de la condición.
Para un espectador
if MatchesDefinitionId()
es más explicativo queif (propVal.PropertyId == context.Definition.Id)
[Personalmente, el primer acercamiento me duele los ojos.]
fuente
MatchesDefinitionId()
está en el límite.Si este es el único lugar donde se usarían estas funciones de predicado, también puede usar
bool
variables locales en su lugar:Estos también podrían desglosarse aún más y reordenarse para que sean más legibles, por ejemplo, con
y luego reemplazando todas las instancias de
propVal.SecondaryFilter.HasValue
. Una cosa que sobresale de inmediato es quehasNoSecondaryFilter
usa AND lógico en lasHasValue
propiedades negadas , mientras quematchesSecondaryFilter
usa un AND lógico en no negadoHasValue
, por lo que no es exactamente lo contrario.fuente
En general, se prefiere este último.
Hace que el sitio de la llamada sea más reutilizable. Es compatible con DRY (lo que significa que tiene menos lugares para cambiar cuando cambian los criterios, y puede hacerlo de manera más confiable). Y muy a menudo esos subcriterios son cosas que se reutilizarán independientemente en otro lugar, lo que le permite hacer eso.
Ah, y hace que estas cosas sean mucho más fáciles de probar por unidad, lo que te da la confianza de que lo has hecho correctamente.
fuente
repo
, que parece un campo / propiedad estático, es decir, una variable global. Los métodos estáticos deben ser deterministas y no utilizar variables globales.Si está entre estas dos opciones, entonces la última es mejor. ¡Sin embargo, estas no son las únicas opciones! ¿Qué tal dividir la función individual en múltiples ifs? Pruebe las formas de salir de la función para evitar pruebas adicionales, emulando aproximadamente un "cortocircuito" en una prueba de una sola línea.
Esto es más fácil de leer (es posible que deba verificar la lógica de su ejemplo, pero el concepto es cierto):
fuente
Me gusta más la opción 2, pero sugeriría un cambio estructural. Combine las dos verificaciones en la última línea del condicional en una sola llamada.
La razón por la que sugiero esto es que las dos comprobaciones son una sola unidad funcional, y el paréntesis anidado en un condicional es propenso a errores: tanto desde el punto de vista de escribir inicialmente el código como desde el punto de vista de la persona que lo lee. Este es especialmente el caso si los subelementos de la expresión no siguen el mismo patrón.
No estoy seguro de si
MatchesSecondaryFilterIfPresent()
es el mejor nombre para la combinación; pero nada mejor viene a mi mente de inmediato.fuente
Aunque en C #, el código no está muy orientado a objetos. Está utilizando métodos estáticos y lo que parecen campos estáticos (por ejemplo
repo
). En general, se sostiene que las estadísticas hacen que su código sea difícil de refactorizar y difícil de probar, al tiempo que dificultan la reutilización y, a su pregunta: el uso estático como este es menos legible y mantenible que la construcción orientada a objetos.Debería convertir este código a una forma más orientada a objetos. Cuando lo haga, encontrará que hay lugares razonables para colocar el código que hace la comparación de objetos, campos, etc. Es probable que luego pueda pedirle a los objetos que se comparen entre sí, lo que reduciría su declaración big if a solicitud simple de comparación (por ejemplo
if ( a.compareTo (b) ) { }
, que podría incluir todas las comparaciones de campo).C # tiene un rico conjunto de interfaces y utilidades de sistema para hacer comparaciones en objetos y sus campos. Más allá de lo obvio
.Equals
método, para empezar, mira enIEqualityComparer
,IEquatable
y los servicios públicos comoSystem.Collections.Generic.EqualityComparer.Default
.fuente
Este último es definitivamente preferido, he visto casos con la primera forma y casi siempre es imposible de leer. Cometí el error de hacerlo de la primera manera y me pidieron que lo cambiara a métodos predicados.
fuente
Yo diría que los dos son casi iguales, SI agrega un espacio en blanco para facilitar la lectura y algunos comentarios para ayudar al lector sobre las partes más oscuras.
Recuerde: un buen comentario le dice al lector lo que estaba pensando cuando escribió el código.
Con los cambios como he sugerido, probablemente seguiría el enfoque anterior, ya que es menos abarrotado y difuso. Las llamadas de subrutina son como notas al pie: proporcionan información útil pero interrumpen el flujo de lectura. Si los predicados fueran más complejos, los dividiría en métodos separados para que los conceptos que encarnan se puedan construir en fragmentos comprensibles.
fuente
Bueno, si hay partes que tal vez desee reutilizar, separarlas en funciones separadas con nombre apropiado es obviamente la mejor idea.
Incluso si nunca los reutiliza, hacerlo podría permitirle estructurar mejor sus condiciones y darles una etiqueta que describa lo que significan .
Ahora, echemos un vistazo a su primera opción, y reconozcamos que su sangría y salto de línea no fueron tan útiles, ni el condicional se estructuraba tan bien:
fuente
El primero es absolutamente horrible. Has estado usando || por dos cosas en la misma línea; eso es un error en su código o una intención de ofuscarlo.
Eso es al menos a mitad de formato decente (si el formato es complicado, es porque la condición if es complicada), y tiene al menos la oportunidad de descubrir si algo no tiene sentido. En comparación con su basura formateada si, cualquier otra cosa es mejor. Pero parece que solo puede hacer extremos: un desorden completo de una declaración if o cuatro métodos completamente inútiles.
Tenga en cuenta que (cond1 && cond2) || (! cond1 && cond3) se puede escribir como
lo que reduciría el desorden. Yo escribiria
fuente
No me gusta ninguna de esas soluciones, son difíciles de razonar y difíciles de leer. La abstracción a métodos más pequeños solo por métodos más pequeños no siempre resuelve el problema.
Idealmente, creo que compararía metaprográticamente las propiedades, por lo que no tiene que definir un nuevo método o si se bifurca cada vez que desea comparar un nuevo conjunto de propiedades.
No estoy seguro acerca de c #, pero en javascript, algo como esto sería MUCHO mejor y al menos podría reemplazar MatchesDefinitionId y MatchesParentId
fuente
compareContextProp(propVal, "PropertyId", context.Definition.Id)
sería más fácil de leer que la combinación booleana del OP de ~ 5 comparaciones del formulariopropVal.PropertyId == context.Definition.Id
. Es significativamente más largo y agrega una capa adicional sin ocultar realmente la complejidad del sitio de la llamada. (si importa, no voté en contra)