[路杨Perl随笔]为什么你的Perl程序维护困难

提记:最近老有人抱怨自己的Perl程序越来越难维护,也在抱怨Perl的执行效率低下,特写下自己的不成熟的体会,和同好者共勉之。

1。你的程序使用 use strictuse warnings 了吗?
不得不承认,Perl语法的随意性虽然很灵活多变,但有时间给维护和效率都带来了很大麻烦,如果碰见自己的糟糕的编程习惯和随意的语法,那么太长的代码就意味着一场噩梦,但是幸好我们有 use strictuse warnings . 有了这个,我们可以很快的找到变量的拼写错误(类似“$xxx 只使用了一次”的提醒),use strict 迫使你的语法变的严谨。当然如果你是维护别人的旧代码,而原始作者又是习惯使用 全局变量(Global symbol ) 的家伙,那么,加上 use strict 将让你陷入更大的维护危机(除非你想全部重写他的代码)。加 use strictuse warnings 困难吗? 不,你只需要在 你的程序的开头 '#!/usr/local/bin/perl' 后面加上下句就行:

use strict;
use warnings;

我的建议:
养成良好的编程习惯,尽管 Perl 语法允许你灵活和随意,新写的程序一定要加上use strict 和 use warnings .这样可以迫使你定义变量的范围,免避以后出现的效率低下和变量污染(一般都是Global symbol惹的祸 )

2。你使用模块了吗?
噢。。懒惰是 Perl 文化中的美德,难道是我们要使用现成模块而少写大量的代码的原因?我的回答是:不全是。
合理的使用Perl的标准模块或者CPAN上的成熟模块能让你的程序的后期维护变的简单,而且还可能(一般是肯定)使你的程序变的健壮和运行效率提高。
说的简单的例子,在国内的的多如牛毛的Perl在线资料(不知道经过多少次转载的东西了,叹息)中不乏有这样的 Perl CGI应用之CGI提交变量获取:

if ($ENV{'REQUEST_METHOD'} eq "POST") {
read(STDIN,$buffer,$ENV{'CONTENT_LENGTH'});
} elsif ($ENV{'REQUEST_METHOD'} eq "GET") {
$buffer=$ENV{'QUERY_STRING'};
}
@pairs = split(/&/, $buffer); #(注1)

foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair);
$value =~ tr/+/ /;
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
$forms{$name} = $value;
}


OK,我承认这段 code 很经典,但是有必要吗? 如果对于一个大的网站的CGI实现,每个需要处理提交参数的脚本程序都在前面加上这段话,不说其他的,就是不小心写错一个字符,也估计是维护人员的噩梦吧?不抱怨才怪。
另外,看见我标明 #(注1) 红色的代码吧?这段代码只能识别以 & 为分割的参数,而现在多数流行以 ; (分号)为分割的参数,如果要改的话,对维护人员也是个头疼的事情吧。
再分析,这段代码只能处理 key1=val1&key2=val2 这样格式的参数,如果要处理key=val1&key=val2 ( 即一个变量多个值的情况)就无能为力了:)
其实这段代码完全可以用 CGI.pm 改写,只需要以下代码:

use CGI;
$my $query =CGI->new();
my %forms = map { $_ =>$query->param($_) } $query->param(); #(注2)

就可以了,不单解决了维护人员的代码噩梦,而且可以完全处理分号隔离和一键多值问题。更可贵的,CGI.pm是Perl的标准模块(无须安装),运行效率有兴趣的朋友可以测试下,效率绝对要高的多:)

OK,关于代码维护困难的问题就简单的说这些,一家之言,只是我在学习使用Perl中的一点体会而已,请各位斧正。下次有时间,我准备和大家探讨下Perl程序效率低下的原因。



#(注2): 其实这句还是存在这安全漏洞,就好象原来的那段长代码也存在安全隐患一样,在正式的程序,还需要对获取的变量进行安全处理(比如过滤掉特殊的字符),防止产生 WebShell,让Cracker有机可乘。
办法也很简单:
my %forms = map { $_ => mysub($query->param($_)) } $query->param();

其中 mysub() 就是自定义的安全过滤函数:)
另外, 加上 my 是我的习惯,也是为了符合 use strict;,如果要彻底和上面的那一长段code等价的话,去掉这个就是了。