*风格问题很容易变成信仰问题。
*我要告诉你们的绝大部分是个人意见。其中既有泛泛之论,也有指路明灯。
*警告:我不一定总是按自己说的做!:)
*我并不期望你们永远和我保持一致。选择一种风格,坚持下去。连贯性才是最重要的!
*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转换成空格似乎是必要的。但^别这么做^!