为什么正则表达式如此具有争议性?

时间:2009-04-18 21:33:08

标签: regex

在探索正则表达式(也称为RegEx-es)时,有许多人似乎将正则表达式视为圣杯。看起来如此复杂的东西 - 只是必须回答任何问题。他们倾向于认为使用正则表达式可以解决所有问题。

另一方面,也有很多人试图不惜一切代价避免使用正则表达式。他们试图找到解决正则表达式的方法并接受额外的编码,即使正则表达式是一个更紧凑的解决方案。

为什么正则表达式被认为是如此有争议?是否存在关于它们如何工作的普遍误解?或者可能是一个普遍的信念,正则表达式通常很慢?

22 个答案:

答案 0 :(得分:132)

我不认为人们会反对正则表达式,因为它们很慢,而是因为它们难以读写,而且很难做到正确。虽然在某些情况下正则表达式可以为问题提供有效,紧凑的解决方案,但有时它们会被用于更好地使用易于阅读,可维护的代码段的情况。

答案 1 :(得分:121)

使正则表达式可维护

揭开以前被称为“正则表达式”的模式的一个重大进展是Perl的/x正则表达式标志 - 有时在嵌入时写成(?x) - 允许空格(换行,缩进)和注释。这严重提高了可读性,从而提高了可维护性。白色空间允许认知分块,因此您可以看到哪些组具有什么。

现在,现代模式现在也支持相对编号和命名的反向引用。这意味着您不再需要计算捕获组,以确定您需要$4\7。这有助于创建可以包含在其他模式中的模式。

以下是相对编号的捕获组的示例:

