Perl风格:各有所爱

来源:互联网 发布:长歌正太捏脸数据 编辑:程序博客网 时间:2024/06/06 16:40



*风格问题很容易变成信仰问题。
*我要告诉你们的绝大部分是个人意见。其中既有泛泛之论,也有指路明灯。
*警告:我不一定总是按自己说的做!:)
*我并不期望你们永远和我保持一致。选择一种风格,坚持下去。连贯性才是最重要的!
*K&P, K&R, S&W, Rob Pike, 和Larry Wall的基础性工作对本文有间接贡献。Jon Orwant, 
Mark-Jason Dominus, 和Nat Torkington则直接参与了本文草稿的审阅。
*Rob Pike说:“无论如何不要因为我说怎样编程你就怎样编程。如何编程取决于你对程序目的和
如何表现这一目的的认识。照这样坚持不懈、一丝不苟地做下去。”


Perl风格:写Perl程序,不是C/BASIC/Java/Pascal等等


*<<Perl编程>>中说:“仅仅因为你能用某种方式做事并不意味着你就应该用这种方式做事。”
*当你发现自己写的代码看上去象C,或BASIC,或JAVA,或PASCAL,你很可能正在欺骗自己。你
需要学习写符合习惯的Perl -- 这不是说写晦涩的Perl程序。这说的是写有Perl特色的Perl程序:
原汁Perl。
*有人说某些Perl习惯应该避免,否则负责维护代码的人如果不懂Perl就理解不了程序。这实属大
谬,可笑已极。如果不懂Perl就别维护Perl程序。你写英语的时候不会考虑法国人、德国人懂不懂,
写希腊语的时候也不会用拉丁文。


Perl风格:大方


*Dennis Ritchie说:“要做到既正确(紧凑、无误)又可用(统一、有吸引力),难。”
*写程序要努力做到有用,精练,灵活,易懂 -- 不一定是按这个顺序。
*某些情况下把程序写得短一些可以改善可维护性,另一些情况下就不行。


Perl风格:谨慎编程


*use strict
*#!/usr/bin/perl -w
*检查所有系统调用的返回值,打印$!
*用$?检查外部程序的失败。
*在eval或s///ee之后检查$@。
*参数声明。
*#!/usr/bin/perl -T
*在一串elsif后一定要加一个else
*在列表的最后放个逗号,这样别人在列表后面再加点东西的时候就不会出错。


Perl风格:注释艺术


*解释代码的作用,而不只是把代码翻成英语(汉语)。
*不要弄成花哨的招牌式的东西。
*用/x在正则表达式里加上注解。
*给整段代码加注,而不是给单句加注。
*Rob Pike说:“给数据加注比给算法加注通常要有用得多。”
*Rob Pike说:“基本避免注解。如果你的程序需要注解才能让人理解,最好重写,让它更好懂一些。”


Perl风格:命名原则(形式)


*Rob Pike说:“我不会在名字里放大写字母。对我看惯了散文的双眼来说它们看上去很不舒服,就象是
些拼写错误一样晃来晃去。”
*`IEschewEmbeddedCapitalLettersInNames ToMyProseOrientedEyes 
TheyAreTooAwkwardToReadComfortably TheyJangleLikeBadTypography.' (译注:这是重复上
句的原文,用原文所反对的风格写出的,看上去很难受吧!)
*虽然把两三个短词放在一起做名字比如$getit大概没事,最好还是用下划线把单词分隔开。一般说来
$var_names_like_this比$VarNamesLikeThis要好读,特别是对于不说英语的人。这条简单的规则和
VAR_NAMES_LIKE_THIS这样的变量名也能合作愉快。
*用变量名的大小写表示变量的范围和性质会很有帮助。例:


   $ALL_CAPS_HERE   只用于常量(小心和Perl的内在变量名冲突!)
   $Some_Caps_Here  全局变量、静态变量
   $no_caps_here    函数范围的 my() 或 local() 变量
*函数或方法名称最好全用小写,比如:$obj->as_string();


Perl风格:命名原则(内容)


*Rob Pike说:“过程的名称应该反映它做了什么,函数的名称应该反映它返回什么。”
*对象命名应使其阅读顺利。比如,谓语性质的函数通常用"is", "does", "can", "has"命名。所以,
$is_ready是比$ready更好的函数名。
*如上所述,&cannonize是过程名,&canonical_version表示一个返回值的函数,而&is_canonical
做一个布尔量检查。
*象&abc2xyz或&abc_to_xyz这样形式的名称为转换函数或哈希表对映函数所普遍采用。
*哈希表通常表示键的性质,代表“某物所拥有的”这样一个概念。所以为哈希表中的值来命名哈希,而不是
为键来命名。


   好例:
        %color = ('apple' => 'red', 'banana' => 'yellow');
        print $color{'apple'};          # Prints `red'


   坏例:
        %fruit = ('apple' => 'red', 'banana' => 'yellow');
        print $fruit{'apple'};          # Prints `red'


