专注收集记录技术开发学习笔记、技术难点、解决方案
网站信息搜索 >> 请输入关键词:
您当前的位置: 首页 > CGI

perl CGI复建原则

发布时间:2010-05-20 14:01:29 文章来源:www.iduyao.cn 采编人员:星星草
perl CGI重构原则
帝都的宇宙中心,古老文明的发源地,coding的传统在码农手中世代延续,CGI作为传承了一千多年的古老工艺,并没有被AJAX收割殆尽,仍在这里焕发着勃勃生机。--舌尖上的ABCD

背景

借着开发Stroy的机会,把一个古老的CGI脚本做了一下重构,有点心得,赶紧写下来,因为以后不太可能有机会经常接触perl的CGI了。
产品中现有的CGI并非像很多年以前那样用来产生web页面,而是作为系统操作的工具,在运行过程中修改Linux系统的一些配置以及进行其他一些比较底层的操作。
不想多说具体的编程规范方面的问题,如命名、注释等,虽然这些对于代码的可维护性、健壮性也非常重要,但本文主要想总结一下perl代码的功能区域、函数调用、异常处理等方面的问题。这些也谈不上架构设计,因为除了一些perl module外,其他的CGI脚本基本都是平级的,没有特别复杂的接口、模式之类的东西。

1.脚本功能内聚

CGI脚本最好按特性划分,通常一个脚本文件只实现一组相关的功能,只包含一个具体的特性。特性不宜过大,如果一个特性很大的话,可以通过目录的方式组织在一个文件夹下,每个脚本只完成一个小特性,同时修改服务器的路由配置。
每个文件的长度不要超过1000行,太长了就应该考虑一下是否可以拆分为多个文件,将不太相关的功能剥离开,以提高代码的可维护性和执行效率。
文件(夹)的命名应该有统一的约定,方便代码查找,在一个脚本内部,可以包括相关的CRUD及其他逻辑。

2.合理的代码布局

这里指的是一个CGI脚本内部包括的内容以及它们的位置。
Perl CGI脚本通常包括shebang,use modules, 全局配置,全局变量定义以及逻辑代码,上面的顺序也应该就是脚本中代码出现的顺序,如:
#!/usr/bin/perl
# Copyright 2009-2015 ***. All rights reserved.
use CGI;
use warnings;
use strict;

$|           = 1;   ## turn autoflush on
$ENV{"PATH"} = "/bin:/sbin:/usr/bin:/usr/sbin";
$<           = $>;

my $q      = new CGI;
my $error  = 0;
my $status = "SUCCESS";

print $q->header( { -type => 'text/plain' } );
#logical code here
前面几部分通常比较固定,不会有太大的变化。但是一个好的设计中,不应该有太多的全局变量,除了从url中获取传入参数、常量定义外,其他的变量应该都包含在函数内部,尤其是不能出现多个函数通过全局变量来传递状态的情况。perl解释器是C语言编写的,其实这个问题也是C语言开发过程中经常碰到的。
逻辑代码部分可能会比较复杂,因为要完成具体的业务逻辑,操作各种数据,然后返回处理结果等,内容比较多。CGI不需要main函数,全局部分的代码就相当于是main函数的代码,但是满篇位置随意的全局代码可读性非常差。建议提供一个main入口进行功能分发,其他逻辑以函数的方式被入口调用。这就要求在传入的参数中要包含行为参数,比如“operation=eat",然后使用switch(Swich模块提供)或者given(perl6内置)进行路由,当然实在不行,if...else...也可以,如:
switch ($operation)
{
    case "eat"  { eat(); }
    case "walk" { walk(); }
    case "swim" { swim(); }
    else {print "I don't know what to do!"; }
}
main后面一般就可以exit 0了,看代码的话看到这里就知道这个脚本都能做什么事情了。
行为函数如果比较复杂的话,建议进一步拆分,拆出private的函数,行为函数只管流程调度,具体的实现放到private函数中。比如swim动作,拆分为几个私有函数,包括stroke、kick、breathe,swim函数的实现应该类似于
sub swim() {
   while(not reach the destination) {
       _stroke();
       _kick();
       _breathe();
   }
}
perl本身没有private/public的说法,在我这里行为函数表示从main入口的函数,可以认为是public函数,而private是被public函数或其他private调用的函数,private函数所需的参数除了常量外都要从@_处得到,区别起见,在命名上private函数以"_"开头。

3.结构化返回结果

前面说到,这里的CGI不是输出html网页,而是执行一些系统操作。纵然没有html格式的束缚,返回结果也需要有一个结构化的输出,便于调用者处理。常用的两种格式化语法是XML和json,我用过的几种语言都提供有两者的解析类库,如果是从javascript来调用,肯定是json无疑,直接就是javascript对象,方便操作,其他场景下,视情况而定,json灵活简单,XML严谨但有些冗余。产品里CGI返回结果使用的是XML,都按照下面的格式:
<response>
    <message><![CDATA[...]]></message>
   <errorcode>$error</errorcode>
    <status>$status</status>
