取势 明道 优术

作者为 扶 凯 发表

对岸台湾的朋友写的perl最佳实践的重点.这本书可是不可多得的好书,无论是做为新手,还是老手都非常有用,做为团队开发更加是好得不得了.
如果每个写perl的人都能照这个要求写.perl就不会在被讲人讲成不可读的程序了.

原文链接:http://nkfust.twbbs.org/cgi-bin/bbscon?board=perl&file=M.1157543710.A&num=1

我把 Perl Best Practices 的重点节录了出来,并作了翻译.
希望对 Perl 的使用者有所帮助.
由于没有翻译的经验,读起来难免觉得不够流畅,加上没有校稿,应该有不少错误和瑕疵.
恳请各位先进指正,谢谢.

书摘内容如下:

==== 程序码排版 ====

– 使用 K&R 的括号风格,不要用 BSD 风格,也不要用 GNU 的风格

– 在关键字和括号中间加上空白
 – 不写: foreach(@_), while($counter < 10)
   要写: foreach (@_), while ($counter < 10)

– 子函数和括号间,或是变量和括号间,不要加上空白
 – 不写: somefunc (@_), $elements [$i]
   要写: somefunc(@_), $elements[$i]

– 使用内建函数时,不要使用不必要的括号

– 包住复杂的索引值的前后括号要留空白
 – 不写: $candidates[$i] = $incumbent{$candidates[$i]{get_region( )}};
   要写: $candidates[$i] = $incumbent{ $candidates[$i]{ get_region( ) } };

– 使用空白让二元算符和变量分开
 – 不写: my $displacement=$initial_velocity*$time+0.5*$acceleration*$time**2;
 – 要写: my $displacement
            = $initial_velocity * $time  +  0.5 * $acceleration * $time**2;

– 在每一行叙述最后加上一个分号
 – 区块里的最后一行可以不加分号,但是最好还是加上.如果加上了新的叙述,就不会产生语法错误

– 在一个跨多行的数组宣告中,每一值的最后加上一个逗号
 – 如果需要重排数组里面的内容,就比较不用担心语法错误

– 一行最多78个字元

– 请使用4个空白的缩排
 – 最好把超过四层或是五层以上的缩排重新写到子函数里

– 缩排请用空白,不要用 tab
 – 因为每个人的编辑器的 tab 宽度都不一样

– 不要在同一行上放上两个以上的叙述
 – 就算使用 map, grep, sort 也一样

– 一个段落一个段落的写程序
 – 一个段落里包含了几行程序码,目的是要完成一个小的子任务
 – 在每一个段落的前面,加上一行的注解,增加可阅读性.
   注解里写的是这个段落的目的,而不只是程序码的换句话说
 – 每个段落之间用一行空行隔开