Perl风格:变量名长度


*Mark-Jason Dominus说:“合适的变量名长度与其作用范围的大小成反比。”
*变量名长不是优点,清楚才是。不要这样写:


    for ($index = 0; $index < @$array_pointer; $index++) {
         $array_pointer->[$index] += 2;
    }
应该这样写:


    for ($i = 0; $i < @$ap; $i++) {
         $ap->[$i] += 2;
    }
(虽然你可以说某个变量名会比$ap好,但也不见得....)


*全局变量名应该比局部变量名长一些,因为它们的语境比较难发现一些。比如%State_Table是一个程序
的全局变量,而$func只是一个局部指针。


    foreach $func (values %State_Table) { ... }


Perl风格:并列对齐


*一致性和并列对齐能使代码可读性大大增加。比较这段代码:


    my $filename =    $args{PATHNAME};
    my @names    = @{ $args{FIELDNAMES} };
    my $tab      =    $args{SEPARATOR};


和这段代码:


    my $filename = $args{PATHNAME};
    my @names = @{$args{FIELDNAMES}};
    my $tab = $args{SEPARATOR};


*把注释和所有的|| die语句对齐放在一列上,象这样:


    socket(SERVER, PF_UNIX, SOCK_STREAM, 0) || die "socket $sockname: $!";
    bind  (SERVER, $uaddr)                  || die "bind $sockname: $!";
    listen(SERVER,SOMAXCONN)                || die "listen $sockname: $!";


Perl风格:在控制和赋值里多用&&和||


*Perl里的&&和||操作符会象C里的一样短路,但返回值不一样:Perl返回的是第一个满足条件的值。
*以下情况经常用||来完成:


    ++$count{ $shell || "/bin/sh" };


    $a = $b || 'DEFAULT';


    $x ||= 'DEFAULT';


*时候也可以用&&来完成,通常返回的假值是空值而不是0。(在Perl里测试为假返回的是空值而
不是0!)


    $nulled_href = $href . ($add_nulls && "\0");


Perl风格:学习优先性
*可以象使用标点符号一样使用and和or的说法是胡说八道。它们的结合优先性不同。你^必须^学
会结合优先性。多用括号不会有坏处。


   print FH $data      || die "Can't write to FH: $!";  # 错
   print FH $data      or die "Can't write to FH: $!";  # 对


   $a = $b or $c;      # 错了,是个虫虫
   ($a = $b) or $c;    # 等于这样
   $a = $b || $c;      # 应该这么写


   @info = stat($file) || die;     # 嘿,stat()会返回一个单值
   @info = stat($file) or die;     # 这样才对


*这个怎么加括号?


   $a % 2 ? $a += 10 : $a += 2


是这个意思:


   (($a % 2) ? ($a += 10) : $a) += 2


而不是:


   ($a % 2) ? ($a += 10) : ($a += 2)


Perl风格:别把?:用过了头


*在控制结构中用?:会带来麻烦。最好还是用if/else。千万别在控制结构中嵌套使用?:


    # 坏:
    ($pid = fork) ? waitpid($pid, 0) : exec @ARGS;


    # 好:
    if ($pid = fork) {
        waitpid($pid, 0);
    } else {
        die "can't fork: $!"    unless defined $pid;
        exec @ARGS;
        die "can't exec @ARGS: $!";
    }


*最好当表达式用:


    $State = (param() != 0) ? "Review" : "Initial";


    printf "%-25s %s\n", $Date{$url}
            ? (scalar localtime $Date{$url})
            : "<NONE SPECIFIED>",


