【问题标题】:What makes Perl code maintainable?是什么让 Perl 代码可维护?
【发布时间】:2011-05-19 22:43:52
【问题描述】:

我已经编写 Perl 好几年了,它是我首选的文本处理语言(我处理的许多遗传学/基因组学问题很容易归结为文本处理问题)。 Perl 作为一种语言可以是非常宽容的,并且可以用 Perl 编写非常糟糕但功能强大的代码。就在前几天,我的朋友说他称 Perl 是一种只写语言:编写一次,理解一次,完成后永远不要试图回去修复它。

虽然我有时确实为编写糟糕的脚本而感到内疚,但我觉得我也用 Perl 编写了一些非常清晰且可维护的代码。但是,如果有人问我是什么使代码清晰和可维护,我将无法给出自信的答案。

是什么让 Perl 代码可维护?或者更好的问题是是什么让 Perl 代码难以维护?假设我不是唯一会维护代码的人,并且其他贡献者,比如我,不是专业的 Perl 程序员,而是具有编程经验的科学家。

【问题讨论】:

  • 作为一个相关的子问题:您应该在多大程度上使用/依赖 $_ ?在我看来很明显,虽然循环遍历一个数组然后正则表达式 foreach (@array) { if (/.../) { ... 的项目是明确的,但如果不是只写的话,它也是非常仅限 Perl 的。
  • (而不是foreach my $item (@array) { if ($item =~ /.../) { ...
  • 也许我应该补充一点,我已经开始采用一种策略,只要我发现自己不经常操纵 $_,而只是将其作为默认值传递,我就会感到舒服.一旦我需要开始对 $_ 做很多事情,那么它应该被命名。这听起来像是一个合理的起点吗?
  • 现在有点晚了,但这整个问题对程序员来说会更好。 programmers.stackexchange.com
  • @Joel:如果您的表达完全地道且合理的话。如果你必须经常使用$_ 的名字,那么是的,它应该被命名。否则,可能不会。

标签: perl maintainability


【解决方案1】:

是什么让 Perl 代码无法维护?几乎任何使任何其他程序都无法维护的东西。假设除了用于执行明确定义的任务的简短脚本之外的任何内容,这些是:

  • 全局变量
  • 缺乏关注点分离:单体脚本
  • 不使用自记录标识符(变量名和方法名)。例如。您应该从变量的名称中知道变量的用途。 $c 不好。 $count 更好。 $token_count 好。
    • 拼出标识符。程序规模不再是最重要的问题。
    • 名为doWork 的子例程或方法没有任何说明
    • 便于从另一个包中查找符号源。要么使用显式包前缀,要么显式导入通过 use MyModule qw(list of imports) 使用的每个符号。
  • Perl 特定:
    • 过度依赖快捷方式和晦涩的内置变量
    • 滥用子程序原型
    • 不使用strict 也不使用warnings
  • 重新发明轮子,而不是使用已建立的库
  • 未使用一致的缩进样式
  • 不使用水平和垂直空白来引导读者

等等等等等等

基本上,如果您认为 Perl 是 -f>@+?*<.-&'_:$#/%!,并且您渴望在生产代码中编写类似的东西,那么,是的,您会遇到问题。

人们往往会将 Perl 程序员为了好玩而做的事情(例如,JAPH、高尔夫等)与优秀的 Perl 程序应该是什么样子混为一谈。

我仍然不清楚他们如何能够将为 IOCCC 编写的 code 与可维护的 C 区分开来。

【讨论】:

  • +1 表示“过度依赖快捷方式和晦涩的内置变量”。
  • +1 避免内置变量是“使用命名变量”的另一种说法,这导致了另一个可维护性的关键——特别是,为变量和方法使用良好的、自记录的名称。编程是一种写作形式,选词很关键。
  • 完全同意 FM re:与 Perl 无关的“自文档标识符”概念。仅仅因为 Perl 本身有 $|,它不是创建 $cnt 或更好的 $c 而不是 $count 或在某些情况下更语义精确的 $character_count 的借口
  • 另一个想法,虽然可能有争议,但不要过度使用导入的符号,而不是写出完整的包路径。虽然使代码更长,因此有时可读性稍差,但使用完整的包路径在“这个标识符到底是在哪里定义的?”方面有很大帮助。当使用大量模块以及“我没有意识到它是来自 My::Sort::Apples 模块的“sorta()”函数而不是 Perl 内置的“sort()”时
  • @DVK 我发现短变量名完全可以接受,如果它们的上下文使它们的目的显而易见。
【解决方案2】:

我建议:

  1. 不要太聪明地使用 Perl。如果您开始使用代码打高尔夫球,则会导致代码难以阅读。您编写的代码需要可读性和清晰性,而不是聪明。
  2. 记录代码。如果是模块,请添加描述典型用法和方法的 POD。如果是程序,请添加 POD 来描述命令行选项和典型用法。如果有一个复杂的算法,请将其记录下来并尽可能提供参考 (URL)。
  3. 使用 /.../x 形式的正则表达式,并记录它们。不是每个人都能很好地理解正则表达式。
  4. 了解什么是耦合,以及高/低耦合的优缺点。
  5. 了解什么是内聚,以及高/低内聚的优缺点。
  6. 适当地使用模块。一个定义明确、包含良好的概念是一个很棒的模块。目标是重用这些模块。不要仅仅为了减小单体程序的大小而使用模块。
  7. 为您的代码编写单元测试。一个好的测试套件不仅可以让你证明你的代码今天可以工作,而且明天也可以。它还可以让您在未来做出更大胆的改变,并确信您不会破坏旧的应用程序。如果您确实破坏了某些东西,那么,您的测试套件还不够广泛。

但总体而言,您足够关心可维护性并提出问题这一事实告诉我,您已经处于一个好的位置并且正在思考正确的方式。

【讨论】:

    【解决方案3】:

    我没有使用所有Perl Best Practices,但这是达米安写它的目的。无论我是否使用所有建议,它们都值得至少考虑。

    【讨论】:

      【解决方案4】:

      是什么让 Perl 代码可维护?

      至少:

      use strict;
      use warnings;
      

      请参阅perldoc perlstyle,了解一些使您的程序更易于阅读、理解和维护的一般指南。

      【讨论】:

      • 这只是十步程序中的零步,imo。实现尽可能多的 OO 设计将使您在可维护性方面比过程方法更进一步——无论是否严格。 (当然我总是使用它)
      【解决方案5】:

      我在其他答案中没有提到的对代码可读性非常重要的一个因素是空格的重要性,它与 Perl 无关,并且在某些方面是 Perl 特定的。

      Perl 允许您编写非常简洁的代码,但简洁的块并不意味着它们必须全部捆绑在一起。

      当我们谈论可读性时,空白有很多含义/用途,并非所有这些都被广泛使用但最有用:

      • 标记周围的空格更容易在视觉上将它们分开。

        这个空格在 Perl 中具有双重重要性,因为即使在最佳风格的 Perl 代码中也普遍存在行噪声字符。

        我发现$myHashRef->{$keys1[$i]}{$keys3{$k}} 在生产紧急情况中的凌晨 2 点与间隔时间相比不太可读: $myHashRef->{ $keys1[$i] }->{ $keys3{$k} }

        附带说明一下,如果您发现您的代码执行大量深度嵌套的引用表达式都以同一个根开头,那么您绝对应该考虑将该根分配给一个临时指针(参见 Sinan 的评论/答案)。

        这方面的一个部分但非常重要的特殊情况当然是正则表达式。我记得的所有主要材料(PBP、RegEx O'Reilly 书等)都已经说明了这种差异,所以除非有人要求 cmets 中的示例,否则我不会进一步延长这篇文章。

      • 正确和统一的缩进。哦。明显地。然而,由于糟糕的缩进,我看到太多代码 100% 不可读,而且当一半代码由一个编辑器使用 4 个字符选项卡的人使用 TAB 缩进,另一个由一个编辑器使用 8 个字符 TAB 的人缩进时,可读性更差。只需将你的编辑器设置为软(例如空间模拟)TAB,不要让其他人痛苦。

      • 逻辑上独立的代码单元(块和行集)周围的空行。你可以用 1000 行好的 Perl 编写一个 10000 行的 Java 程序。现在,如果您在这 1000 行中添加 100-200 条空行以使内容更具可读性,那么现在就不会觉得像 Benedict Arnold。

      • 将超长表达式拆分为多行,紧接着...

      • 正确的垂直对齐方式。见证两者之间的区别:

        if ($some_variable > 11 && ($some_other_bigexpression < $another_variable || $my_flag eq "Y") && $this_is_too_bloody_wide == 1 && $ace > my_func() && $another_answer == 42 && $pi == 3) {
        

        if ($some_variable > 11 && ($some_other_bigexpression < $another_variable || 
            $my_flag eq "Y") && $this_is_too_bloody_wide == 1 && $ace > my_func()
            && $another_answer == 42 && $pi == 3) {
        

        if (   $some_variable > 11
            && ($some_other_bigexpression < $another_variable || $my_flag eq "Y")
            && $this_is_too_bloody_wide == 1
            && $ace > my_func()
            && $another_answer == 42
            && $pi == 3) {
        

        就个人而言,我更喜欢通过对齐 LHS 和 RHS 来进一步修复垂直对齐(这在长 SQL 查询的情况下尤其可读,但在 Perl 代码本身中,无论是像这样的长条件还是多行分配和哈希/数组初始化):

        if (   $some_variable               >  11
            && ($some_other_bigexpression   <  $another_variable || $my_flag eq "Y")
            && $this_is_too_bloody_wide    ==  1
            && $ace                         >  my_func()
            && $another_answer             ==  42
            && $pi                         ==  3  ) {
        

        附带说明一下,在某些情况下,如果一开始就没有这么长的表达式,可以使代码更具可读性/可维护性。例如。如果if(){} 块的内容是return,那么执行多个if/unless 语句,每个语句都有一个返回块可能会更好。

      【讨论】:

      • +1 除了您可能希望避免首先编写具有这么多条件的 if 语句。例如,从子例程中提前返回应该可以帮助您。我对子程序中的多次返回感到满意。
      • 另外,对于深度散列解引用,是的,空格会有所帮助,但是通过将要解引用的最深级别分配给临时变量来多次进行多级散列解引用也是如此。我知道你知道这一切,但它可能对读者有用。如果您选择合并这些 cmets,请提醒我,以便我进行清理。
      • 而且,真的,这应该是 +0xC0DE 用于空格!
      • 多行垂直对齐是一个很少被讨论的因素,它有助于使代码更易于维护。它允许相似之处在背景中淡化,因此差异突出。
      • @tchrist - 我发现它非常有用的部分原因是我选择的编辑器 (UltraEdit) 允许矩形选择和列编辑。所以好处在于可读性和实际代码更改。
      【解决方案6】:

      我认为这是人们被告知 perl 不可读的问题,他们开始对自己代码的可维护性做出假设。如果你足够认真地认为可读性是高质量代码的标志,那么这个批评很可能不适用于你。

      大多数人在讨论可读性时会引用正则表达式。正则表达式是嵌入在 perl 中的 dsl,您可以阅读或不阅读它们。如果有人不能花时间去理解对许多语言来说如此基本和必不可少的东西,我不担心试图弥合一些推断的认知差距……他们应该站起来,阅读 perldocs,并在必要时提出问题.

      其他人会引用 perl 对短格式变量的使用,例如 @_、$!等等这些都很容易消除歧义...我对让 perl 看起来像 java 不感兴趣。

      所有这些怪癖的好处是,用该语言编写的代码库通常简洁紧凑。我宁愿读十行 perl 而不是读一百行 java。

      对我来说,“可维护性”不仅仅是拥有易于阅读的代码。编写测试,做出断言……尽你所能依靠 perl 及其生态系统来保持代码正确。

      简而言之:编写程序首先是正确的,然后是安全的,然后是性能良好的......一旦这些目标都实现了,然后担心在火附近蜷缩起来很舒服。

      【讨论】:

      • @Brad - 你有在企业环境中开发 perl 的经验吗? (这意味着(1)许多人协作到同一个代码库;(2)代码得到大量维护;(3)在维护代码时破坏东西的成本非常高)
      • @Brad - 在这样的环境中,我很容易采用一个有点正确、不安全、性能不佳的代码,我可以轻松阅读并因此维护而无需花费半生去探索它,因为这样我就可以以很小的成本用编写良好的代码修复上述所有问题,但修复编写不佳的代码的成本通常几乎等同于完全重写
      • 你们说的好像我在告诉你们把代码质量抛诸脑后。我不是。我是说使用 perl 作为 perl。好的 perl 的阅读方式总是与其他语言不同,但人们期望阅读它的人知道“perl 方式”。拜托,如果是这个 lisp 甚至 C,答案都是一样的——看不懂的东西你看不懂,别怪作者。
      • @brad - 我们正在谈论的是遵循您的建议并编写不可读的“首先正确,然后安全,然后性能良好”的代码是一个坏主意,因为以后使该代码可读on 是一个非常昂贵的提议,在现实世界中永远不会分配资源。无论您的措辞是否有意暗示,这都会有效地将代码质量抛诸脑后。
      • @brad - 同样,在重新阅读您的问题后,对不起,但您在这里提出了稻草人的论点。很少有人知道正则表达式或您引用的特定变量(@_,$!)是 Perl 代码不可读的主要原因,尽管注释正则表达式在代码可读性方面优于未注释正则表达式。使用完整 Perl 功能的简洁编写良好的 Perl 代码和使用糟糕风格的高尔夫代码之间存在重大区别,这个问题显然是关于后者,而您为前者提供了激烈的辩护......
      【解决方案7】:

      我会说打包/对象模型,它反映在 .pm 文件的目录结构中。在攻读博士学位时,我写了很多 Perl 代码,之后我会重复使用这些代码。它用于自动 LaTeX 图表生成器。

      【讨论】:

        【解决方案8】:

        我会讲一些积极的的事情来使 Perl 可维护。

        确实,您通常不应该对非常密集的语句(如 return !$@;#% 等)过于聪明,但使用列表处理运算符(如 mapgrep 和 list-从 split 和类似运算符之类的上下文返回,以便以函数式样式编写代码可以对可维护性做出积极贡献。在我的上一个雇主,我们也有一些时髦的哈希操作函数,它们以类似的方式工作(hashmaphashgrep,虽然从技术上讲,我们只为它们提供了偶数大小的列表)。例如:

        # Look for all the servers, and return them in a pipe-separated string
        # (because we want this for some lame reason or another)
        return join '|', 
               sort
               hashmap {$a =~ /^server_/ ? $b : +()} 
               %configuration_hash;
        

        另请参阅Higher Order Perlhttp://hop.perl.plover.com - 如果您可以避免元编程本身妨碍,那么充分利用元编程可以使定义任务更加连贯和可读。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-06-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-07
          • 2023-03-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多