正则表达式真的可以维护吗?

时间:2008-09-29 21:27:43

标签: regex coding-style

我见过的任何使用Regexes的代码都会将它们用作黑盒子:

  1. 输入字符串
  2. Magic Regex
  3. 拿出字符串
  4. 在生产代码中使用这似乎不是一个特别好的主意,因为即使是一个小小的改变通常也会导致完全不同的正则表达式。

    除了标准是永久性和不变的情况之外,还是正确的做法,或者尝试不同的方法更好吗?

20 个答案:

答案 0 :(得分:27)

如果正则表达式很长并且难以理解,那么它们很难维护,那么就应该对它们进行评论。

许多正则表达式实现允许您使用空格和注释填充正则表达式 见http://www.regular-expressions.info/comments.html
和编码恐怖:Regular Expressions: Now You Have Two Problems

  

我见过的任何使用Regexes的代码都会将它们用作黑盒子:

如果用黑盒子表示抽象,那就是所有编程都是,试图抽象出困难的部分(解析字符串),这样你就可以专注于问题领域(我想要匹配哪种字符串)。

  

即使是一个小的改变也经常会导致一个完全不同的正则表达式。

任何代码都是如此。只要您正在测试正则表达式以确保它与您期望的字符串匹配,理想情况下使用单元测试,那么您应该有信心更改它们。

编辑:还请阅读Jeff对此答案的评论。

答案 1 :(得分:14)

Obligatory.

这真的归结为正则表达式。如果这是一个巨大的单片表达式,那么是的,这是一个可维护性问题。如果你能简洁地表达它们(也许是通过分解它们),或者如果你有很好的评论和工具来帮助你理解它们,那么它们就可以成为一个强大的工具。

答案 2 :(得分:8)

我不知道您正在使用哪种语言,但Perl - 例如 - 支持x标志,因此除非转义,否则在正则表达式中忽略空格,因此您可以将其分成几行并对所有内容进行注释内联:

$foo =~ m{
    (some-thing)          # matches something
    \s*                   # matches any amount of spaces
    (match another thing) # matches something else
}x;

这有助于使长正则表达式更具可读性。

答案 3 :(得分:7)

如果你不理解正则表达式,它似乎只是魔术。生产代码中的任何小的变化都可能导致重大问题,因此在我看来,这不是一个很好的理由,不使用正则表达式。彻底的测试应该指出任何问题。

答案 4 :(得分:7)

任何语言中任何代码的细微更改都可能导致完全不同的结果。其中一些甚至阻止编译。

用“C”或“C#”或“Java”或“Python”或“Perl”或“SQL”或“Ruby”或“awk”代替正则表达式或......任何东西,真的,你得到相同的问题

正则表达式只是另一种语言,Huffman coded在字符串匹配方面很有效。就像Java,Perl,PHP或者特别是SQL一样,每种语言都有优点和缺点,当你编写(或维护它)以获得高效的希望时,你需要知道你正在编写的语言。

编辑:Mike,正则表达式是Huffman编码,因为常见的事情要比简单的事情短。文本的文字匹配通常是单个字符(您要匹配的字符)。存在特殊字符 - 常见字符很短。特殊构造,例如(?:)更长。这些与通用语言(如Perl,C ++等)中常见的不同,因此霍夫曼编码的目标是此专业化。

答案 5 :(得分:6)

复杂的正则表达式对我来说是不可思议的。写它,测试它,当它工作时,写一个评论它做什么,我们没事。

但是,在许多情况下,您可以将正则表达式细分为较小的部分,也可以编写一些结合了这些正则表达式的文档齐全的代码。但是如果你在代码中找到一个多行正则表达式,你最好不要那个必须维护它的人:)

听起来很熟悉?这或多或少都适用于任何代码。你不想拥有很长的方法,你不想拥有很长的类,并且你不想拥有很长的正则表达式,尽管方法和类更容易重构。但实质上,它是相同的概念。

