我应该重构主要由一个正则表达式组成的大型函数吗?[关闭]


15

我刚刚编写了一个跨越约100行的函数。听到这个消息,您可能很想告诉我有关单一责任的事,并敦促我进行重构。这也是我的直觉,但这是问题所在:函数一件事。它执行复杂的字符串操作,并且函数主体主要由一个详细的正则表达式组成,分为许多行,并记录在案。如果我将正则表达式分解为多个功能,我会感觉实际上会失去可读性,因为我正在有效地切换语言,并且无法利用正则表达式提供的某些功能。现在是我的问题:

在使用正则表达式进行字符串操作时,大型函数体是否仍然是反模式?似乎命名捕获组的作用与功能非常相似。顺便说一下,我对通过正则表达式的每个流进行测试。


3
考虑到文档很大一部分,我认为您的功能没有任何问题。但是,首先使用大的正则表达式可能会存在可维护性问题。
乔尔·科内特

2
您确定大型正则表达式是解决问题的最佳解决方案吗?您是否考虑过更简单的选择,例如解析器库或将自定义文件格式替换为标准格式(XML,JSON等)?
lortabac 2014年

2
使用此正则表达式的更改/增强/简化版本还有其他功能吗?那将是重构应该发生的重要指示。如果没有,我也照原样保留。像这样需要一个复杂的字符串操作本身就是一个黄色标志(嗯,我不知道上下文,因此只有黄色),向下重构该函数在我看来更像是一种赎罪的仪式它;)
Konrad Morawski 2014年

8
100行正则表达式只能做1件事?
Pieter B

@lortabac:输入是用户生成的文本(散文)
DudeOnRock 2014年

Answers:


36

您正在遇到的是认知失调,这种失调来自于听取那些以“最佳实践”为幌子,偏执于理性地做出决策的人们的信奉。

您显然已经完成了作业:

  • 该功能的目的已被理解。
  • 其实现的工作方式是可以理解的(即可读的)。
  • 有完整的实施测试。
  • 这些测试通过了,这意味着您相信实现是正确的。

如果以上几点都不成立,我将首先说您的功能需要工作。因此,对于将代码保持原样,只有一票赞成。

第二票来自查看您的选择以及您从每种选择中获得(或失去)的结果:

  • 重构。这样可以使您遵守某人对功能应该持续多长时间的想法,并且会牺牲可读性。
  • 没做什么。 这样可以保持现有的可读性,并且无法满足某人对功能应该持续多长时间的想法。

这个决定取决于您对它的重视程度:可读性或长度。我陷入了一个阵营,认为长度长,但可读性很重要,并且在任何一天的工作中都比后者更重要

底线:如果没有损坏,请不要修复。


10
+1表示“如果它没有损坏,请不要修复它”。
Giorgio

确实。桑迪·梅斯(Sandy Metz)的规则(gist.github.com/henrik/4509394)很好,但在youtube.com/watch?v=VO-NvnZfMA4#t=1379上,她谈到了规则的产生以及人们为什么要服用他们太认真了。
阿玛丹2014年

@Amdan:借助视频中的额外上下文,梅斯所做的事情很有道理。她向一位客户提出的建议在一端是极端的,以应对另一端的极端行为,以将其拖到更合理的中间。剩下的讨论归结为我的回答的重点:推理而非信念是确定最佳行动方案的方式。
Blrfl 2014年

19

老实说,您的函数可能会“做一件事”,但正如您自己所说的那样

我可以开始将正则表达式分解为多个功能,

这意味着您的reg ex代码可以完成很多事情。而且我想它可能会分解成较小的,可单独测试的单元。但是,如果这是一个好主意,则不容易回答(特别是在没有看到实际代码的情况下)。正确的答案可能不是“是”或“否”,而是“还没有,但是下次您必须在该reg exp中进行一些更改”。

但由于我实际上是在切换语言,因此我觉得这样实际上会失去可读性

