第八章 效率和风格

程序质量、程序BUG、程序的维护和扩展、多个程序员协同,一个好的程序需要平衡这一切。一个漂亮但是不解决问题的程序是毫无价值的,但一个有用却不可维护的程序也是一个定时炸弹。

为了把程序写好,你必须要理解该语言,还得培养对语言和设计的品位。要达到这个目标,需要不断地练习--不仅仅是写代码,还要维护代码和阅读优秀的代码。

这条路没有捷径,但有路标。

写能维护的Perl

可维护性的高低就是指理解、修改程序的难易程度。比如现在写的代码,6个月后再来看它,如果要修复一个BUG或者添加一个新功能,你需要花多长时间呢?这个就是可维护性的体现。

可维护性不是指你是否要去看内置语法和库函数,也不是指一个没有编程经验的人是否能读懂你程序。它着眼的是****一个称职合格的程序员是不是很容易就理解你的程序、修改你的程序,修复一个bug或增加一个功能时他会遇到哪些问题。****
(网上有很多关于Perl维护性的谣言)

要编写出能维护的软件,需要你具有解决实际问题的经验。开发过程中有这些原则:

  • 删除重复
    重复的代码(或相似的代码)容易有坑---当你修复这一段代码中的错误时,你会不会修复那一段呢;更新这一段时,会不会更新另一段呢?精心设计的系统中通常不会出现重复。通过使用函数,模块,对象,角色来提取出重复的代码形成单独的组件,这样就能精确的控制问题域。好的设计有时甚至可以通过删除代码来增加功能。
  • 起个好名字
    编写代码就是在讲一个故事。你为变量,函数,模块和类起的每一个名字都可以表明(或混淆)你的意图。仔细选择名字,如果你不知道取什么样的名字好,那么再考虑下你的设计或者再了解下问题的细节。
  • 别耍小聪明
    简洁的代码是好的,只要它能体现出来代码的意图。小聪明和华而不实的招数不利于展示你的意图。在Perl中,你总是可以选择更明显的的方式来解决问题。有些问题可能确实需要一些特别的解决方法,当遇到这种情况时,封装起来,对外提供简单的接口,并且提供文档(详细记录了你聪明才智的文档)。
  • 保持简单
    如果不考虑其他的,简单的程序总是更容易维护。简单意味着你知道什么是最重要的东西并且只做这个。

有时候你需要功能强大,可靠的代码;有时候只需要一个单行程序,****简单意味着你知道自己需要的什么。****错误检测、安全性验证这些都是必须要做的,简单的代码也可以使用高级特性,简单的代码可以使用CPAN模块,简单的代码也需要理解原理。简单的代码能有效的解决问题,并且不做多余的工作。

写地道的Perl

