1.一些声明
关于本例子,本例子原来是为本协会(中山大学GNU/Linux协会)内部会员编写的,因此从比较长远的角度考虑来编写,考虑比较多的是代码编写规范和习惯与算法。考虑到这些因素对编程也是很重要的,因此相关的章节与内容我并没有删除,如果读者觉得这部分没必要的话请email通知笔者,谢谢。
2.代码的编写风格
这里讲的不是如何写注释或者是如何写版权信息,而是提出若干比较有代表性和比较优美的编程习惯(这里称做风格)。一般来说,php和perl等web脚本程序,都不可避免地和html等扯上关系。而相当多的程序员并没有很好地注意自己的php/perl代码和html代码的关系,造成了程序行艰涩难懂,结构混乱最后连自己都懒得看的结局。
本人从开发ZSU-Slash-Pro的这一个月里发现,优秀的程序不仅仅有优秀的算法,还有优美而易懂的程序结构。尤其对于大型程序来说,如果代码含糊,结构混乱的话,即使是程序开发者本人,看起代码也会觉得格外费力,其他人看起来就更加辛苦了。
因此很有必要摸索并制订出一套合适的编写代码的规范,用这套规范来约束和统一程序开发人员开发的代码风格。使之易于阅读和修改,方便日后维护和进一步开发的人员,使调试程序简单化成为可能。
这里,我主要参考了PHPSlash和phplib和FreeTrade等几个中大型程序,结合自己的一点小经验,总结出了以下几点:
(1).通用内容做成包含文件
这个方法是将每个生成的页面都会包含的通用内容做成包含文件,这样减少了程序的长度。便于统一修改例如phpslash中它每个页面都要使用一个统一的Header/Footer,因此只需要创建两个包含文件slashhead.inc和slashfoot.inc文件,然后在每个php程序中使用require语句包含它们进入代码内部就可以了。
注意:并不是只规定使用header和footer,如果你愿意,你可以添加任意多个require的部分。
(2).公共函数库
对于那些经常使用的函数和变量,没必要在每个程序内都定义和声明,而只需要将它们放到一个公共的函数/配置文件内就行了。
例如PHPSlash中有几十个函数,这些函数都统一地存放到functions.inc中,并被包含在了配置文件config.php3中。这样所有页面调用到里面的函数时候就不需要定义而只需要直接调用就可以了。
(3).html代码生成函数(库)
对于一些经常需要编写的html代码,可以用一个专门的函数生成这些html代码。
例如一个显示登陆的对话框,你就可以将这个对话框的内容编写一个函数叫dsp_login()。这样每次你需要显示的时候只需要直接调用这个函数就可以了。同样道理,你可以将生成页面的头部和尾部都做成一个函数,例如叫put_header()和put_footer()等。这样就很轻松地将html代码和php代码分离了。
(4).封装过程
尽量隐藏具体操作的过程,取代的是面向对象的类或者某个操作函数。这样的好处就是将过程封装起来,使主程序简洁明快、层次清楚。
例如某个注册程序,它需要完成显示注册窗口、检索用户、显示出错、插入用户、发送电子邮件等5个功能。如果把这5个功能都全部用一般的过程设计的话,代码相对难懂。如果换成5个操作函数(或者说把具体分析操作过程封装起来的话)那么就非常简洁而易懂。
以下是程序主体:
dsp_reg();
check_user();
if ($return=="true")
{
insert_user();
mail_user();
}
else dsp_error();
可见这样的程序即使是初学者都很容易看懂。
(5).功能分块化
尽量将程序需要执行的功能分化成对应的操作/动作,也就是分块化,好处是易于程序调试,条理清楚。
一个最好的例子就是PHPSlash的admin.php3。它将所有的功能都归纳成了一个op值。 例如要删除一个用户-->$op="userdelete"。其实它这里还有一个小技巧:就是不是直接调用某个op而是在表单发送的时候发的是另外一个值user_ops,这个值有几个可能:删除/添加/更新。那么它在程序内有一个判断阵列,判断出这个user_ops应该对应什么op,因此方便设计程序。这里假设一个发布新闻的简单系统,所需要的功能就是发布新闻,删除新闻,更新新闻。
那么归纳成以下3个op:post/delete/update,以下是结构流程(用swich语句):
swich($op) {
case "post":
postnews();
break;
case "delete":
delete();
break;
case "update":
update();
break;
default:
dsp_news();
}
由以上几点可以看出,如果是能够按照上面提到的几点设计程序的话,那么你设计的就会是个结构良好,代码优美易懂的好程序。不过,这还必须取决于良好的程序设计构思。以下给出一个注册程序的例子,它体现了本文和本编程规范提到的大部分内容。整个程序从设计到完工的时间仅仅用了5个小时。
虽然并不快,但是基本上是一写好就运转正常,没有什么逻辑错误,结构也比较清楚。
接下来读者将看到如何利用以上技巧和原则编写一个具体的程序。
3.具体程序设计
(1).功能要求:
这里需要做的是一个注册程序,它的功能要求是:能够自动注册一个合法用户,并且将自动为用户生成一个8位的密码并发送到用户的邮箱。其中标识用户的是唯一的用户名字和唯一且真实的电子邮箱。
申请的流程是:先输入用户想注册的用户名字和他的真实电子邮箱。
------> 然后系统查询这个用户的名字和邮箱是否存在于数据库中
------> 如果其中一者存在,则显示错误并返回重新输入
------> 如果两者都在系统数据库中没有记录,说明用户可以注册,显示欢迎画面
------> 显示确认画面并注册用户信息
------> 自动生成密码并发送到用户邮箱。
(2).程序流程:
基本上都和申请流程类似。根据每个功能的不同设计不同的函数进行操作:
a.显示查询页面
dsp_query();//空白模式
输入查询数据,下一步
b.执行查询函数query();
如果成功,则下一步
如果不成功则显示错误并返回
c.显示确认页面
confirm_reg();
dsp_reg();//保存模式 发送表单
d.插入用户
reg();
加入判断:是否多次刷新页面,如果是就不显示“恭喜”字样。
insert();
mail_user();
success();
(3).功能函数:
这里动用到10个函数。各个函数的功能如下:
dsp_query();//显示查询画面和确认画面并自动根据不同画面选择不同op
查询: op=check 确认: op=insert
query();//查询函数,查询输入的用户和邮箱是否存在
confirm_reg();//确认要注册的函数
reg();插入用户的主函数,完成主要的动作
make_passwd();//生成8位随机密码
insert();//插入用户信息的函数
mail_user();发送注册信息和用户密码到用户信箱的函数
success();//注册成功函数,显示成功信息
error_msg();//拥有各种模式的错误提示函数
current_time();//提示当前系统时间的函数
(4).页面结构:
采用了header和footer包含的技术。并且所有和数据库连接的类和配置都定义在config.php3中并且使用Zsulib。其中调用到Zsulib中的面向对象的数据库连接技术。
主体结构是:
Header.inc
register.php3 -------> require "config.php3";
(5).开始具体编写
编写Header.inc,Footer.inc(略)
编写具体函数和相关接口
(6).解决关键的技术问题
a.如何编写query()呢?
这个函数需要的功能就是查询发送过来的用户名和邮箱是否已经给注册,如果其中之一或者全部都给注册了后就返回错误信息,否则进入confim_reg()函数。
设计思路:先向数据库查询发送过来的$username和$email看是否返回空值。然后判断两个字段的查询值是否==""。这就完成了基本的查询功能了。
以下是query();的完整代码:
function query($username,$email)
{
$sl_q = new userDB;
$sl_q->query("SELECT username,email FROM user WHERE username='$username' or email='$email'");
$sl_q->next_record(); $tusername = $sl_q->Record["username"];
$temail = $sl_q->Record["email"];
if ($username==$tusername && $tusername!="") error_msg("user_exist");
elseif ($email==$temail && $temail!="") error_msg("mail_exist");
elseif ($tusername=="" && $tmail=="" && $username!="" && $email!="")
confirm_reg($username,$email);
else error_msg("unknown");
}
b.如何编写make_passwd()呢?
此函数负责生成随机的8位密码。 设计思路:可以利用unix系统的random守护进程产生的随机因子,也可以利用php的随机函数rand同时为了获得指定的8为密码必须采用一个能够从字符串中切取到给定长度字符串的函数。这里我们采用substr。
以下是make_passwd();基本代码
function make_passwd()
{
$ran = rand();
$passwd = substr($ran,0,8);
return $passwd;
}
c.如何编写confim_reg()?
要达到能够提示你确认这个功能并不容易。因为必须两次调用dsp_query()而两次dsp_query()却需要完成不同的功能。因此必须让confim_reg()传送若干参数给dsp_query()让它知道应该如何操作。
设计思路:让confim_reg()传一个$msg给dsp_query(),让它知道这是确认的信息以转换功能。
以下是confim_reg()完整代码:
function confirm_reg($un,$em)
{$time = current_time();
$msg ="<center>恭喜你!在<font color=red>$time</font>这一刻你的用户名和邮箱还没有给注册!<br>
如果你确实想成为本站会员,请按<font color=red><b>注册</b></font>按钮。
<p></center>";
dsp_query($un,$em,"我要注册","$msg");
}
d.如何编写dsp_query()呢?
该函数功能稍微要复杂点,就是需要根据$msg来决定它自己要实现什么任务。
设计思路:由于查询和确认注册两者是不同的,因此查询按钮应该自动换成注册按钮。这个利用参数$botton实现。判断到底是处于查询还是确认,可以利用$msg是否空来判断(因为确认函数会发一个$msg过来)。另外要区分两者的话,必须在发标单的时候自动选择$op=?因此也可以通过$msg是否空来给$op赋值。查询是:check,确认是insert。
以下是dsp_query()的基本代码:
function dsp_query($un,$em,$botton,$msg)
{
$head = "<tr>";
if ($botton == "" ) $botton="查询";
if ($msg !="") $action="insert";//如果有确认信息就insert
else $action="check";//否则就只是一般查询
$cation = "欢迎到本站注册。一旦帐号生效你就可以享受到本站提供给会员第一流的服务";
$post ="<td align=left width=40%><form method=\"POST\" action=\"$PHP_SELF\">
<input type=\"hidden\" name=op value=$action>
<p>用户名:<input type=text name=\"un\" size=20 value=$un></p>
<p>信箱名:<p><input type=text name=\"em\" size=20 value=$em></p>
<p> <input type=submit value=\"$botton\" name=submit>
<input type=reset value=\"复原\"></p>
</form></td>";
$foot = "</tr>";
if ($msg =="") fancybox("100%","查询",$head.$post."
<td width=60%>". $cation ."</td>".$foot,"r");
else {
fancybox("100%","确认",$head.$post."<td width=60%>".$msg."</td>".$foot,"r");
}
}
e.最后的判断语句:
判断程序的操作流程和函数调用。这里仅仅给出代码(非常简单的代码):
switch ($op) {
case "check":
query($un,$em);
break;
case "insert":
reg($un,$em);
case "null":
break;
default:
dsp_query();
}
(7).结束编写