这就是核心点-您有一段用reg ex语言编写的代码。这种语言本身并没有提供任何好的抽象方法(我不认为“命名捕获组”可以替代功能)。因此,“以正则表达式语言”重构实际上是不可能的,并且将较小的reg exp与宿主语言交织实际上并不能提高可读性(至少,您这种感觉,但您对此表示怀疑,否则您将不会提出这个问题) 。所以这是我的建议

  • 向其他高级开发人员展示您的代码(也许在/codereview//上),以确保其他人以您的方式考虑可读性。对其他人可能找不到像您这样可读的100行reg exp持开放态度。有时,仅用第二双眼睛就可以克服“不易分解成小块”的概念。

  • 观察实际的可扩展性-当新的需求到达并且您必须实施和测试它们时,您闪亮的reg exp看起来仍然很好吗?只要您的reg exp有效,我就不会碰它,但是每当需要更改某些内容时,我都会重新考虑将所有内容都放入这个大块中是否真的是一个好主意-并且(认真地!)重新考虑是否拆分为较小的部分将不是更好的选择。

  • 观察可维护性-您能很好地有效调试当前形式的reg exp吗?尤其是在您必须更改某些内容之后,现在您的测试告诉您某些错误之后,您是否拥有一个reg exp调试器来帮助您找到根本原因?如果调试困难,那也将是重新考虑设计的机会。


我会说命名捕获组(实际上,通常是捕获组)与最终/一次写入变量或宏最相似。它们允许您从正则表达式处理器返回的match对象或正则表达式本身后面引用匹配的特定部分。
JAB 2014年

4

有时,较长的功能只能执行一项操作,这是处理工作单元的最合适方法。当您开始处理查询数据库时(使用您喜欢的查询语言),可以轻松地使用很长的函数。在使功能(或方法)仅限于其既定目的的同时,使功能(或方法)更具可读性是我认为最理想的功能结果。

当涉及到代码大小时,长度是一个任意的“标准”。在C#中100行函数可能被认为很长的情况下,在某些版本的汇编中它很小。我已经看到了一些SQL查询,它们很好地进入了200行代码范围,它们为报表返回了一组非常复杂的数据。

完全有效的代码,就是您可以合理地使之简单而成为目标。

不要仅仅因为它长而改变它。


3

您总是可以将正则表达式分解为子正则表达式,然后逐步组成最终表达式。这可能有助于理解很大的图案,尤其是在同一子图案重复多次的情况下。例如在Perl中;

my $start_re = qr/(?:\w+\.\w+)/;
my $middle_re = qr/(?:DOG)|(?:CAT)/;
my $end_re = qr/ => \d+/;

my $final_re = $start_re . $middle_re . $end_re;
# or: 
# my $final_re = qr/${start_re}${middle_re}${end_re}/

我使用详细标志,它比您建议的更加方便。
DudeOnRock 2014年

1

我要说的是打破它,如果它是易碎的。从可维护性和可能的​​可恢复性的角度来看,打破它是有意义的,但是当然,您必须考虑功能的自然性,输入的方式以及输入的返回值。

我记得我当时正在将流式分块数据解析为对象,所以我基本上将其分为两个主要部分,一个是从编码文本中构建完整的String单元,第二部分将这些单元解析为数据字典并组织它们(可能是不同对象的随机属性),而不是更新或创建对象。

另外,我可以将每个主要部分分解为几个更小,更具体的功能,因此最终我有5个不同的功能来完成整个工作,并且可以在不同的地方重用某些功能。


1

您可能考虑或未考虑的一件事是以您使用的语言编写一个小型解析器,而不是使用该语言的正则表达式。这可能更易于阅读,测试和维护。


我自己已经考虑过了。问题在于输入内容是散文,我从上下文和格式中汲取了一些线索。如果有可能为这样的事情编写一个解析器,我想了解更多有关它的信息!我自己找不到任何东西。
DudeOnRock 2014年

1
如果正则表达式可以解析它,则可以解析它。您的回答使我觉得您可能不精通解析。如果是这种情况,您可能要坚持使用正则表达式。要么学习新技能,要么。
Thomas Eding 2014年

我很想学习一种新技能。有什么好的资源可以建议吗?我也对它背后的理论感兴趣。
DudeOnRock 2014年

1

在大多数情况下,巨型正则表达式是一个错误的选择。以我的经验,由于开发人员不熟悉解析,所以经常使用它们(请参阅Thomas Eding的答案)。

无论如何,假设您要坚持使用基于正则表达式的解决方案。

由于我不知道实际的代码,因此我将研究两种可能的情况:

  • 正则表达式很简单(很多文字匹配和很少的选择)

    在这种情况下,单个正则表达式提供的高级功能并不是必不可少的。这意味着您可能会从拆分中受益。

  • 正则表达式很复杂(很多替代方法)

    在这种情况下,您实际上不可能拥有完整的测试范围,因为您可能有数百万个可能的流量。因此,为了对其进行测试,您需要对其进行拆分。

我可能缺乏想象力,但我无法想到在任何现实情况下100行正则表达式都是一个很好的解决方案。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.