¿Tiene sentido que un refactor termine con un LOC más alto? [cerrado]

25

¿Hay casos en que un código más detallado (como en las declaraciones más lógicas) es más limpio que un código más conciso?

Nombre de usuario incorrecto
fuente
18
Claro que lo hace. Incluso al eliminar la duplicación, el código para definir la nueva función extraída ocupa espacio propio. Escribir una nueva función puede tomar cuatro líneas y guardar solo dos, y aún así vale la pena hacerlo.
Kilian Foth
55
¿"código más conciso"? Realmente odio la creencia equivocada de que un recuento de líneas más pequeño significa "mejor" código. No lo hace. De hecho, suele ser lo contrario. Llega un punto, y se alcanza muy rápido, donde cada vez más significado en cada vez menos espacio hace que el código sea más difícil de entender. De hecho, hay una competencia completa, el Concurso Internacional de Código C Ofuscado , donde muchos ganadores confían en esos límites de comprensión humana para escribir código impenetrable.
Andrew Henle
1
El título de su pregunta y su pregunta en sí están haciendo preguntas diferentes. Por ejemplo, una instrucción if se puede cambiar a una expresión ternaria que es la misma lógicamente pero solo 1 línea.
Capitán Man
1
-1 *** código más detallado (como en las declaraciones más lógicas) *** verbosidad y el número de declaraciones lógicas son dos cosas no relacionadas. Es mala forma de cambiar las definiciones.
Pieter B

Respuestas:

70

Para responder eso, tomemos un ejemplo del mundo real que me sucedió. En C # una biblioteca que mantengo, tenía el siguiente código:

TResult IConsFuncMatcher<T, TResult>.Result() =>
    TryCons(_enumerator) is var simpleMatchData && !simpleMatchData.head.HasValue
        ? _emptyValue.supplied
            ? _emptyValue.value
            : throw new NoMatchException("No empty clause supplied");
        : _recursiveConsTests.Any() 
            ? CalculateRecursiveResult() 
            : CalculateSimpleResult(simpleMatchData);

Al discutir esto con sus compañeros, el veredicto unánime fue que las expresiones ternarias anidadas, junto con el uso "inteligente" de is varresultaron en código conciso, pero difícil de leer.

Entonces lo refactoricé para:

TResult IConsFuncMatcher<T, TResult>.Result()
{
    var simpleMatchData = TryCons(_enumerator);

    if (!simpleMatchData.head.HasValue)
    {
        return _emptyValue.supplied
            ? _emptyValue.value
            : throw new NoMatchException("No empty clause supplied");
    }

    return _recursiveConsTests.Any() 
        ? CalculateRecursiveResult() 
        : CalculateSimpleResult(simpleMatchData);
}

La versión original contenía solo una expresión compuesta con una implícita return. La nueva versión ahora contiene una declaración de variable explícita, una ifdeclaración y dos explícitos returns. Contiene más declaraciones y más líneas de código. Sin embargo, todos los que consulté consideraron que era más fácil leer y razonar, que son aspectos clave del "código limpio".

Entonces, la respuesta a su pregunta es un "sí" enfático, más detallado puede ser más limpio que el código conciso y, por lo tanto, es una refactorización válida.

David Arno
fuente
34
En la actualidad, los cerebros de los desarrolladores son un recurso más escaso que el disco, la CPU, la RAM o el ancho de banda de la red. Esas otras cosas son importantes, y en ciertas aplicaciones pueden ser su factor limitante, pero en la mayoría de los casos, desea optimizar la capacidad de sus desarrolladores para comprender el código primero , y luego esas otras cosas.
anaximander
2
@anaximander, absolutamente de acuerdo. Escriba el código para que otras personas lo lean primero y el compilador después. Es por eso que me parece útil que otros revisen mi código, incluso si soy el único que lo está desarrollando.
David Arno
44
Si estuviera revisando eso, sugeriría invertir el orden de las declaraciones de devolución y eliminar el !de la condición. También sugeriría poner el segundo retorno en un else. Pero incluso como está, es una mejora masiva.
Martin Bonner apoya a Monica el
2
@DavidArno Veo esa lógica, y si if (!foo.HasValue)es un idioma en su código, aún más fuerte. Sin embargo, en realidadif no es una salida temprana: es un "hacer esto o aquello dependiendo".
Martin Bonner apoya a Monica el
2
@fabric La comparación de valores booleanos es peligrosa. Lo evito tanto como puedo.
Martin Bonner apoya a Monica el
30

1. Falta de correlación entre LOC y la calidad del código.

El objetivo de la refactorización es mejorar la calidad de un código.

LOC es una métrica muy básica que, a veces, se correlaciona con la calidad de un código: por ejemplo, es probable que un método con unos pocos miles de LOC tenga problemas de calidad. Cabe señalar, sin embargo, que LOC no es el único métrica, y en muchos casos carece de la correlación con la calidad. Por ejemplo, un método 4 LOC no es necesariamente más legible o más fácil de mantener que un método 6 LOC.

