我见过的任何使用正则表达式的代码都倾向于将它们用作黑匣子:
- 放入字符串
- 魔术正则表达式
- 拿出字符串
在生产代码中使用这似乎不是一个特别好的主意,因为即使是很小的更改通常也会导致完全不同的正则表达式。
除了标准是永久不变的情况外,正则表达式是做事的方式,还是尝试不同的方法更好?
我见过的任何使用正则表达式的代码都倾向于将它们用作黑匣子:
在生产代码中使用这似乎不是一个特别好的主意,因为即使是很小的更改通常也会导致完全不同的正则表达式。
除了标准是永久不变的情况外,正则表达式是做事的方式,还是尝试不同的方法更好?
如果正则表达式冗长且难以理解,使它们难以维护,那么应该对它们进行评论。
许多正则表达式实现允许您用空格和注释填充正则表达式。
请参阅https://www.regular-expressions.info/freespacing.html#parenscomment
和编码恐怖:正则表达式:现在你有两个问题
我见过的任何使用正则表达式的代码都倾向于将它们用作黑匣子:
如果黑盒是指抽象,那就是所有编程,试图抽象出困难的部分(解析字符串),以便您可以专注于问题域(我想匹配什么样的字符串)。
即使是很小的变化也常常会导致完全不同的正则表达式。
任何代码都是如此。只要您正在测试您的正则表达式以确保它与您期望的字符串匹配,理想情况下使用单元测试,那么您应该有信心更改它们。
编辑:还请阅读 Jeff 对此答案关于生产代码的评论。
它真的归结为正则表达式。如果是这个巨大的单体表达式,那么是的,这是一个可维护性问题。如果你能简洁地表达它们(也许通过分解它们),或者如果你有很好的评论和工具来帮助你理解它们,那么它们可以成为一个强大的工具。
我不知道您使用的是哪种语言,但 Perl - 例如 - 支持该x
标志,因此除非转义,否则正则表达式中的空格将被忽略,因此您可以将其分成几行并内联注释所有内容:
$foo =~ m{
(some-thing) # matches something
\s* # matches any amount of spaces
(match another thing) # matches something else
}x;
这有助于使长正则表达式更具可读性。
如果您不了解正则表达式,这似乎只是魔术。生产代码中的任何微小更改都可能导致重大问题,因此在我看来,这不是不使用正则表达式的好理由。彻底的测试应该指出任何问题。
对任何语言的任何代码的微小更改都可能导致完全不同的结果。其中一些甚至阻止编译。
用“C”或“C#”或“Java”或“Python”或“Perl”或“SQL”或“Ruby”或“awk”或......替换正则表达式,真的,你会得到同样的问题。
正则表达式只是另一种语言,霍夫曼编码以有效地进行字符串匹配。就像 Java、Perl、PHP 或特别是 SQL 一样,每种语言都有优点和缺点,并且您需要在编写(或维护)它时了解您正在编写的语言,以便有希望提高生产力。
编辑:迈克,正则表达式是霍夫曼编码的,常见的事情比稀有的事情要短。文本的文字匹配通常是单个字符(您要匹配的字符)。存在特殊字符 - 常见字符很短。特殊结构,例如 (?:) 更长。这些与 Perl、C++ 等通用语言中常见的东西不同,因此 Huffman 编码针对的是这种专业化。
复杂的正则表达式对我来说是一劳永逸的。编写它,测试它,当它工作时,写一个评论它做了什么,我们很好。
但是,在许多情况下,您可以将正则表达式分解为更小的部分,也许可以编写一些文档齐全的代码来组合这些正则表达式。但是如果你在你的代码中发现了一个多行的正则表达式,你最好不要去维护它:)
听起来很熟悉?任何代码或多或少都是如此。你不想有很长的方法,你不想有很长的类,你也不想有很长的正则表达式,尽管方法和类更容易重构。但本质上,它是同一个概念。
正则表达式不是做某事的唯一方法。您可以在代码中逻辑地执行正则表达式可以执行的所有操作。正则表达式只是
如果您利用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
关于正则表达式的名言:
“有些人在遇到问题时会想‘我知道,我会使用正则表达式。现在他们有两个问题。” ——杰米·扎温斯基
当我确实使用正则表达式时,我发现它们是可维护的,但它们在特殊情况下使用。通常有一个更好的、非正则表达式的方法来做几乎所有的事情。
当有意识地使用正则表达式时,它是一种强大的机制,可以让您免于可能的文本解析行和行。当然,它们应该被正确记录并有效地跟踪,以验证初始假设是否仍然有效,并相应地更新它们。关于维护恕我直言,最好更改一行代码(正则表达式模式),而不是理解解析代码的行和行或任何正则表达式的目的。
正则表达式是做事的方式吗?这取决于任务。
与所有编程事物一样,没有固定的正确或错误答案。
如果正则表达式快速简单地解决特定任务,那么它可能比更详细的解决方案更好。
如果一个正则表达式试图完成一项复杂的任务,那么更冗长的东西可能更容易理解并因此维护。
有很多可能性可以使 RegEx 更易于维护。最后,它只是(好的?)程序员在进行重大(或有时甚至是次要)更改时必须学习的技术。当没有一些真正优秀的专业人士时,没有人会因为它们复杂的语法而打扰他们。但它们在工作中快速、紧凑且非常灵活。
对于 .NET 人来说,可能会有“ Linq to RegEx ”库看起来更糟或“可读正则表达式库”。它使它们更易于维护且更易于编写。我在自己的项目中使用了它们,我知道我用它们分析的 html 源代码可以随时更改。
但请相信我:当你对他们有所了解时,他们甚至可以在写作和阅读方面取笑。:)
我有一个彻底评论非平凡正则表达式的政策。这意味着描述和证明每个与自身不匹配的原子。某些语言(例如 Python)提供忽略空格并允许注释的“冗长”正则表达式;尽可能使用它。否则,在正则表达式上方的评论中逐个原子地进行。
问题不在于正则表达式本身,而在于它们被视为黑匣子。与任何编程语言一样,可维护性更多地与编写它的人和阅读它的人有关,而不是与语言本身有关。
使用正确的工具完成工作还有很多要说的。在您在对原始帖子的评论中提到的示例中,正则表达式是用于解析 HTML 的错误工具,正如在 PerlMonks 上经常提到的那样。如果您尝试仅使用正则表达式以类似于一般方式的任何方式解析 HTML,那么您最终将要么以不正确且脆弱的方式进行解析,要么编写一个可怕且无法维护的正则表达式怪物,或者(很可能)两个都。
您的问题似乎与正则表达式本身无关,而仅与通常用于表达正则表达式的语法有关。在许多核心程序员中,这种语法已经被认为非常简洁和强大,但对于更长的正则表达式,它实际上是不可读和不可维护的。
有些人已经在 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。
我在我的应用程序中使用它们,但我将实际的 regEx 表达式保留在配置文件中,因此如果我正在解析的源文本(例如电子邮件)由于某种原因更改格式,我可以快速更新配置以处理更改而无需重新构建应用程序。
正则表达式肯定被称为“只写”编程语言。但是,我不认为这意味着你应该避免它们。我只是认为您应该出于他们的意图发表评论。我通常不喜欢解释一行做什么的评论,我可以阅读代码,但正则表达式是个例外。评论一切!
我通常会编写扫描仪规范文件。扫描仪或“扫描仪生成器”本质上是优化的文本解析器。由于我通常使用 Java,因此我首选的方法是 JFlex ( http://www.jflex.de ),但也有 Lex、YACC 和其他几种方法。
扫描程序处理可以定义为宏的正则表达式。然后当正则表达式匹配部分文本时实现回调。
当涉及到代码时,我有一个包含所有解析逻辑的规范文件。我通过选择的扫描仪生成器工具运行它,以选择语言生成源代码。然后我只是将所有这些包装到解析器函数或某种类中。这种抽象使得管理所有的正则表达式逻辑变得很容易,并且性能非常好。当然,如果你只使用一两个正则表达式,那就太过分了,而且至少需要 2-3 天的时间来了解到底发生了什么,但如果你曾经使用过 5 或 6 或 30 个其中,它成为一个非常好的特性,实现解析逻辑开始只需几分钟,并且它们易于维护和记录。
我一直将这个问题视为一个构建块问题。
你不只是写一些 3000 个字符的正则表达式并希望最好。你写了一堆你加在一起的小块。
例如,要匹配一个 URI,你有协议、权限、子域、域、tld、路径、参数(至少)。其中一些是可选的!
我相信你可以编写一个怪物来处理它,但是编写块并将它们加在一起更容易。
我通常将正则表达式拆分为带有注释的部分,然后将它们全部放在一起进行最后的推动。片段可以是子字符串或数组元素
两个 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))