17

我见过的任何使用正则表达式的代码都倾向于将它们用作黑匣子:

  1. 放入字符串
  2. 魔术正则表达式
  3. 拿出字符串

在生产代码中使用这似乎不是一个特别好的主意,因为即使是很小的更改通常也会导致完全不同的正则表达式。

除了标准是永久不变的情况外,正则表达式是做事的方式,还是尝试不同的方法更好?

4

20 回答 20

27

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

许多正则表达式实现允许您用空格和注释填充正则表达式。
请参阅https://www.regular-expressions.info/freespacing.html#parenscomment
和编码恐怖:正则表达式:现在你有两个问题

我见过的任何使用正则表达式的代码都倾向于将它们用作黑匣子:

如果黑盒是指抽象,那就是所有编程,试图抽象出困难的部分(解析字符串),以便您可以专注于问题域(我想匹配什么样的字符串)。

即使是很小的变化也常常会导致完全不同的正则表达式。

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

编辑:还请阅读 Jeff 对此答案关于生产代码的评论。

于 2008-09-29T21:33:47.833 回答
14

强制性的。

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

于 2008-09-29T21:31:37.043 回答
8

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

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

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

于 2008-09-29T21:35:19.107 回答
7

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

于 2008-09-29T21:32:45.340 回答
7

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

用“C”或“C#”或“Java”或“Python”或“Perl”或“SQL”或“Ruby”或“awk”或......替换正则表达式,真的,你会得到同样的问题。

正则表达式只是另一种语言,霍夫曼编码以有效地进行字符串匹配。就像 Java、Perl、PHP 或特别是 SQL 一样,每种语言都有优点和缺点,并且您需要在编写(或维护)它时了解您正在编写的语言,以便有希望提高生产力。

编辑:迈克,正则表达式是霍夫曼编码的,常见的事情比稀有的事情要短。文本的文字匹配通常是单个字符(您要匹配的字符)。存在特殊字符 - 常见字符很短。特殊结构,例如 (?:) 更长。这些与 Perl、C++ 等通用语言中常见的东西不同,因此 Huffman 编码针对的是这种专业化。

于 2008-09-29T21:34:12.123 回答
6

复杂的正则表达式对我来说是一劳永逸的。编写它,测试它,当它工作时,写一个评论它做了什么,我们很好。

但是,在许多情况下,您可以将正则表达式分解为更小的部分,也许可以编写一些文档齐全的代码来组合这些正则表达式。但是如果你在你的代码中发现了一个多行的正则表达式,你最好不要去维护它:)

听起来很熟悉?任何代码或多或少都是如此。你不想有很长的方法,你不想有很长的类,你也不想有很长的正则表达式,尽管方法和类更容易重构。但本质上,它是同一个概念。

于 2008-09-29T21:31:52.253 回答
3

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

  1. 快速地
  2. 经过测试和证明
  3. 强大的
于 2008-09-29T21:31:45.810 回答
3

如果您利用Perl 5.10. 我所指的功能是来自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
于 2008-10-16T04:23:25.377 回答
2

关于正则表达式的名言:

“有些人在遇到问题时会想‘我知道,我会使用正则表达式。现在他们有两个问题。” ——杰米·扎温斯基

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

于 2008-09-29T21:32:13.593 回答
2

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

于 2008-09-29T21:33:40.703 回答
2

正则表达式是做事的方式吗?这取决于任务。

与所有编程事物一样,没有固定的正确或错误答案。

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

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

于 2008-09-29T21:35:36.173 回答
2

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

对于 .NET 人来说,可能会有“ Linq to RegEx ”库看起来更糟或“可读正则表达式库”。它使它们更易于维护且更易于编写。我在自己的项目中使用了它们,我知道我用它们分析的 html 源代码可以随时更改。

但请相信我:当你对他们有所了解时,他们甚至可以在写作和阅读方面取笑。:)

于 2008-09-29T21:57:33.347 回答
1

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

于 2008-09-29T21:37:13.707 回答
1

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

使用正确的工具完成工作还有很多要说的。在您在对原始帖子的评论中提到的示例中,正则表达式是用于解析 HTML 的错误工具,正如在 PerlMonks 上经常提到的那样。如果您尝试仅使用正则表达式以类似于一般方式的任何方式解析 HTML,那么您最终将要么以不正确且脆弱的方式进行解析,要么编写一个可怕且无法维护的正则表达式怪物,或者(很可能)两个都。

于 2008-09-29T21:54:08.533 回答
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。

于 2010-04-19T10:57:41.550 回答
0

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

于 2008-09-29T21:34:24.977 回答
0

正则表达式肯定被称为“只写”编程语言。但是,我不认为这意味着你应该避免它们。我只是认为您应该出于他们的意图发表评论。我通常不喜欢解释一行做什么的评论,我可以阅读代码,但正则表达式是个例外。评论一切!

于 2008-09-29T21:35:23.693 回答
0

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

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

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

于 2008-09-29T21:45:37.273 回答
0

我一直将这个问题视为一个构建块问题。

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

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

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

于 2008-10-10T21:02:04.677 回答
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))
于 2008-11-11T20:19:03.303 回答