2. Algunas técnicas de refactorización consisten en agregar LOC.

Si toma una lista de técnicas de refactorización , puede detectar fácilmente las que consisten en agregar LOCs intencionalmente . Ejemplos:

Ambas son técnicas de refactorización muy útiles, y su efecto en el LOC es completamente irrelevante cuando se considera si usarlas o no.

Evita usar LOC.

LOC es una métrica peligrosa. Es muy fácil de medir y es muy difícil de interpretar correctamente.

Hasta que se familiarice con las técnicas de medición de la calidad del código, considere evitar medir LOC en primer lugar. La mayoría de las veces, no obtendrá nada relevante, y habrá casos en los que lo engañará para que disminuya la calidad de su código.

Arseni Mourzenko
fuente
Refactorizó su respuesta y mejoró la calidad al agregar MUCHO (líneas de texto): p
grinch
12

Si desea ver el resultado final de solo minimizar el recuento de bytes o el recuento de LoC de su código fuente, eche un vistazo a los envíos al sitio de Stack Exchange Code Golf .

Si su código fuente se reduce de tal manera, pronto tendrá un desastre imposible de mantener. Incluso si usted es la persona que escribió dicho código y lo entiende completamente en ese momento, ¿qué tan eficiente será cuando regrese a él dentro de seis meses? Tampoco hay evidencia de que ese código mínimo realmente se ejecute más rápido.

El código debe estar escrito de tal manera que cualquier miembro de su equipo pueda verlo y comprender lo que está haciendo de inmediato.

Halcón peregrino
fuente
Quizás redundante, pero solo para explicarlo; si refactoriza el código de golf para facilitar la lectura, siempre terminas con más
LoC
1

Sí, la refactorización definitivamente puede generar más líneas de código.

El caso más común de IMO es cuando tomas código que no es genérico y lo haces más genérico / flexible . El código genérico hace que las líneas de código aumenten significativamente (a veces por un factor de dos o más).

Si espera que el código genérico nuevo sea utilizado por otros (en lugar de solo como un componente interno de software) como una biblioteca, generalmente terminará agregando código de prueba de unidad y marcado de documentación en código que aumentará las líneas de código nuevamente.

Por ejemplo, aquí hay un escenario muy común que ocurre para cada desarrollador de software:

  • su producto necesita una nueva característica urgente de alta prioridad o corrección de errores o mejora en dos semanas (o cualquier período de tiempo que se considere urgente para el tamaño de su proyecto / tamaño de la empresa / etc.)
  • trabajas duro y entregas XYZ a tiempo y funciona. ¡Felicidades! ¡Gran trabajo!
  • Mientras desarrollaba XYZ, su diseño / implementación de código existente realmente no era compatible con XYZ, pero pudo colocar XYZ en la base de código
  • el problema es que la cuña es fea y tiene un olor a código terrible porque hiciste algunas cosas difíciles / inteligentes / feas / malas-prácticas-pero-un poco-funciona
  • cuando encuentre tiempo más tarde, refactorice el código, lo que puede cambiar muchas de las clases o agregar una nueva capa de clases y su nueva solución se "hace bien" y ya no tiene el mal olor del código ... sin embargo, al hacerlo "forma correcta" ahora ocupa muchas más líneas de código.

Algunos ejemplos concretos que me vienen a la cabeza:

  • para una interfaz de línea de comando , podría tener 5000 líneas de código if / else-if o podría usar devoluciones de llamada ... cada devolución de llamada sería mucho más pequeña y más fácil de leer / probar / verificar / depurar / etc. pero si cuenta líneas de codifique las 5000 líneas feas de código if / else-if probablemente sería más pequeño
  • para un fragmento de código de procesamiento que admite N métodos de procesamiento , podría usar nuevamente declaraciones if / else que se verían más feas ...
    • o podría cambiar a devoluciones de llamada, que serían mejores / mejores, pero las devoluciones de llamada toman más líneas de código (aunque todavía es tiempo de compilación)
    • o puede abstraer más y hacer complementos que se pueden cambiar en tiempo de ejecución. los complementos son buenos porque no tiene que volver a compilar el producto principal para cada nuevo complemento o modificación de un complemento existente. y puede publicar la API para que otros puedan ampliar el producto. PERO de nuevo, un enfoque de complemento utiliza más líneas de código.
  • para una GUI, creas un nuevo widget genial
    • usted o un compañero de trabajo observan que el nuevo widget sería excelente para XYZ y ABC, pero en este momento el widget está estrechamente integrado solo para funcionar para XYZ
    • refactoriza el widget para que funcione para ambos, pero ahora las líneas totales de código aumentan
Trevor Boyd Smith
fuente