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 = [email protected];
    $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,也没有找到其他人关于这方面的一些实践经验,所以,上面说得可能不完全正确,如果有不对的地方,敬请指出,谢谢。

时间: 2024-10-30 12:39:05

perl CGI重构原则的相关文章

Apache服务之php/perl/cgi语言的支持

安装php软件包: 安装文本浏览器 安装apache的帮助文档: 测试下是否ok 启动Apache服务关闭火墙: 编辑一个php测试页测试下: perl语言包默认系统已经安装了,直接测试下: Apache服务队cgi语言的配置: 测试下是否ok 在apache服务的主目录下有index.Php文件和apache.html文件,为什么要先执行php文件呢? apache的配置文件最前面写的是这些服务: 所以apache服务启动时会优先考虑这下面的服务 Apache服务之php/perl/cgi语言

重构摘要2_重构原则

何谓重构 对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提交其可理解性,降低其修改成本. 重构的目的是软件更容易理解和修改: 重构不会改变软件可观察的行为. 两顶帽子比喻 添加新功能 不修改既有代码,只管添加新功能,并通过测试 重构 不添加功能,只管改进程序结构 为何重构 重构改进软件设计 改进的重要方向就是消除重复代码. 重构使软件更容易理解 准确说出我所要的 利用重构来协助我理解不熟悉的代码 随着代码渐趋简洁,发现可以看到一些以前看不到的设计层面的东西. 重构帮助找到BUG

重构学习-重构原则

什么是重构: 视上下文重构有两个不同的定义,第一个定义是名词形式 对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本 重构的另一人用法是动词形式 使用一系列的重构手法,在不改变软件可观察行为的前提下调整其结构. 有人说重构就是整理代码 ,从某种角度上来说,是,但是重构不止于此,因为它提供了一种更为高效且受控的代码整理 技术,运用代码重构技术后你会发现对代码的整理会比以前更加高效. 重构的目的是使软件更容易被理解和侯.你可以在软件的内部做很多修改,但必须

Perl CGI编程

http://www.runoob.com/perl/perl-cgi-programming.html 什么是CGI CGI 目前由NCSA维护,NCSA定义CGI如下: CGI(Common Gateway Interface),通用网关接口,它是一段程序,运行在服务器上如:HTTP服务器,提供同客户端HTML页面的接口. 网页浏览 为了更好的了解CGI是如何工作的,我们可以从在网页上点击一个链接或URL的流程: 1.使用你的浏览器访问URL并连接到HTTP web 服务器. 2.Web服务

代码重构原则--很重要,供以后参阅

重构目的 相同的代码最好只出现一次 主次方法 主方法 只包含实现完整逻辑的子方法 思维清楚,便于阅读 次方法 实现具体逻辑功能 测试通过后,后续几乎不用维护 重构的步骤 新建一个方法 新建方法 把要抽取的代码,直接复制到新方法中 根据需求调整参数 调整旧代码 注释原代码,给自己一个后悔的机会 调用新方法 测试 优化代码 在原有位置,因为要照顾更多的逻辑,代码有可能是合理的 而抽取之后,因为代码少了,可以检查是否能够优化 分支嵌套多,不仅执行性能会差,而且不易于阅读 测试 修改注释 在开发中,注释

代码组织与重构原则

1.正交分解原则,拥有三原色可以组装出所有的颜色,如果不是原色的话则需要拥有更多.将代码陈述逻辑分解成功能抽象的部分,能最大限度提供重用,简化陈述,正如哲学和诗歌能简化文章表达一样. 2. 迭代原则化难为量,吃自己的狗粮.这样不但能提供效率,而且能提供改进.尽一切可能,用已有的东西,哪怕是设计不善的代码,只要他是迭代里程碑上的某个重要节点,经过了某种实战的考验,那么尽最大可能重用它.如果要修改,读懂的基础上,将原代码分门归类抄到本代码的组织中去,在测试中排除移植的问题,然后产生一个新的迭代节点,

代码重构原则

1. 总则 总则规定了一些大体原则,必须要作的.最需要注意的事项.也是面向目前我们的代码中亟需解决的一些问题: (1)头文件.源文件布局混乱,直接影响编译效率 (2)有编程规范,但遵守的很差 (3)过长函数 (4)大量重复代码 1.1 源文件 源文件原则: ● 函数行数尽量不要超过50行,超过50行的目前阶段并非严格禁止,但需要说明理由 ● 源文件长度尽量不要超过500行, 不同子功能.子模块的代码不要放在一个源文件中:理论上源文件分的越细越好.保证同一源文件中的代码“强内聚”. ● 无特殊情况

java重构原则

1.合并条件表达式:一系列表达式得到同一个结果,将这些结果测试合并为一个表达式 * 2.嵌套内的if else和最外层并没有什么关联性,完全可以提取到最顶层,改为平行关系,而非包含.废除 * 临时变量,直接return * 3.减少嵌套和移除临时变量,维持正常流程代码再最外层.将条件反转,使异常状况先退出,让正常流程维持在主干 * 4.箭头型代码,嵌套过深.解决方法是异常条件先退出,保持主干流程是核心流程. * 5.把if-else内的代码都封装成一个公共函数,针对状态处理的代码, * 一种优雅

读 《重构 : 改善既有代码的设计》

初读本书是因为一个朋友的介绍,踩着大学的尾巴,那时候还真不知道在想些什么,头脑简单,无所事事,那个时间段就恍恍惚惚,都毕业答辩完了,同学们都陆续回家或者工作,每次!不管是初中,高中还是大学,我都貌似习惯了看着相处三四年的同学各奔东西,这种滋味太难受了,好了,扯远了.因为我计划要北上,so~,必须好好准备筹码.就看了好几本书,本文要说的这本书是其中一本.但是这本书在毕业前看了一半,大概不到两百页,但是其中很多东西都是提到我在日常coding当中忽视的问题,也有可能是编程习惯不好吧.工作之后,然后写