¿Debo refactorizar funciones grandes que consisten principalmente en una expresión regular? [cerrado]

15

Acabo de escribir una función que abarca aproximadamente 100 líneas. Al escuchar eso, probablemente tengas la tentación de contarme sobre responsabilidades individuales y de instarme a refactorizar. Este es mi instinto también, pero aquí está el problema: la función hace una cosa. Realiza una manipulación de cadena compleja, y el cuerpo de la función consiste principalmente en una expresión regular detallada, dividida en muchas líneas que están documentadas. Si dividiera la expresión regular en múltiples funciones, siento que en realidad perdería legibilidad, ya que estoy cambiando de idioma de manera efectiva y no podré aprovechar algunas de las características que ofrecen las expresiones regulares. Aquí ahora es mi pregunta:

Cuando se trata de la manipulación de cadenas con expresiones regulares, ¿los cuerpos de funciones grandes siguen siendo un antipatrón? Parece que los grupos de captura con nombre tienen un propósito muy similar a las funciones. Por cierto, tengo pruebas para cada flujo a través de la expresión regular.

DudeOnRock
fuente
3
No creo que haya ningún problema con su función, considerando que gran parte de ella es documentación . Sin embargo, puede haber un problema de mantenimiento con el uso de una expresión regular grande en primer lugar.
Joel Cornett
2
¿Estás seguro de que una expresión regular gigante es la mejor solución a tu problema? ¿Ha considerado alternativas más simples, como una biblioteca de analizador o la sustitución de un formato de archivo personalizado por uno estándar (XML, JSON, etc.)?
lortabac
2
¿Hay otras funciones, usando una versión alterada / mejorada / simplificada de esta expresión regular? Ese sería un indicador importante de que debería llevarse a cabo la refactorización. Si no, lo dejaría como está también. Necesitar una manipulación de cadena compleja como esa es una bandera amarilla por derecho propio (bueno, no conozco el contexto, por lo tanto, solo amarillo), y refactorizar la función hacia abajo me parece más un ritual para redimir de la culpa que uno siente. it;)
Konrad Morawski
8
¿Cómo puede una expresión regular de 100 líneas hacer solo 1 cosa?
Pieter B
@lortabac: La entrada es texto generado por el usuario (prosa)
DudeOnRock

Respuestas:

36

Lo que está encontrando es la disonancia cognitiva que proviene de escuchar a las personas que favorecen la adhesión servil a las pautas bajo la apariencia de "mejores prácticas" sobre la toma de decisiones razonadas.

Claramente has hecho tu tarea:

  • Se entiende el propósito de la función.
  • Se entiende el funcionamiento de su implementación (es decir, legible).
  • Hay pruebas de cobertura total de la implementación.
  • Esas pruebas pasan, lo que significa que cree que la implementación es correcta.

Si alguno de esos puntos no fuera cierto, sería el primero en decir que su función necesita trabajo. Entonces hay un voto para dejar el código tal como está.

El segundo voto proviene de mirar sus opciones y lo que obtiene (y pierde) de cada una:

  • Refactorizador Esto le permite cumplir con la idea de alguien de cuánto tiempo debe durar una función y sacrifica la legibilidad.
  • Hacer nada. Esto mantiene la legibilidad existente y sacrifica el cumplimiento de la idea de alguien de cuánto tiempo debe durar una función.

Esta decisión se reduce a lo que valoras más: legibilidad o duración. Caigo en el campamento que cree que la longitud es agradable, pero la legibilidad es importante y tomaré lo último sobre lo primero cualquier día de la semana.

En pocas palabras: si no está roto, no lo arregles.

Blrfl
fuente
10
+1 para "Si no está roto, no lo arregles".
Giorgio
En efecto. Las reglas de Sandy Metz ( gist.github.com/henrik/4509394 ) son agradables y todo, pero en youtube.com/watch?v=VO-NvnZfMA4#t=1379 ella habla sobre cómo surgieron y por qué la gente está tomando ellos demasiado en serio.
Amadan
@Amdan: con el contexto adicional del video, lo que hizo Metz tiene sentido. Su recomendación a ese cliente fue intencionalmente extrema en un extremo para contrarrestar el comportamiento que era extremo en el otro extremo como una forma de arrastrarlo al medio más razonable. El resto de esa discusión se reduce a la esencia de mi respuesta: el razonamiento, no la fe, es la forma de determinar el mejor curso de acción.
Blrfl
19

Honestamente, su función puede "hacer una cosa", pero como usted mismo dijo

Podría comenzar a dividir la expresión regular en múltiples funciones,

lo que significa que su código ex reg hace muchas cosas. Y supongo que podría desglosarse en unidades más pequeñas y comprobables individualmente. Sin embargo, si esta es una buena idea, no es fácil de responder (especialmente sin ver el código real). Y la respuesta correcta puede ser ni "sí" o "no", sino "todavía no, pero la próxima vez que tenga que cambiar algo en ese registro exp".

pero siento que realmente perdería la legibilidad de esa manera, ya que estoy cambiando efectivamente los idiomas