Perl借用了很多其他语言的特性,Perl允许你随心所欲的写代码。经常的,有C背景的程序员写成C风格,java背景的写出java风格,其实高效的Perl程序员会写地道的Perl。这里有几个小提示:

  • 了解社区智慧
    Perl程序员经常会就技巧和习惯进行激烈的辩论,同时也乐于分享他们的经验(不仅仅是在CPAN上)。你可以从中了解到不同的风格和想法,并从中获益。
  • 遵循社区规范
    Perl有很多工具帮助我们解决着各种各样的问题,比如静态代码分析(Perl::Critic),格式化(Perl::Tidy),私人分发系统(CPAN::Mini, Carton, Pinto)。利用CPAN来学习,按照CPAN的规范来书写文档、打包代码、测试代码和发布代码。
  • 阅读代码
    加入一个邮件列表,如 Perl Beginners(http://learn.perl.org/faq/beginners.html)浏览 PerlMonks (http:
    //perlmonks.org/),积极参与社区。阅读代码,回答问题,这些都是很好的学习机会。

CPAN developers,,Perl mongers,还有各种邮件列表,里面包含有来之不易的经验和各种解决方案。与他们交谈,阅读他们的代码,问他们问题,向他们学习,同时,也让他们有机会来学习你。

写高效的Perl

编写能维护的代码意味着要去设计能维护的代码。好的设计源自好的习惯:

  • 写可测试的代码
    编写有效的测试用例其实也在训练你编写有效代码的能力。完善的测试会给你自信--自信你的代码总能正常运行。
  • 模块化
    抽象出边界、进行封装、找出组件之间的正确接口、起个合适名字并且将它们放在合适的地方。模块化迫使你去思考模型和抽象代码,更好的理解各个部分是如何组合起来的。修改不合理的组合方式,优化组合方式。
  • 遵循合理的编码标准
    优秀的编码指导会涉及到错误处理、安全性、封装、API设计、布局和可维护性相关的内容。优秀的编码指导有利于开发人员的互相沟通和理解。你用代码来解决问题,所以让你的代码清楚的表达含义--让你的代码会说话。
  • 利用好CPAN
    Perl程序员喜欢解决问题、共享解决方案。CPAN就是力量的聚集器,你可以利用它来开阔思路,解决问题,无需付费。如果你发现了BUG可以上报,能力有余也可自行修复。人人为我,我为人人。

异常

好的程序员总会考虑到异常情况的发生:应该存在的文件却不见了;超大容量的硬盘永远不会写满,但是写满了;从来没断过的网络,断了;牢不可破的数据库突然崩溃了。

异常总会发生,健壮的软件必须要能处理这些情况。如果能恢复,当然最好;如果不能恢复,起码也得记录必要的信息。

抛出异常

假设你要写一个日志文件,如果你无法打开这个文件,那么就发生错误,可以用die来抛出异常:

sub open_log_file
{
my $name = shift;
open my $fh, '>>', $name
or die "Can't open logging file '$name': $!";
return $fh;
}

die()会设置全局变量$@并且立即退出当前函数,并且不返回任何值。这个就是抛出异常,抛出的异常会留在调用堆栈顶端,等待某个东西来取;如果没有东西来取,程序就会报错并退出。

异常处理使用动态范围的局部变量。

捕获异常

有时候在异常发生时报错并退出程序是很实用的,但有时候却不希望退出程序,而是继续程序并做一些相应处理。比如错误日志的文件写满了,我们并不希望程序因此而退出,同时也希望能发个短信提醒下管理员。

使用eval操作符的程序块形式来捕获异常:

# log file may not open
my $fh = eval { open_log_file( 'monkeytown.log' ) };

如果文件打开成功,$fh就会包含一个文件句柄;如果打开文件失败,$fh就是未定义的,程序将会继续运行。程序块作为eval的参数,若发生异常,异常将会被eval捕获。

异常处理是一个迟钝的工具,它可以捕获程序块范围内的所有异常。要了解发生的是哪种异常,那就检查$@的值。一定要先本地化,因为$@是一个全局变量。

local $@;
# log file may not open
my $fh = eval { open_log_file( 'monkeytown.log' ) };
# caught exception
if (my $exception = $@) { ... }

将$@的值马上复制出来,避免后面的代码改变全局变量$@的值,因为你不知道还有哪些别的地方会设置该值。

$@通常包含了描述异常的字符串。

if (my $exception = $@)
{
die $exception unless $exception =~ /^Can't open logging/;
$fh = log_to_syslog();
}

再次调用die()抛出异常。这里使用了一个正则表达式来匹配异常内容,不过这个是不可靠的,因为异常的内容并不是固定的。你也可增加额外的信息,如行号、文件名或者是其他调试信息。

CPAN模块Exception::Class提供面向对象的用法:

package Zoo::Exceptions
{
use Exception::Class
'Zoo::AnimalEscaped',
'Zoo::HandlerEscaped';
}

sub cage_open
{
my $self = shift;
Zoo::AnimalEscaped->throw
unless $self->contains_animal;
...
}

sub breakroom_open
{
my $self = shift;
Zoo::HandlerEscaped->throw
unless $self->contains_handler;
...
}

注意事项

抛出异常很简单,捕获异常也很简单,但是$@有一些微妙的地方:

  • 在动态范围本地化$@再使用,否则可能改变它(全局变量)
  • $@可能包含了一个对象,这个对象在布尔语境下返回假值
  • 信号处理程序可能改变$@的值
  • 对象的析构函数可能会调用eval并改变$@值

Modern Perl已经修复了其中的一些问题。这些现象极其少见,但极难调试。建议使用Try::Tiny模块来更好的进行异常处理:

use Try::Tiny;

my $fh = try { open_log_file( 'monkeytown.log' ) }
catch { log_exception( $_ ) };

用try 代替了 eval,try捕获了异常后,catch语句块就会执行。异常会放在$_里面。

内置异常

Perl有自己的内置异常,如语法错误,这种异常会在编译时就会抛出;其他的异常可以在运行时进行处理。比如:

  • 在锁定的哈希中使用了不被允许的键。
  • bless一个不存在的引用
  • 在一个无效的调用者上调用方法
  • 调用不存在的方法
  • 使用了被污染的值(污染模式)
  • 修改只读的值
  • 对引用执行了无效的操作

当然你也可以捕获autodie(编译指示)产生的异常,也可以动态地将警告提升为异常。

编译指示

大多数的Perl模块是提供新功能或者定义类,但是有些模块比如strict,warnings则只是影响语言自身的行为(它们不提供新功能),这类模块就是编译指示。按照惯例,编译指示使用小写以区别于其他模块。

编译指示和有效范围

编译指示在调用它的词法范围内生效,就像词法变量一样。

{
# $lexical不可见,strict未生效
{
use strict;
my $lexical = 'available here';
# $lexical可见,strict启用生效
}
# $lexical不可见,strict未生效
}

很像词法变量吧。

# 作用域持续整个文件
use strict;
{
# 嵌套作用域,strict生效
my $inner = 'another lexical';
}

使用编译指示

编译指示的使用和其他模块一样(本来就是模块),也接受参数:

# 要求变量声明,禁止裸字
use strict qw( subs vars );

# 使用2014的规则
use Modern::Perl '2014';

如果你想禁用特性使用关键字no:

use Modern::Perl; 
# 或 use strict;
{
no strict 'refs';
# 该词法范围内不启用refs的限制,这样就能操作符号表了
}

有用的编译指示

这里有一些有用的编译指示:

  • strict
    让编译器启用以下检查:符号引用,裸字使用,变量声明。
  • warnings
    对不规范的行为发出警告。
  • utf8
    告诉解析器使用UTF-8编码来理解当前文件的源代码。
  • autodie
    启用对系统调用和内部函数的自动错误检查。
  • constant
    允许你使用编译时常量。
  • vars

允许你声明包全局变量。

  • feature
    允许你启用新特性。如
use 5.14; #启用5.14版本的新特性,启用strict编译指示
use feature ':5.14'; #跟上面一样的 
  • less
    演示如何写一个编译指示。

可查看并了解更多:perldoc perlpragma。

还有一些CPAN上的编译指示,可能还没有被广泛使用,有兴趣的你可以自己去体验。

  • autovivification
  • indirect
  • autobox
  • perl5i
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容