$dupword = qr{ \b (?: ( \w+ ) (?: \s+ \g{-1} )+ ) \b }xi;
$quoted  = qr{ ( ["'] ) $dupword  \1 }x;

以下是命名捕获的优越方法的一个例子:

$dupword = qr{ \b (?: (?<word> \w+ ) (?: \s+ \k<word> )+ ) \b }xi;
$quoted  = qr{ (?<quote> ["'] ) $dupword  \g{quote} }x;

Grammatical Regexes

最重要的是,这些命名的捕获可以放在(?(DEFINE)...)块中,这样您就可以将声明与模式中各个命名元素的执行区分开来。这使得它们的行为更像是模式中的子程序  在this answerthis one中可以找到这种“语法正则表达式”的一个很好的例子。这看起来更像是语法宣言。

后者提醒你:

  

...确保永远不要写行噪声模式。你不必,你不应该。没有编程语言可以维护,禁止使用空格,注释,子例程或字母数字标识符。所以在你的模式中使用所有这些东西。

这不能过分强调。当然,如果你不在你的模式中使用这些东西,你经常会制造一个噩梦。但是,如果您 使用它们,则无需使用它们。

这是现代语法模式的另一个例子,这个用于解析RFC 5322:     使用5.10.0;

$rfc5322 = qr{

   (?(DEFINE)

     (?<address>         (?&mailbox) | (?&group))
     (?<mailbox>         (?&name_addr) | (?&addr_spec))
     (?<name_addr>       (?&display_name)? (?&angle_addr))
     (?<angle_addr>      (?&CFWS)? < (?&addr_spec) > (?&CFWS)?)
     (?<group>           (?&display_name) : (?:(?&mailbox_list) | (?&CFWS))? ; (?&CFWS)?)
     (?<display_name>    (?&phrase))
     (?<mailbox_list>    (?&mailbox) (?: , (?&mailbox))*)

     (?<addr_spec>       (?&local_part) \@ (?&domain))
     (?<local_part>      (?&dot_atom) | (?&quoted_string))
     (?<domain>          (?&dot_atom) | (?&domain_literal))
     (?<domain_literal>  (?&CFWS)? \[ (?: (?&FWS)? (?&dcontent))* (?&FWS)?
                                   \] (?&CFWS)?)
     (?<dcontent>        (?&dtext) | (?&quoted_pair))
     (?<dtext>           (?&NO_WS_CTL) | [\x21-\x5a\x5e-\x7e])

     (?<atext>           (?&ALPHA) | (?&DIGIT) | [!#\$%&'*+-/=?^_`{|}~])
     (?<atom>            (?&CFWS)? (?&atext)+ (?&CFWS)?)
     (?<dot_atom>        (?&CFWS)? (?&dot_atom_text) (?&CFWS)?)
     (?<dot_atom_text>   (?&atext)+ (?: \. (?&atext)+)*)

     (?<text>            [\x01-\x09\x0b\x0c\x0e-\x7f])
     (?<quoted_pair>     \\ (?&text))

     (?<qtext>           (?&NO_WS_CTL) | [\x21\x23-\x5b\x5d-\x7e])
     (?<qcontent>        (?&qtext) | (?&quoted_pair))
     (?<quoted_string>   (?&CFWS)? (?&DQUOTE) (?:(?&FWS)? (?&qcontent))*
                          (?&FWS)? (?&DQUOTE) (?&CFWS)?)

     (?<word>            (?&atom) | (?&quoted_string))
     (?<phrase>          (?&word)+)

     # Folding white space
     (?<FWS>             (?: (?&WSP)* (?&CRLF))? (?&WSP)+)
     (?<ctext>           (?&NO_WS_CTL) | [\x21-\x27\x2a-\x5b\x5d-\x7e])
     (?<ccontent>        (?&ctext) | (?&quoted_pair) | (?&comment))
     (?<comment>         \( (?: (?&FWS)? (?&ccontent))* (?&FWS)? \) )
     (?<CFWS>            (?: (?&FWS)? (?&comment))*
                         (?: (?:(?&FWS)? (?&comment)) | (?&FWS)))

     # No whitespace control
     (?<NO_WS_CTL>       [\x01-\x08\x0b\x0c\x0e-\x1f\x7f])

     (?<ALPHA>           [A-Za-z])
     (?<DIGIT>           [0-9])
     (?<CRLF>            \x0d \x0a)
     (?<DQUOTE>          ")
     (?<WSP>             [\x20\x09])
   )

   (?&address)

}x;

不是那么了不起 - 而且很精彩?您可以采用BNF风格的语法并将其直接转换为代码而不会失去其基本结构!

如果现代语法模式仍然对你来说还不够,那么Damian Conway’s brilliant Regexp::Grammars module提供了更清晰的语法,并且具有出色的调试功能。以下是将RFC 5322重新编译为该模块中的模式的相同代码:

#!/usr/bin/perl

use strict;
use warnings;
use 5.010;
use Data::Dumper "Dumper";

my $rfc5322 = do {
    use Regexp::Grammars;    # ...the magic is lexically scoped
    qr{

    # Keep the big stick handy, just in case...
    # <debug:on>

    # Match this...
    <address>

    # As defined by these...
    <token: address>         <mailbox> | <group>
    <token: mailbox>         <name_addr> | <addr_spec>
    <token: name_addr>       <display_name>? <angle_addr>
    <token: angle_addr>      <CFWS>? \< <addr_spec> \> <CFWS>?
    <token: group>           <display_name> : (?:<mailbox_list> | <CFWS>)? ; <CFWS>?
    <token: display_name>    <phrase>
    <token: mailbox_list>    <[mailbox]> ** (,)

    <token: addr_spec>       <local_part> \@ <domain>
    <token: local_part>      <dot_atom> | <quoted_string>
    <token: domain>          <dot_atom> | <domain_literal>
    <token: domain_literal>  <CFWS>? \[ (?: <FWS>? <[dcontent]>)* <FWS>?

    <token: dcontent>        <dtext> | <quoted_pair>
    <token: dtext>           <.NO_WS_CTL> | [\x21-\x5a\x5e-\x7e]

    <token: atext>           <.ALPHA> | <.DIGIT> | [!#\$%&'*+-/=?^_`{|}~]
    <token: atom>            <.CFWS>? <.atext>+ <.CFWS>?
    <token: dot_atom>        <.CFWS>? <.dot_atom_text> <.CFWS>?
    <token: dot_atom>        <.CFWS>? <.dot_atom_text> <.CFWS>?
    <token: dot_atom_text>   <.atext>+ (?: \. <.atext>+)*

    <token: text>            [\x01-\x09\x0b\x0c\x0e-\x7f]
    <token: quoted_pair>     \\ <.text>

    <token: qtext>           <.NO_WS_CTL> | [\x21\x23-\x5b\x5d-\x7e]
    <token: qcontent>        <.qtext> | <.quoted_pair>
    <token: quoted_string>   <.CFWS>? <.DQUOTE> (?:<.FWS>? <.qcontent>)*
                             <.FWS>? <.DQUOTE> <.CFWS>?

    <token: word>            <.atom> | <.quoted_string>
    <token: phrase>          <.word>+

    # Folding white space
    <token: FWS>             (?: <.WSP>* <.CRLF>)? <.WSP>+
    <token: ctext>           <.NO_WS_CTL> | [\x21-\x27\x2a-\x5b\x5d-\x7e]
    <token: ccontent>        <.ctext> | <.quoted_pair> | <.comment>
    <token: comment>         \( (?: <.FWS>? <.ccontent>)* <.FWS>? \)
    <token: CFWS>            (?: <.FWS>? <.comment>)*
                             (?: (?:<.FWS>? <.comment>) | <.FWS>)

    # No whitespace control
    <token: NO_WS_CTL>       [\x01-\x08\x0b\x0c\x0e-\x1f\x7f]

    <token: ALPHA>           [A-Za-z]
    <token: DIGIT>           [0-9]
    <token: CRLF>            \x0d \x0a
    <token: DQUOTE>          "
    <token: WSP>             [\x20\x09]

    }x;

};


while (my $input = <>) {
    if ($input =~ $rfc5322) {
        say Dumper \%/;       # ...the parse tree of any successful match
                              # appears in this punctuation variable
    }
}

the perlre manpage中有很多好东西,但基本正则表达式设计功能的这些显着改进绝不仅限于Perl。确实the pcrepattern manpage可能更容易阅读,并涵盖相同的领域。

现代模式几乎与您在有限自动机类中教授的原始事物没有任何共同之处。

答案 2 :(得分:68)

正则表达式是一个很好的工具,但是人们会想“嘿,这是一个很棒的工具,我会用它做X!”其中X是不同工具更适合的东西(通常是解析器)。这是使用锤子的标准,你需要一个螺丝刀问题。

答案 3 :(得分:52)

我认识的几乎每个人都经常使用正则表达式(双关语)来自Unix-ish背景,他们使用将RE视为一流编程结构的工具,如grep,sed,awk和Perl。由于使用正则表达式几乎没有语法上的开销,因此当他们这样做时,他们的工作效率会提高。

相比之下,使用RE是外部库的语言的程序员往往不会考虑正则表达式可以带到表中的内容。程序员的“时间成本”是如此之高,以至于a)RE从未作为他们培训的一部分出现,或者b)他们没有在RE方面“思考”,而是倾向于回归更熟悉的模式。

答案 4 :(得分:43)

正则表达式允许您以紧凑的方式编写自定义有限状态机(FSM),以处理输入字符串。使用正则表达式很难有至少两个原因:

  • 老派软件开发涉及很多规划,纸模型和仔细思考。正则表达式非常适合这个模型,因为正确地编写一个有效的表达式需要大量的盯着它,可视化FSM的路径。

    现代软件开发人员更愿意敲定代码,并使用调试器逐步执行,以查看代码是否正确。正则表达式不能很好地支持这种工作方式。正则表达式的一个“运行”实际上是原子操作。很难在调试器中观察逐步执行。

  • 编写一个偶然接受比你想要的更多输入的正则表达式太容易了。正则表达式的值实际上不匹配有效输入,而无法匹配无效输入。对正则表达式进行“否定测试”的技术不是很先进,或者至少没有广泛使用。

    这使得正则表达式难以阅读。仅仅通过查看正则表达式,需要大量注意力来可视化应该被拒绝的所有可能的输入,但是被错误地接受。曾经尝试调试某人 else的正则表达式代码吗?

如果今天软件开发人员对使用正则表达式有抵触,我认为这主要是由于这两个因素。

答案 5 :(得分:36)

人们倾向于认为正则表达很难;但那是因为他们错了。编写复杂的单行,没有任何注释,缩进或命名捕获。 (你不要在一行中填写复杂的SQL表达式,没有注释,缩进或别名,不是吗?)。所以是的,对很多人来说,他们没有意义。

但是,如果你的工作任何与解析文本有关(大概是任何网络应用程序......)并且你不知道正则表达式,那你就吮吸你的工作和你'浪费自己和雇主的时间。那里有excellent resources教你关于他们的一切你需要知道的东西,等等。

答案 6 :(得分:29)

因为他们缺乏普遍接受的IDE中最受欢迎的学习工具:没有正则表达式向导。甚至没有Autocompletion。你必须自己编写所有的代码。

答案 7 :(得分:16)

我不认为他们是那么有争议。

我也认为你已经回答了自己的问题,因为你指出在任何地方使用它们是多么愚蠢(Not everything is a regular language 2)或者完全避免使用它们。作为程序员,您必须明智地决定正则表达式何时会帮助代码或者对代码造成伤害。面对这样的决定时,要记住的两件重要事情是可维护性(这意味着可读性)和可扩展性。

对于那些特别厌恶他们的人,我的猜测是他们从未学过如何正确使用它们。我认为大多数只花几个小时使用体面教程的人会把它们搞清楚,并且很快就会流利。以下是我对从哪里开始的建议:

http://docs.python.org/howto/regex

虽然该页面讨论了Python上下文中的正则表达式,但我发现这些信息在其他地方非常适用。有一些特定于Python的东西,但我相信它们清晰可闻,易于记忆。

答案 8 :(得分:16)

Regular Expressions: Now You Have Two Problems”是杰夫阿特伍德关于此事的精彩文章。基本上,正则表达式“很难”!他们可以创造新的问题。然而,它们是有效的。

答案 9 :(得分:11)

正则表达式是将算术运算符与数字串联起来,我不认为它们是有争议的。我认为,即使是像我一样相当苛刻的OO活动家(他倾向于选择其他物品而不是弦乐)也很难拒绝它们。

答案 10 :(得分:7)

问题在于,正则表达式可能非常强大,以至于您可以使用它们来执行不同的操作。

一个优秀的程序员应该知道在哪里使用它们,哪里不知道。典型的例子是解析非常规语言(参见Deciding whether a language is regular)。

如果您首先将自己限制为真正的正则表达式(无扩展名),我认为您不会出错。一些扩展可以让您的生活更轻松,但如果您发现很难表达为真正的正则表达式,这很可能表明正则表达式不是正确的工具。

答案 11 :(得分:5)

你几乎可以问为什么goto是有争议的。

基本上,当你获得如此“明显”的力量时,人们很容易滥用它们,因为它们不是最好的选择。例如,要求在正则表达式中解析CSV或XML或HTML的人数让我震惊。这是工作的错误工具。但是有些用户仍坚持使用正则表达式。

就个人而言,我试图找到快乐的媒介 - 使用正则表达式来获取它们的优点,并在它们不是最佳时避免它们。

请注意,正则表达式仍可用于解析CSV,XML,HTML等。但通常不在单个正则表达式中。

答案 12 :(得分:4)

这是一个有趣的主题 许多 regexp 爱好者似乎将公式的简洁性与效率相混淆。
除此之外,需要大量思考的正则表达式为其作者带来了巨大的满足感,使其成为合法的直接程。

但是......当性能不是问题而且你需要快速处理文本输出时,例如在Perl中,正则表达式是所以。此外,虽然性能问题,但我们可能不希望尝试通过使用可能有缺陷或效率较低的自制算法来击败正则表达式库。

此外,有很多理由可以对正则表达式进行不公平的批评,例如

  • 正则表达式效率不高,因为构建顶部的不是很明显
  • 一些程序员“忘记”只编译一次regexp多次使用(比如Java中的静态模式)
  • 一些程序员参加试验和错误策略 - 使用regexp工作更少!

答案 13 :(得分:4)

我不认为“有争议的”是正确的词。

但是我看过很多例子,人们会说“我需要做什么正则表达式,这样的字符串操作?”这是X-Y问题。

换句话说,他们开始假设正则表达式是他们需要的,但他们最好使用split(),像perl的tr ///这样的翻译,其中字符替换为其他,或只是一个索引()。

答案 14 :(得分:3)

我认为学习正则表达式并保持正则表达式不受欢迎, 大多数开发人员都很懒,或者他们中的大多数依靠外部库为他们做解析...他们依靠谷歌的答案,甚至在论坛中询问他们问题的完整代码。 但是当实现或修改/维护正则表达式时,它们就会失败。

有一句流行的说法“朋友不要让朋友使用正则表达式来解析HTML”

但就我而言,我已经使用正则表达式制作了完整的HTML解析器,我发现我的自我正则表达式更好地解析html字符串,包括速度和内存(如果你有一个想法你想要实现什么) :))

答案 15 :(得分:2)

对于包括我自己在内的很多人而言,正则表达式是一个严重的谜。它效果很好,但就像看数学方程式一样。我很高兴地报告,虽然有人最终在http://regexlib.com/创建了各种正则表达式函数的合并位置。现在,如果微软只创建一个正则表达式类,它会自动执行大部分常见的事情,比如消除字母或过滤日期。

答案 16 :(得分:1)

我发现正则表达式有时非常宝贵。当我需要做一些“模糊”搜索,并可能取代。当数据可能变化并具有一定的随机性时。 但是,当我需要进行简单的搜索和替换,或检查字符串时,我不使用正则表达式。虽然我认识很多人,但他们会把它用于一切。这就是争议。

如果你想在墙上贴一个钉子,不要用锤子。是的,它会起作用,但是当你拿到锤子时,我可以在墙上放上20个钉子。

正则表达式应该用于它们的设计目的,而不是更少。

答案 17 :(得分:0)

在某些情况下,我认为你必须使用它们。例如,建立一个词法分析器。

在我看来,这是一个可以写regexp的人和没有(或几乎没有)的人的观点。 我个人认为这是一个很好的想法,例如有效的表单输入,无论是在javascript中警告用户,还是在服务器端语言。

答案 18 :(得分:0)

我认为这是程序员中鲜为人知的技术。因此,没有广泛接受它。如果您有一位非技术经理来审核您的代码或审核您的工作,那么正则表达式非常糟糕。您将花费数小时编写一个完美的正则表达式,并且您将获得很少的标记,因为他/她已经编写了如此少的代码行。 另外,正如其他地方所说,阅读正则表达式是非常困难的任务。

答案 19 :(得分:0)

虽然我认为正则表达式是一个必不可少的工具,但最令人讨厌的是有不同的实现。语法,修饰符和特别是“贪婪”之间的微小差异会使事情变得非常混乱,需要反复试验,有时会产生令人费解的错误。

答案 20 :(得分:0)

在lex和yacc中用于编译器定义的体系正则表达式系统是好的,非常有用且干净。在这些系统中,表达类型是根据其他类型定义的。这是在perl和sed代码(等等)中常见的难以理解的难以理解的线性噪声巨型单行正则表达式,它们是“有争议的”(垃圾)。

答案 21 :(得分:-4)

正则表达式的最佳有效和正常用法是用于电子邮件地址格式验证。

这是一个很好的应用。

我在TextPad中无数次使用正则表达式来按摩平面文件,创建csv文件,创建SQL插入语句等等。

写得好的正则表达式不应该太慢。通常替代方案,比如大量调用Replace,选项要慢得多。不妨一次性完成。

许多情况需要完全正则的表达式而不需要其他任何内容。

用无害字符替换特殊的非打印字符是另一个很好的用法。

我当然可以想象有些代码库过度使用正则表达式而不利于可维护性。我从未见过自己。实际上我已经被代码审查员所避免,因为我没有足够使用正则表达式。