Y este es el punto central: tiene un fragmento de código escrito en un lenguaje regular . Este lenguaje no proporciona ningún buen medio de abstracción en sí mismo (y no considero que los "grupos de captura con nombre" reemplacen las funciones). Por lo tanto, la refactorización "en el lenguaje reg ex" no es realmente posible, y entrelazar los registros reg más pequeños con el idioma del host puede no mejorar la legibilidad (al menos, así lo siente , pero tiene dudas, de lo contrario no habría publicado la pregunta) . Así que aquí está mi consejo

  • muestre su código a otro desarrollador avanzado (tal vez en /codereview// ) para asegurarse de que otros piensen en la legibilidad como lo hace usted. Sea abierto a la idea de que otros pueden no encontrar un registro de 100 líneas tan legible como usted. A veces, la noción de "no se puede romper fácilmente en pedazos más pequeños" se puede superar con un segundo par de ojos.

  • observe la capacidad de evolución real: ¿su brillante registro aún se ve tan bien cuando llegan nuevos requisitos y tiene que implementarlos y probarlos? Mientras su registro exp funcione, no lo tocaría, pero cada vez que hay que cambiar algo, lo reconsideraría si realmente fuera una buena idea poner todo en este gran bloque, y (¡en serio!) Repensar si se divide en piezas más pequeñas no serían una mejor opción.

  • observe la capacidad de mantenimiento: ¿puede depurar efectivamente el registro en el formulario actual muy bien? Especialmente después de que tiene que cambiar algo, y ahora sus pruebas le dicen que algo está mal, ¿tiene un depurador de registros regulares que lo ayude a encontrar la causa raíz? Si la depuración se vuelve difícil, esa también sería una ocasión para reconsiderar su diseño.

Doc Brown
fuente
Diría que los grupos de captura con nombre (grupos de captura en general, en realidad) son más similares a las variables finales / de escritura única, o quizás macros. Le permiten hacer referencia a partes específicas de la coincidencia, ya sea desde el objeto de coincidencia devuelto por el procesador de expresiones regulares o más tarde en la propia expresión regular.
JAB
4

A veces, una función más larga que hace una cosa es la forma más adecuada de manejar una unidad de trabajo. Puede acceder fácilmente a funciones muy largas cuando comience a consultar una base de datos (utilizando su lenguaje de consulta favorito). Hacer que una función (o método) sea más legible y limitarla a su propósito establecido es lo que consideraría el resultado más deseable de una función.

La longitud es un "estándar" arbitrario cuando se trata del tamaño del código. Cuando una función de 100 líneas en C # puede considerarse alargada, sería pequeña en algunas versiones de ensamblaje. He visto algunas consultas SQL que estaban bien en el rango de 200 líneas de código que devolvieron un conjunto de datos muy complicado para un informe.

Código completamente funcional , que es tan simple como razonablemente puede hacer que sea el objetivo.

No lo cambie solo porque es largo.

Adam Zuckerman
fuente
3

Siempre puedes dividir la expresión regular en sub-expresiones regulares y gradualmente componer la expresión final. Esto podría ayudar a la comprensión de un patrón muy grande, particularmente si el mismo subpatrón se repite muchas veces. Por ejemplo en Perl;

my $start_re = qr/(?:\w+\.\w+)/;
my $middle_re = qr/(?:DOG)|(?:CAT)/;
my $end_re = qr/ => \d+/;

my $final_re = $start_re . $middle_re . $end_re;
# or: 
# my $final_re = qr/${start_re}${middle_re}${end_re}/
Rory Hunter
fuente
Utilizo la bandera detallada, que es aún más conveniente de lo que estás sugiriendo.
DudeOnRock
1

Yo diría romperlo si es rompible. desde el punto de vista de la mantenibilidad y quizás la resabilidad, tiene sentido romperlo, pero, por supuesto, debe tener en cuenta su función y la forma en que obtiene información y lo que va a devolver.

Recuerdo que estaba trabajando en analizar la transmisión de datos fragmentados en objetos, así que lo que hice básicamente fue dividirlo en dos partes principales, una fue construir una unidad completa de Cadena de texto codificado y en la segunda parte analizar esas unidades en el diccionario de datos y organizar ellos (podrían ser propiedades aleatorias para diferentes objetos) y que actualizar o crear objetos.

También podría dividir cada parte principal en varias funciones más pequeñas y más específicas, por lo que al final tenía 5 funciones diferentes para hacer todo y podría reutilizar algunas de las funciones en un lugar diferente.

arfo
fuente
1

Una cosa que puede o no haber considerado es escribir un pequeño analizador en el idioma que está usando en lugar de usar una expresión regular en ese idioma. Esto puede ser más fácil de leer, probar y mantener.

Thomas Eding
fuente
He pensado en esto yo mismo. El problema es que la entrada es en prosa y estoy tomando señales del contexto y el formato. Si es posible escribir un analizador sintáctico para algo como esto, ¡me encantaría saber más! No pude encontrar nada yo mismo.
DudeOnRock
1
Si un regex puede analizarlo, puede analizarlo. Su respuesta me hace pensar que es posible que no esté bien versado en el análisis. Si ese es el caso, es posible que desee seguir con la expresión regular. O eso o aprender una nueva habilidad.
Thomas Eding
Me encantaría aprender una nueva habilidad. ¿Algún buen recurso que pueda sugerir? Estoy interesado en la teoría detrás de esto también.
DudeOnRock
1

Las expresiones regulares gigantes son una mala elección en la mayoría de los casos. En mi experiencia, a menudo se usan porque el desarrollador no está familiarizado con el análisis (ver la respuesta de Thomas Eding ).

De todos modos, supongamos que desea apegarse a una solución basada en expresiones regulares.

Como no conozco el código real, examinaré los dos escenarios posibles:

  • La expresión regular es simple (muchas coincidencias literales y pocas alternativas)

    En este caso, las funciones avanzadas que ofrece una expresión regular única no son indispensables. Esto significa que probablemente se beneficiará al dividirlo.

  • La expresión regular es compleja (muchas alternativas)

    En este caso, no puede tener una cobertura de prueba completa de manera realista, porque probablemente tenga millones de flujos posibles. Entonces, para probarlo, debes dividirlo.

Puede que me falte imaginación, pero no puedo pensar en ninguna situación del mundo real en la que una expresión regular de 100 líneas sea una buena solución.

lortabac
fuente