概述*
风格问题很容易变成信仰问题。 *我要告诉你们的绝大部分是个人意见。其中既有泛泛之论,也有指路明灯。 *警告:我不一定总是按自己说的做!:) *我并不期望你们永远和我保持一致。选择一种风格,坚持下去。连贯性才是最重要的! *K&P, K&R, S&W, Rob Pike, 和Larry Wall的基础性工作对本文有间接贡献。Jon Orwant, Mark-Jason Dominus, 和Nat *风格问题很容易变成信仰问题。 *我要告诉你们的绝大部分是个人意见。其中既有泛泛之论,也有指路明灯。 *警告:我不一定总是按自己说的做!:) *我并不期望你们永远和我保持一致。选择一种风格,坚持下去。连贯性才是最重要的! *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转换成空格似乎是必要的。但^别这么做^! 总结
以上是内存溢出为你收集整理的Perl风格:各有所爱全部内容,希望文章能够帮你解决Perl风格:各有所爱所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
评论列表(0条)