</response>
调用者看status中就能知道本次调用成功与否,从errorcode可以得到具体的错误,一些格式不固定的具体执行结果都放在CDATA部分中。
在实现上,可以定义两个工具函数,分别print以CDATA的内容分开的前后两部分,switch之前打印前半部分,在exit 0之前将结果的后半部分打印出来。

4.异常处理

看久了下面的代码总感觉有点胸闷气短,整个屏幕都是if...else...地处理错误码,密集恐惧症要犯了
my $error = doSomething();
if ($error == 0) {/*normal process*/; return;}
elsif ($error == 1) {}
elsif ($error == 2) {}
...
else {}
实际上,这个问题就跟我刚刚接触Java的时候一样,可以返回错误码,也可以抛出自定义异常,到底用那种方式呢?好纠结,好纠结。发散一下,讲个笑话,昨晚刚听到的,说女人看到男人哭时会想:“他是不是有外遇了,他是不是瞒着我做了什么见不得人的事了...”; 而男人看到女人哭时总是想:“她又哭了。她怎么又哭了?她怎么又哭了!”
从现实主义的角度看,我琢磨着一是看返回值是否还有其他用处,二是能否让代码逻辑清晰、可读性更高,可能有时候可能还要考虑异常的性能开销。
在这次重构中,我将所有函数中出错需要return的地方都修改为die "message" if (something wrong)或者 doSomething() or die "message"的方式,在上面说的main入口处统一进行异常捕捉。封装了两个函数:
sub _throw {
   my $msg = shift;
   my $args = @_;

   my $e = sprintf($msg, @$args);
   die $e."n";     # "n"不会让die输出行号,只有你自己写的错误信息
}
sub _catch {
    my $e = $@;
    $error = -1;    #这个error是给返回结果用的
    print "error reason ==> " . $e;
}
上面的那个switch block变成下面的样子:
eval {
    switch ($operation)
    {
        ...
    }

    1;
} or _catch();
eval就相当于Java中的try,在编译时会有一点小开销,但不会影响运行效率(要区别于eval一个表达式)。这样所有的错误处理都可以在_catch中统一处理,比如记日志,格式化错误提示等,同时其他函数中的代码大大精简。

5.统一的常量定义

在开发业务逻辑时,不可避免地需要处理异常情况,很有可能临时起意写了一个错误码或者一个错误提示的字符串。但是,同样的错误可能在其他地方也会遇到,怎么办?再写一遍吗?写完了,如果需要修改的话是不是要满篇地查找啊?
另外,还有一些配置常量,比如说端口号,可能在很多地方都用到了,如果有一天要修改端口号,又是一堆的查找替换。
解决这些问题的方案可能都不必细说,因为在其他语言中都司空见惯了--使用统一的常量定义。Perl提供了const和Readonly关键字来定义常量。

6.复用公共逻辑

重构最常做的事情可能就是提取公共代码进行复用了。
多个CGI脚本之间可能会有一些公共的业务逻辑,比如对数据库操作的封装、对其他第三方工具的存取等,以及一些切面上的操作,比如防止代码注入的安全地执行shell的方法,记录操作日志等。
这些逻辑可以提取出来做成perl module,在CGI开头use一下就可以调用了,同时也可以查一下CPAN,有没有现成的module可以拿来用,省得再造一遍轮子。

7.执行进度显示

这个问题可以参考我之前的一篇文章《Java动态展现CGI执行进度》。但是目前在实现上还有些不尽如人意的地方,主要问题是侵入性太大,粒度较粗。
比如说,对于一个比较长的业务调用来说,逻辑可能拆分到各个子函数中去了,如果要将进度表现得详细些,就要在更多的地方打印百分比,而且在A函数中打印了10%,在B函数中要打印20%,比较奇怪。
在CPAN中能够找到几个progress相关的module,但是都带有一些UI元素,不是我想要的。
这个问题目前还是没有妥善解决,打算自己写一下,好用的话再来分享,不好用就算了。

以上,是我这段时间重构perl CGI的一点心得,都是我遇到并需要解决的实际问题,希望对大家有所帮助。不过,因为没有特别深入地去研究CPAN的各个module,也没有找到其他人关于这方面的一些实践经验,所以,上面说得可能不完全正确,如果有不对的地方,敬请指出,谢谢。
友情提示:
信息收集于互联网,如果您发现错误或造成侵权,请及时通知本站更正或删除,具体联系方式见页面底部联系我们,谢谢。

其他相似内容:

热门推荐: