Todos los programadores parecen estar de acuerdo en que la legibilidad del código es mucho más importante que las líneas simples sintaxis que funcionan, pero requieren que un desarrollador senior interprete con algún grado de precisión, pero esa parece ser exactamente la forma en que se diseñaron las expresiones regulares. ¿Había alguna razón para esto?
Todos estamos de acuerdo en que selfDocumentingMethodName()
es mucho mejor que e()
. ¿Por qué eso no se aplica también a las expresiones regulares?
Me parece que en lugar de diseñar una sintaxis de lógica de una línea sin organización estructural:
var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})(0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
¡Y esto ni siquiera es un análisis estricto de una URL!
En cambio, podríamos hacer una estructura de tubería organizada y legible, para un ejemplo básico:
string.regex
.isRange('A-Z' || 'a-z')
.followedBy('/r');
¿Qué ventaja ofrece la sintaxis extremadamente breve de una expresión regular que no sea la sintaxis lógica y de operación más corta posible? En última instancia, ¿existe una razón técnica específica para la escasa legibilidad del diseño de sintaxis de expresión regular?
Respuestas:
Hay una gran razón por la cual las expresiones regulares se diseñaron de manera tan concisa como fueron: se diseñaron para usarse como comandos para un editor de código, no como un lenguaje para codificar. Más precisamente,
ed
fue uno de los primeros programas en usar expresiones regulares , y desde allí las expresiones regulares comenzaron su conquista por la dominación mundial. Por ejemplo, eled
comandog/<regular expression>/p
pronto inspiró un programa separado llamadogrep
, que todavía está en uso hoy. Debido a su poder, posteriormente fueron estandarizados y utilizados en una variedad de herramientas comosed
yvim
Pero suficiente para la trivia. Entonces, ¿por qué este origen favorecería una gramática breve? Porque no escribes un comando de editor para leerlo ni una vez más. Es suficiente que puedas recordar cómo armarlo, y que puedes hacer las cosas que quieres hacer con él. Sin embargo, cada carácter que tiene que escribir ralentiza su progreso editando su archivo. La sintaxis de la expresión regular fue diseñada para escribir búsquedas relativamente complejas de una manera descartable, y eso es precisamente lo que le da dolor de cabeza a las personas que las usan como código para analizar alguna entrada a un programa.
fuente
grep
es un "agarre" mal pronunciado, en realidad proviene deg
/re
(para expresión regular) /p
?<aaa bbb="ccc" ddd='eee'>
, no hay etiquetas anidadas dentro de ella. No puede anidar etiquetas, lo que anida son elementos (etiqueta abierta, contenido que incluye elementos secundarios, etiqueta de cierre), que la pregunta no era sobre el análisis. Las etiquetas HTML son un lenguaje normal: el equilibrio / anidamiento ocurre en un nivel superior a las etiquetas.La expresión regular que usted cita es un desastre terrible y no creo que nadie esté de acuerdo en que sea legible. Al mismo tiempo, gran parte de esa fealdad es inherente al problema que se está resolviendo: hay varias capas de anidamiento y la gramática de URL es relativamente complicada (ciertamente demasiado complicada para comunicarse sucintamente en cualquier idioma). Sin embargo, es cierto que hay mejores formas de describir lo que esta expresión regular describe. Entonces, ¿por qué no se usan?
Una gran razón es la inercia y la ubicuidad. No explica cómo se hicieron tan populares en primer lugar, pero ahora que lo son, cualquiera que conozca las expresiones regulares puede usar estas habilidades (con muy pocas diferencias entre dialectos) en cien idiomas diferentes y mil herramientas de software adicionales ( por ejemplo, editores de texto y herramientas de línea de comandos). Por cierto, este último no podría ni podría usar ninguna solución que equivalga a escribir programas , ya que los no programadores los usan mucho.
A pesar de eso, las expresiones regulares a menudo se usan en exceso, es decir, se aplican incluso cuando otra herramienta sería mucho mejor. No creo que la sintaxis de expresiones regulares sea terrible . Pero claramente es mucho mejor con patrones cortos y simples: el ejemplo arquetípico de identificadores en lenguajes tipo C,
[a-zA-Z_][a-zA-Z0-9_]*
se puede leer con un mínimo absoluto de conocimiento de expresiones regulares y una vez que se cumple esa barra, es obvio y muy sucinto. Requerir menos caracteres no es inherentemente malo, sino todo lo contrario. Ser conciso es una virtud siempre que permanezcas comprensible.Hay al menos dos razones por las que esta sintaxis sobresale en patrones simples como estos: no requiere escapar para la mayoría de los caracteres, por lo que se lee de forma relativamente natural, y utiliza todos los signos de puntuación disponibles para expresar una variedad de combinadores de análisis simples. Quizás lo más importante, no requiere nada en absoluto para la secuenciación. Escribes lo primero, luego lo que viene después. Compare esto con su
followedBy
, especialmente cuando el siguiente patrón no es una expresión literal sino más complicada.Entonces, ¿por qué se quedan cortos en casos más complicados? Puedo ver tres problemas principales:
No hay capacidades de abstracción. Las gramáticas formales, que se originan en el mismo campo de la informática teórica que las expresiones regulares, tienen un conjunto de producciones, por lo que pueden dar nombres a las partes intermedias del patrón:
Como pudimos ver arriba, el espacio en blanco que no tiene un significado especial es útil para permitir un formato que sea más fácil para los ojos. Lo mismo con los comentarios. Las expresiones regulares no pueden hacer eso porque un espacio es solo eso, un literal
' '
. Sin embargo, tenga en cuenta que algunas implementaciones permiten un modo "detallado" en el que se ignora el espacio en blanco y es posible realizar comentarios.No existe un metalenguaje para describir patrones y combinadores comunes. Por ejemplo, uno puede escribir una
digit
regla una vez y seguir usándola en una gramática libre de contexto, pero no se puede definir una "función", por así decir, que se le da una producciónp
y crea una nueva producción que hace algo extra con ella, por ejemplo, crear una producción para una lista separada por comas de ocurrencias dep
.El enfoque que propone ciertamente resuelve estos problemas. Simplemente no los resuelve muy bien, porque los intercambia con mucha más concisión de lo necesario. Los primeros dos problemas se pueden resolver mientras se mantiene dentro de un lenguaje específico de dominio relativamente simple y conciso. El tercero, bueno ... una solución programática requiere un lenguaje de programación de propósito general, por supuesto, pero en mi experiencia, el tercero es, con mucho, el menor de esos problemas. Pocos patrones tienen suficientes ocurrencias de la misma tarea compleja que el programador anhela la capacidad de definir nuevos combinadores. Y cuando esto es necesario, el lenguaje a menudo es lo suficientemente complicado como para que no pueda y no deba analizarse con expresiones regulares de todos modos.
Existen soluciones para esos casos. Hay aproximadamente diez mil bibliotecas de combinador de analizadores que hacen aproximadamente lo que usted propone, solo con un conjunto diferente de operaciones, a menudo una sintaxis diferente, y casi siempre con más poder de análisis que las expresiones regulares (es decir, tratan con lenguajes libres de contexto o algunos de tamaño considerable subconjunto de esos). Luego están los generadores de analizadores sintácticos, que van con el enfoque de "usar un DSL mejor" descrito anteriormente. Y siempre existe la opción de escribir algunos de los análisis a mano, en el código adecuado. Incluso puede mezclar y combinar, utilizando expresiones regulares para subtareas simples y haciendo las cosas complicadas en el código que invoca las expresiones regulares.
No sé lo suficiente sobre los primeros años de la informática para explicar cómo las expresiones regulares llegaron a ser tan populares. Pero están aquí para quedarse. Solo tiene que usarlos sabiamente y no usarlos cuando sea más sabio.
fuente
I don't know enough about the early years of computing to explain how regular expressions came to be so popular.
Sin embargo, podemos arriesgarnos a adivinar: un motor de expresión regular básico es muy fácil de implementar, mucho más fácil que un analizador eficiente sin contexto.grep
(Versión 3 vs Versión 4). Parece que el primer uso importante deyacc
fue creado en 1975, toda la idea de los analizadores LALR (que se encontraban entre la primera clase de analizadores prácticamente utilizables de sus kind) se originó en 1973. Mientras que la primera implementación del motor regexp que JIT compiló expresiones (!) se publicó en 1968. Pero tienes razón, es difícil decir qué lo hizo girar, de hecho es difícil decir cuándo las expresiones regulares comenzaron a "tomar apagado". Pero sospechaba que una vez que se pusieron en editores de texto que usaron los desarrolladores, también querían usarlos en su propio software.with very few differences between dialects
Yo no diría que son "muy pocos". Cualquier clase de caracteres predefinida tiene varias definiciones entre diferentes dialectos. Y también hay peculiaridades de análisis específicas para cada dialecto.Perspectiva historica
El artículo de Wikipedia es bastante detallado sobre los orígenes de las expresiones regulares (Kleene, 1956). La sintaxis original relativamente simple, con solamente
*
,+
,?
,|
y agrupación(...)
. Fue breve ( y legible, los dos no son necesariamente opuestos), porque los lenguajes formales tienden a expresarse con notables anotaciones matemáticas.Más tarde, la sintaxis y las capacidades evolucionaron con los editores y crecieron con Perl , que intentaba ser breve por diseño ( "las construcciones comunes deberían ser cortas" ). Esto complementó mucho la sintaxis, pero tenga en cuenta que las personas ahora están acostumbradas a las expresiones regulares y son buenas para escribirlas (si no leerlas). El hecho de que a veces son de solo escritura sugiere que cuando son demasiado largos, generalmente no son la herramienta adecuada. Las expresiones regulares tienden a ser ilegibles cuando se abusa de ellas.
Más allá de las expresiones regulares basadas en cadenas
Hablando de sintaxis alternativas, echemos un vistazo a una que ya existe ( cl-ppcre , en Common Lisp ). Su expresión regular larga se puede analizar de la
ppcre:parse-string
siguiente manera:... y da como resultado la siguiente forma:
Esta sintaxis es más detallada, y si observa los comentarios a continuación, no necesariamente es más legible. Así que no asuma que debido a que tiene una sintaxis menos compacta, las cosas se aclararán automáticamente .
Sin embargo, si comienza a tener problemas con sus expresiones regulares, convertirlas a este formato podría ayudarlo a descifrar y depurar su código. Esta es una ventaja sobre los formatos basados en cadenas, donde un error de un solo carácter puede ser difícil de detectar. La principal ventaja de esta sintaxis es manipular expresiones regulares utilizando un formato estructurado en lugar de una codificación basada en cadenas. Eso le permite componer y construir expresiones como cualquier otra estructura de datos en su programa. Cuando uso la sintaxis anterior, esto generalmente se debe a que quiero construir expresiones a partir de partes más pequeñas (consulte también mi respuesta de CodeGolf ). Para su ejemplo, podemos escribir 1 :
Las expresiones regulares basadas en cadenas también se pueden componer, utilizando la concatenación de cadenas y / o la interpolación envuelta en funciones auxiliares. Sin embargo, existen limitaciones con las manipulaciones de cadenas que tienden a desordenar el código (piense en problemas de anidación, no muy diferente de los backticks vs.
$(...)
en bash; también, los caracteres de escape pueden causar dolores de cabeza).Tenga en cuenta también que el formulario anterior permite
(:regex "string")
formularios para que pueda mezclar anotaciones concisas con árboles. Todo eso lleva a mi humilde opinión a una buena legibilidad y componibilidad; aborda los tres problemas expresados por delnan , indirectamente (es decir, no en el lenguaje de las expresiones regulares en sí).Para concluir
Para la mayoría de los propósitos, la notación concisa es de hecho legible. Existen dificultades cuando se trata de notaciones extendidas que implican retroceso, etc., pero su uso rara vez se justifica. El uso injustificado de expresiones regulares puede conducir a expresiones ilegibles.
Las expresiones regulares no necesitan ser codificadas como cadenas. Si tiene una biblioteca o una herramienta que puede ayudarlo a construir y componer expresiones regulares, evitará muchos errores potenciales relacionados con las manipulaciones de cadenas.
Alternativamente, las gramáticas formales son más legibles y son mejores para nombrar y abstraer subexpresiones. Las terminales generalmente se expresan como simples expresiones regulares.
1. Es posible que prefiera construir sus expresiones en el momento de la lectura, porque las expresiones regulares tienden a ser constantes en una aplicación. Ver
create-scanner
yload-time-value
:fuente
digits
,ident
y componerlos. La forma en que lo veo hecho es generalmente con manipulaciones de cadenas (concatenación o interpolación), lo que trae otros problemas como el escape adecuado. Busque las ocurrencias de\\\\`
en paquetes de emacs, por ejemplo. Por cierto, esto se agrava debido a que el mismo carácter de escape se utiliza tanto para caracteres especiales como\n
y\"
y para la sintaxis de expresiones regulares\(
. Un ejemplo no lisp de buena sintaxis esprintf
, donde%d
no entra en conflicto con\d
.greedy-repetition
no son intuitivos y aún deben ser aprendidos). Sin embargo, sacrifica la usabilidad para los expertos, ya que es mucho más difícil ver y comprender todo el patrón.do {optional (many1 (letter) >> char ':'); choice (map string ["///","//","/",""]); many1 (oneOf "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-."); optional (char ':' >> many1 digit); optional (char '/' >> many (noneOf "?#")); optional (char '?' >> many (noneOf "#")); optional (char '#' >> many (noneOf "\n")); eof}
. Con unas pocas líneas como designar la cadena larga comodomainChars = ...
ysection start p = optional (char start >> many p)
parece bastante simple.El mayor problema con la expresión regular no es la sintaxis demasiado breve, es que tratamos de expresar una definición compleja en una sola expresión, en lugar de componerla a partir de bloques de construcción más pequeños. Esto es similar a la programación donde nunca usa variables y funciones y en su lugar incrusta su código en una sola línea.
Compare la expresión regular con BNF . Su sintaxis no es mucho más limpia que la expresión regular, pero se usa de manera diferente. Empieza definiendo símbolos con nombre simples y los compone hasta que llegue a un símbolo que describa el patrón completo que desea hacer coincidir.
Por ejemplo, mire la sintaxis de URI en rfc3986 :
Podría escribir casi lo mismo usando una variante de la sintaxis de expresiones regulares que admite incrustar subexpresiones con nombre.
Personalmente, creo que una expresión regular breve como la sintaxis está bien para las características de uso común como las clases de caracteres, la concatenación, la elección o la repetición, pero para características más complejas y raras como los nombres detallados de anticipación son preferibles. Muy similar a cómo usamos operadores como
+
o*
en la programación normal y cambiamos a funciones con nombre para operaciones más raras.fuente
¿Lo es? Hay una razón por la cual la mayoría de los idiomas tienen {y} como delimitadores de bloque en lugar de BEGIN y END.
A la gente le gusta la terquedad, y una vez que conoce la sintaxis, la terminología corta es mejor. Imagine su ejemplo de expresiones regulares si d (para el dígito) fuera 'dígito', la expresión regular sería aún más horrible de leer. Si lo hiciera más fácil de analizar con caracteres de control, se vería más como XML. Tampoco son tan buenos una vez que conoces la sintaxis.
Sin embargo, para responder adecuadamente a su pregunta, debe darse cuenta de que la expresión regular proviene de los días en que la concisión era obligatoria. Es fácil pensar que un documento XML de 1 MB no es gran cosa hoy, pero estamos hablando de días en que 1 MB era más o menos toda su capacidad de almacenamiento. También se usaban menos idiomas en ese momento, y la expresión regular no está a un millón de millas de distancia de perl o C, por lo que la sintaxis sería familiar para los programadores de la época que estarían felices de aprender la sintaxis. Así que no había razón para hacerlo más detallado.
fuente
selfDocumentingMethodName
está generalmente aceptado que ser mejor quee
porque la intuición programador no se alinea con la realidad en términos de lo que realmente constituye la legibilidad o código de buena calidad . Las personas que están de acuerdo están equivocadas, pero así es como es.e()
es mejor queselfDocumentingMethodName()
?e()
un nombre de método de autodocumentación . ¿Puede explicar en qué contexto es una mejora usar nombres de métodos de una letra en lugar de nombres de métodos descriptivos?Regex es como piezas de lego. A primera vista, verá algunas piezas de plástico de formas diferentes que se pueden unir. Podrías pensar que no habría demasiadas cosas diferentes posibles que puedas moldear, pero luego ves las cosas increíbles que hacen otras personas y te preguntas cómo es un juguete increíble.
Regex es como piezas de lego. Hay pocos argumentos que se pueden usar, pero encadenarlos en diferentes formas formará millones de patrones de expresiones regulares diferentes que se pueden usar para muchas tareas complicadas.
La gente rara vez usaba solo los parámetros de expresiones regulares. Muchos idiomas le ofrecen funciones para verificar la longitud de una cadena o dividir las partes numéricas. Puede usar funciones de cadena para cortar textos y reformarlos. El poder de la expresión regular se nota cuando utiliza formularios complejos para realizar tareas complejas muy específicas.
Puede encontrar decenas de miles de preguntas de expresiones regulares en SO y rara vez se marcan como duplicadas. Esto solo muestra los posibles casos de uso únicos que son muy diferentes entre sí.
Y no es fácil ofrecer métodos predefinidos para manejar estas tareas únicas tan diferentes. Tiene funciones de cadena para ese tipo de tareas, pero si esas funciones no son suficientes para su tarea específica, entonces es hora de usar expresiones regulares.
fuente
Reconozco que este es un problema de práctica más que de potencia. El problema generalmente surge cuando las expresiones regulares se implementan directamente , en lugar de asumir una naturaleza compuesta. Del mismo modo, un buen programador descompondrá las funciones de su programa en métodos concisos.
Por ejemplo, una cadena de expresiones regulares para una URL podría reducirse de aproximadamente:
a:
Las expresiones regulares son cosas ingeniosas, pero son propensas a ser abusadas por aquellos que se vuelven absortos en su aparente complejidad. Las expresiones resultantes son retóricas, ausentes de un valor a largo plazo.
fuente
Como dice @cmaster, las expresiones regulares se diseñaron originalmente para usarse solo sobre la marcha, y es simplemente extraño (y un poco deprimente) que la sintaxis de ruido de línea siga siendo la más popular. Las únicas explicaciones que se me ocurren son la inercia, el masoquismo o el machismo (no es frecuente que la "inercia" sea la razón más atractiva para hacer algo ...)
Perl hace un intento bastante débil de hacerlos más legibles al permitir espacios en blanco y comentarios, pero no hace nada remotamente imaginativo.
Hay otras sintaxis. Una buena es la sintaxis scsh para regexps , que en mi experiencia produce expresiones regulares que son razonablemente fáciles de escribir, pero aún legibles después del hecho.
[ scsh es espléndido por otros motivos, uno de los cuales es su famoso texto de agradecimientos ]
fuente
Creo que las expresiones regulares fueron diseñadas para ser tan 'generales' y simples como sea posible, por lo que pueden usarse (aproximadamente) de la misma manera en cualquier lugar.
regex.isRange(..).followedBy(..)
Su ejemplo está acoplado tanto a la sintaxis de un lenguaje de programación específico como a un estilo orientado a objetos (encadenamiento de métodos).¿Cómo se vería exactamente esta 'expresión regular' en C, por ejemplo? El código tendría que ser cambiado.
El enfoque más "general" sería definir un lenguaje conciso simple que luego pueda integrarse fácilmente en cualquier otro idioma sin cambios. Y eso es (casi) lo que son las expresiones regulares.
fuente
Los motores de expresión regular compatibles con Perl se usan ampliamente, proporcionando una sintaxis de expresión regular concisa que muchos editores e idiomas entienden. Como @JDługosz señaló en los comentarios, Perl 6 (no solo una nueva versión de Perl 5, sino un lenguaje completamente diferente) ha intentado hacer que las expresiones regulares sean más legibles al construirlas a partir de elementos definidos individualmente. Por ejemplo, aquí hay una gramática de ejemplo para analizar URL de Wikilibros :
Dividir la expresión regular de esta manera permite que cada bit se defina individualmente (por ejemplo, restringir
domain
a ser alfanumérico) o extenderse a través de subclases (por ejemplo,FileURL is URL
que las restriccionesprotocol
sean solo"file"
).Entonces: no, no hay una razón técnica para la brevedad de las expresiones regulares, ¡pero las formas más nuevas, más limpias y más legibles de representarlas ya están aquí! Esperemos ver algunas ideas nuevas en este campo.
fuente