regulares - regex replace()
¿Las expresiones regulares son realmente mantenibles? (20)
¿Las expresiones regulares son la forma de hacer las cosas? Depende de la tarea.
Al igual que con todo lo relacionado con la programación, no existe una respuesta correcta o incorrecta.
Si una expresión regular resuelve una tarea en particular rápida y simplemente, entonces es posiblemente mejor que una solución más detallada.
Si una expresión regular está tratando de lograr una tarea complicada, entonces algo más detallado podría ser más simple de entender y, por lo tanto, mantener.
Cualquier código que he visto que usa Regex tiende a usarlos como una caja negra:
- Poner en una cuerda
- Magic Regex
- Saca la cuerda
Esto no parece una idea particularmente buena para usar en el código de producción, ya que incluso un pequeño cambio a menudo puede dar como resultado una expresión regular completamente diferente.
Además de los casos en los que el estándar es permanente e inmutable, ¿las expresiones regulares son la forma de hacer las cosas, o es mejor probar diferentes métodos?
Cuando se usan de forma consciente, las expresiones regulares son un mecanismo poderoso que le evita las líneas y líneas de posible análisis de texto. Por supuesto, deben documentarse de manera correcta y eficiente para verificar si las suposiciones iniciales aún son válidas y de lo contrario actualizarlas en consecuencia. Con respecto al mantenimiento en mi humilde opinión, es mejor cambiar una sola línea de código (el patrón de expresión regular) que comprender las líneas y líneas de código de análisis o cualquiera que sea el propósito de las expresiones regulares.
El problema no es con las expresiones regulares, sino con su tratamiento como una caja negra. Al igual que con cualquier lenguaje de programación, el mantenimiento tiene más que ver con la persona que lo escribió y la persona que lo lee que con el idioma en sí.
También hay mucho que decir sobre el uso de la herramienta adecuada para el trabajo. En el ejemplo que mencionaste en tu comentario en la publicación original, una expresión regular es la herramienta incorrecta que se debe usar para analizar HTML, como se menciona con frecuencia en PerlMonks. Si tratas de analizar HTML en cualquier cosa que se parezca a una manera general utilizando solo una expresión regular, entonces vas a terminar haciéndolo de una manera incorrecta y frágil, escribiendo una monstruosidad horrendo e inmanejable de una expresión regular, o (lo más probable) ambos.
Hay muchas posibilidades para que RegEx sea más fácil de mantener. Al final, es solo una técnica que un (programador) tiene que aprender cuando se trata de cambios importantes (o a veces incluso menores). Cuando no había algunos profesionales realmente buenos, nadie se molestaría con ellos debido a su compleja sintaxis. Pero son rápidos, compactos y muy flexibles en su trabajo.
Para las personas de .NET podría haber una peor biblioteca de la biblioteca " Linq to RegEx " o " Biblioteca legible de expresiones regulares ". Los hace más fáciles de mantener y aún más fáciles de escribir. Los usé a los dos en proyectos propios. Sabía que el código fuente html que analicé con ellos podría cambiar en cualquier momento.
Pero confía en mí: cuando te acuerdas de ellos, incluso podrían burlarse de escribir y leer. :)
Las expresiones complejas son fuego-y-olvídate para mí. Escríbelo, pruébalo y, cuando funcione, escribe un comentario sobre lo que hace y estamos bien.
Sin embargo, en muchos casos, puede descomponer las expresiones regulares en partes más pequeñas, tal vez escribir algún código bien documentado que combine estas expresiones regulares. Pero si encuentras una expresión regular multilínea en tu código, será mejor que no seas el que debe mantenerla :)
¿Suena familiar? Eso es más o menos cierto de cualquier código. No desea tener métodos muy largos, no desea tener clases muy largas, y no desea tener expresiones regulares muy largas, aunque los métodos y las clases son mucho más fáciles de refactorizar. Pero, en esencia, es el mismo concepto.
Normalmente voy al extremo de escribir un archivo de especificación de escáner. Un escáner, o "generador de escáner" es esencialmente un analizador de texto optimizado. Como normalmente trabajo con Java, mi método preferido es JFlex ( http://www.jflex.de ), pero también hay Lex, YACC y muchos más.
Los escáneres trabajan en expresiones regulares que puede definir como macros. Luego implementa devoluciones de llamada cuando las expresiones regulares coinciden con una parte del texto.
Cuando se trata del código, tengo un archivo de especificación que contiene toda la lógica de análisis. Lo ejecuto a través de la herramienta de generador de escáner de elección para generar el código fuente en el idioma de elección. Luego simplemente envuelvo todo eso en una función de analizador o clase de algún tipo. Esta abstracción facilita la gestión de toda la lógica de expresión regular y su rendimiento es muy bueno. Por supuesto, es excesivo si trabajas con solo una o dos expresiones regulares, y lleva al menos 2-3 días aprender qué diablos está pasando, pero si alguna vez trabajas con, digamos, 5 o 6 o 30 de ellos, se convierte en una característica realmente agradable y la implementación de la lógica de análisis comienza a tomar solo unos minutos y son fáciles de mantener y fáciles de documentar.
Pequeños cambios a cualquier código en cualquier idioma pueden dar lugar a resultados completamente diferentes. Algunos de ellos incluso impiden la compilación.
Sustituye la expresión regular con "C" o "C #" o "Java" o "Python" o "Perl" o "SQL" o "Ruby" o "awk" o ... cualquier cosa, en realidad, y obtienes la misma pregunta.
Regex es solo otro idioma, Huffman codificado para ser eficiente en la coincidencia de cadenas. Al igual que Java, Perl, PHP o, especialmente, SQL, cada idioma tiene fortalezas y debilidades, y necesita saber el idioma en el que está escribiendo cuando lo está escribiendo (o manteniéndolo) para tener alguna esperanza de ser productivo.
Editar: Mike, las expresiones regulares están codificadas por Huffman en que las cosas comunes que hacer son más cortas que las cosas más raras. Las coincidencias literales de texto generalmente son un solo caracter (el que quiere hacer coincidir). Existen caracteres especiales, los comunes son cortos. Las construcciones especiales, como (? :) son más largas. Estas no son las mismas cosas que serían comunes en lenguajes de uso general como Perl, C ++, etc., por lo que la codificación de Huffman estaba dirigida a esta especialización.
Regex ha sido referido como un lenguaje de programación de "solo escritura" seguro. Sin embargo, no creo que eso signifique que debes evitarlos. Creo que deberías comentar todo lo posible. Normalmente no soy un gran admirador de los comentarios que explican lo que hace una línea, puedo leer el código para eso, pero los Regex son la excepción. Comenta todo!
Regex no es la ÚNICA manera de hacer algo. Puedes hacer lógicamente en código todo lo que puede hacer una expresión regular. Las expresiones regulares son justas
- Rápido
- Probado y probado
- Poderoso
Solo parece mágico si no entiendes la expresión regular. Cualquier cantidad de pequeños cambios en el código de producción puede causar problemas importantes, por lo que no es una buena razón, en mi opinión, para no utilizar expresiones regex. Las pruebas exhaustivas deben señalar cualquier problema.
Tengo una política de comentar minuciosamente expresiones regulares no triviales. Eso significa describir y justificar cada átomo que no coincide. Algunos lenguajes (Python, por ejemplo) ofrecen expresiones regulares "detalladas" que ignoran los espacios en blanco y permiten comentarios; usa esto siempre que sea posible. De lo contrario, ve átomo por átomo en un comentario sobre la expresión regular.
cita famosa sobre expresiones regulares:
"Algunas personas, cuando se enfrentan con un problema, piensan" Lo sé, usaré expresiones regulares ". Ahora tienen dos problemas". - Jamie Zawinski
Cuando uso expresiones regulares, creo que son mantenibles, pero se usan en casos especiales. Generalmente hay un método mejor, no regex para hacer casi todo.
Realmente se reduce a la expresión regular. Si se trata de esta enorme expresión monolítica, entonces sí, es un problema de mantenimiento. Si puede expresarlos de manera sucinta (tal vez dividiéndolos), o si tiene buenos comentarios y herramientas para ayudarlo a comprenderlos, entonces pueden ser una herramienta poderosa.
No sé qué idioma estás usando, pero Perl, por ejemplo, admite el indicador x
, por lo que los espacios se ignoran en expresiones regulares, a menos que se escapen, por lo que puedes dividirlo en varias líneas y comentar todo en línea:
$foo =~ m{
(some-thing) # matches something
/s* # matches any amount of spaces
(match another thing) # matches something else
}x;
Esto ayuda a hacer que las expresiones regulares largas sean más legibles.
Si las expresiones regulares son largas e impenetrables, por lo que es difícil mantenerlas, deben ser comentadas.
Muchas implementaciones de expresiones regulares le permiten rellenar expresiones regulares con espacios en blanco y comentarios.
Ver http://www.regular-expressions.info/comments.html
y codificación de terror: expresiones regulares: ahora tienes dos problemas
Cualquier código que he visto que usa Regex tiende a usarlos como una caja negra:
Si en una caja negra te refieres a abstracción, eso es lo que es toda la programación, tratando de abstraer la parte difícil (analizar cadenas de caracteres) para que puedas concentrarte en el dominio del problema (qué tipo de cadenas quiero que coincidan).
incluso un pequeño cambio a menudo puede dar como resultado una expresión regular completamente diferente.
Eso es cierto de cualquier código. Siempre y cuando esté probando su expresión regular para asegurarse de que coincide con las cadenas que espera, idealmente con pruebas unitarias , entonces debe tener confianza en cambiarlas.
Editar: lea también el comentario de Jeff sobre esta respuesta sobre el código de producción.
Siempre he abordado este problema como un problema fundamental.
No solo escribes algunos caracteres regex de 3000 caracteres y esperamos lo mejor. Escribes un montón de pequeños trozos que agregas juntos.
Por ejemplo, para hacer coincidir un URI, tiene el protocolo, autoridad, subdominio, dominio, tld, ruta, argumentos (al menos). ¡Y algunos de estos son opcionales!
Estoy seguro de que podrías escribir un monstruo para manejarlo, pero es más fácil escribir fragmentos y agregarlos juntos.
RegExs puede ser muy fácil de mantener, si utiliza las nuevas características introducidas por Perl 5.10
. Las características a las que me refiero son características de Back-ported de Perl 6
.
Ejemplo copiado directamente de perlretut .
Definición de patrones con nombre
Algunas expresiones regulares usan subpatrones idénticos en varios lugares. Comenzando con Perl 5.10, es posible definir subpatrones con nombre en una sección del patrón para que puedan ser llamados por nombre en cualquier parte del patrón. Este patrón sintáctico para este grupo de definición es (?(DEFINE)(?<name>pattern)...)
. Una inserción de un patrón con nombre se escribe como (?&name)
.
El siguiente ejemplo ilustra esta característica usando el patrón para números de coma flotante que se presentó anteriormente. Los tres subpatrones que se usan más de una vez son el signo opcional, la secuencia de dígitos para un entero y la fracción decimal. El grupo DEFINE
al final del patrón contiene su definición. Tenga en cuenta que el patrón de fracción decimal es el primer lugar donde podemos reutilizar el patrón entero.
/^
(?&osg)/ * ( (?&int)(?&dec)? | (?&dec) )
(?: [eE](?&osg)(?&int) )?
$
(?(DEFINE)
(?<osg>[-+]?) # optional sign
(?<int>/d++) # integer
(?<dec>/.(?&int)) # decimal fraction
)
/x
Comúnmente dividí la expresión regular en partes con comentarios, luego los puse todos juntos para el empujón final. Las piezas pueden ser subcadenas o elementos de matriz
Dos ejemplos de PHP PCRE (los específicos o el uso particular no son importantes):
1)
$dktpat = ''/^[^a-z0-9]*''. // skip any initial non-digits
''([a-z0-9]:)?''. // division within the district
''(/d+)''. // year
''((-)|-?([a-z][a-z])-?)''. // type of court if any - cv, bk, etc.
''(/d+)''. // docket sequence number
''[^0-9]*$/i''; // ignore anything after the sequence number
if (preg_match($dktpat,$DocketID,$m)) {
2)
$pat= array (
''Row'' => ''/s*(/d*)'',
''Parties'' => ''(.*)'',
''CourtID'' => ''<a[^>]*>([a-z]*)</a>'',
''CaseNo'' => ''<a[^>]*>([a-z0-9:/-]*)</a>'',
''FirstFiled'' => ''([0-9//]*)'',
''NOS'' => ''(/d*)'',
''CaseClosed'' => ''([0-9//]*)'',
''CaseTitle'' => ''(.*)'',
);
// wrap terms in table syntax
$pat = ''#<tr>(<td[^>]*>''.
implode(''</td>)(</tr><tr>)?(<td[^>]*>'',$pat).
''</td>)</tr>#iUx'';
if (preg_match_all ($pat,$this->DocketText,$matches, PREG_PATTERN_ORDER))
Su pregunta no parece pertenecer a las expresiones regulares, sino solo a la sintaxis generalmente utilizada para expresar expresiones regulares. Entre muchos codificadores hardcore, esta sintaxis ha llegado a ser aceptada como bastante breve y poderosa, pero para expresiones regulares más largas, en realidad es realmente ilegible e imposible de mantener.
Algunas personas ya han mencionado la bandera "x" en Perl, lo que ayuda un poco, pero no mucho.
Me gustan mucho las expresiones regulares, pero no la sintaxis. Sería bueno poder construir una expresión regular a partir de nombres de métodos legibles y significativos. Por ejemplo, en lugar de este código C #:
foreach (var match in Regex.Matches(input, @"-?(?<number>/d+)"))
{
Console.WriteLine(match.Groups["number"].Value);
}
podrías tener algo mucho más detallado pero mucho más legible y mantenible:
int number = 0;
Regex r = Regex.Char(''-'').Optional().Then(
Regex.Digit().OneOrMore().Capture(c => number = int.Parse(c))
);
foreach (var match in r.Matches(input))
{
Console.WriteLine(number);
}
Esta es solo una idea rápida; Sé que hay otros problemas de mantenimiento no relacionados con esto (aunque yo diría que son menos y más pequeños). Un beneficio adicional de esto es la verificación en tiempo de compilación.
Por supuesto, si crees que esto está por encima y es demasiado detallado, aún puedes tener una sintaxis de expresión regular que esté en algún punto intermedio, tal vez ...
instead of: -?(?<number>/d+)
could have: ("-" or "") + (number = digit * [1..])
Esto es aún un millón de veces más legible y solo el doble de tiempo. Tal sintaxis se puede hacer fácilmente para tener el mismo poder expresivo que las expresiones regulares normales, y ciertamente se puede integrar en un compilador de lenguaje de programación para el análisis estático.
Realmente no sé por qué hay tanta oposición a repensar la sintaxis para las expresiones regulares incluso cuando se repensan los lenguajes de programación completos (por ejemplo, Perl 6, o cuando C # era nuevo). Además, la idea muy detallada antes mencionada ni siquiera es incompatible con las expresiones regulares "viejas"; la API podría implementarse fácilmente como una que construye una expresión regular de estilo antiguo bajo el capó.
Los uso en mis aplicaciones pero conservo la expresión regEx real en el archivo de configuración, así que si el texto de origen que estoy analizando (un correo electrónico, por ejemplo) cambia de formato por alguna razón, puedo actualizar rápidamente la configuración para manejar el cambio sin construyendo la aplicación.