Perl风格:不要定义TRUE和FALSE


*Perl理解布尔量,不要试图自行定义。以下的代码十分糟糕:


     $TRUE  = (1 == 1);
     $FALSE = (0 == 1);


     if ( ($var =~ /pattern/ == $TRUE  ) { .... }
     if ( ($var =~ /pattern/ == $FALSE ) { .... }
     if ( ($var =~ /pattern/ eq $TRUE  ) { .... }
     if ( ($var =~ /pattern/ eq $FALSE ) { .... }


     sub getone { return "This string is true" }


     if ( getone() == $TRUE  ) { .... }
     if ( getone() == $FALSE ) { .... }
     if ( getone() eq $TRUE  ) { .... }
     if ( getone() eq $FALSE ) { .... }


*想象一下如下的引申是多么愚蠢,还是在第一个语句后就停下吧。


     if (    getone() )                  { .... }                   
     if (    getone() == $TRUE  )      { .... }
     if (   (getone() == $TRUE) == $TRUE  )      { .... }
     if ( ( (getone() == $TRUE) == $TRUE) == $TRUE  ) { .... }


Perl风格:多用正则表达式


*正则表达式是你的朋友。不仅如此,它还是一种全新的思考方式。
*就象象棋选手会在棋盘上的子力分布上看出模式,Perl对分析数据中的模式很拿手。虽然大多数现代
编程语言都提供一些模式匹配的基本工具,通常是用外带的库,但Perl的模式匹配可是直接整合在语言核
心里的。象/..../, $1什么的。
*Perl的模式匹配提供许多别的语言没有的强大功能,这些功能会鼓励你用一套全新的眼光看待数据。


Perl风格:边走边换


*拷贝和替换可以一次完成。例:


    chomp($answer = <TTY>);


    ($a += $b) *= 2;


    # 把路径名去掉
    ($progname = $0)        =~ s!^.*/!!;


    # 所有单词第一字母大写
    ($capword  = $word)     =~ s/(\w+)/\u\L$1/g;


    # /usr/man/man3/foo.1 换成 /usr/man/cat3/foo.1
    ($catpage  = $manpage)  =~ s/man(?=\d)/cat/;


    @bindirs = qw( /usr/bin /bin /usr/local/bin );
    for (@libdirs = @bindirs) { s/bin/lib/ }
    print "@libdirs\n";
  | /usr/lib /lib /usr/local/lib


Perl风格:用负数作数列下标


*要得到数列的最后一个元素,用$array[-1]而不要用$array[$#array]。前者对列表和数列都有效,
而后者不是。
*请记住substr,index,rindex和splice都接受负数下标,从列表尾部开始计数。


  split(@array, -2);   # 两次pop


*请记住substr可以被赋值。例:


    substr($s, -10) =~ s/ /./g;


Perl风格:多用哈希


*当你用哈希方式思考的时候才开始理解Perl。哈希常常可以代替冗长的循环和复杂的算法。
*当你想表达一个集,或关系,或表,或结构,或记录的时候,请用哈希。
*“在。。。中”,“唯一”,“第一”,“重复”这样的字眼应该引起你条件反射式的大喊:“哈希!”如果你把
这些字眼和“列表”放在一个句子里,恐怕有些东西不太对头。


Perl风格:用哈希处理集


*下面这段代码找出两个列表@a和@b的并集和交集:


    foreach $e (@a) { $union{$e} = 1 }
    foreach $e (@b) {
        if ( $union{$e} ) { $isect{$e} = 1 }
        $union{$e} = 1;
    }
    @union = keys %union;
    @isect = keys %isect;


下面的代码做的是同样的事情,而利用了Perl的特色:


    foreach $e (@a, @b) { $union{$e}++ && $isect{$e}++ }
    @union = keys %union;
    @isect = keys %isect;


Perl风格:用哈希记录做过什么


*哈希是追踪你有没有做过什么事的好办法。
*多用 ... unless $seen{$item}++ 这样的办法,例:


    %seen = ();
    foreach $item (genlist()) {
        func($item) unless $seen{$item}++;
    }


Perl风格:用哈希储存记录,不要用并行列表


*学习使用哈希结构储存记录,再把这些记录保存在列表或哈希里,不要用并行列表,象这样:


    $age{"Jason"} = 23;
    $dad{"Jason"} = "Herbert";


应该这样:


    $people{"Jason"}{AGE} = 23;
    $people{"Jason"}{DAD} = "Herbert";


或者这样(注意这里for的用法):


    for $his ($people{"Jason"}) {
        $his->{AGE} = 23;
        $his->{DAD} = "Herbert";
    }


不过在象下面这样做之前,最好^三思^:


    @{ $people{"Jason"} }{"AGE","DAD"} = (23, "Herbert");


Perl风格:在代码不长时使用$_


*和新手的想法恰恰相反,使用$_可以改善代码的可读性。比较下面这段代码:


    while ($line = <>) {
        next if $line =~ /^#/;
        $line =~ s/left/right/g;
        $line =~ tr/A-Z/a-z/;
        print "$ARGV:";
        print $line;
    }


和这段:


    while ( <> ) {
        next if /^#/;
        s/left/right/g;
        tr/A-Z/a-z/;
        print "$ARGV:";
        print;
    }


Perl风格:使用foreach()循环


*foreach循环的隐含重命名和局部化可是强力工具。例:


    foreach $e (@a, @b) { $e *= 3.14159 }


    for (@lines) {
        chomp;
        s/fred/barney/g;
        tr[a-z][A-Z];
    }


*记住你可以把拷贝和修改一次完成:


    foreach $n (@square = @single) { $n **= 2 }


*你还可以用哈希片段来改变哈希值。例:


    # 把单量、列表里的氖有值、哈希里的所有值中的
    # 空白字符都去掉。
    foreach ($scalar, @array, @hash{keys %hash}) {
        s/^\s+//;
        s/\s+$//;
    }


Perl风格:避免字节处理


*C程序员常常在处理字符串时一次处理一个字节。别这么做!Perl在处理大串字符时很轻松!
*不要用getc,一次抓一整行,对一整行进行处理。
*即使是传统上在C语言里一次处理一个字符的操作,比如语义分析,也应采用不同的方法。例:


    @chars = split //, $input;
    while (@chars) {
      $c = shift @chars;
      # State machine;
    }
这样的办法太底层了。试试这样:


    sub parse_expr {
local $_ = shift;
my @tokens = ();
my $paren = 0;
my $want_term = 1;


while (length) {
   s/^\s*//;


   if (s/^\(//) {
return unless $want_term;
push @tokens, '(';
$paren++;
$want_term = 1;
next;
   } 


   if (s/^\)//) {
return if $want_term;
push @tokens, ')';
if ($paren < 1) {
   return;

--$paren;
$want_term = 0;
next;
   } 


   if (s/^and\b//i || s/^&&?//) {
return if $want_term;
push @tokens, '&';
$want_term = 1;
next;
   } 


   if (s/^or\b//i || s/^\|\|?//) {
return if $want_term;
push @tokens, '|';
$want_term = 1;
next;
   } 


   if (s/^not\b//i || s/^~// || s/^!//) {
return unless $want_term;
push @tokens, '~';
$want_term = 1;
next;
   } 


   if (s/^(\w+)//) {
push @tokens, '&' unless $want_term;
push @tokens, $1 . '()';
$want_term = 0;
next;
   } 


   return;


}
return "@tokens";
    }


Perl风格:避免使用符号引用


*新手常常想用一个变量包含另一个变量的名字:


    $fred    = 23;
    $varname = "fred";
    ++$varname;         # $fred now 24


*有时候这也奏效,不过这归根结底是个馊点子。^符号引用只对全局变量有效^,而全局变量是要大力避免
的,太容易引起重名冲突了。
*当你用了use strict的时候,它就失效了。
*它们不是真正的引用,不计入引用计数,也不被Perl的垃圾回收站回收。
*应该使用哈希或真正的引用。


Perl风格:想用$$name的时候,用哈希


*用变量包含另一个变量的名字总是意味着此人对哈希掌握不够。虽然可以这样写:


    $name = "fred";
    $$name{WIFE} = "wilma";     # set %fred


    $name = "barney";           # set %barney
    $$name{WIFE} = "betty";


最好还是这样写:


    $folks{"fred"}  {WIFE} = "wilma";
    $folks{"barney"}{WIFE} = "betty";


Perl风格:不要对eof作测试


*不要这样写(死锁):


    while (!eof(STDIN)) {
        statements;
    }


*应当这样写:


    while (<STDIN>) {
        statements;
    }


*在eof不成立的时候给用户提示是很烦人的。试试这个:


   $on_a_tty = -t STDIN && -t STDOUT;
   sub prompt { print "yes? " if $on_a_tty }
   for ( prompt(); <STDIN>; prompt() ) {
        statements;
   }


Perl风格:不要滥用反斜杠


*Perl允许你选用自定的分隔符来分隔模式和引文,这样可避免“牙签成堆综合症”(指许多反斜杠连续出现)。
请多用自定分隔符。例:


    m#^/usr/spool/m(ail|queue)#


    qq(Moms said, "That's all, $kid.")


    tr[a-z]
      [A-Z];


    s { /          }{::}gx;
    s { \.p(m|od)$ }{}x;


Perl风格:减少复杂性


*但当可能的时候请把next和redo放在循环的最前面。
*使用unless和until。
*但不要使用 unless ... else ... 
*从Pascal的暴政下逃脱。不要在经历无谓的弯弯绕之后最后才退出循环或函数。不要这样写:


    while (C1) {
        if (C2) {
            statement;
            if (C3) {
                statements;
            }
        } else {
            statements;
        }
    }


Perl风格:减少复杂性(解决方案)


*应该这样写:




    while (C1) {
        unless (C2) {
            statement;
            next;
        }
        statements;
        next unless C3;
        statements;
    }


*或者这样写:


    while (C1) {
        statement, next unless C2;
        statements;
        next unless C3;
        statements;
    }


Perl风格:减少重复


*把重复代码放到段落之外。例:修改前:


    if (...) {
        X;  Y;
    } else {
        X;  Z;
    }


修改后:


    X;
    if (...) {
        Y;
    } else {
        Z;
    }


Perl风格:化整为零


*把子函数分成便于管理的单位。
*不要试图用一个正则表达式匹配所有的东西。
*在ARGV上下点工夫。例:


    # 程序期待环境变量
    @ARGV = keys %ENV       unless @ARGV;


    # 程序期待源程序
    @ARGV = glob("*.[chyC]") unless @ARGV;


    # 对gzip文件也能行
    # from Perl Cook Book 16.6
    @ARGV = map { /^\.(gz|Z)$/ ? "gzip -dc $_ |" : $_  } @ARGV;


Perl风格:把程序分为不同的进程


*学习使用特殊形式的open:


    # from Perl Cookbook 16.5
    head(100);
    sub head {
        my $lines = shift || 20;
        return if $pid = open(STDOUT, "|-");
        die "cannot fork: $!" unless defined $pid;
        while (<STDIN>) {
            print;
            last if --$lines < 0;
        }
        exit;
    }


(译者按:读者最好阅读一下Perl Cookbook的相关章节,本节内容较深。)


Perl风格:面向数据的编程


*数据结构比代码更重要。
*Rob Pike说:“数据至高无上。只要你选择了正确的数据结构并进行了合理的组织,算法总是不言自明的。
数据结构,而不是算法,是编程的核心。(参阅Brooks, 第102页)”
*用数据概括普遍,用代码处理异常。(Kernighan)
*如果在两个地方看到类似的功能,把它们统一起来。这叫做“子函数”。
*考虑做一个函数指针的哈希结构来代表状态表或switch语句。


Perl风格:配置文件


*如果需要配置文件,用do语句来加载。
*你于是得以使用Perl的全部威力:


    # 摘自 Perl Cookbook 8.16
    $APPDFLT = "/usr/local/share/myprog";
    do "$APPDFLT/sysconfig.pl";
    do "$ENV{HOME}/.myprogrc";


    # 在配置文件中这样写
    $NETMASK = '255.255.255.0';
    $MTU     = 0x128;
    $DEVICE  = 'cua1';
    $RATE    = 115_200;


*请在此处(http://www.perl.com/CPAN/authors/id/TOMC/scripts/)参阅本文作者的clip和cliprc
文件。


Perl风格:函数作为数据


*将函数指针用在数据结构或用作函数参数。例:


    # from MxScreen in TSA (see also PCB 19.12)
    %State_Table = (
        Initial  => \&show_top,
        Execute  => \&run_query,
        Format   => \&get_format,
        Login    => \&resister_login,
        Review   => \&review_selections,
        Sorting  => \&get_sorting,
        Wizard   => \&wizards_only,
    );


    foreach my $state (sort keys %State_Table) {
        my $function = $State_Table{$state};
        my $how      = ($action == $function)
                        ? SCREEN_DISPLAY
                        : SCREEN_HIDDEN;
        $function->($how);
    }


Perl风格:闭包(closure)


*用闭包克隆相似的函数。例:


    # from MxScreen in TSA
    no strict 'refs';
    for my $color (qw[red yellow orange green blue purple violet]) {
        *$color = sub { qq<<FONT COLOR="\U$color\E">@_</FONT>> };
    }
    undef &yellow;      # lint happiness
    *yellow = \&purple; # function aliasing


*或类似地:


    # from psgrep (in TSA, or PCB 1.18)
    my %fields;
    my @fieldnames = qw(FLAGS UID PID PPID PRI NICE SIZE
                        RSS WCHAN STAT TTY TIME COMMAND);


    for my $name (@fieldnames) {
        no strict 'refs';
        *$name = *{lc $name} = sub () { $fields{$name} };
    }


Perl风格:学习用for作开关语句


*虽然Perl没有switch语句,这实际上是个机会而不是困难。
*做一个开关控制很容易。for这个词有时念作“switch”


   SWITCH: for ($where) {
               /In Card Names/     && do { push @flags, '-e'; last; };
               /Anywhere/          && do { push @flags, '-h'; last; };
               /In Rulings/        && do {                    last; };
               die "unknown value for form variable where: `$where'";
           }


*就像一系列的elsif,开关语句一定要有一个缺省设置。即使这种缺省情况“永远也不可能发生”。


Perl风格:创造性地用do{}语句来作开关语句


*另一种有趣的办法是用do{}语句返回的值来做成开关语句。例:


   $amode = do {
       if     ($flag & O_RDONLY) { "r" }       # XXX: isn't this 0?
       elsif  ($flag & O_WRONLY) { ($flag & O_APPEND) ? "a" : "w" }
       elsif  ($flag & O_RDWR)   {
           if ($flag & O_CREAT)  { "w+" }
           else                  { ($flag & O_APPEND) ? "a+" : "r+" }
       }
   };


Perl风格:用&&和||来作开关语句


*要小心,&&的右边总为真。


   $dir = 'http://www.wins.uva.nl/~mes/jargon';
   for ($ENV{HTTP_USER_AGENT}) {
       $page  =    /Mac/            && 'm/Macintrash.html'
                || /Win(dows )?NT/  && 'e/evilandrude.html'
                || /Win|MSIE|WebTV/ && 'm/MicroslothWindows.html'
                || /Linux/          && 'l/Linux.html'
                || /HP-UX/          && 'h/HP-SUX.html'
                || /SunOS/          && 's/ScumOS.html'
                ||                     'a/AppendixB.html';
   }


Perl风格:更创造性地使用for和do来构造开关语句


*有时审美也很重要。:)


    for ($^O) {
        *struct_flock =                do                           {


                                /bsd/  &&  \&bsd_flock
                                       ||
                            /linux/    &&    \&linux_flock
                                       ||
                          /sunos/      &&      \&sunos_flock
                                       ||
                  die "unknown operating system $^O, bailing out";
        };
    }


Perl风格:管好模块


*使用Pod为你的模块写文档,用pod2man和pod2html作检查。
*使用Carp模块里的carp,croak,confess,不要用warn和die。
*写得好的模块很少需要用到::。用户应该可以用import和类函数得到模块内容。
*传统的模块也不错。不要因为用对象编程看着很酷就急急忙忙跳上去。明显需要的时候再用不迟。
*使用对象应该通过调用对象函数。
*对象函数本身也应该通过对象指针使用类数据。


Perl风格:补丁


*当你和别人写的代码打交道的时候,遵循他们的范例。
*不要因为会产生巨大的diff文件就重新样式化代码。
*有时候为了对付某些自定义tab键的空格数的坏蛋,把tab转换成空格似乎是必要的。但^别这么做^!

原创粉丝点击