帝都的宇宙中心,古老文明的发源地,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支持闭包,可以用来保存状态变量,让代码更内聚)。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,也没有找到其他人关于这方面的一些实践经验,所以,上面说得可能不完全正确,如果有不对的地方,敬请指出,谢谢。

Perl CGI重构原则相关推荐

  1. Perl/CGI脚本语言编程学习资料及开发工具下载大全

    Practical Extraction and Report Language Perl 最初的设计者为拉里·沃尔(Larry Wall),它于1987年12月18日发表.Perl借取了C.sed. ...

  2. 《重构 改善既有代码的设计 1》重构原则

    /** @startTime 2020-12-16 23:22 @endTime 2020-12-16 23:59 @startPage 1 @endPage 55 @efficiency 56/1 ...

  3. Perl CGI简介

    发信人: chenhao (努力学习), 信区: Linux 标 题: Perl CGI简介 发信站: 北大未名站 (2000年11月19日15:57:07 星期天) , 转信 Perl CGI简介 ...

  4. 【《重构 改善既有代码的设计》学习笔记2】重构原则

    [<重构 改善既有代码的设计>学习笔记2]重构原则 本篇文章的内容来自<重构 改善既有代码的设计>一书学习笔记整理并且加上自己的浅显的思考总结! 上一篇通过一个简单的例子体验了 ...

  5. java代码重构原则_重构原则

    重构原则 重构:对软件内部结构的一种调整,目的是在不改变软件客观察行为的前提下,提高其可理解性,降低修改成本. 何为重构 重构改进软件的设计.如果没有重构,程序的设计会逐渐腐败变质.而改进设计的一个重 ...

  6. perl cgi session php,如何使用Perl中的CGI :: Session处理Web会话?

    我使用CGI在Perl中创建Web应用程序.此应用程序实现模型视图控制器体系结构,并且系统在根目录中具有以下结构: -楷模 -views -controllers -index.pl 文件index. ...

  7. 重构-改善既有代码的设计:重构原则(二)

    1.什么是重构 重构(Refactoring):在不改变软件的功能和外部可见性的情况下,为了改善软件的结构,提高清晰性.可扩展性和可重用性而对软件进行的改造,对代码内部的结构进行优化. 2.为何重构 ...

  8. perl CGI::Session

    用于认证 eg: ligin my $session = new CGI::Session( "driver:File", $cgi, { Directory => '/tm ...

  9. 重构 代码重构原则 总结

    重构定义 如果你发现自己需要为程序添加一个特性 而代码结构使你无法很方便地达成目的 那就先重构那个程序 使特性的添加比较容易进行 然后再添加特性 重构之前 首先检查自己是否有一套可靠的测试机制 这些测 ...

最新文章

  1. 买得嗨更要聊得嗨 阿里通免费电话惠战双11
  2. 25个好用到爆的一行 Python 代码,建议收藏
  3. JavaWeb实现分页的四种方法
  4. 香港大学顾佳涛:非自回归神经机器翻译 | 直播预告
  5. Ceph 的数据回填和恢复
  6. 单片机的引脚,你都清楚吗?
  7. Linux学习之ARM开发板连接ubuntu18.04LTS及NFS相关配置
  8. Java编程作业体会_Java作业的几点总结感想
  9. python docker实战_「docker实战篇」python的docker-docker镜像的创建使用dockerfile(3
  10. iPhone 12 Pro系列终于不怕弯了!
  11. perl CGI::Session
  12. cf500B New Year Permutation
  13. 【VS2010学习笔记】【编程实例】 (在Visual Studio中使用C++创建和使用DLL)
  14. 如何修复win7蓝牙服务器,处理Bluetooth外围设备驱动在win7中出错的修复方法
  15. c++实现n阶行列式计算
  16. The remote device or resource won't accept the connect
  17. K8S部署DevOps
  18. php极光短信验证码,极光短信验证码验证问题
  19. 5.3 定积分的换元积分法和分部积分法
  20. 【BYM】Android 实现相机快门动画,android音视频何俊林

热门文章

  1. 阿里云虚拟主机,免费用
  2. 在计算机上怎么搜共享打印机,如何共享电脑中的打印机?共享电脑打印机步骤...
  3. mysql高效查出重复的手机号_Mysql必读MySQL大表中重复字段的高效率查询方法
  4. 时序预测 | python实现仿生算法优化LSTM时间序列预测(全网最全仿生算法)
  5. nginx 反向代理和正向代理区别
  6. oracle 视图、索引、序列、同义词
  7. R语言使用循环语句一次性画出多幅图
  8. iOS 修改导航栏背景色
  9. 从数组的响应式看下一代响应式数据
  10. - Statement