什么因素使Perl代码易于维护?

23

我已经使用Perl写了几年的代码,它是我文本处理的首选语言(我处理的许多遗传/基因组问题都可以轻松转化为文本处理问题)。Perl作为一种语言可以非常宽容,你可以用Perl编写非常糟糕但功能正常的代码。就在前几天,我的朋友说他把Perl称为“只能写不能读”的语言:写一次,理解一次,完成后不要尝试回头修复。

虽然我有时候确实会写糟糕的脚本,但我觉得我也写过一些非常清晰和易于维护的Perl代码。然而,如果有人问我是什么让代码清晰和易于维护,我无法给出自信的答案。

什么让Perl代码易于维护?或者更好的问题是,什么让Perl代码难以维护?我们假设我不是唯一一个维护代码的人,其他贡献者像我一样不是专业的Perl程序员,而是具有编程经验的科学家。


作为相关的子问题:您应该在多大程度上使用/依赖于 $_?对我来说,当循环遍历数组并对项目进行正则表达式时,“foreach (@array) { if (/.../) { ...”是清晰明了的,但另一方面它非常适用于 Perl,如果不是写入,则只能读取。 - Joel Berger
(而不是 foreach my $item (@array) { if ($item =~ /.../) { ... ) - Joel Berger
1
也许我应该补充一下,我已经开始采用这样的策略:只要我不经常操纵 $,而是将其作为默认传递的变量使用,那么我就感到很舒适。一旦我需要开始对 $ 进行大量操作,那么它就应该被命名了。这听起来像一个明智的起点吗? - Joel Berger
现在有点晚了,但是这个问题最好放在程序员的网站上讨论。http://programmers.stackexchange.com/ - Orbling
1
@Joel:你的表达方式非常地道和合理。如果你必须经常使用 $_ 的名称,那么它应该被命名。否则,可能不需要。 - tchrist
8个回答

33
使Perl代码难以维护的因素与其他任何程序难以维护的因素基本相同。除了旨在执行明确定义任务的简短脚本之外,这些因素包括:
- 全局变量 - 缺乏关注点分离:单体脚本 - 不使用自说明标识符(变量名和方法名)。例如,您应该从变量名称中知道变量的用途。$c不好,$count更好,$token_count最好。 - 拼出标识符。程序大小不再是最重要的问题。 - 名为doWork的子例程或方法什么也没说。 - 使其易于从另一个包中找到符号的源。使用显式的包前缀,或通过use MyModule qw(导入列表)明确导入使用的每个符号。
- 特定于Perl: - 过度依赖快捷方式和晦涩的内置变量 - 滥用子例程原型 - 不使用strict和不使用warnings - 重新发明轮子而不是使用已建立的库 - 不使用一致的缩进风格 - 不使用水平和垂直空白来指导读者
等等等等。
基本上,如果你认为Perl就是-f>@+?*<.-&'_:$#/%!,并且希望在生产代码中编写这样的东西,那么,是的,你会遇到问题。
人们往往将Perl程序员做的有趣的事情(例如JAPHs、golf等)与良好的Perl程序应该看起来像混淆。
我仍然不清楚他们如何能够将为IOCCC编写的code与可维护的C分开。

3
对于“过度依赖简便方法和不常用的内置变量”,给予肯定。 - JSBձոգչ
1
完全同意FM关于“自说明标识符”的Perl无关概念。仅仅因为Perl本身有$|,这并不是创建$cnt或更好的$c而不是$count的借口,甚至在某些情况下,更好的语义精确的$character_count - DVK
1
另一个想法,虽然可能有争议,就是不要过度使用导入的符号,而是写出完整的包路径。虽然这会使代码变长,因此有时稍微难以阅读,但使用完整的包路径在使用大量模块时对于“这个标识符定义在哪里?”以及“我没有意识到它是来自My::Sort::Apples模块的‘sorta()’函数而不是Perl内置的‘sort()’”等方面非常有帮助。 - DVK
1
@DVK:你写成了$|,但我想你的意思是$. - tchrist
2
@DVK,如果变量名的上下文能够清晰地表明它们的用途,那么我认为使用短变量名是完全可以接受的。 - converter42
显示剩余6条评论

15
我建议:
  1. 不要在Perl中过于聪明。如果你开始用代码打高尔夫球,会导致更难阅读的代码。你编写的代码需要比聪明更易读和清晰。
  2. 文档化代码。如果是模块,添加POD描述典型用法和方法。如果是程序,请添加POD以描述命令行选项和典型用法。如果有复杂算法,请记录它并提供参考(URL)(如果可能)。
  3. 使用/…/x形式的正则表达式,并对其进行文档化。并非每个人都能很好地理解正则表达式。
  4. 了解耦合是什么,以及高/低耦合的利弊。
  5. 了解内聚是什么,以及高/低内聚的利弊。
  6. 适当使用模块。一个良好定义且包含完整概念的模块是一个很好的选择。重用这样的模块是目标。不要仅仅使用模块来减少单片程序的大小。
  7. 为您的代码编写单元测试。一个好的测试套件不仅可以帮助您证明您的代码今天可以工作,而且还可以帮助您证明它明天也可以工作。它还将让您在未来自信地进行更大胆的更改,而不会破坏旧应用程序。如果您破坏了东西,那么您的测试套件就不够全面。

但总的来说,你关心可维护性并问一个关于它的问题,告诉我你已经处于一个良好的状态并且想得到正确的方式。


13

我并不是所有Perl 最佳实践都使用,但这就是 Damian 写这本书的目的。无论我是否使用所有建议,它们都值得考虑。


9

什么因素能够使Perl代码易于维护?

至少需要考虑以下几点:

use strict;
use warnings;

请参考perlstyle,其中包含一些通用的指南,可以使您的程序更易于阅读、理解和维护。


这只是十步程序中的第零步,我认为。尽可能实现面向对象设计将比过程化方法更有助于可维护性 - 无论是否启用严格模式。(当然,我总是使用严格模式) - zanlok

5

在其他答案中没有提到的对于代码可读性非常重要的因素是空格的使用,这既与 Perl 无关,又在某些方面与 Perl 有关。

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

空格在我们谈论可读性时有很多含义和用途,其中并不都是广泛使用的,但大多数都很有用:

  • Spaces around tokens to easier separate them visually.

    This space is doubly important in Perl due to prevalence of line noise characters even in best-style Perl code.

    I find $myHashRef->{$keys1[$i]}{$keys3{$k}} to be less readable at 2am in the middle of producion emergency compared to spaced out: $myHashRef->{ $keys1[$i] }->{ $keys3{$k} }.

    As a side note, if you find your code doing a lot of deep nested reference expressions all starting with the same root, you should absolutely consider assigning that root into a temporary pointer (see Sinan's comment/answer).

    A partial but VERY important special case of this is of course regular expressions. The difference was illustrated to death in all the main materials I recall (PBP, RegEx O'Reilly book, etc..) so I won't lengthen this post even further unless someone requests examples in the comments.

  • Correct and uniform indentation. D'oh. Obviously. Yet I see way too much code 100% unreadable due to crappy indentation, and even less readable when half of the code was indented with TABs by a person whose editor used 4 character tabs and another by a person whose editor used 8 character TABs. Just set your bloody editor to do soft (e.g. space-emulated) TABs and don't make others miserable.

  • Empty lines around logically separate units of code (both blocks and just sets of lines). You can write a 10000 line Java program in 1000 lines of good Perl. Now don't feel like Benedict Arnold if you add 100-200 empty lines to those 1000 to make things more readable.

  • Splitting uber-long expressions into multiple lines, closely followed by...

  • Correct vertical alignment. Witness the difference between:

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

    and

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

    and

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

    Personally, I prefer to fix the vertical alignment one more step by aligning LHS and RHS (this is especially readable in case of long SQL queries but also in Perl code itself, both the long conditionals like this one as well as many lines of assignments and hash/array initializations):

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

    As a side note, in some cases the code could be made even more readable/maintainable by not having such long expressions in the first place. E.g. if the contents of the if(){} block is a return, then doing multiple if/unless statements each of which has a return block may be better.


1
除非你想要避免在第一次编写时使用那么多条件的if语句。例如,从子程序中早期返回应该可以帮助你解决这个问题。我对子程序中有多个返回感到满意。 - Sinan Ünür
关于深度哈希解引用,使用空格确实有帮助,但是将最深层次的解引用赋值给临时变量,避免多级哈希解引用超过一次也同样有帮助。我知道你已经了解这些,但对读者可能有用。如果你选择采纳这些评论,请提前告诉我,以便我进行清理。 - Sinan Ünür
而且,实际上,这应该是+0xC0DE用于空格! - Sinan Ünür
1
多行垂直对齐是帮助使代码更易于维护的一个很少被讨论的因素。它可以让相似之处淡化到背景中,以便突出差异。 - tchrist
@tchrist - 我发现它如此明显有用的部分原因是我的首选编辑器(UltraEdit)允许矩形选择和列编辑。因此,好处在于可读性和实际代码更改两方面。 - DVK
我完全同意可读性方面的观点...在这方面,perltidy是一个很好的工具。 - Zaid

4
我认为问题在于人们被告知Perl难以阅读,因此他们开始对自己代码的可维护性做出假设。如果你足够有良心,认为易读性是高质量代码的标志,那么这个批评可能并不适用于你。
大多数人在讨论易读性时会引用正则表达式。正则表达式是嵌入在Perl中的DSL,你可以读懂它们,也可以不读懂。如果有人连基本和许多语言都必不可少的东西都没有耐心去理解,我就不担心试图弥合某种推断出的认知差距......他们应该勇敢一点,阅读Perldoc,并在必要时提出问题。
其他人则会引用Perl使用短变量(如@_,$!等)的方式。所有这些都很容易区分......我不想让Perl看起来像Java。
所有这些怪癖和Perlisms的好处在于,用这种语言编写的代码库通常很简洁紧凑。我宁愿读十行Perl也不愿读一百行Java。
对我来说,“可维护性”远不止于仅有易读的代码。编写测试,进行断言......尽一切可能利用Perl及其生态系统使代码正确无误。
简而言之:先编写正确的程序,然后是安全的、性能良好的程序......一旦达到这些目标,再考虑使它看起来漂亮,像在火炉旁舒适地蜷缩。

4
@Brad - 你有在企业环境中开发 Perl 的经验吗?这意味着:(1)需要多人协作使用同一个代码库;(2)需要频繁维护代码;(3)维护代码时出现错误的代价非常高。请注意不要改变原意。 - DVK
3
在这种情况下,我会毫不犹豫地选择一段有些正确但不安全、性能不佳的代码,因为我可以轻松阅读并维护它,而不需要花费大量时间去理解它。这样,我可以用编写良好的代码来修复上述问题,成本较小,但修复编写不良代码的成本通常几乎等同于完全重写。 - DVK
1
你们都在说,好像我让你们把代码质量抛到脑后一样。但事实并非如此。我是在说要用 Perl 来写 Perl。好的 Perl 代码总是与其他语言不同,但人们阅读它时会有一个“Perl 的方式”的期望。如果这是 Lisp 或者 C,答案也是一样的——你不能读懂你不理解的东西,不要责怪作者。 - Brad Clawsie
1
@brad - 我们讨论的是根据你的建议编写“首先正确,然后安全,最后高效”的代码,但这种不可读的做法是不明智的,因为以后想要使代码可读性提升起来将是一项非常昂贵的任务,而在现实世界中几乎不会有资源用于此。这等于是将代码质量抛之脑后,无论你的措辞是否有意暗示这一点。 - DVK
1
@brad - 另外,重新阅读你的问题后,很抱歉,但你在这里提出了草人论证。极少数有头脑的人认为正则表达式或你引用的具体变量(@_,$!)是Perl代码难以阅读的主要原因,尽管注释过的正则表达式比未注释的正则表达式在代码可读性方面具有显著优势。简洁而写得好的Perl代码与使用糟糕风格的高尔夫代码之间存在重大差异,而这个问题显然是关于后者,而你却对前者进行了积极的辩护... - DVK
显示剩余5条评论

2

我认为包装/对象模型在.pm文件的目录结构中得到了体现。 在我的博士论文中,我编写了大量的Perl代码,后来进行了重复使用。 它是用于自动LaTeX图表生成器的。


1

我将谈论一些积极的事情,以使Perl易于维护。

确实,通常不应该使用非常密集的语句,比如return !$@;#%等等,但是在使用列表处理运算符(如mapgrep)时,适当地巧妙运用,并从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 - 良好的元编程使用可以使任务定义更加连贯和易读,只要您能够避免元编程本身成为干扰。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接