答案 6 :(得分:3)

正则表达式不是唯一的做事方式。您可以在代码中逻辑地执行正则表达式可以执行的所有操作。正则表达式只是

  1. 快速
  2. 经过测试和验证
  3. 强大

答案 7 :(得分:3)

如果您使用Perl 5.10引入的新功能,则RegEx可以非常维护。我引用的功能是Perl 6的后端移植功能。

直接从perlretut

复制的示例

定义命名模式

一些正则表达式在几个地方使用相同的子模式。从Perl 5.10开始,可以在模式的一个部分中定义命名子模式,以便可以在模式中的任何位置按名称调用它们。此定义组的语法模式为(?(DEFINE)(?<name>pattern)...)。命名模式的插入写为(?&name)

下面的示例使用前面介绍的浮点数模式说明了此功能。不止一次使用的三个子模式是可选符号,整数的数字序列和小数部分。模式末尾的DEFINE组包含其定义。请注意,小数分数模式是我们可以重用整数模式的第一个地方。

/^
  (?&osg)\ * ( (?&int)(?&dec)? | (?&dec) )
        (?: [eE](?&osg)(?&int) )?
 $
 (?(DEFINE)
     (?<osg>[-+]?)         # optional sign
     (?<int>\d++)          # integer
     (?<dec>\.(?&int))     # decimal fraction
 )
/x

答案 8 :(得分:2)

关于正则表达式的着名引言:

  

“有些人在面对问题时会想到   “我知道,我会使用正则表达式。”现在他们有两个问题。“ - Jamie Zawinski

当我使用正则表达式时,我发现它们是可维护的,但它们在特殊情况下使用。通常有一种更好的,非正则表达式的方法来处理几乎所有事情。

答案 9 :(得分:2)

当有意识地使用时,正则表达式是一种强大的机制,可以使您从可能的文本解析的行和行中解脱出来。它们当然应该被正确记录并有效地跟踪,以验证初始假设是否仍然有效,并相应地更新它们。关于维护IMHO更好地改变单行代码(正则表达式模式),而不是理解解析代码的行和行或者正则表达式的目的。

答案 10 :(得分:2)

regexes是做事的方式吗?这取决于任务。

与所有编程一样,没有一个硬性和快速的正确或错误的答案。

如果正则表达式快速而简单地解决了某个特定任务,那么它可能会比一个更详细的解决方案更好。

如果正则表达式试图完成一项复杂的任务,那么更详细的内容可能更容易理解并因此维护。

答案 11 :(得分:2)

有很多可能使RegEx更易于维护。最后,它只是一种技术(好的?)程序员在主要(有时甚至是次要的)变化时必须学习。当没有一些真正优秀的专业人士时,没有人会因为语法复杂而烦恼。但是他们工作快速,紧凑且非常灵活。

对于.NET People,可能会有“Linq to RegEx”库看起来更糟或“Readable Regular Expressions Library”。它使它们更容易维护,更容易编写。我在自己的项目中使用了它们,我知道我用它们分析的html源代码可以随时改变。

但请相信我:当你向他们施压时,他们甚至可以通过写作和阅读来取笑。 :)

答案 12 :(得分:1)

您的问题似乎与正则表达式本身无关,只有语法通常用于表达正则表达式。在许多硬核编码器中,这种语法已被接受为非常简洁和强大,但对于更长的正则表达式,它实际上是不可读的和不可维护的。

有些人已经在Perl中提到了“x”标志,这有点帮助,但并不多。

我喜欢正则表达式,但不是语法。能够从可读,有意义的方法名称构造正则表达式会很好。例如,而不是这个C#代码:

foreach (var match in Regex.Matches(input, @"-?(?<number>\d+)"))
{
    Console.WriteLine(match.Groups["number"].Value);
}

你可以拥有更冗长,但更具可读性和可维护性的东西:

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);
}