– 不要"紧抱"着else
 – 不写: } else {
   要写: }
        else {

– 垂直对齐可增加可读性
 – 例如:
     my @months = qw(
       January   February   March
       April     May        June
       July      August     September
       October   November   December
     );
 – 又例如:
     $name   = standardize_name($name);
     $age    = time – $birth_date;
     $status = ‘active’;
– 分解长运算式时,在算符前作切分
 – 不写:
   push @steps, $steps[-1] +
       $radial_velocity * $elapsed_time +
       $orbital_velocity * ($phase + $phase_shift) –
       $DRAG_COEFF * $altitude;
 – 要写:
   push @steps, $steps[-1]
                + $radial_velocity * $elapsed_time
                + $orbital_velocity * ($phase + $phase_shift)
                – $DRAG_COEFF * $altitude
                ;

– 如果长运算式在叙述的中间时,最好重写到另一行,赋值到另一个新的变量
 – 不写: add_step( \@steps, $steps[-1]
                           + $radial_velocity * $elapsed_time
                           + $orbital_velocity * ($phase + $phase_shift)
                           – $DRAG_COEFF * $altitude
                           , $elapsed_time);
 – 要写: my $next_step = $steps[-1]
                        + $radial_velocity * $elapsed_time
                        + $orbital_velocity * ($phase + $phase_shift)
                        – $DRAG_COEFF * $altitude
                        ;
        add_step( \@steps, $next_step, $elapsed_time);

– 在长叙述的里具有较低优先权的算符前作切分
 – 不写:
   push @steps, $steps[-1] + $radial_velocity
                * $elapsed_time + $orbital_velocity
                * ($phase + $phase_shift) – $DRAG_COEFF
                * $altitude
                ;
 – 要写:
   push @steps, $steps[-1]
                + $radial_velocity * $elapsed_time
                + $orbital_velocity
                    * ($phase + $phase_shift)
                – $DRAG_COEFF * $altitude
                ;

– 在长叙述的赋值算符前作切分
 – 不写:
   $predicted_val = $average
                        + $predicted_change * $fudge_factor
                        ;

 – 要写:
   $predicted_val
           = $average + $predicted_change * $fudge_factor;

– 请依栏位对齐一连串的三元运符
 – 例如:
    my $salute = $name eq $EMPTY_STR                      ? ‘Customer’
               : $name =~ m/\A((?:Sir|Dame) \s+ \S+) /xms ? $1
               : $name =~ m/(.*), \s+ Ph[.]?D \z     /xms ? "Dr $1"
               :                                            $name
               ;
 – 就算只有一个三元算符也要这样作
    my $salute = $name eq $EMPTY_STR                      ? ‘Customer’
               :                                            $name
               ;

– 为长数组加上括号
 – 数组的最后一个元素也是要加上逗号

– 机械化的强制选择的程序码排版风格
 – 使用 perltidy

==== 命名规则 ====

– 使用语法样版产生识别字
 – 例如:
     namespace -> Noun :: Adjective :: Adjective
     namespace -> Noun :: Adjective :: Adjective
                  |
                  Noun :: Adjective :: Noun

– 为每一个布林变量或是子函数取个适当的名字
 – 常常这类的变量或是子函数会加上 is_ 或是 has_ 的前缀

– 储存参照的变量名称要加上 _ref 的后缀
 – 这样看到 _ref 的时候,可以联想应该有个 -> 接在后面

– 阵列名称用复数,hash名称用单数
 – 不过如果阵列是用作随机存取的话,用跟hash一样的命名规则,用单数

– 用底线分开多单字的识别字
 – 不写: $OpenedFiles
   要写: $opened_files

– 不同的用处的变量用不同的大小写
 – 子函数,成员函数,变量名称,具名引数 => 小写
 – 套件名称 => 大小写混用
 – 常数 => 全大写

– 用单字的前缀来作缩写
 – 如果用前缀来作缩写,再加上最后的子音也是可以接受的.
   尤其是最后的子音是代表复数的话.
 – 这个规则不能用在常见的缩写上,例如: Ctrl, Mbps, tty, src, msg

– 如果缩写之后不会造成混淆才缩写
 – 这会造成混淆
    $term_val      # terminal value or termination valid?
        = $temp    # temperature or temporary?
          * $dev;  # device or deviation?

– 避免在变量名称里使用原本就容易混淆的字
 – left      (左边 v.s. 剩下的)
 – right     (右边 v.s. 授权)
 – no        (否定 v.s. number的缩写)
 – abstract  (抽象的 v.s. 摘要)
 – contract  (缩小 v.s. 契约)
 – second    (第二位的 v.s. 秒)
 – close     (靠近的 v.s. 关上)

– 在内部专用的子函数之前加上底线
 – sub _internal_subroutine {
   }

==== 变量值和expression ====

– 只有当真的需要字串安插时才使用具有字串安插功能的分隔符号

– 不要用 "" 或 ” 表示空字串,用 q{} 之类的表示法

– 写单字元的字串时,不要造成视觉上的混淆
 – 使用 q{ }
 – 使用 "\t" 而不是 ‘       ‘

– 使用具名的字元跳脱,不要使用纯数字的跳脱
 – 不写:
        $escape_seq = "\127\006\030Z";       # DEL-ACK-CAN-Z
 – 要写:
        use charnames qw( :full );
        $escape_seq = "\N{DELETE}\N{ACKNOWLEDGE}\N{CANCEL}Z";

– 使用具名常数,但是不要用 use constant,用 Readonly 模组
 – 用 constant 模组作出来的常数,不能被安插到字串中
 – Readonly 模组可以作出区域常数,constant 模组只能作出全域的
 – 如果因为种种原因,不能使用 Readonly 模组,用constant模组还是比直接写值在程序里好.

– 不要在十进位前面加上0
 – 数字前面加上 0, 在 Perl 里代表的是八进位制的意思
 – 如果要写八进位制的数字,也不要在前面加 0, 请用 oct 函数
   例如: oct(600), oct(644)

– 使用底线增加大数字的可读性
 – 不写: 10990000000000
   要写: 10_990_000_000_000
 – 底线也可以用在浮点数或是16进位数字
   3.141592_653589
   0xFF_FF_FF_80;

– 如果有 \n 请依 \n 换行,再把字串衔接起来

– 当多行字串超过两行的时候,使用即席文件

– 如果即席文件破坏了缩排的话,请使用"theredoc": 把即席文件的内容放到另一个变量

– 用一个全大写的识别字加上一个标准的前缀作为每一个即席文字的终止符
 – 例如:
   print <<"END_USAGE";
   END_USAGE

– 当加入即席文件时,为终止符加上引号
 – 不写
   print <<END_USAGE;
   END_USAGE
 – 要写
   print <<"END_USAGE"; # 允许变量安插
   END_USAGE
   或是
   print <<‘END_USAGE’; # 不允许变量安插
   END_USAGE

– 不要使用未经修饰的识别字
 – 在 Perl 里,任何不属于子函数、套件名称、档案代号、标签、内建函数的识别字,都被视为未加引号的字串.
 – use strict 可以在编译期间找出未经修饰的识别字

– 把 => 保留给(名字∕值)使用
 – %default_service_record  = (
       name   => ‘<unknown>’,
       rank   => ‘Recruit’,
       serial => undef,
   );
 – $text = format_text(src=>$raw_text,  margins=>[1,62],);
 – Readonly my $ESCAPE_SEQ => "\N{DELETE}\N{ACKNOWLEDGE}\N{CANCEL}Z";
 – 除了上面的用法,其他都比较会造成混淆

– 不要用逗号条列叙述
 – C/C++ 程序设计师习惯会用 Perl 写 C/C++ 式的循环.
   在 for 循环可以用逗号条列几个叙述
 – 纯量语境里,逗号可以用来条列叙述;但是在数组语境里,逗号只是个数组元素的分隔符号
 – 不写: for ($min=0,$max=$#samples, $found_target=0; $min<=$max; ) {}
   要写: ($min, $max, $found_target) = (0, $#samples, 0);
        while ($min<=$max) {}

– 不要混用高优先权的布林算符及低优先权的布林算符
 – not 比 ! 看起来更容易阅读, 但是有可能会造成一些问题
 – 在条件判断里,只用有较高优先权的算符(! && ||)
 – 避免使用 and 和 not
 – ‘or’ 只用在作一些错误处理
    open my $source, ‘<‘, $source_file
       or croak "Couldn’t access source code: $OS_ERROR";

– 为每个数组都加上括号
 –             @weekends = ‘Sat’, ‘Sun’;
   其实是      (@weekends = ‘Sat’), ‘Sun’
   要写成       @weekends = (‘Sat’, ‘Sun’);
   但是不要写成  @weekends = [ ‘Sat’, ‘Sun’ ];

– 使用查表法来检查某字串是否存在于数组中. 使用 List::MoreUtils 的 any()来检查资料是否存在于数组中
 – any() 和 grep() 很像,差别在于 any() 看到一个符合的资料时,就会马上回传.
   grep则是会跑过数组里每一个元素
 – 但如果使用的判断函数是 ‘eq’ 的话,请不要用 any,请建个hash,使用查表法


==== 变量 ====

– 避免使用非区块变量
 – 使用 my,除非不得已或是你要使用内建的全域变量, 像是 $_, @ARGV, $AUTOLOAD, $a, $b
 – 使用非区块变量,会增加程序码的耦合性
 – 其他的一些全域变量
   $1,$2,$3,etc. => 在数组语境使用,或是在比对完后,马上把它们转成其他名称的区块变量
                    不过在作字串取代,还是可以用 $1, $2, $3
   $&            => 在整个常规表示式上再包上括号,或是使用 Regexp::MatchContext
   $`            => 在常规表示式最前面加上 ((?s).*?),或是使用 Regexp::MatchContext
   $’            => 在常规表示式最后面加上 ((?s).*?),或是使用 Regexp::MatchContext
   $*            => 使用 /m
   $.            => 使用 IO::Handle 所提供的 input_line_number()
   $|            => 使用 IO::Handle 所提供的 autoflush()
   $"            => 使用 join
   $% $= $- $~   => 使用 Perl6::Form::form
   $^ $: $^L $^A
   $[            => 绝对不要改变它
   @F            => 不要使用 perl 的 -a 选项
   $^W           => Perl 5.6.1 及其之后的版本,请用 use warnings

– 不要使用套件变量.不要用 our,用 my.
 – 如果别的套件有需要存取目前套件里的变量,请多写个存取函数,还是不要直接输出给别的套件

– 如果你必须要修改套件变量,把它 localize
 – 不写
   use YAML;
   $YAML::Indent = 4;
 – 要写
   use YAML;
   local $YAML::Indent = 4;

– 给每一个变量初始值
 – 变量 localize 之后,初始值为 undef. 必须手动的赋值.

– 比较不常见的标点符号变量,请 use English 可以大幅增加可阅读性

– 如果你必须要修改标点符号变量,请作 localize
 – 不要暂存到别的变量, 之后再把它们取回来

– 不要使用 $` $& $’. 使用 English 模组时,不要用 $PREMATCH, $MATCH, and $POSTMATCH
 – use English qw( -no_match_vars );
 – 请用括号作出 $` $& $’的效果
 – 使用 Regexp::MatchContext

– 要注意任何在 $_ 上的修改
 – $_ 常常是当作其他变量的别名,修改 $_ 等于修改其他变量

– 负数的索引值是从阵列的尾巴算回来
 – 不写: $frames[@frames-1] 或是 $frames[$#frames]
   要写: $frames[-1]

– 好好利用阵列切片和hash切片
 – 要用负数索引值取出阵列后面的元素,要注意顺序
   @frames[-1..-3] 是不合法的
   要写 reverse @frames[-3..-1]

– 用表格的方式排版阵列切片或hash切片
 – @frames[-1,-2,-3]
       = @active{‘top’, ‘prev’, ‘backup’};

– 制作索引值和hash键值的对应表,帮忙作阵列或是hash切片
 – 例如:
   Readonly my %CORRESPONDING => (
     # %active…     @frames…
       ‘top’        =>  -1,
       ‘prev’       =>  -2,
       ‘backup’     =>  -3,
       ’emergency’  =>  -4,
       ‘spare’      =>  -5,
       ‘rainy day’  =>  -6,
       ‘alternate’  =>  -7,
       ‘default’    =>  -8,
   );
   @frames[ values %CORRESPONDING ] = @active{ keys %CORRESPONDING };

==== 控制结构 ====

– 使用 if(){}, 不要使用倒装句型的 if

– 当流程控制的时候才用倒装句型的 if
 – 当使用 next, last, redo, return, goto, die, croak, or throw
 – foreach my $line (@line) {
      next if $line =~ /[abc]/;
   }

– 不要使用倒装句型的 unless, for, while, 或是 until.
 – 倒装的句型几乎都会被改成区块的型式;修改程序码就是代表会产生新的臭虫.

– 绝对不要使用 unless 或是 until
 – 对大多数人来说这些比较不熟悉
 – 不容易扩张
 – 如果真的要用 until 或是 unless,就不可以在测试条件里用上否定的判断. 避免双重否定

– 避免使用 C 风格的 for 循环
 – 不写:
   for (my $n=4; $n<=$MAX; $n+=2) {
       print $result[$n];
   }
 – 要写:
   RESULT:
   for my $n (4..$MAX) {
       next RESULT if odd($n);
       print $result[$n];
   }

– 避免在循环里使用不必要的subscripting
 – 不写:
   for my $n (0..$#clients) {
       $clients[$n]->tally_hours(  );
       $clients[$n]->bill_hours(  );
       $clients[$n]->reset_hours(  );
   }
 – 要写:
   for my $client (@clients) {
       $client->tally_hours(  );
       $client->bill_hours(  );
       $client->reset_hours(  );
   }

– 不得已时,也不要在循环里使用subscript超过一次

– 用具名的变量作为循环的iterator, 不要用 $_
 – 不写: foreach (@names){}
   要写: foreach $name (@names){}

– 循环的 iterator 永远都要加上 my

– 当要从旧的数组生出一个新的时,用 map,不要用 for
 – 用 for 比较慢,且阅读性比较差
 – 不写:
   my @sqrt_results;
   for my $result (@results) {
       push @sqrt_results, sqrt($result);   # 慢在这里
   }

   要写:
   my @sqrt_results = map { sqrt $_ } @results;

– 使用 grep 或是 List::Util 的 first 找寻数组里的资料,而不要用 for

– 在原阵列作转换或是改变,请用 for 不要用 map
 – map 会耗额外的记忆体

– 使用子函数分解复杂的数组转换

– 在处理数组的函数里,千万不要修改 $_
 – 在 map, grep, for 里的 $_ 都是所指变量的别名

– 避免使用一连串的 if-elsif-elsif-elsif-else

– 使用查表法会比一连串的相等测试好

– 当要产生一个值时,使用表格化的三元算符叙述
 – 例如:
   my $salute = $name eq $EMPTY_STR                       ? ‘Dear Customer’
              : $name =~ m/ \A((?:Sir|Dame) \s+ \S+) /xms ? "Dear $1"
              : $name =~ m/ (.*), \s+ Ph[.]?D \z     /xms ? "Dear Dr $1"
              :                                             "Dear $name"
              ;

– 不要使用 do…while 循环. 阅读性较差

– 尽可能、尽早地跳过循环

– 不要只是为了强化控制结构而扭曲循环的结构
 – 不使用巢状的 if, 不使用旗标变量. 这样阅读性变差

– 使用 for 加 redo,避免使用 while加上不规则的计数器

– 为每个循环都加上个标签.每一个next, last, 还有redo都要加上标签
 – 例如:
   INPUT:
   for my $try (1..$MAX_TRIES) {
       print ‘Enter an integer: ‘;
       $int = <>;

       last INPUT if not defined $int;
       redo INPUT if $int eq "\n";

       chomp $int;
       last INPUT if $int =~ $INTEGER;
   }

==== 文件 ====

– 区分使用者文件和技术文件的不同
 – 使用者文件: =head1, =head2, =over/=item/=back
   技术文件:   =for, =begin/=end

– 为模组及应用程序制作标准的 POD 样版
 – 模组文件范例:
   =head1 NAME
   =head1 VERSION
   =head1 SYNOPSIS
   =head1 DESCRIPTION
   =head1 SUBROUTINES/METHODS
   =head1 DIAGNOSTICS
   =head1 CONFIGURATION AND ENVIRONMENT
   =head1 DEPENDENCIES
   =head1 INCOMPATIBILITIES
   =head1 BUGS AND LIMITATIONS
   =head1 LICENCE AND COPYRIGHT
 – 使用者手册范例:
   =head1 NAME
   =head1 VERSION
   =head1 USAGE
   =head1 REQUIRED ARGUMENTS
   =head1 OPTIONS
   =head1 DESCRIPTION
   =head1 DIAGNOSTICS
   =head1 CONFIGURATION AND ENVIRONMENT
   =head1 DEPENDENCIES
   =head1 INCOMPATIBILITIES
   =head1 BUGS AND LIMITATIONS
   =head1 AUTHOR
   =head1 LICENCE AND COPYRIGHT

– 延伸及客制化的你的标准 POD 样版
 – 在 POD 文件里还可以在加上
   =head1 EXAMPLES
   =head1 FREQUENTLY ASKED QUESTIONS
   =head1 COMMON USAGE MISTAKES
   =head1 SEE ALSO
   =head1 (DISCLAIMER OF) WARRANTY
   =head1 ACKNOWLEDGEMENTS

– 把使用者文件放在源码档案里

– 把所有的使用者文件放在源码档案里的单一个地方

– 把 POD 放在靠档案结尾最近的地方

– 适当的把技术文件再作细分

– 使用注解样版写重要的注解

– 用一整行的注解解释所用的演算法

– 使用单行注解或是行末注解记下小细节

– 把所有让你混淆或是骗到你的东西全部写到注解里

– 有时写写注解会比重写程序好的多

– 使用"隐身"的 POD 段落纪录较长的技术上的讨论文字

– 检查文件里的拼字,句型,是否合理


==== 内建函数 ====

– 不要在排序函数里重算键值
 – 可以先算好,或是利 Orcish Maneuvre 作快取

– 使用 reverse 倒转数组

– 使用 scalar reverse 反转一个纯量变量

– 使用 unpack 抓取出固定长度的栏位

– 使用 split 抓取不定长度的栏位

– 使用 Text::CSV_XS 抓取较复杂,不固定长度的栏位

– 避免 eval 字串
 – 每次使用 eval 必须重叫一次 parser 和 compiler
 – eval 没有编译期间的警告

– 可以考虑使用 Sort::Maker 打造你自己的排序函数

– 用四个引数的substr() 而不要使用左值的substr()
 – 左值函数呼叫并不常见,容易混淆
– 谨慎使用左值的values()
 – Perl 5.005_04之后的版本,values回传的是一串别名,而不是拷贝值

– 用 glob, 不要用 <>
 – <$fh> 是 readline
   <> 是 输入算符
   <DATA> 是从 __DATA__ 里面读资料出来

– 避免直接使用 select() 作出非整数秒的 sleep
 – use Time::HiRes qw( sleep );
   sleep 0.5;
 – 硬要用 select(),请作好封装
   sub sleep_for {
       my ($duration) = @_;
       select undef, undef, undef, $duration;
       return;
   }
   sleep_for(0.5);

– 使用 map 和 grep 时,永远要用 {}

– 使用 Scalar::Util, List::Util, 和 List::MoreUtils 所提供的"非内建函数的内建函数"

==== 子函数 ====

– 呼叫子函数记得要加上(), 但是不要在前面加上 &
 – & 会造成混淆.是指AND或是指函数呼叫?
 – &func 会造成行为不明确,因为 &func 指的是 func(@_)
 – &func 保留给设定回呼函数时使用
   例如:
     set_handler( \&func );

– 自己写的子函数不要和内建函数撞名

– 永远要记得转换 @_ 成有意义的变量
 – 除了几种情况,可以选择不要这么作
   – 子函数简单又短
   – 不会修改到引数
   – 只会使用整个 @_,不会存取 @_ 里个别的元素
   – 只会参考到 @_ 几次而己(最好是一次)
   – 效率为第一考量时

– 任何子函数超过三个引数,就使用hash作成具名引数
 – 使用匿名hash,不要使用具名hash
   好处是使用匿名hash,在编译期间,当碰到奇数个元素hash,会回报错误

– 用 defined() 或是 exists() 检查是否有不见的引数
– 一当 @_ 转换成有意义的变量后,马上设定好预设值
 – 不要使用 ||=   它在 0 的时候可能会失败

– 纯量语境里,永远使用 return scalar
 – 不写: sub func1 {
           return func2();
        }
   要写: sub func1 {
           return scalar func2();
        }

– 让回传数组的子函数在纯量语境下,回传"明显且直觉"的值

– 没有"明显且直觉"的值的时候,则使用 Contextual::Return

– 不要使用子函数原型
 – 在呼叫的时候,可能看不出原来的定义

– 子函数回传,永远都要写 return

– 用未加修饰的 return 回传失败

==== I/O ====

– 不要使用未经修饰的识别字作为档案代号
 – 不写:
    open FILE, ‘<‘, "file.txt";

– 使用间接的档案代号
 – open $FILE, ‘<‘, "file.txt";

– 如果要用套件变量,先把它作localize

– 开档不是用 IO::File 就是用三个引数的 open()

– 绝对别忘记检查 open, close, print资料到档案的回传值
 – 不写:
   open my $out,  ‘>’, $out_file;
   print {$out} @results;
   close $out;
 – 要写:
   open my $out,  ‘>’, $out_file
                or croak "Couldn’t open ‘$out_file’: $OS_ERROR";
   print {$out} @results
                or croak "Couldn’t write ‘$out_file’: $OS_ERROR";
   close $out   or croak "Couldn’t close ‘$out_file’: $OS_ERROR";

– 清楚地关上档案, 愈早关档愈好

– 使用 while (<>) 不要用 for (<>)
 – for (<>) 要等到 EOF 才会开始处理. 不能互动
 – for (<>) 会建立一个暂时的阵列,浪费资源
 – for (1..1_000_000_000) 并不会产生一个很大的阵列

– 一行一行的吃进档案里全部内容,不要一次全部吃到一个变量里

– 用一个 do {} 吃进档案里所有的内容.这样比较干净
 – 例如:
   my $code = do { local $/; <$in> };

– 用强大又简洁的 Perl6::Slurp 读入串流里全部的资料
 – 例如:
   use Perl6::Slurp;
   # 从档案代号吃进资料
   my $text = slurp $file_handle;
   # 从某档案吃进资料
   my $text = slurp $filename;
   # 阵列语境
   my @lines = slurp $filename;
   # 阵列语境,每行去 \n
   my @lines = slurp $filename, {chomp => 1};

– 不要使用 *STDIN,除非你是认真的
 – 可以用 ARGV

– 把资料print到档案代号时,永远在档案代号前后加上大括号
 – 或是用 IO::Handle

– 永远都要用提示行得到使用者的输入

– 不要重发明互动性的测试
 – 使用 Scalar::Util 所提供的 openhandle
 – 或是用 IO::Interactive 的特别的档案代号: interactive

– 使用 IO::Prompt 作出提示行

– 在互动的应用程序,永远都要让使用者知道非互动性作业的进度

– 用 Smart::Comments 来作进度显示

– 自动更新缓冲区时,不要直接使用 select


==== 参照 ====

– 尽可能在任何地方解参照的时,都用 ->
 – 不写:
   print ‘Searching from ‘, ${$list_ref}[0],  "\n",
         ‘            to ‘, ${$list_ref}[-1], "\n";

   要写:
   print ‘Searching from ‘, $list_ref->[0] ,  "\n",
         ‘            to ‘, $list_ref->[-1] , "\n";

 – 当然这里没办法用 ->
   my ($from, $to) = @{$list_ref}[0, -1];

– 当不可避免地要在变量前加上sigil解参照的时候,在参照的前后加上大括号
 – 不写: @$list_ref
   要写: @{$list_ref}

– 不要用符号参照
 – 如果使用 use strict ‘refs’,符号参照就不能用
 – 使用符号参照在某方面代表的是错误的资料结构设计.用一个hash来解决问题

– 使用 Scalar::Util 的 weaken() 避免循回的资料结构造成记忆体流失
 – 环状数组在 Perl 里很少见,因为它不是有效率的解决方向,实作起来也不是特别容易.
   一般都是用阵列加上"余数长度"来实作,较为简单,也较为稳固

==== 常规表示式 ====

– 使用 /x
 – 常规表示式也是程序码
 – 用空白让常规表示式更容易阅读
 – 在常规表示式加上注解

– 使用 /m
 – 在 Unix 里, ^ 是一行的头, $ 是一行的尾
 – 在 Perl 里, ^ 是字串的头, $ 是字串的尾

– 使用 \A 和 \z 标明字串的边界
 – ^ $ 可能不是所有人都了解
 – \A 和 \z 的意义是绝对的
 – \A \z 和 ^ $ 可以写在同一个常规表示式里
   $text =~ s{\A \s* | ^– [^\n]* $ | \s* \z}{}gxm;

– 用 \z,而不是 \Z,来标明字串结尾
 – \Z 相当于 \n? \z

– 使用 /s
 – . 一般来说并不会比对到换行字元 \n
 – 加了 /s, . 就可以比对到 \n
 – 在没有加 /s 的常规表示式里的 . 加了 /s 之后,请用 [^\n]

– 可以考虑让 Regexp::DefaultFlags 自动开启 /xsm

– 多行的常规表示式用 m{…} 会比 /…/ 来的好

– 不要用 /…/ 和 m{…} 以外的分隔符号

– 用单字符类别取代跳脱的元字符
 – 不写: m/ \{ . \. \d{2} \} /xms
   要写: m/ [{] . [.] \d{2} [}] /xms
 – 不过这个可能会降低效能.作benchmark来试试

– 爱用具名字元跳脱元字元
 – 不写:
        /\177 \006 \030 Z/xms
   要写:
        use charnames qw( :full );
        m/\N{DELETE} \N{ACKNOWLEDGE} \N{CANCEL} Z/xms

– 使用具名字元属性,不要直接
 – 不写: qr/ [A-Z] [A-Za-z]* /xms
   要写: qr/ \p{Uppercase}  \p{Alphabetic}* /xms;

– 除了资料格式是固定的情况外,考虑比对任意长度的空白字元,而不是特定长度的
 – 不写:
   $config_line =~ m{ ($IDENT)  [\N{SPACE}]  =  [\N{SPACE}]  (.*) }xms
   要写:
   $config_line =~ m{ ($IDENT)  \s*  =  \s*  (.*) }xms

– 明确地说到底要比对多少
 – .* 很利害, 用 .*? 比较安全
 – . 不够精确,可以用 [^.] 来取代

– 当真的要抓出资料时,才加上括号
 – 无意义的抓取只是浪费 CPU
 – 可以用 (?:) 而不用 ()

– 只有当确定比对成功,才能使用$1, $2, $3, etc.
 – 错误用法:
   # 比对可能失败,而 $1 可能是之前抓取的值
   $full_name =~ m/\A (Mrs?|Ms|Dr) \s+ (\S+) \s+ (\S+) \z/xms;
   if (defined $1) {
       ($title, $first_name, $last_name) = ($1, $2, $3);
   }
 – 正确用法:
   if ($full_name =~ m/\A (Mrs?|Ms|Dr) \s+ (\S+) \s+ (\S+) \z/xms) {
       ($title, $first_name, $last_name) = ($1, $2, $3);
   }

– 永远要给括号抓出来的子字串适当的名字. $1, $2太难懂

– 用 /gc 旗标来切字符
 – 传统切字符的方式是一点一点地慢慢吃.如果字串很长,慢慢吃会变得超慢慢吃
 – Perl 5.004 以后的版本提供 /gc,让你可以"走"过字串

– 从表格里取出元素来打造常规表示式

– 从简单的常规表示式打造出复杂的常规表示式

– 考虑使用 Regexp::Common 而不要自己写常规表示式

– 使用字符类别,而不要用单字符的比对选择.
 – 不写: m{\A (?: a | d | i | q | r | w | x ) \z}xms
   要写: m{\A [adiqrwx] \z}xms
 – 不写: m{\A (?: qq | qr | qx | q | s | y | tr ) \z}xms
   要写: m{\A (?: q[qrx] | [qsy] | tr ) \z}xms

– 从多重常规表示式选项中抓出共同的前后缀

– 避免没用处的回溯 (backtracking)

– 尽量使用和固定字串作 eq 的比较,不要用固定样式的常规表示式比对

==== 错误处理 ====

– 丢出例外,而不要回传特殊的值或是设定旗标变量
 – 旗标变量及回传值可以被忽略.
 – eval, exec, flock, open, print, stat, system 并没有使用相同的变量储存错误讯息.
 – 可以使用 eval{} 处理例外

– 让内建函数同样地也丢出例外
 – 使用 Fatal 模组

– 在任何语境,让所有例外都丢出严重错误的讯息

– 注意 system() 的回传值
 – system() 成功时回传失败,失败时回传成功
 – 可以使用 POSIX 模组的 WIFEXITED 函数
 – 或是使用 Perl6::Builtins 的 system()

– 所有的失败都要丢出例外,包括可以修复的失败

– 在 caller 的位置印出例外报告,而不是例外被丢出来的地方
 – 使用 croak() 可以印出 caller 的资讯
 – 如果例外完全是来自自己程序的问题时,可以使用 die

– 用接受者的语言来撰写错误讯息

– 用接受者的语言为每一个错误讯息写上文件

– 使用例外物件
 – 每当失败的资料需要被送到另一个处理器(handler)时
 – 好处是每次例外发生可以知道类别

– 当错误讯息可能会改变的时候,使用例外物件

– 当有两个以上的例外是有关联时,使用例外物件

– 当例外物件有继承关系时,要检查目前的例外物件是不是最下层的.

– 自动化地建立例外类别

– 把 $@ 的内容存到别的变量,以防被其他的例外处理器修改了

==== 命令列处理 ====

– 强力要求单一一贯的命令列结构

– 命令列的语法要遵循标准的规范
 – 除了档名,其他的命令列选项都要加上个旗标
 – 如果每个档案有不同用途,也在档名前加上旗标
 – 短的旗标用 –
 – 长的旗标用 —
 – 如果一个旗标有个关联值,有的加上 = ,有的不加,有的两种都可以
 – 让单字符的旗标可以绑到一个 –
 – 每个单字符的旗标都有个多字符的版本
 – 允许 – 为特别的档案,作为读入 STDIN 的资料用
 – 用 — 代表档名数组的标记

– 标准化元选项
 – 元选项是用来告知使用者如何使用软体,而不是控制软体的行为
 – 每个程序至少要有四个元选项
   – usage
   – help
   – version
   – man

– 允许同一个档名同时指定为输入及输出

– 使用单一的标准处理命令列

– 确保你的介面,执行期间的讯息,及文件都是一致的风格

– 共同的命令列介面元件分解到一个共用的模组

==== 物件 ====

– 物件导向是个选择,但不一定是首选
 – 另外参考 Mark Jason Dominus 所写的 High Order Perl

– 符合适当的规则,才选择物件导向
 – 系统很大,或是可能变得很大
 – 资料可以集成明显的结构
 – 集成的资料形成自然的阶层关系,可以直接套用继承和多型
 – 你有一份资料,有许多作业会来处理它
 – 你要重复同样的作业在许多相关的而差别又不大的资料结构上
 – 很可能会加入新的资料型别时
 – 资料间的交互作用可以用运算子重载表示时
 – 个别模组的实作很可能随着时间而改变时
 – 系统的设计已经是物件导向了
 – 有许多其他的程序设计师会用你的程序码

– 不要使用虚拟hash
 – 基本上,虚拟hash完全是个错误

– 不要使用受限hash (restricted hashes)
 – 受限hash所指的是在hash上用了 Hash::Util 的 lock_keys(), lock_value(),
   或是 lock_hash()

– 永远都要使用封装良好的物件
 – 直接存取物件内部会比较快,但是程序码会比较不稳固
 – 使用 Inside-out 类别  (不知道 Inside-out 应该怎么翻译)

– 帮每一个建构子取个同样的标准名字
 – 用 new()

– 不要让建构子复制物件
 – 复制物件交给 clone()

– 永远都要为每个 inside-out 类别附上一个解构子
 – 以hash为基础的类别不用特别写解构子,但是 inside-out 类别需要.

– 写作成员函数,请遵照子函数的指导原则.

– 读和写的存取函数要分开
 – 例如:
   $obj->set_name("BLAH");
   $obj->get_name();
   不要写成只有一个 name() 函数
   $obj->name("BLAH");
   $obj->name();

– 不要使用左值存取函数

– 不要使用间接的成员函数呼叫写法
 – 不写: $value = method $obj;
   要写: $value = $obj->method();

– 最小的介面不等于最佳的介面

– 只有当类别和代数类别有同构的运算才可以使用运算子重载

– 永远考虑重载布林,数值,字串语境下的物件
 内定值:
   – 物件在布林语境,永远都为真
   – 物件在数值语境,会变成记忆体区块的位址.用来索引阵列可能会造成 Segfault
   – 物件在字串语境,会印出 reference 的内容

==== 类别阶层架构 ====

– 不要直接在 @ISA 上面动手脚,请用 use base.
 – @ISA     => run-time 决定继承关系
 – use base => compile-time 决定继承关系

– 使用封装良好的物件.
 – 使用 inside-out 物件的好处是继承类别和基底类别可以有相同的属性名称.
   用单一hash没有办法作到.

– 绝不使用单一引数的 bless()
 – 不写: bless {};
   要写: bless {}, $class;

– 使用hash作为建构子的参数
 – 不写: Some::Class->new($arg1, $arg2);
   要写: Some::Class->new(field1 => $arg1,
                         field2 => $arg2);

– 给基底类别的引数依照基底类别的名称作区分

– 把物件建构,物件初始化,物件解构的过程分开来

– 使用 Class::Std 自动建立标准类别架构

– 使用 Class::Std 自动化的解构成员资料

– 使用 Class::Std 初始化成员资料并作验证

– 用 :STRINGIFY, :NUMERIFY, 和 :BOOLIFY 成员函数作型别转换.

– 使用 :CUMULATIVE 而不要用 SUPER::

– 不要使用 AUTOLOAD()
 – 使用 Class::Std 的 AUTOMETHOD()

==== 模组 ====

– 先设计模组的介面

– 一开始先直接把程序写在你游标所在的位置,然后把重复的程序码放到子函数里,然后再把重复的复函数放到模组里

– 使用三个数字组成的版本号码.不要使用v字串
 – 在 5.8.1 之前不能用
 – 在 5.10 之后不能用

– 把相依模组的版本需求写在程序里,而不只是在注解里

– 只在有收到请求时,再合理地输出变量或子函数

– 可以考虑在宣告叙述上加上输出的讯息

– 千万不要把变量变成模组的其中一个介面

– 使用自动化的方式建立新的模组架构

– 尽可能的使用核心模组,而不要自己写

– 尽量使用 CPAN 模组

==== 测试及除错 ====

– 测试先行

– 用 Test::Simple 或 Test::More 写出标准的测试

– 用 Test::Harness 让你的测试套件标准化

– 不只写程序正常运作的测试情况,也写不正常运作的测试情况

– 测试最可能的情形和最"不"可能的情形

– 在开始除错前先写新的测试

– use strict

– 永远明确的打开警告讯息

– 绝不假设没有警告的编译代表着程序是正确的

– 如要关掉警告,请明确的关,选择性的关,而且要在有效范缩的愈小愈好

– 至少学一点 perl debugger

– 当"手动"除错时,使用序列化的警告讯息
 – 大部分的人不爱用 perl debugger
 – 手动印错误讯息,请用 warn ,而不用 print
 – 再用 Data::Dumper 把变量内容序列化

– 可以考虑使用 Smart::Comments,而不用 warn()

==== 杂项 ====

– 使用版本控制系统
 – 毕竟 ‘rm *’ 并没有多少个字元,很容易打

– 用 Inline:: 等的模组(通常是 Inline::C) 整合非 Perl 的程序码到你的应用程序里
 – 如果勇敢的话,可以用 xsubpp

– 设定档的语法不要变的太复杂
 – 设定档是一般使用者用的
 – 用 INI 的格式,或是将其衍伸就很够用
 – 有三个模组不错用
   – Config::General
   – Config::Std
   – Config::Tiny

– 不要使用 format, 用 Perl6::Form

– 不要使用绑定变量或是绑定档案代号 (Tie)

– 不要耍小聪明
 – 小聪明会产生不易维护的程序码
 – 不写: $optimal_result = [$result1=>$result2]->[$result2<=$result1];
   要写: use List::Util qw( min );
        $optimal_result = min($result1, $result2);

– 如果硬要耍点小聪明,请把它封装起来
 – 不写: @requests  = keys %{ {map {$_=>1} @raw_requests} };
   要写: sub unique {
            my %seen;
            return grep {!$seen{$_}++} @_;
        }
        @requests = unique(@raw_requests);

– 最佳化前先作效能测试
 – 除非十分了解 Perl 最底层的实作,请不要无意识的猜测,较精简的程序码会执行的比较快

– 最佳化资料结构前先测量一下
 – 最佳化资料结构有可能增加了更多的记忆体用量
 – 使用 Devel::Size 来帮忙

– 看到机会就使用快取
 – 当执行较复杂的计算, 可以用空间换取时间

– 自动化子函数的快取
 – 使用 Memoize 模组来为子函数的输出及输入建立快取

– 为每一个你所用的快取策略作效能测试

– 最佳化你的应用程序前先作 profile

– 作句法重构(refactoring)时,注意要保留原本的语义
 

来了就留个评论吧! 3个评论



    863 2010年05月12日 的 17:08

    Joseph1980215 2010年09月21日 的 12:07

    非常优雅。。。
    谢谢!