为什么正则expression式如此引起争议?
在探索正则expression式(也称为正则expression式)时,有许多人似乎将正则expression式视为圣杯。 看起来如此复杂的东西 – 只是任何问题的答案。 他们倾向于认为每个问题都可以用正则expression式来解决。
另一方面,也有许多人不惜一切代价避免正规expression。 他们试图find一个正则expression式的方法,并接受额外的编码,即使正则expression式是一个更紧凑的解决scheme。
为什么正则expression式被认为是有争议的? 对他们如何工作有广泛的误解吗? 或者可能是一个宽泛的信念,正则expression式通常是缓慢的?
我不认为人们反对正则expression式,因为他们很慢,而是因为他们很难读写,而且很难正确。 虽然在某些情况下正则expression式可以为问题提供一个有效的,紧凑的解决scheme,但是它们有时会被用来代替使用易于阅读和维护的代码段。
使正则expression式可维护
将之前被称为“正则expression式”的模式神秘化的主要进展是Perl的/x
regex标志 – 有时在embedded时被写入(?x)
– 允许空格(换行,缩进)和注释。 这严重提高了可读性,因此可维护性。 白色的空间允许认知分块,所以你可以看到什么组。
现在,现代模式现在也支持相对编号和命名的反向引用。 这意味着你不再需要计算捕获组来确定你需要$4
或者\7
。 这有助于创build可以包含在其他模式中的模式。
以下是一个相对编号的捕获组的例子:
$ word word q q q \ \ \ </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> </s> $ quoted = qr {([“'])$ dupword \ 1} x;
这里是一个命名捕获的优越方法的例子:
$dupword = qr{ \b (?: (?<word> \w+ ) (?: \s+ \k<word> )+ ) \b }xi; $quoted = qr{ (?<quote> ["'] ) $dupword \g{quote} }x;
语法正则expression式
最重要的是 ,这些命名的捕获可以放在一个(?(DEFINE)...)
块中,这样你就可以将这个声明从你的模式的单个命名元素的执行中分离出来。 这使得它们在模式中像子程序一样行事。
在这个答案和这个 答案中可以find这种 “语法正则expression式”的一个很好的例子。 这些看起来更像是一个语法声明。
正如后者提醒你:
…确保永远不要写行噪音模式。 你不必,也不应该。 禁止使用禁止空格,注释,子例程或字母数字标识符的编程语言。 所以在你的模式中使用所有这些东西。
这不能过分强调。 当然,如果你不使用这些东西,你会经常创造一个噩梦。 但是,如果你真的使用它们,那么你不需要。
这里是现代语法模式的另一个例子,这个语法模式用于parsingRFC 5322:use 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) | (?"ed_string)) (?<domain> (?&dot_atom) | (?&domain_literal)) (?<domain_literal> (?&CFWS)? \[ (?: (?&FWS)? (?&dcontent))* (?&FWS)? \] (?&CFWS)?) (?<dcontent> (?&dtext) | (?"ed_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) | (?"ed_pair)) (?<quoted_string> (?&CFWS)? (?&DQUOTE) (?:(?&FWS)? (?&qcontent))* (?&FWS)? (?&DQUOTE) (?&CFWS)?) (?<word> (?&atom) | (?"ed_string)) (?<phrase> (?&word)+) # Folding white space (?<FWS> (?: (?&WSP)* (?&CRLF))? (?&WSP)+) (?<ctext> (?&NO_WS_CTL) | [\x21-\x27\x2a-\x5b\x5d-\x7e]) (?<ccontent> (?&ctext) | (?"ed_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的辉煌的Regexp::Grammars
模块提供了一个更清晰的语法,并具有出色的debuggingfunction。 以下是将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 } }
在perlre手册页中有很多好东西,但是这些基本的正则expression式devise特性的显着改进决不仅限于Perl。 事实上, pcrepattern的手册页可能更容易阅读,并涵盖相同的领域。
现代模式与您在有限自动机课程中教授的原始东西几乎没有任何共同之处。
正则expression式是一个很好的工具,但是人们会想:“嘿,多么伟大的工具,我会用它来做X!” 其中X是一个不同的工具更适合(通常是一个parsing器)的东西。 这是使用锤子的标准,你需要一个螺丝刀的问题。
几乎每个我认识的经常使用正则expression式(双关语)的人都来自于Unix-ish背景,他们使用的工具将RE视为一stream的编程结构,比如grep,sed,awk和Perl。 由于几乎没有使用正则expression式的语法开销,所以当他们使用正则expression式时,他们的生产力就会提高。
相反,使用REs是外部库的语言的程序员往往不会考虑正则expression式可以带来什么样的表。 程序员的“时间成本”是如此之高,以至于a)从来没有将RE作为他们培训的一部分出现,或者b)他们不以RE的方式“思考”,而是倾向于回到更熟悉的模式。
正则expression式允许您以紧凑的方式编写自定义有限状态机(FSM),以处理一串input。 为什么使用正则expression式是困难的至less有两个原因:
-
老派软件开发涉及到很多计划,纸模型和仔细思考。 正则expression式很好地融入到这个模型中,因为要正确地写出有效的expression式需要大量的注意,可视化FSM的path。
现代软件开发人员更应该重点关注代码,并使用debugging器逐步执行,以查看代码是否正确。 正则expression式不太支持这种工作风格。 一个正则expression式的“运行”实际上是一个primefaces操作。 在debugging器中很难观察到逐步执行。
-
写一个正则expression式太容易了,它意外地接受比你想要的更多的input。 正则expression式的值不是真正匹配有效的input,而是无法匹配无效的input 。 对正则expression式进行“否定testing”的技术并不是很先进,至less没有被广泛使用。
这正是难以阅读的正则expression式。 只要看一个正则expression式,就需要大量的注意力来把所有可能的投入想象成应该被拒绝的东西,但却被错误地接受了。 曾经尝试debugging别人的正则expression式代码?
如果今天在软件开发者中使用正则expression式是有阻力的,我认为主要是由于这两个因素。
人们倾向于认为正则expression式是困难的; 但那是因为他们错用了他们。 编写复杂的一行,没有任何评论,缩进或命名捕获。 (你不会在一行中填充复杂的SQLexpression式,没有注释,缩进或别名,是吗?)。 所以是的,对很多人来说,他们没有道理。
但是,如果你的工作与parsing文本(大致上是任何networking应用程序…)有关,而你不知道正则expression式,那么你就会吮吸自己的工作,而你正在浪费自己的时间,雇主。 那里有很好的资源来教你一切你需要知道的事情,等等。
因为他们缺乏普遍接受的IDE中最stream行的学习工具:没有正则expression式向导。 甚至没有Autocompletion。 你必须自己编码整个事情。
我不认为他们是有争议的。
我也觉得你已经回答了你自己的问题,因为你指出在任何地方使用它们是非常愚蠢的( 不是所有的东西都是普通的语言 2 ),或者根本不使用它们。 程序员必须对正则expression式何时帮助代码或者伤害代码做出明智的决定。 面对这样一个决定时,要记住两个重要的事情是可维护性(这意味着可读性)和可扩展性。
对于那些对他们特别厌恶的人,我的猜测是,他们从来没有学会正确使用它们。 我认为大多数花了几个小时的人才会拿出一个体面的教程,很快就会变得stream利。 这里是我的build议,从哪里开始:
http://docs.python.org/howto/regex
尽pipe这个页面在Python的上下文中讨论了正则expression式,但是我发现这些信息在其他地方是非常适用的。 有一些Python特有的东西,但是我相信它们被清楚地logging下来,并且很容易记住。
“ 正则expression式:现在你有两个问题 ”是Jeff Atwood关于此事的一篇很棒的文章。 基本上,正则expression式是“硬”! 他们可以创造新的问题。 但是,它们是有效的。
正则expression式是将算术运算符的数字串起来,我不认为它们是有争议的。 我认为即使是一个像我这样的OO活动家,他也会倾向于select其他的东西,而不愿意拒绝他们。
问题是,正则expression式可能非常强大,你可以用它们做事情,你应该使用不同的东西。
一个好的程序员应该知道在哪里使用它们,哪里不是。 典型的例子是parsing非常规语言(请参阅决定语言是否正常 )。
我认为如果你先把自己限制在真正的正则expression式(没有扩展名),你就不会出错。 一些扩展可以让你的生活变得简单一些,但是如果你发现一些难以expression的东西是一个真正的正则expression式,这可能是一个正则expression式不是正确的工具。
你几乎可能会问为什么goto是有争议的。
基本上,当你有这么多的“显而易见”的权力,人们往往滥用他们的情况下,他们不是最好的select。 例如,要求在正则expression式中parsingCSV或XML或HTML的人数惊人。 这是工作的错误工具。 但有些用户坚持使用正则expression式。
就我个人而言,我试图find那些乐于使用正则expression式的中等正则expression式,并且当它们不是最优时避免它们。
请注意,regexes仍然可以用来parsingCSV,XML,HTML等,但通常不是在一个正则expression式。
我不认为“有争议”是正确的话。
但是我已经看到了很多例子,人们说“我需要做什么样的正则expression式来做这样一个string操作? 这是XY的问题。
换句话说,他们从一个正则expression式就是他们所需要的假设开始,但是他们最好用split(),像perl的tr ///这样的翻译,其中字符replace为另一个,或者只是一个索引()。
这是一个有趣的主题。
许多正规爱好者似乎混淆了公式的简洁性和效率。
最重要的是,需要大量思考的正则expression式给作者带来了巨大的满足感,使其立即合法化。
但是…正则expression式是非常方便的,当性能不是问题,你需要快速处理文本输出,例如在Perl中。 此外,虽然性能是一个问题,但人们可能更倾向于不使用可能是越野车或效率较低的自制algorithm来击败正则expression式库。
例如,还有一些原因是正则expression式被不公正地批评了
- 正则expression式效率不高,因为构build最高的一个并不明显
- 一些程序员“忘记”只编译一次正则expression式,多次使用(像Java中的静态模式)
- 一些程序员去尝试和错误策略 – 用正则expression式更less!
正则expression式对包括我自己在内的许多人来说是一个严重的谜团。 它工作的很好,但就像在看一个math方程。 我很高兴地报告说,有人最终在http://regexlib.com/创build了各种正则expression式函数的合并位置。; 现在,如果微软只会创build一个正则expression式类,它会自动完成很多常见的东西,例如消除字母或过滤date。
我认为是学习正则expression式和维护正则expression式使不受欢迎的,大多数的开发人员是懒惰的,或大多数依靠外部库来parsing的事情他们…他们依靠谷歌的答案,甚至要求在论坛他们的问题的完整代码。 但是当实现或修改/维护一个正则expression式时,他们只是失败了。
有一个stream行的说法“朋友不让朋友使用正则expression式parsingHTML”
但就我而言,我已经使用正则expression式完成了HTMLparsing器,我发现我的自我,正则expression式更好地parsinghtmlstring速度和内存明智(如果你有一个想法你要什么:))
我发现正则expression式有时是非常有价值的。 当我需要做一些“模糊”search,也许replace。 当数据可能变化并具有一定的随机性时。 但是,当我需要做一个简单的search和replace,或检查一个string,我不使用正则expression式。 虽然我认识很多人,但是他们都用它。 这是争议。
如果你想在墙上放一个钉子,不要用锤子。 是的,它会起作用,但当你拿到锤子的时候,我可以在墙上放20个钉子。
正则expression式应该用于他们devise的内容,而不是什么。
获取RegexBuddy 。 那么你会像专业人士一样抛出正则expression式! 奖金 ! 你开始了解他们!
虽然我认为正则expression式是一个不可或缺的工具,但最令人讨厌的是有不同的实现。 语法,修饰语和特别是“贪婪”之间的细微差别会使事情变得非常混乱,需要反复试验,有时会产生令人费解的错误。
在某些情况下,我认为你必须使用它们。 例如build立一个词法分析器。
在我看来,这是一个可以写正则expression式的人和不写(或几乎)不写的人的观点。 我personnaly的东西这是一个好的想法,例如有效的表单的input,在javascript中警告用户,或在服务器端语言。
我认为这是一个程序员之间鲜为人知的技术。 所以没有被广泛接受。 如果你有非技术经理来审查你的代码或审查你的工作,那么正则expression式是非常糟糕的。 你会花上几个小时写出一个完美的正则expression式,你会得到几个模块认为他/她写了几行代码的标志。 另外,正如其他地方所说,阅读正则expression式是非常困难的任务。
像lex和yacc中用于编译器定义的体面的正则expression式系统是好的,非常有用和干净。 在这些系统中,expression式types是以其他方式定义的。 这是可怕的畸形的不可读的线性噪声巨大的单线正则expression式,通常在“争议”(垃圾)的perl和sed代码(等)中find。
正则expression式的最佳有效和正常用法是用于电子邮件地址格式validation。
这是一个很好的应用程序。
我在TextPad中使用了无数次的正则expression式来按摩平面文件,创buildcsv文件,创buildSQL插入语句等等。
写得好的正则expression式不应该太慢。 通常情况下,替代方法,如吨的调用是很慢的select。 不妨一次完成。
许多情况下只需要正则expression式就可以了。
用无害字符replace特殊的非打印字符是另一个好用法。
我当然可以想象,有一些代码库过度使用正则expression式,而不利于可维护性。 我从来没有见过我自己。 我实际上已经被代码审查人员避免了不使用正则expression式。