第八章 效率和风格

作者: 可以没名字吗 | 来源:发表于2016-03-15 14:27 被阅读282次

    程序质量、程序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

    相关文章

      网友评论

        本文标题:第八章 效率和风格

        本文链接:https://www.haomeiwen.com/subject/flzflttx.html