这只是一个快速的想法;我知道还有其他不相关的可维护性问题(尽管我认为它们更少,更小)。这样做的另一个好处是编译时验证。

当然,如果您认为这是在顶部并且详细,您仍然可以使用介于两者之间的正则表达式语法,也许......

instead of:   -?(?<number>\d+)
could have:   ("-" or "") + (number = digit * [1..])

这仍然是可读性的一百万倍,而且只有两倍。这样的语法可以很容易地具有与普通正则表达式相同的表达能力,并且它当然可以集成到编程语言的编译器中进行静态分析。

我真的不知道为什么即使在重新思考整个编程语言时(例如Perl 6,或者当C#是新的),重新考虑正则表达式的语法也有很多反对意见。此外,上述非常冗长的想法甚至与“旧的”正则表达不相容; API可以很容易地实现为构建旧式正则表达式的API。

答案 13 :(得分:1)

问题不在于正则表达式本身,而在于它们作为黑盒子的处理。与任何编程语言一样,可维护性更多地与编写它的人和阅读它的人有关,而不是与语言本身有关。

使用正确的工具进行工作也有很多要说的。在您对原始帖子的评论中提到的示例中,正则表达式是用于解析HTML的错误工具,正如在PerlMonks上经常提到的那样。如果你试图用一般的方式解析HTML只使用一个正则表达式,那么你最终会以一种不正确和脆弱的方式进行,写一个可怕的,不可维护的正则表达式,或者(很可能)两者。

答案 14 :(得分:1)

我有一个彻底评论非平凡正则表达式的政策。这意味着描述和证明每个与自身不匹配的原子。有些语言(Python,一个)提供“冗长”的正则表达式,它们忽略空格并允许注释;尽可能使用它。否则,在正则表达式上方的注释中逐个原子。

答案 15 :(得分:0)

我通常会去写一个扫描仪规范文件。扫描仪或“扫描仪生成器”本质上是优化的文本解析器。由于我通常使用Java,我首选的方法是JFlex(http://www.jflex.de),但也有Lex,YACC和其他几个。

扫描程序可以处理您可以定义为宏的正则表达式。然后在正则表达式与文本的一部分匹配时实现回调。

说到代码,我有一个包含所有解析逻辑的规范文件。我通过选择的扫描仪生成器工具运行它,以选择的语言生成源代码。然后我将所有这些包装到解析器函数或某种类中。这种抽象使得管理所有正则表达式逻辑变得容易,而且性能非常好。当然,如果你只使用一两个正则表达式就太过分了,而且至少需要2-3天来了解到底是怎么回事,但如果你曾经使用过,比如5或6或30其中,它成为一个非常好的功能,实现解析逻辑开始只需要几分钟,并且易于维护和易于记录。

答案 16 :(得分:0)

我一直把这个问题作为构建块问题来处理。

你不只是写了3000个字符的正则表达式,并希望最好。你写了一堆你加在一起的小块。

例如,要匹配URI,您需要协议,权限,子域,域,tld,路径,参数(至少)。其中一些是可选的!

我确信你可以编写一个怪物来处理它,但是更容易编写块并将它们加在一起。

答案 17 :(得分:0)

Regex一直被称为“只写”编程语言。但是,我认为这并不意味着你应该避免它们。我只是认为你应该对他们的意图进行评论。我通常不是解释行的注释的忠实粉丝,我可以读取代码,但正则表达式是例外。评论一切!

答案 18 :(得分:0)

我通常将正则表达式分成带有注释的片段,然后将它们全部放在一起进行最后的推送。片段可以是子串或数组元素

两个PHP PCRE示例(具体或特定用途并不重要):

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))

答案 19 :(得分:0)

我在我的应用程序中使用它们但是我在配置文件中保留了实际的regEx表达式,所以如果我正在解析的源文本(例如电子邮件)由于某种原因改变格式我可以快速更新配置来处理更改无需重新构建应用程序。